From d06daa8746cd6609ede848ac21ea1712d3c01a23 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 27 Jul 2026 14:02:49 +0100 Subject: [PATCH 001/347] RFD 299C: Display scoped workload identities correctly in UI (#68444) --- lib/web/workload_identity.go | 2 ++ lib/web/workload_identity_test.go | 25 ++++++++++++++++++- .../List/WorkloadIdentitiesList.tsx | 5 ++++ .../WorkloadIdentities.test.tsx | 5 ++++ .../src/services/workloadIdentity/types.ts | 2 ++ .../src/test/helpers/workloadIdentities.ts | 7 ++++++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/web/workload_identity.go b/lib/web/workload_identity.go index 00c7f522e424..d2ccc5d33330 100644 --- a/lib/web/workload_identity.go +++ b/lib/web/workload_identity.go @@ -67,6 +67,7 @@ func (h *Handler) listWorkloadIdentities(_ http.ResponseWriter, r *http.Request, uiItems := tslices.Map(result.GetWorkloadIdentities(), func(item *workloadidentityv1.WorkloadIdentity) WorkloadIdentity { uiItem := WorkloadIdentity{ Name: item.GetMetadata().GetName(), + Scope: item.GetScope(), SpiffeID: item.GetSpec().GetSpiffe().GetId(), SpiffeHint: item.GetSpec().GetSpiffe().GetHint(), Labels: item.GetMetadata().GetLabels(), @@ -88,6 +89,7 @@ type ListWorkloadIdentitiesResponse struct { type WorkloadIdentity struct { Name string `json:"name"` + Scope string `json:"scope,omitempty"` SpiffeID string `json:"spiffe_id"` SpiffeHint string `json:"spiffe_hint"` Labels map[string]string `json:"labels"` diff --git a/lib/web/workload_identity_test.go b/lib/web/workload_identity_test.go index 0416d409cb25..5d23c9d0fc84 100644 --- a/lib/web/workload_identity_test.go +++ b/lib/web/workload_identity_test.go @@ -73,6 +73,23 @@ func TestListWorkloadIdentities(t *testing.T) { }.Build()) require.NoError(t, err) + scopedName := uuid.New().String() + + _, err = env.server.Auth().CreateWorkloadIdentity(ctx, workloadidentityv1pb.WorkloadIdentity_builder{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: scopedName, + }.Build(), + Scope: "/staging", + Spec: workloadidentityv1pb.WorkloadIdentitySpec_builder{ + Spiffe: workloadidentityv1pb.WorkloadIdentitySPIFFE_builder{ + Id: "/staging/_/svc", + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + response, err := pack.clt.Get(ctx, endpoint, url.Values{}) require.NoError(t, err) assert.Equal(t, http.StatusOK, response.Code(), "unexpected status code") @@ -80,7 +97,8 @@ func TestListWorkloadIdentities(t *testing.T) { var instances ListWorkloadIdentitiesResponse require.NoError(t, json.Unmarshal(response.Bytes(), &instances), "invalid response received") - assert.Len(t, instances.Items, 1) + assert.Len(t, instances.Items, 2) + // Scoped identities sort after unscoped ones in the name-ordered stream. require.Empty(t, cmp.Diff(instances, ListWorkloadIdentitiesResponse{ Items: []WorkloadIdentity{ { @@ -92,6 +110,11 @@ func TestListWorkloadIdentities(t *testing.T) { "label-2": "value-2", }, }, + { + Name: scopedName, + Scope: "/staging", + SpiffeID: "/staging/_/svc", + }, }, })) } diff --git a/web/packages/teleport/src/WorkloadIdentity/List/WorkloadIdentitiesList.tsx b/web/packages/teleport/src/WorkloadIdentity/List/WorkloadIdentitiesList.tsx index 10ba56d44044..38a00a8938aa 100644 --- a/web/packages/teleport/src/WorkloadIdentity/List/WorkloadIdentitiesList.tsx +++ b/web/packages/teleport/src/WorkloadIdentity/List/WorkloadIdentitiesList.tsx @@ -75,6 +75,11 @@ export function WorkloadIdetitiesList({ key: 'name', headerText: 'Name', isSortable: true, + // Scoped workload identities are shown as their scope-qualified + // name, e.g. /staging::my-identity. + render: ({ name, scope }) => ( + {scope ? `${scope}::${name}` : name} + ), }, { key: 'spiffe_id', diff --git a/web/packages/teleport/src/WorkloadIdentity/WorkloadIdentities.test.tsx b/web/packages/teleport/src/WorkloadIdentity/WorkloadIdentities.test.tsx index 833784c0e968..c65e40bb71df 100644 --- a/web/packages/teleport/src/WorkloadIdentity/WorkloadIdentities.test.tsx +++ b/web/packages/teleport/src/WorkloadIdentity/WorkloadIdentities.test.tsx @@ -163,6 +163,11 @@ describe('WorkloadIdentities', () => { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' ) ).toBeInTheDocument(); + + // Scoped workload identities show their scope-qualified name. + expect( + screen.getByText('/staging::test-workload-identity-4') + ).toBeInTheDocument(); }); it('Allows paging', async () => { diff --git a/web/packages/teleport/src/services/workloadIdentity/types.ts b/web/packages/teleport/src/services/workloadIdentity/types.ts index 6f07b6555921..c686cc5eb20e 100644 --- a/web/packages/teleport/src/services/workloadIdentity/types.ts +++ b/web/packages/teleport/src/services/workloadIdentity/types.ts @@ -23,6 +23,8 @@ export type ListWorkloadIdentitiesResponse = { export type WorkloadIdentity = { name: string; + /** scope is empty or omitted for unscoped workload identities. */ + scope?: string; spiffe_id: string | null | undefined; labels: Record | null | undefined; spiffe_hint: string | null | undefined; diff --git a/web/packages/teleport/src/test/helpers/workloadIdentities.ts b/web/packages/teleport/src/test/helpers/workloadIdentities.ts index 0c241f2358fb..9bcad0075aa8 100644 --- a/web/packages/teleport/src/test/helpers/workloadIdentities.ts +++ b/web/packages/teleport/src/test/helpers/workloadIdentities.ts @@ -48,6 +48,13 @@ export const listWorkloadIdentitiesSuccess = ( spiffe_hint: '', labels: { 'test-label-4': 'test-value-4' }, }, + { + name: 'test-workload-identity-4', + scope: '/staging', + spiffe_id: '/staging/_/70b7ee2f-ea48-42e8-b6e1-9c47dec6e295', + spiffe_hint: '', + labels: {}, + }, ], next_page_token: 'page-token-1', } From 8c8a9e0f719c8492dc999878f32af09ea7426afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:10:03 +0200 Subject: [PATCH 002/347] `RequestEnrollPairingApproval`: Accept pairing rather than token (#68836) * RequestEnrollPairingApproval: Accept pairing rather than token * Move nil device test to table * Assert the `CompareFailed` message --- lib/services/enroll_pairing.go | 11 +-- lib/services/local/enroll_pairing.go | 16 +--- lib/services/local/enroll_pairing_test.go | 107 +++++++++++----------- 3 files changed, 65 insertions(+), 69 deletions(-) diff --git a/lib/services/enroll_pairing.go b/lib/services/enroll_pairing.go index 955f50a87221..c4e9bbeb6d66 100644 --- a/lib/services/enroll_pairing.go +++ b/lib/services/enroll_pairing.go @@ -37,12 +37,11 @@ type EnrollPairing interface { // matches token. Returns NotFound if no pairing matches. GetEnrollPairingByToken(ctx context.Context, token string) (*devicepb.EnrollPairing, error) - // RequestEnrollPairingApproval transitions the EnrollPairing identified by - // token from AWAITING_DEVICE to AWAITING_APPROVAL, persisting device for the - // Web UI to display and for retry gating, and returns the updated pairing. - // Returns NotFound if no pairing matches token and CompareFailed if the - // pairing is no longer awaiting a device. - RequestEnrollPairingApproval(ctx context.Context, token string, device *devicepb.EnrollPairingDevice) (*devicepb.EnrollPairing, error) + // RequestEnrollPairingApproval transitions pairing from AWAITING_DEVICE to + // AWAITING_APPROVAL, persisting device for the Web UI to display and for + // retry gating, and returns the updated pairing. + // Returns CompareFailed if the pairing is no longer awaiting a device. + RequestEnrollPairingApproval(ctx context.Context, pairing *devicepb.EnrollPairing, device *devicepb.EnrollPairingDevice) (*devicepb.EnrollPairing, error) } // MarshalEnrollPairing marshals an EnrollPairing resource to JSON. diff --git a/lib/services/local/enroll_pairing.go b/lib/services/local/enroll_pairing.go index 3a28d34a34a1..5ecc7aee4fab 100644 --- a/lib/services/local/enroll_pairing.go +++ b/lib/services/local/enroll_pairing.go @@ -197,22 +197,16 @@ func (s *EnrollPairingService) GetEnrollPairingByToken(ctx context.Context, toke return pairing, nil } -// RequestEnrollPairingApproval transitions the pairing identified by token from -// AWAITING_DEVICE to AWAITING_APPROVAL, persisting device, and returns the -// updated pairing. -func (s *EnrollPairingService) RequestEnrollPairingApproval(ctx context.Context, token string, device *devicepb.EnrollPairingDevice) (*devicepb.EnrollPairing, error) { - if token == "" { - return nil, trace.BadParameter("token required") +// RequestEnrollPairingApproval transitions pairing from AWAITING_DEVICE to +// AWAITING_APPROVAL, persisting device, and returns the updated pairing. +func (s *EnrollPairingService) RequestEnrollPairingApproval(ctx context.Context, pairing *devicepb.EnrollPairing, device *devicepb.EnrollPairingDevice) (*devicepb.EnrollPairing, error) { + if pairing == nil { + return nil, trace.BadParameter("pairing required") } if device == nil { return nil, trace.BadParameter("device required") } - pairing, err := s.GetEnrollPairingByToken(ctx, token) - if err != nil { - return nil, trace.Wrap(err) - } - const errNotAwaitingDevice = "enroll pairing is not awaiting a device" // TODO(ravicious): Make the call retryable from the same device, as described // in the Interrputibility section of RFD 32e. diff --git a/lib/services/local/enroll_pairing_test.go b/lib/services/local/enroll_pairing_test.go index a9a67fcf7cfd..7a01b111f9f7 100644 --- a/lib/services/local/enroll_pairing_test.go +++ b/lib/services/local/enroll_pairing_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -173,9 +174,6 @@ func TestEnrollPairingService_RequestEnrollPairingApproval(t *testing.T) { t.Parallel() s := newEnrollPairingService(t) - existingPairing, err := s.CreateEnrollPairing(t.Context(), "pairing-for-validation-tests") - require.NoError(t, err) - existingToken := existingPairing.GetStatus().GetToken() device := makeDevice() t.Run("transitions to awaiting approval and persists the device", func(t *testing.T) { @@ -183,9 +181,8 @@ func TestEnrollPairingService_RequestEnrollPairingApproval(t *testing.T) { ctx := t.Context() created, err := s.CreateEnrollPairing(ctx, "approve-ok") require.NoError(t, err) - token := created.GetStatus().GetToken() - updated, err := s.RequestEnrollPairingApproval(ctx, token, device) + updated, err := s.RequestEnrollPairingApproval(ctx, created, device) require.NoError(t, err) assert.Equal(t, devicepb.EnrollPairingState_ENROLL_PAIRING_STATE_AWAITING_APPROVAL, @@ -193,7 +190,7 @@ func TestEnrollPairingService_RequestEnrollPairingApproval(t *testing.T) { assert.Empty(t, cmp.Diff(device, updated.GetStatus().GetDevice(), protocmp.Transform())) // The transition is persisted and the pairing is still resolvable by token. - got, err := s.GetEnrollPairingByToken(ctx, token) + got, err := s.GetEnrollPairingByToken(ctx, created.GetStatus().GetToken()) require.NoError(t, err) assert.Empty(t, cmp.Diff(updated, got, protocmp.Transform())) }) @@ -203,13 +200,38 @@ func TestEnrollPairingService_RequestEnrollPairingApproval(t *testing.T) { ctx := t.Context() created, err := s.CreateEnrollPairing(ctx, "approve-twice") require.NoError(t, err) - token := created.GetStatus().GetToken() - _, err = s.RequestEnrollPairingApproval(ctx, token, device) + _, err = s.RequestEnrollPairingApproval(ctx, created, device) + require.NoError(t, err) + + // created has advanced past AWAITING_DEVICE, so a second attempt is rejected. + _, err = s.RequestEnrollPairingApproval(ctx, created, device) + assert.ErrorAs(t, err, new(*trace.CompareFailedError)) + assert.ErrorContains(t, err, "enroll pairing is not awaiting a device") + }) + + t.Run("rejects a stale pairing that lost the compare-and-swap", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + created, err := s.CreateEnrollPairing(ctx, "approve-stale") + require.NoError(t, err) + + // Claim via a fresh copy, advancing the stored revision so that created, + // still AWAITING_DEVICE in memory, no longer matches the backend. + fresh, err := s.GetEnrollPairingByToken(ctx, created.GetStatus().GetToken()) + require.NoError(t, err) + _, err = s.RequestEnrollPairingApproval(ctx, fresh, device) require.NoError(t, err) - _, err = s.RequestEnrollPairingApproval(ctx, token, device) + _, err = s.RequestEnrollPairingApproval(ctx, created, device) assert.ErrorAs(t, err, new(*trace.CompareFailedError)) + assert.ErrorContains(t, err, "enroll pairing is not awaiting a device") + }) + + t.Run("rejects a nil pairing", func(t *testing.T) { + t.Parallel() + _, err := s.RequestEnrollPairingApproval(t.Context(), nil, device) + assert.ErrorAs(t, err, new(*trace.BadParameterError)) }) badOSType := makeDevice() @@ -219,61 +241,42 @@ func TestEnrollPairingService_RequestEnrollPairingApproval(t *testing.T) { badSerialNumber := makeDevice() badSerialNumber.SetSerialNumber(" ") - tests := []struct { - name string - token string - device *devicepb.EnrollPairingDevice - errTarget any - errMsg string + badParameterTests := []struct { + name string + device *devicepb.EnrollPairingDevice + errMsg string }{ { - name: "returns NotFound for an unknown token", - token: "does-not-exist", - device: device, - errTarget: new(*trace.NotFoundError), - }, - { - name: "rejects empty token", - token: "", - device: device, - errTarget: new(*trace.BadParameterError), + name: "rejects a nil device", + device: nil, + errMsg: "device required", }, { - name: "rejects nil device", - token: "any-token", - device: nil, - errTarget: new(*trace.BadParameterError), + name: "rejects empty device OS type", + device: badOSType, + errMsg: "os_type is missing", }, { - name: "rejects empty device OS type", - token: existingToken, - device: badOSType, - errTarget: new(*trace.BadParameterError), - errMsg: "os_type is missing", + name: "rejects empty device OS version", + device: badOSVersion, + errMsg: "os_version is missing", }, { - name: "rejects empty device OS version", - token: existingToken, - device: badOSVersion, - errTarget: new(*trace.BadParameterError), - errMsg: "os_version is missing", - }, - { - name: "rejects empty device serial number", - token: existingToken, - device: badSerialNumber, - errTarget: new(*trace.BadParameterError), - errMsg: "serial_number is missing", + name: "rejects empty device serial number", + device: badSerialNumber, + errMsg: "serial_number is missing", }, } - for _, test := range tests { + for _, test := range badParameterTests { t.Run(test.name, func(t *testing.T) { t.Parallel() - _, err := s.RequestEnrollPairingApproval(t.Context(), test.token, test.device) - assert.ErrorAs(t, err, test.errTarget) - if test.errMsg != "" { - assert.ErrorContains(t, err, test.errMsg) - } + ctx := t.Context() + created, err := s.CreateEnrollPairing(ctx, uuid.NewString()) + require.NoError(t, err) + + _, err = s.RequestEnrollPairingApproval(ctx, created, test.device) + assert.ErrorAs(t, err, new(*trace.BadParameterError)) + assert.ErrorContains(t, err, test.errMsg) }) } } From 3b7ccc8edf1116fcc08c4c37bbbc56a85952370b Mon Sep 17 00:00:00 2001 From: Michael Patterson-Muir Date: Mon, 27 Jul 2026 15:47:36 +0100 Subject: [PATCH 003/347] discovery: surface Azure subscription resolution failures as UserTasks (#68814) --- api/types/usertasks/object.go | 33 ++++++--- api/types/usertasks/object_test.go | 28 ++++++++ lib/srv/discovery/azure_vm_error_parser.go | 9 +++ .../discovery/azure_vm_error_parser_test.go | 14 ++++ lib/srv/discovery/discovery.go | 37 +++++++++- lib/srv/discovery/status.go | 18 +++++ lib/srv/discovery/status_test.go | 20 ++++++ lib/srv/server/azure_watcher.go | 59 ++++++++-------- lib/srv/server/azure_watcher_test.go | 70 +++++++++++++++---- .../azure-vm-subscription-list-denied.md | 18 +++++ 10 files changed, 254 insertions(+), 52 deletions(-) create mode 100644 lib/usertasks/descriptions/azure-vm-subscription-list-denied.md diff --git a/api/types/usertasks/object.go b/api/types/usertasks/object.go index 0510d0b70ce9..b11adf31d99a 100644 --- a/api/types/usertasks/object.go +++ b/api/types/usertasks/object.go @@ -316,6 +316,11 @@ var DiscoverRDSIssueTypes = []string{ // List of Auto Discover Azure VM issues identifiers. // This value is used to populate the UserTasks.Spec.IssueType for Discover Azure VM tasks. const ( + // AutoDiscoverAzureVMIssueSubscriptionListDenied indicates the integration + // lacks permission to list subscriptions while expanding a wildcard + // subscription matcher. + AutoDiscoverAzureVMIssueSubscriptionListDenied = "azure-vm-subscription-list-denied" + // AutoDiscoverAzureVMIssueMissingRunCommandsPermission is used to identify VMs that failed to auto-enroll // because the Azure integration is missing runCommands permissions (runCommands/write or runCommands/read). AutoDiscoverAzureVMIssueMissingRunCommandsPermission = "azure-vm-missing-run-commands-permission" @@ -335,6 +340,7 @@ const ( // DiscoverAzureVMIssueTypes is a list of issue types that can occur when trying to auto enroll Azure VMs. var DiscoverAzureVMIssueTypes = []string{ + AutoDiscoverAzureVMIssueSubscriptionListDenied, AutoDiscoverAzureVMIssueMissingRunCommandsPermission, AutoDiscoverAzureVMIssueVMNotRunning, AutoDiscoverAzureVMIssueVMAgentNotAvailable, @@ -625,14 +631,20 @@ func validateDiscoverAzureVMTaskType(ut *usertasksv1.UserTask) error { if discover == nil { return trace.BadParameter("%s: discover_azure_vm field is required", TaskTypeDiscoverAzureVM) } - switch { - case discover.SubscriptionId == "": - return trace.BadParameter("%s: discover_azure_vm.subscription_id field is required", TaskTypeDiscoverAzureVM) - case discover.ResourceGroup == "": - return trace.BadParameter("%s: discover_azure_vm.resource_group field is required", TaskTypeDiscoverAzureVM) - case discover.Region == "": - return trace.BadParameter("%s: discover_azure_vm.region field is required", TaskTypeDiscoverAzureVM) + isSubscriptionListIssue := spec.IssueType == AutoDiscoverAzureVMIssueSubscriptionListDenied + if !isSubscriptionListIssue { + switch { + case discover.SubscriptionId == "": + return trace.BadParameter("%s: discover_azure_vm.subscription_id field is required", TaskTypeDiscoverAzureVM) + case discover.ResourceGroup == "": + return trace.BadParameter("%s: discover_azure_vm.resource_group field is required", TaskTypeDiscoverAzureVM) + case discover.Region == "": + return trace.BadParameter("%s: discover_azure_vm.region field is required", TaskTypeDiscoverAzureVM) + case len(discover.Instances) == 0: + return trace.BadParameter("%s: discover_azure_vm.instances field is required", TaskTypeDiscoverAzureVM) + } } + expectedTaskName := taskNameForDiscoverAzureVM( TaskGroup{ Integration: spec.Integration, @@ -650,9 +662,12 @@ func validateDiscoverAzureVMTaskType(ut *usertasksv1.UserTask) error { ut.Metadata.Name, ) } - if len(discover.Instances) == 0 { - return trace.BadParameter("%s: discover_azure_vm.instances field is required", TaskTypeDiscoverAzureVM) + + // Subscription resolution fails before VM scope or instance data is known. + if isSubscriptionListIssue { + return nil } + for vmID, vmIssue := range discover.Instances { switch { case vmIssue == nil: diff --git a/api/types/usertasks/object_test.go b/api/types/usertasks/object_test.go index c402d7130510..21524fc0c25b 100644 --- a/api/types/usertasks/object_test.go +++ b/api/types/usertasks/object_test.go @@ -150,6 +150,20 @@ func TestValidateUserTask(t *testing.T) { require.NoError(t, err) return userTask } + baseAzureVMPermissionTask := func(t *testing.T) *usertasksv1.UserTask { + userTask, err := NewDiscoverAzureVMUserTask( + TaskGroup{ + Integration: "my-integration", + IssueType: AutoDiscoverAzureVMIssueSubscriptionListDenied, + }, + time.Now().Add(24*time.Hour), + &usertasksv1.DiscoverAzureVM{ + Instances: map[string]*usertasksv1.DiscoverAzureVMInstance{}, + }, + ) + require.NoError(t, err) + return userTask + } const noError = "" @@ -619,6 +633,20 @@ func TestValidateUserTask(t *testing.T) { task: baseAzureVMDiscoverTask, wantErr: noError, }, + { + name: "DiscoverAzureVM: subscription list permission issue allows empty scope and instances", + task: baseAzureVMPermissionTask, + wantErr: noError, + }, + { + name: "DiscoverAzureVM: subscription list permission issue requires deterministic task name", + task: func(t *testing.T) *usertasksv1.UserTask { + ut := baseAzureVMPermissionTask(t) + ut.Metadata.Name = "another-name" + return ut + }, + wantErr: "task name must be", + }, { name: "DiscoverAzureVM: invalid issue type", task: func(t *testing.T) *usertasksv1.UserTask { diff --git a/lib/srv/discovery/azure_vm_error_parser.go b/lib/srv/discovery/azure_vm_error_parser.go index 7fc92a42a093..76a3a136cca9 100644 --- a/lib/srv/discovery/azure_vm_error_parser.go +++ b/lib/srv/discovery/azure_vm_error_parser.go @@ -34,6 +34,15 @@ func classifyAzureInstallResultIssue(installResult server.AzureInstallResult) st return classifyAzureVMEnrollmentError(installResult.APIError) } +// classifyAzureSubscriptionListError classifies errors returned while +// resolving a wildcard subscription matcher. +func classifyAzureSubscriptionListError(err error) string { + if trace.IsAccessDenied(err) || trace.IsNotFound(err) { + return usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied + } + return "" +} + // classifyAzureVMEnrollmentError classifies Azure API errors into user-facing // messages for VM auto-discovery. This is best-effort based on error strings // which may change without notice. The matching logic may require future diff --git a/lib/srv/discovery/azure_vm_error_parser_test.go b/lib/srv/discovery/azure_vm_error_parser_test.go index 87af03816f3a..109f01e42211 100644 --- a/lib/srv/discovery/azure_vm_error_parser_test.go +++ b/lib/srv/discovery/azure_vm_error_parser_test.go @@ -106,3 +106,17 @@ func TestClassifyAzureVMEnrollmentError(t *testing.T) { }) } } + +func TestClassifyAzureSubscriptionListError(t *testing.T) { + t.Parallel() + + require.Equal(t, + usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied, + classifyAzureSubscriptionListError(trace.AccessDenied("missing subscriptions/read")), + ) + require.Equal(t, + usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied, + classifyAzureSubscriptionListError(trace.NotFound("no accessible subscriptions")), + ) + require.Empty(t, classifyAzureSubscriptionListError(trace.ConnectionProblem(nil, "Azure unavailable"))) +} diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index bcddd8b4aa85..100f7ef17517 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -855,7 +855,42 @@ func (s *Server) azureServerFetchersFromMatchers(matchers []types.AzureMatcher, return matcherType == types.AzureMatcherVM }) - return server.MatchersToAzureInstanceFetchers(s.ctx, s.Log, serverMatchers, s.getAzureClients, discoveryConfigName, s.getAzureSubscriptionList) + var allFetchers []server.Fetcher[*server.AzureInstances] + for _, matcher := range serverMatchers { + fetchers, err := server.MatcherToAzureInstanceFetchers( + s.ctx, + s.Log, + matcher, + s.getAzureClients, + discoveryConfigName, + s.getAzureSubscriptionList, + ) + if err != nil { + s.Log.WarnContext(s.ctx, "Failed to resolve Azure subscription wildcard in discovery configuration", + "integration", matcher.Integration, + "discovery_config", discoveryConfigName, + "error", err, + ) + s.handleAzureSubscriptionListError(matcher.Integration, err) + continue + } + allFetchers = append(allFetchers, fetchers...) + } + return allFetchers +} + +func (s *Server) handleAzureSubscriptionListError(integration string, err error) { + issueType := classifyAzureSubscriptionListError(err) + if integration == "" || issueType == "" { + return + } + + if err := s.taskUpdater().upsertAzureSubscriptionListTask(integration, issueType); err != nil { + s.Log.WarnContext(s.ctx, "Failed to upsert Azure subscription list permission User Task", + "integration", integration, + "error", err, + ) + } } func (s *Server) getAzureSubscriptionListNoCache(ctx context.Context, integration string) ([]string, error) { diff --git a/lib/srv/discovery/status.go b/lib/srv/discovery/status.go index 06702d82e39c..2edb4c7d86a6 100644 --- a/lib/srv/discovery/status.go +++ b/lib/srv/discovery/status.go @@ -1049,6 +1049,24 @@ type taskUpdater struct { Log *slog.Logger } +func (s *taskUpdater) upsertAzureSubscriptionListTask(integration, issueType string) error { + task, err := usertasks.NewDiscoverAzureVMUserTask( + usertasks.TaskGroup{ + Integration: integration, + IssueType: issueType, + }, + s.clock.Now().Add(2*s.PollInterval), + usertasksv1.DiscoverAzureVM_builder{ + Instances: map[string]*usertasksv1.DiscoverAzureVMInstance{}, + }.Build(), + ) + if err != nil { + return trace.Wrap(err) + } + + return trace.Wrap(s.mergeUpsertUserTask(task, s.mergeAzure)) +} + func (s *taskUpdater) mergeUpsertUserTask(newTask *usertasksv1.UserTask, mergeUserTasks func(oldTask *usertasksv1.UserTaskSpec, newTask *usertasksv1.UserTaskSpec)) error { taskName := newTask.GetMetadata().GetName() diff --git a/lib/srv/discovery/status_test.go b/lib/srv/discovery/status_test.go index d603304f5cf6..c773d7d3d31b 100644 --- a/lib/srv/discovery/status_test.go +++ b/lib/srv/discovery/status_test.go @@ -339,6 +339,26 @@ func newTaskUpdater(t *testing.T, existingTasks ...*usertasksv1.UserTask) (*task return manager, ap } +func TestUpsertAzureSubscriptionListTask(t *testing.T) { + t.Parallel() + + updater, ap := newTaskUpdater(t) + require.NoError(t, updater.upsertAzureSubscriptionListTask( + "azure-integration", + usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied, + )) + require.Len(t, ap.tasks, 1) + + for _, task := range ap.tasks { + require.Equal(t, usertasks.TaskTypeDiscoverAzureVM, task.GetSpec().GetTaskType()) + require.Equal(t, usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied, task.GetSpec().GetIssueType()) + require.Equal(t, "azure-integration", task.GetSpec().GetIntegration()) + require.Empty(t, task.GetSpec().GetDiscoverAzureVm().GetSubscriptionId()) + require.Empty(t, task.GetSpec().GetDiscoverAzureVm().GetInstances()) + require.True(t, updater.clock.Now().Add(2*updater.PollInterval).Equal(task.GetMetadata().GetExpires().AsTime())) + } +} + func TestAWSEC2Tasks_AddFailedEnrollment(t *testing.T) { t.Parallel() diff --git a/lib/srv/server/azure_watcher.go b/lib/srv/server/azure_watcher.go index 2ba7e6978041..6133e7a35c3d 100644 --- a/lib/srv/server/azure_watcher.go +++ b/lib/srv/server/azure_watcher.go @@ -176,44 +176,44 @@ type azureClientGetter func(ctx context.Context, integration string) (azure.Clie type listSubscriptionsFunc func(ctx context.Context, integration string) (subscriptions []string, err error) -// MatchersToAzureInstanceFetchers converts a list of Azure VM Matchers into a list of Azure VM Fetchers. -func MatchersToAzureInstanceFetchers( +// MatcherToAzureInstanceFetchers converts an Azure VM matcher into Azure VM fetchers. +func MatcherToAzureInstanceFetchers( ctx context.Context, logger *slog.Logger, - matchers []types.AzureMatcher, + matcher types.AzureMatcher, getClient azureClientGetter, discoveryConfigName string, listSubs listSubscriptionsFunc, -) []Fetcher[*AzureInstances] { - ret := make([]Fetcher[*AzureInstances], 0) - for _, matcher := range matchers { - matcher.Subscriptions = expandAzureMatcherSubscriptions(ctx, logger, matcher.Subscriptions, matcher.Integration, listSubs) - for _, subscription := range matcher.Subscriptions { - for _, resourceGroup := range matcher.ResourceGroups { - fetcher := newAzureInstanceFetcher(azureFetcherConfig{ - Matcher: matcher, - Subscription: subscription, - ResourceGroup: resourceGroup, - AzureClientGetter: getClient, - DiscoveryConfigName: discoveryConfigName, - Logger: logger, - }) - ret = append(ret, fetcher) - } +) ([]Fetcher[*AzureInstances], error) { + subscriptions, err := expandAzureMatcherSubscriptions(ctx, matcher.Subscriptions, matcher.Integration, listSubs) + if err != nil { + return nil, trace.Wrap(err) + } + + fetchers := make([]Fetcher[*AzureInstances], 0, len(subscriptions)*len(matcher.ResourceGroups)) + for _, subscription := range subscriptions { + for _, resourceGroup := range matcher.ResourceGroups { + fetchers = append(fetchers, newAzureInstanceFetcher(azureFetcherConfig{ + Matcher: matcher, + Subscription: subscription, + ResourceGroup: resourceGroup, + AzureClientGetter: getClient, + DiscoveryConfigName: discoveryConfigName, + Logger: logger, + })) } } - return ret + return fetchers, nil } // expandAzureMatcherSubscriptions fetches the subscriptions for any wildcard // subscriptions and replaces the wildcard with the subscriptions list. func expandAzureMatcherSubscriptions( ctx context.Context, - logger *slog.Logger, subscriptions []string, integration string, listSubs listSubscriptionsFunc, -) []string { +) ([]string, error) { var out []string for _, sub := range subscriptions { if sub != types.Wildcard { @@ -221,17 +221,18 @@ func expandAzureMatcherSubscriptions( continue } subs, err := listSubs(ctx, integration) + // Azure can return a successful response with no subscriptions when the + // identity has no subscription-scoped access. Treat this as a resolution + // failure so wildcard discovery doesn't silently produce 0 fetchers. + if err == nil && len(subs) == 0 { + err = trace.NotFound("Azure returned no subscriptions for wildcard in discovery configuration") + } if err != nil { - // TODO(gavin): make a user task - logger.WarnContext(ctx, "Failed to fetch Azure subscription list for wildcard in discovery configuration", - "integration", integration, - "error", err, - ) - continue + return nil, trace.Wrap(err) } out = append(out, subs...) } - return utils.Deduplicate(out) + return utils.Deduplicate(out), nil } type azureFetcherConfig struct { diff --git a/lib/srv/server/azure_watcher_test.go b/lib/srv/server/azure_watcher_test.go index dbab8698d471..0a94d87af12b 100644 --- a/lib/srv/server/azure_watcher_test.go +++ b/lib/srv/server/azure_watcher_test.go @@ -268,20 +268,20 @@ func TestAzureWatcher(t *testing.T) { watcher := NewWatcher[*AzureInstances](ctx, logger) const noDiscoveryConfig = "" - watcher.SetFetchers(noDiscoveryConfig, - MatchersToAzureInstanceFetchers( - t.Context(), - logger, - []types.AzureMatcher{tc.matcher}, - func(ctx context.Context, integration string) (azure.Clients, error) { - return &clients, nil - }, - noDiscoveryConfig, - func(ctx context.Context, integration string) (subscriptions []string, err error) { - return []string{sub1, sub2}, nil - }, - ), + fetchers, err := MatcherToAzureInstanceFetchers( + t.Context(), + logger, + tc.matcher, + func(ctx context.Context, integration string) (azure.Clients, error) { + return &clients, nil + }, + noDiscoveryConfig, + func(ctx context.Context, integration string) (subscriptions []string, err error) { + return []string{sub1, sub2}, nil + }, ) + require.NoError(t, err) + watcher.SetFetchers(noDiscoveryConfig, fetchers) go watcher.Run() t.Cleanup(watcher.Stop) @@ -309,6 +309,50 @@ func TestAzureWatcher(t *testing.T) { } } +func TestMatcherToAzureInstanceFetchersSubscriptionListError(t *testing.T) { + t.Parallel() + + permissionErr := trace.AccessDenied("missing subscriptions/read") + fetchers, err := MatcherToAzureInstanceFetchers( + t.Context(), + logtest.NewLogger(), + types.AzureMatcher{ + Types: []string{types.AzureMatcherVM}, + Subscriptions: []string{types.Wildcard}, + ResourceGroups: []string{types.Wildcard}, + Integration: "azure-integration", + }, + func(context.Context, string) (azure.Clients, error) { return nil, nil }, + "discovery-config", + func(context.Context, string) ([]string, error) { return nil, permissionErr }, + ) + + require.Empty(t, fetchers) + require.ErrorIs(t, err, permissionErr) +} + +func TestMatcherToAzureInstanceFetchersEmptySubscriptionList(t *testing.T) { + t.Parallel() + + fetchers, err := MatcherToAzureInstanceFetchers( + t.Context(), + logtest.NewLogger(), + types.AzureMatcher{ + Types: []string{types.AzureMatcherVM}, + Subscriptions: []string{types.Wildcard}, + ResourceGroups: []string{types.Wildcard}, + Integration: "azure-integration", + }, + func(context.Context, string) (azure.Clients, error) { return nil, nil }, + "discovery-config", + func(context.Context, string) ([]string, error) { return nil, nil }, + ) + + require.Empty(t, fetchers) + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) +} + func TestAzureInstances_FilterExistingNodes(t *testing.T) { t.Parallel() diff --git a/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md b/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md new file mode 100644 index 000000000000..e2847f2bb9e6 --- /dev/null +++ b/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md @@ -0,0 +1,18 @@ +# Cannot access Azure subscriptions + +Teleport could not resolve the wildcard subscription matcher because the Azure integration cannot access any subscriptions. + +Verify that the integration uses the intended Azure tenant and managed identity or service principal. Grant that identity a role with the following actions at the management group or subscription scope that Teleport should discover: + +- `Microsoft.Compute/virtualMachines/read` +- `Microsoft.Compute/virtualMachines/runCommands/write` +- `Microsoft.Compute/virtualMachines/runCommands/read` +- `Microsoft.Compute/virtualMachineScaleSets/read` +- `Microsoft.Compute/virtualMachineScaleSets/virtualMachines/read` +- `Microsoft.Compute/virtualMachineScaleSets/virtualMachines/runCommands/write` +- `Microsoft.Compute/virtualMachineScaleSets/virtualMachines/runCommands/read` +- `Microsoft.Resources/subscriptions/read` + +Alternatively, replace the wildcard (`"*"`) in the DiscoveryConfig with explicit subscription IDs. The integration identity must still have the required VM discovery permissions in those subscriptions. + +Azure role assignments may take several minutes to propagate. After the permissions propagate, update the DiscoveryConfig to trigger reconciliation. This task expires automatically if the issue is not observed again. From 016d271a4c32af2c5b305bb2e03526900eed8bd1 Mon Sep 17 00:00:00 2001 From: Tyler Richardson Date: Mon, 27 Jul 2026 10:51:04 -0400 Subject: [PATCH 004/347] Record node name on early join rejection audit events (#68787) * Record node name on early join rejection audit events * Increase wait and retry duration to reduce pressure and allow more time for retries Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> * Clarify host name sanitization boundary --------- Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --- .../join/v1/joinservice_protohybrid.pb.go | 31 ++++++++-- .../join/v1/joinservice_protoopaque.pb.go | 27 +++++++-- api/proto/teleport/join/v1/joinservice.proto | 6 ++ lib/join/internal/messages/messages.go | 5 ++ lib/join/join_test.go | 57 +++++++++++++++++-- lib/join/joinclient/join.go | 3 +- lib/join/joinv1/messages.go | 2 + lib/join/server.go | 6 ++ 8 files changed, 124 insertions(+), 13 deletions(-) diff --git a/api/gen/proto/go/teleport/join/v1/joinservice_protohybrid.pb.go b/api/gen/proto/go/teleport/join/v1/joinservice_protohybrid.pb.go index 7de2a5a15029..4b8b65434e6e 100644 --- a/api/gen/proto/go/teleport/join/v1/joinservice_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/join/v1/joinservice_protohybrid.pb.go @@ -114,8 +114,13 @@ type ClientInit struct { // accepting ProxySuppliedParams. ForwardedByProxy bool `protobuf:"varint,4,opt,name=forwarded_by_proxy,json=forwardedByProxy,proto3" json:"forwarded_by_proxy,omitempty"` ProxySuppliedParameters *ClientInit_ProxySuppliedParams `protobuf:"bytes,5,opt,name=proxy_supplied_parameters,json=proxySuppliedParameters,proto3,oneof" json:"proxy_supplied_parameters,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // HostName is the user-friendly node name of a joining host, sent early so it + // can be recorded on the join audit event even when the join is rejected + // before the host params are received. It is advisory; the host name used for + // issued certificates is carried in HostParams. + HostName string `protobuf:"bytes,6,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ClientInit) Reset() { @@ -178,6 +183,13 @@ func (x *ClientInit) GetProxySuppliedParameters() *ClientInit_ProxySuppliedParam return nil } +func (x *ClientInit) GetHostName() string { + if x != nil { + return x.HostName + } + return "" +} + func (x *ClientInit) SetJoinMethod(v string) { x.JoinMethod = &v } @@ -198,6 +210,10 @@ func (x *ClientInit) SetProxySuppliedParameters(v *ClientInit_ProxySuppliedParam x.ProxySuppliedParameters = v } +func (x *ClientInit) SetHostName(v string) { + x.HostName = v +} + func (x *ClientInit) HasJoinMethod() bool { if x == nil { return false @@ -240,6 +256,11 @@ type ClientInit_builder struct { // accepting ProxySuppliedParams. ForwardedByProxy bool ProxySuppliedParameters *ClientInit_ProxySuppliedParams + // HostName is the user-friendly node name of a joining host, sent early so it + // can be recorded on the join audit event even when the join is rejected + // before the host params are received. It is advisory; the host name used for + // issued certificates is carried in HostParams. + HostName string } func (b0 ClientInit_builder) Build() *ClientInit { @@ -251,6 +272,7 @@ func (b0 ClientInit_builder) Build() *ClientInit { x.SystemRole = b.SystemRole x.ForwardedByProxy = b.ForwardedByProxy x.ProxySuppliedParameters = b.ProxySuppliedParameters + x.HostName = b.HostName return m0 } @@ -5076,7 +5098,7 @@ var File_teleport_join_v1_joinservice_proto protoreflect.FileDescriptor const file_teleport_join_v1_joinservice_proto_rawDesc = "" + "\n" + - "\"teleport/join/v1/joinservice.proto\x12\x10teleport.join.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xa0\x03\n" + + "\"teleport/join/v1/joinservice.proto\x12\x10teleport.join.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xbd\x03\n" + "\n" + "ClientInit\x12$\n" + "\vjoin_method\x18\x01 \x01(\tH\x00R\n" + @@ -5086,7 +5108,8 @@ const file_teleport_join_v1_joinservice_proto_rawDesc = "" + "\vsystem_role\x18\x03 \x01(\tR\n" + "systemRole\x12,\n" + "\x12forwarded_by_proxy\x18\x04 \x01(\bR\x10forwardedByProxy\x12q\n" + - "\x19proxy_supplied_parameters\x18\x05 \x01(\v20.teleport.join.v1.ClientInit.ProxySuppliedParamsH\x01R\x17proxySuppliedParameters\x88\x01\x01\x1a]\n" + + "\x19proxy_supplied_parameters\x18\x05 \x01(\v20.teleport.join.v1.ClientInit.ProxySuppliedParamsH\x01R\x17proxySuppliedParameters\x88\x01\x01\x12\x1b\n" + + "\thost_name\x18\x06 \x01(\tR\bhostName\x1a]\n" + "\x13ProxySuppliedParams\x12\x1f\n" + "\vremote_addr\x18\x01 \x01(\tR\n" + "remoteAddr\x12%\n" + diff --git a/api/gen/proto/go/teleport/join/v1/joinservice_protoopaque.pb.go b/api/gen/proto/go/teleport/join/v1/joinservice_protoopaque.pb.go index a793b9b23a5f..b789b64a192d 100644 --- a/api/gen/proto/go/teleport/join/v1/joinservice_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/join/v1/joinservice_protoopaque.pb.go @@ -102,6 +102,7 @@ type ClientInit struct { xxx_hidden_SystemRole string `protobuf:"bytes,3,opt,name=system_role,json=systemRole,proto3"` xxx_hidden_ForwardedByProxy bool `protobuf:"varint,4,opt,name=forwarded_by_proxy,json=forwardedByProxy,proto3"` xxx_hidden_ProxySuppliedParameters *ClientInit_ProxySuppliedParams `protobuf:"bytes,5,opt,name=proxy_supplied_parameters,json=proxySuppliedParameters,proto3,oneof"` + xxx_hidden_HostName string `protobuf:"bytes,6,opt,name=host_name,json=hostName,proto3"` XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_presence [1]uint32 unknownFields protoimpl.UnknownFields @@ -171,9 +172,16 @@ func (x *ClientInit) GetProxySuppliedParameters() *ClientInit_ProxySuppliedParam return nil } +func (x *ClientInit) GetHostName() string { + if x != nil { + return x.xxx_hidden_HostName + } + return "" +} + func (x *ClientInit) SetJoinMethod(v string) { x.xxx_hidden_JoinMethod = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 5) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 6) } func (x *ClientInit) SetTokenName(v string) { @@ -192,6 +200,10 @@ func (x *ClientInit) SetProxySuppliedParameters(v *ClientInit_ProxySuppliedParam x.xxx_hidden_ProxySuppliedParameters = v } +func (x *ClientInit) SetHostName(v string) { + x.xxx_hidden_HostName = v +} + func (x *ClientInit) HasJoinMethod() bool { if x == nil { return false @@ -235,6 +247,11 @@ type ClientInit_builder struct { // accepting ProxySuppliedParams. ForwardedByProxy bool ProxySuppliedParameters *ClientInit_ProxySuppliedParams + // HostName is the user-friendly node name of a joining host, sent early so it + // can be recorded on the join audit event even when the join is rejected + // before the host params are received. It is advisory; the host name used for + // issued certificates is carried in HostParams. + HostName string } func (b0 ClientInit_builder) Build() *ClientInit { @@ -242,13 +259,14 @@ func (b0 ClientInit_builder) Build() *ClientInit { b, x := &b0, m0 _, _ = b, x if b.JoinMethod != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 5) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 6) x.xxx_hidden_JoinMethod = b.JoinMethod } x.xxx_hidden_TokenName = b.TokenName x.xxx_hidden_SystemRole = b.SystemRole x.xxx_hidden_ForwardedByProxy = b.ForwardedByProxy x.xxx_hidden_ProxySuppliedParameters = b.ProxySuppliedParameters + x.xxx_hidden_HostName = b.HostName return m0 } @@ -4877,7 +4895,7 @@ var File_teleport_join_v1_joinservice_proto protoreflect.FileDescriptor const file_teleport_join_v1_joinservice_proto_rawDesc = "" + "\n" + - "\"teleport/join/v1/joinservice.proto\x12\x10teleport.join.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xa0\x03\n" + + "\"teleport/join/v1/joinservice.proto\x12\x10teleport.join.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xbd\x03\n" + "\n" + "ClientInit\x12$\n" + "\vjoin_method\x18\x01 \x01(\tH\x00R\n" + @@ -4887,7 +4905,8 @@ const file_teleport_join_v1_joinservice_proto_rawDesc = "" + "\vsystem_role\x18\x03 \x01(\tR\n" + "systemRole\x12,\n" + "\x12forwarded_by_proxy\x18\x04 \x01(\bR\x10forwardedByProxy\x12q\n" + - "\x19proxy_supplied_parameters\x18\x05 \x01(\v20.teleport.join.v1.ClientInit.ProxySuppliedParamsH\x01R\x17proxySuppliedParameters\x88\x01\x01\x1a]\n" + + "\x19proxy_supplied_parameters\x18\x05 \x01(\v20.teleport.join.v1.ClientInit.ProxySuppliedParamsH\x01R\x17proxySuppliedParameters\x88\x01\x01\x12\x1b\n" + + "\thost_name\x18\x06 \x01(\tR\bhostName\x1a]\n" + "\x13ProxySuppliedParams\x12\x1f\n" + "\vremote_addr\x18\x01 \x01(\tR\n" + "remoteAddr\x12%\n" + diff --git a/api/proto/teleport/join/v1/joinservice.proto b/api/proto/teleport/join/v1/joinservice.proto index c2ffbdf8ea50..0ad5f5fbbec8 100644 --- a/api/proto/teleport/join/v1/joinservice.proto +++ b/api/proto/teleport/join/v1/joinservice.proto @@ -52,6 +52,12 @@ message ClientInit { string client_version = 2; } optional ProxySuppliedParams proxy_supplied_parameters = 5; + + // HostName is the user-friendly node name of a joining host, sent early so it + // can be recorded on the join audit event even when the join is rejected + // before the host params are received. It is advisory; the host name used for + // issued certificates is carried in HostParams. + string host_name = 6; } // PublicKeys holds public keys sent by the client requested subject keys for diff --git a/lib/join/internal/messages/messages.go b/lib/join/internal/messages/messages.go index ca183b3720c4..56037347dfba 100644 --- a/lib/join/internal/messages/messages.go +++ b/lib/join/internal/messages/messages.go @@ -65,6 +65,11 @@ type ClientInit struct { // nodes join via the proxy address. They must only be trusted if the // incoming join request is authenticated as the Proxy. ProxySuppliedParams *ProxySuppliedParams + // HostName is the user-friendly node name of a joining host, sent early so it + // can be recorded on the join audit event even when the join is rejected + // before the host params are received. It is advisory; the host name used for + // issued certificates is carried in HostParams. + HostName string } func (i *ClientInit) Check() error { diff --git a/lib/join/join_test.go b/lib/join/join_test.go index f3bca6500cf2..bb8b11fe1056 100644 --- a/lib/join/join_test.go +++ b/lib/join/join_test.go @@ -168,7 +168,8 @@ func TestJoinToken(t *testing.T) { ConnectionMetadata: apievents.ConnectionMetadata{ RemoteAddr: "127.0.0.1", }, - Role: "Instance", + Role: "Instance", + NodeName: "node", }, evt, protocmp.Transform(), @@ -381,9 +382,10 @@ func TestJoinToken(t *testing.T) { ConnectionMetadata: apievents.ConnectionMetadata{ RemoteAddr: "127.0.0.1", }, - HostID: identity.ID.HostID(), - Role: "Instance", - Roles: slices.DeleteFunc(token1.GetRoles().StringSlice(), func(role string) bool { return role == types.RoleInstance.String() }), + HostID: identity.ID.HostID(), + Role: "Instance", + NodeName: "node", + Roles: slices.DeleteFunc(token1.GetRoles().StringSlice(), func(role string) bool { return role == types.RoleInstance.String() }), }, evt, protocmp.Transform(), @@ -549,6 +551,53 @@ func TestJoinToken(t *testing.T) { tc.assertRejoinExpectation(t, newIdentity, err) }) } + + t.Run("rejected join records sanitized node name", func(t *testing.T) { + ctx := t.Context() + // The join is rejected before host params are received, so the node name + // on the audit event comes from the advisory (untrusted) ClientInit + // value and must be sanitized. \x00 is stripped to '_'. + _, err := joinclient.Join(ctx, joinclient.JoinParams{ + Token: "invalidtoken", + ID: state.IdentityID{ + Role: types.RoleInstance, + NodeName: "node\x00name", + }, + ProxyServer: utils.NetAddr{ + AddrNetwork: proxyListener.Addr().Network(), + Addr: proxyListener.Addr().String(), + }, + // The proxy's TLS cert for the test is not trusted. + Insecure: true, + }) + require.ErrorAs(t, err, new(*trace.AccessDeniedError)) + require.EventuallyWithT(t, func(t *assert.CollectT) { + evt, err := authService.lastEvent(ctx, "instance.join") + require.NoError(t, err) + require.Empty(t, cmp.Diff( + &apievents.InstanceJoin{ + Metadata: apievents.Metadata{ + Type: "instance.join", + Code: events.InstanceJoinFailureCode, + }, + Status: apievents.Status{ + Success: false, + Error: "token expired or not found", + }, + ConnectionMetadata: apievents.ConnectionMetadata{ + RemoteAddr: "127.0.0.1", + }, + Role: "Instance", + NodeName: "node_name", + }, + evt, + protocmp.Transform(), + cmpopts.IgnoreMapEntries(func(key string, val any) bool { + return key == "Time" || key == "ID" + }), + )) + }, 15*time.Second, 100*time.Millisecond, "expected instance.join failed event not found") + }) } // startOutdatedClientJoin sets up an auth service and proxy, opens a Join stream diff --git a/lib/join/joinclient/join.go b/lib/join/joinclient/join.go index 51af8ba1bc50..54b56446786c 100644 --- a/lib/join/joinclient/join.go +++ b/lib/join/joinclient/join.go @@ -263,11 +263,12 @@ func joinWithClient(ctx context.Context, params JoinParams, client *joinv1.Clien defer stream.CloseSend() // Send the ClientInit message with the intended join method, token name, - // and system role. + // system role, and host name. if err := stream.Send(&messages.ClientInit{ JoinMethod: joinMethodPtr, TokenName: params.Token, SystemRole: params.ID.Role.String(), + HostName: params.ID.NodeName, }); err != nil { // Failing to send the first message on the stream is always a connection error. return nil, &connectionError{trace.Wrap(err, "sending ClientInit message")} diff --git a/lib/join/joinv1/messages.go b/lib/join/joinv1/messages.go index 7610233ef11f..6f40ef17f21f 100644 --- a/lib/join/joinv1/messages.go +++ b/lib/join/joinv1/messages.go @@ -155,6 +155,7 @@ func clientInitToMessage(req *joinv1.ClientInit) *messages.ClientInit { TokenName: req.GetTokenName(), SystemRole: req.GetSystemRole(), ForwardedByProxy: req.GetForwardedByProxy(), + HostName: req.GetHostName(), } if joinMethod := req.GetJoinMethod(); joinMethod != "" { msg.JoinMethod = &joinMethod @@ -174,6 +175,7 @@ func clientInitFromMessage(msg *messages.ClientInit) *joinv1.ClientInit { TokenName: msg.TokenName, SystemRole: msg.SystemRole, ForwardedByProxy: msg.ForwardedByProxy, + HostName: msg.HostName, }.Build() if proxySuppliedParams := msg.ProxySuppliedParams; proxySuppliedParams != nil { req.SetProxySuppliedParameters(joinv1.ClientInit_ProxySuppliedParams_builder{ diff --git a/lib/join/server.go b/lib/join/server.go index 3037cce4b838..e1f6c10335e9 100644 --- a/lib/join/server.go +++ b/lib/join/server.go @@ -295,6 +295,12 @@ func (s *Server) Join(stream messages.ServerStream) (err error) { if clientInit.JoinMethod != nil { i.RequestedJoinMethod = joinutils.SanitizeUntrustedString(*clientInit.JoinMethod) } + if clientInit.HostName != "" { + // Sanitizing won't prevent an invalid hostname from being used + // but we don't care about that because the ClientInit.HostName + // is only used for diagnostic purposes. + i.NodeName = joinutils.SanitizeUntrustedString(clientInit.HostName) + } }) if err := clientInit.Check(); err != nil { return trace.Wrap(err, "validating ClientInit message") From 1f1abdcbe208f3d789eb0f9986c0d5792f1f3a23 Mon Sep 17 00:00:00 2001 From: nixpig <143995476+nixpig@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:51:41 +0100 Subject: [PATCH 005/347] Allow GitHub login for users whose roles come exclusively from access lists (#63829) * Allow GitHub login for users whose roles come exclusively from access lists * Update component test expectation * chore: update design system to v0.2.0 (#68515) * Add suggested nit changes --------- Co-authored-by: Nibras Ohin --- lib/auth/github.go | 23 +- lib/auth/github_test.go | 333 +++++++++++++++++- lib/web/apiserver.go | 2 +- .../teleport/src/Login/LoginFailed.test.tsx | 2 +- .../teleport/src/Login/LoginFailed.tsx | 2 +- 5 files changed, 350 insertions(+), 12 deletions(-) diff --git a/lib/auth/github.go b/lib/auth/github.go index 1af3ce97655d..4dff33a3a638 100644 --- a/lib/auth/github.go +++ b/lib/auth/github.go @@ -50,8 +50,8 @@ import ( "github.com/gravitational/teleport/lib/utils" ) -// ErrGithubNoTeams results from a github user not belonging to any teams. -var ErrGithubNoTeams = trace.BadParameter("user does not belong to any teams configured in connector; the configuration may have typos.") +// ErrGithubNoRoles is returned when a user authenticated via GitHub has no roles assigned in Teleport. +var ErrGithubNoRoles = trace.AccessDenied("GitHub user has no Teleport-assigned roles.") // InvalidClientRedirectErrorMessage is a string added to SSO login errors // caused by an invalid client redirect URL; the presence of this string is @@ -644,6 +644,10 @@ func (a *Server) ValidateGithubAuthRedirect(ctx context.Context, diagCtx *SSODia return nil, trace.Wrap(err) } + if len(userState.GetRoles()) == 0 { + return nil, trace.Wrap(ErrGithubNoRoles) + } + // In test flow skip signing and creating web sessions. if req.SSOTestFlow { diagCtx.Info.Success = true @@ -654,8 +658,15 @@ func (a *Server) ValidateGithubAuthRedirect(ctx context.Context, diagCtx *SSODia }, nil } + // Fetch the roles so the session TTL can be pre-constrained in the case no GitHub role is found. + sessionRoles, err := services.FetchRoles(userState.GetRoles(), a, userState.GetTraits()) + if err != nil { + return nil, trace.Wrap(err) + } + sessionTTL := utils.MinTTL(sessionRoles.AdjustSessionTTL(apidefaults.MaxCertDuration), params.SessionTTL) + // Auth was successful, return session, certificate, etc. to caller. - return a.makeGithubAuthResponse(ctx, req, userState, userResp, params.SessionTTL) + return a.makeGithubAuthResponse(ctx, req, userState, userResp, sessionTTL) } func (a *Server) makeGithubAuthResponse( @@ -663,7 +674,8 @@ func (a *Server) makeGithubAuthResponse( req *types.GithubAuthRequest, userState services.UserState, githubUser *GithubUserResponse, - sessionTTL time.Duration) (*authclient.GithubAuthResponse, error) { + sessionTTL time.Duration, +) (*authclient.GithubAuthResponse, error) { auth := authclient.GithubAuthResponse{ Req: GithubAuthRequestFromProto(req), Identity: githubUser.makeExternalIdentity(req.ConnectorID), @@ -910,9 +922,6 @@ func (a *Server) calculateGithubUser(ctx context.Context, diagCtx *SSODiagContex // Calculate logins, kubegroups, roles, and traits. p.Roles, p.KubeGroups, p.KubeUsers = connector.MapClaims(*claims) - if len(p.Roles) == 0 { - return nil, trace.Wrap(ErrGithubNoTeams) - } p.Traits = map[string][]string{ constants.TraitLogins: {p.Username}, constants.TraitKubeGroups: p.KubeGroups, diff --git a/lib/auth/github_test.go b/lib/auth/github_test.go index bd6611af82b1..865f361ec67b 100644 --- a/lib/auth/github_test.go +++ b/lib/auth/github_test.go @@ -37,7 +37,10 @@ import ( "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/types/header" + "github.com/gravitational/teleport/entitlements" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/authtest" @@ -62,6 +65,8 @@ type githubContext struct { c *clockwork.FakeClock } +// TODO(nixpig): Refactor to avoid using global modules. +// See: https://github.com/gravitational/teleport/pull/63829#discussion_r3646729631 func setupGithubContext(t *testing.T) *githubContext { var tt githubContext t.Cleanup(func() { tt.Close() }) @@ -312,7 +317,7 @@ func TestCalculateGithubUserNoTeams(t *testing.T) { diagCtx := &auth.SSODiagContext{} - _, err = a.CalculateGithubUser(ctx, diagCtx, connector, &types.GithubClaims{ + params, err := a.CalculateGithubUser(ctx, diagCtx, connector, &types.GithubClaims{ Username: "octocat", OrganizationToTeams: map[string][]string{ "org1": {"team1", "team2"}, @@ -320,7 +325,8 @@ func TestCalculateGithubUserNoTeams(t *testing.T) { }, Teams: []string{"team1", "team2", "team1"}, }, &types.GithubAuthRequest{}) - require.ErrorIs(t, err, auth.ErrGithubNoTeams) + require.NoError(t, err) + require.Empty(t, params.Roles) } // Test that calculateGithubUser calls the login rule evaluator, evaluated @@ -620,3 +626,326 @@ func TestBuildAPIEndpoint(t *testing.T) { }) } } + +func TestGithubUserRoleMappingCombinations(t *testing.T) { + modulestest.SetTestModules(t, modulestest.Modules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ + entitlements.Identity: {Enabled: true}, + }, + }, + }) + + ctx := t.Context() + tt := setupGithubContext(t) + + accessRole, err := types.NewRole("access", types.RoleSpecV6{}) + require.NoError(t, err) + + _, err = tt.a.CreateRole(ctx, accessRole) + require.NoError(t, err) + + connector, err := types.NewGithubConnector("github", types.GithubConnectorSpecV3{ + TeamsToRoles: []types.TeamRolesMapping{{ + Organization: "org1", + Team: "teamx", + Roles: []string{"access"}, + }}, + }) + require.NoError(t, err) + + tests := []struct { + testName string + claims *types.GithubClaims + setup func(t *testing.T, username string) + assertion func(t *testing.T, userState services.UserState, err error) + }{ + { + testName: "SSO role mapping and access list role mapping", + claims: &types.GithubClaims{ + Username: "testuser1", + OrganizationToTeams: map[string][]string{"org1": {"teamx"}}, + Teams: []string{"teamx"}, + }, + setup: func(t *testing.T, username string) { + acl, err := accesslist.NewAccessList( + header.Metadata{Name: "test-acl-user1"}, + accesslist.Spec{ + Title: "title", + Owners: []accesslist.Owner{{Name: "owner"}}, + Audit: accesslist.Audit{NextAuditDate: time.Now().Add(24 * time.Hour)}, + MembershipRequires: accesslist.Requires{}, + OwnershipRequires: accesslist.Requires{}, + Grants: accesslist.Grants{Roles: []string{"access"}}, + }, + ) + require.NoError(t, err) + + member, err := accesslist.NewAccessListMember( + header.Metadata{Name: username}, + accesslist.AccessListMemberSpec{ + AccessList: acl.GetName(), + Name: username, + Joined: time.Now(), + Expires: time.Now().Add(24 * time.Hour), + Reason: "test", + AddedBy: "admin", + }, + ) + require.NoError(t, err) + + _, _, err = tt.a.UpsertAccessListWithMembers(ctx, acl, []*accesslist.AccessListMember{member}) + require.NoError(t, err) + + t.Cleanup(func() { + tt.a.DeleteAccessList(ctx, acl.GetName()) + }) + }, + assertion: func(t *testing.T, userState services.UserState, err error) { + require.NoError(t, err) + require.Contains(t, userState.GetRoles(), "access") + }, + }, + { + testName: "SSO role mapping and no access list role mapping", + claims: &types.GithubClaims{ + Username: "testuser2", + OrganizationToTeams: map[string][]string{"org1": {"teamx"}}, + Teams: []string{"teamx"}, + }, + assertion: func(t *testing.T, userState services.UserState, err error) { + require.NoError(t, err) + require.Contains(t, userState.GetRoles(), "access") + }, + }, + { + testName: "no SSO role mapping and access list role mapping", + claims: &types.GithubClaims{ + Username: "testuser3", + OrganizationToTeams: map[string][]string{"org1": {"other-team"}}, + Teams: []string{"other-team"}, + }, + setup: func(t *testing.T, username string) { + acl, err := accesslist.NewAccessList( + header.Metadata{Name: "test-acl-user3"}, + accesslist.Spec{ + Title: "title", + Owners: []accesslist.Owner{{Name: "owner"}}, + Audit: accesslist.Audit{ + NextAuditDate: time.Now().Add(365 * 24 * time.Hour), + }, + MembershipRequires: accesslist.Requires{}, + OwnershipRequires: accesslist.Requires{}, + Grants: accesslist.Grants{Roles: []string{"access"}}, + }, + ) + require.NoError(t, err) + member, err := accesslist.NewAccessListMember( + header.Metadata{Name: username}, + accesslist.AccessListMemberSpec{ + AccessList: acl.GetName(), + Name: username, + Joined: time.Now(), + Expires: time.Now().Add(24 * time.Hour), + Reason: "test", + AddedBy: "admin", + }, + ) + require.NoError(t, err) + + _, _, err = tt.a.UpsertAccessListWithMembers(ctx, acl, []*accesslist.AccessListMember{member}) + require.NoError(t, err) + + t.Cleanup(func() { + tt.a.DeleteAccessList(ctx, acl.GetName()) + }) + }, + assertion: func(t *testing.T, userState services.UserState, err error) { + require.NoError(t, err) + require.Contains(t, userState.GetRoles(), "access") + }, + }, + { + testName: "no SSO role mapping and no access list role mapping", + claims: &types.GithubClaims{ + Username: "testuser4", + OrganizationToTeams: map[string][]string{"org1": {"other-team"}}, + Teams: []string{"other-team"}, + }, + assertion: func(t *testing.T, userState services.UserState, err error) { + require.NoError(t, err) + require.Empty(t, userState.GetRoles()) + }, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + diagCtx := &auth.SSODiagContext{} + + params, err := tt.a.CalculateGithubUser( + ctx, + diagCtx, + connector, + test.claims, + &types.GithubAuthRequest{}, + ) + require.NoError(t, err) + + user, err := tt.a.CreateGithubUser(ctx, params, false) + require.NoError(t, err) + + if test.setup != nil { + test.setup(t, user.GetName()) + } + + require.NoError(t, tt.a.CallLoginHooks(ctx, user)) + + userState, err := tt.a.GetUserOrLoginState(ctx, user.GetName()) + + test.assertion(t, userState, err) + }) + } +} + +func TestGithubCallbackRoleValidation(t *testing.T) { + modulestest.SetTestModules(t, modulestest.Modules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ + entitlements.Identity: {Enabled: true}, + }, + }, + }) + + srv := newTestTLSServer(t) + ctx := t.Context() + + connector, err := types.NewGithubConnector("github", types.GithubConnectorSpecV3{ + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + RedirectURL: "https://example.com/v1/webapi/github/callback", + Display: "Sign in with GitHub", + TeamsToRoles: []types.TeamRolesMapping{ + { + Organization: "org1", + Team: "teamx", + Roles: []string{"access"}, + }, + }, + }) + require.NoError(t, err) + + a := srv.Auth() + a.GithubUserAndTeamsOverride = func() (*auth.GithubUserResponse, []auth.GithubTeamResponse, error) { + return &auth.GithubUserResponse{ + Login: "testuser1", + }, []auth.GithubTeamResponse{{ + Name: "other-team", + Slug: "other-team", + Org: auth.GithubOrgResponse{Login: "org1"}, + }}, nil + } + + _, err = auth.UpsertGithubConnector(ctx, a, connector) + require.NoError(t, err) + + _, err = authtest.CreateRole(ctx, a, "access", types.RoleSpecV6{ + Options: types.RoleOptions{ + // MaxSessionTTL less than default MinCertDuration to ensure session + // duration in constrained. + MaxSessionTTL: types.Duration(defaults.MinCertDuration / 2), + }, + }) + require.NoError(t, err) + + proxyClient, err := srv.NewClient(authtest.TestBuiltin(types.RoleProxy)) + require.NoError(t, err) + + tests := []struct { + testName string + createWebSession bool + setup func(t *testing.T) + assertion func(t *testing.T, resp *authclient.GithubAuthResponse, userState services.UserState, err error) + }{ + { + testName: "user with no roles", + assertion: func(t *testing.T, resp *authclient.GithubAuthResponse, userState services.UserState, err error) { + require.Nil(t, resp) + require.Empty(t, userState.GetRoles()) + require.ErrorIs(t, err, auth.ErrGithubNoRoles) + }, + }, + { + testName: "user with roles from ACL", + createWebSession: true, + setup: func(t *testing.T) { + acl, err := accesslist.NewAccessList( + header.Metadata{Name: "test-acl-user1"}, + accesslist.Spec{ + Title: "title", + Owners: []accesslist.Owner{{Name: "owner"}}, + Audit: accesslist.Audit{NextAuditDate: time.Now().Add(24 * time.Hour)}, + MembershipRequires: accesslist.Requires{}, + OwnershipRequires: accesslist.Requires{}, + Grants: accesslist.Grants{Roles: []string{"access"}}, + }, + ) + require.NoError(t, err) + + member, err := accesslist.NewAccessListMember( + header.Metadata{Name: "testuser1"}, + accesslist.AccessListMemberSpec{ + AccessList: acl.GetName(), + Name: "testuser1", + Joined: time.Now(), + Expires: time.Now().Add(24 * time.Hour), + Reason: "test", + AddedBy: "admin", + }, + ) + require.NoError(t, err) + + _, _, err = a.UpsertAccessListWithMembers(ctx, acl, []*accesslist.AccessListMember{member}) + require.NoError(t, err) + + t.Cleanup(func() { + a.DeleteAccessList(ctx, acl.GetName()) + }) + }, + assertion: func(t *testing.T, resp *authclient.GithubAuthResponse, userState services.UserState, err error) { + require.NotNil(t, resp) + require.NoError(t, err) + require.Contains(t, userState.GetRoles(), "access") + require.WithinDuration(t, time.Now().Add(defaults.MinCertDuration/2), resp.Session.GetExpiryTime(), time.Second*5) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + req, err := proxyClient.CreateGithubAuthRequest(ctx, types.GithubAuthRequest{ + ConnectorID: connector.GetName(), + Type: constants.Github, + CertTTL: defaults.MinCertDuration, + CreateWebSession: tt.createWebSession, + }) + require.NoError(t, err) + + if tt.setup != nil { + tt.setup(t) + } + + resp, err := proxyClient.ValidateGithubAuthCallback(ctx, url.Values{ + "code": []string{"test-code"}, + "state": []string{req.StateToken}, + }) + + userState, userErr := a.GetUserOrLoginState(ctx, "testuser1") + require.NoError(t, userErr) + + tt.assertion(t, resp, userState, err) + }) + } +} diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 0d2cf38a156a..f316e06d2570 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -2569,7 +2569,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr } } } - if errors.Is(err, auth.ErrGithubNoTeams) { + if errors.Is(err, auth.ErrGithubNoRoles) { return sso.LoginFailedUnauthorizedRedirectURL } diff --git a/web/packages/teleport/src/Login/LoginFailed.test.tsx b/web/packages/teleport/src/Login/LoginFailed.test.tsx index a34b9f2e4c08..d4d2bf7f8e85 100644 --- a/web/packages/teleport/src/Login/LoginFailed.test.tsx +++ b/web/packages/teleport/src/Login/LoginFailed.test.tsx @@ -56,7 +56,7 @@ test('unauthorized error path shows unauthorized error', () => { ); expect( screen.getByText( - 'You are not authorized, please contact your SSO administrator.' + 'You are not authorized. Please contact your Teleport administrator.' ) ).toBeInTheDocument(); }); diff --git a/web/packages/teleport/src/Login/LoginFailed.tsx b/web/packages/teleport/src/Login/LoginFailed.tsx index 64f67a9b3dcc..673106de7f3d 100644 --- a/web/packages/teleport/src/Login/LoginFailed.tsx +++ b/web/packages/teleport/src/Login/LoginFailed.tsx @@ -32,7 +32,7 @@ export function LoginFailed() { - + From b8a5ee291b5dd6219771322a3c39b6d81fe0733a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:30:40 +0200 Subject: [PATCH 006/347] Add missing members to `events.OSType` (#69058) --- .../teleport/legacy/types/events/events.proto | 4 + api/types/events/events.pb.go | 164 ++++++++++-------- lib/devicetrust/proto_test.go | 36 ++++ 3 files changed, 127 insertions(+), 77 deletions(-) create mode 100644 lib/devicetrust/proto_test.go diff --git a/api/proto/teleport/legacy/types/events/events.proto b/api/proto/teleport/legacy/types/events/events.proto index caf6a7f5f73c..4b64e1005987 100644 --- a/api/proto/teleport/legacy/types/events/events.proto +++ b/api/proto/teleport/legacy/types/events/events.proto @@ -4715,6 +4715,10 @@ enum OSType { OS_TYPE_MACOS = 2; // Windows. OS_TYPE_WINDOWS = 3; + // iOS. + OS_TYPE_IOS = 4; + // iPadOS. + OS_TYPE_IPADOS = 5; } // DeviceOrigin is the same as teleport.devicetrust.v1.DeviceOrigin. diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index a74117284c0f..97e2c676a46d 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -272,6 +272,10 @@ const ( OSType_OS_TYPE_MACOS OSType = 2 // Windows. OSType_OS_TYPE_WINDOWS OSType = 3 + // iOS. + OSType_OS_TYPE_IOS OSType = 4 + // iPadOS. + OSType_OS_TYPE_IPADOS OSType = 5 ) var OSType_name = map[int32]string{ @@ -279,6 +283,8 @@ var OSType_name = map[int32]string{ 1: "OS_TYPE_LINUX", 2: "OS_TYPE_MACOS", 3: "OS_TYPE_WINDOWS", + 4: "OS_TYPE_IOS", + 5: "OS_TYPE_IPADOS", } var OSType_value = map[string]int32{ @@ -286,6 +292,8 @@ var OSType_value = map[string]int32{ "OS_TYPE_LINUX": 1, "OS_TYPE_MACOS": 2, "OS_TYPE_WINDOWS": 3, + "OS_TYPE_IOS": 4, + "OS_TYPE_IPADOS": 5, } func (x OSType) String() string { @@ -22121,7 +22129,7 @@ func init() { } var fileDescriptor_007ba1c3d6266d56 = []byte{ - // 25360 bytes of a gzipped FileDescriptorProto + // 25381 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x7b, 0x7c, 0x24, 0xc7, 0x79, 0x18, 0x88, 0x79, 0x60, 0x00, 0x7c, 0x78, 0xd7, 0xbe, 0x86, 0xcb, 0x25, 0x87, 0x6c, 0x92, 0xab, 0x5d, 0x89, 0xdc, 0x25, 0x97, 0x0f, 0x89, 0x22, 0x25, 0x72, 0x00, 0x2c, 0x08, 0xec, 0x2e, @@ -23631,82 +23639,84 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x3f, 0xd4, 0x44, 0x5f, 0x41, 0x71, 0xc5, 0x28, 0xfc, 0x09, 0x22, 0xb8, 0xec, 0x82, 0x25, 0xe0, 0xe8, 0x47, 0xf8, 0x57, 0xe1, 0x54, 0x9d, 0xba, 0x22, 0x9a, 0x87, 0x86, 0xaf, 0xcf, 0x93, 0x15, 0x0d, 0x5e, 0x79, 0x4c, 0xfe, 0x7a, 0x41, 0x94, 0xf8, 0x76, 0xcc, 0x97, 0x92, 0xca, 0x45, 0x7a, - 0xea, 0xc9, 0x7a, 0x92, 0x83, 0xa7, 0xfd, 0xf1, 0xff, 0xcf, 0xde, 0xf5, 0xf4, 0xb6, 0x51, 0x44, - 0xf1, 0xae, 0x1d, 0xbb, 0xee, 0x73, 0x42, 0x26, 0x93, 0x86, 0x26, 0x6d, 0xe3, 0xb6, 0xa6, 0xe5, - 0x8f, 0x85, 0x54, 0x54, 0x04, 0x12, 0x12, 0x07, 0xc6, 0xbb, 0xb3, 0xf1, 0x90, 0xfd, 0x63, 0x76, - 0xd6, 0x49, 0x8d, 0x90, 0x56, 0x1b, 0x67, 0x5b, 0x2f, 0xb8, 0xb6, 0xb5, 0xeb, 0xa6, 0x70, 0x47, - 0xe2, 0x2b, 0x20, 0x0e, 0x5c, 0xf8, 0x0e, 0x9c, 0x39, 0xf6, 0xc8, 0x27, 0x40, 0x90, 0x23, 0xdf, - 0x80, 0x0b, 0x42, 0x33, 0xbb, 0x8e, 0x77, 0xad, 0x38, 0x52, 0xe0, 0x52, 0x94, 0xdc, 0x66, 0x7e, - 0xef, 0xf7, 0x66, 0xde, 0xcc, 0xbc, 0xb5, 0xbd, 0xde, 0xf7, 0xde, 0x2a, 0x70, 0xeb, 0x8c, 0xa1, - 0x71, 0x17, 0xaa, 0x19, 0xbd, 0xd4, 0x07, 0x6f, 0x4f, 0x8d, 0x7a, 0xfc, 0xc1, 0x7b, 0x1f, 0x2d, - 0x38, 0x89, 0x99, 0x52, 0x6e, 0xe5, 0x33, 0x18, 0xef, 0x40, 0xa9, 0xd7, 0xf7, 0xc3, 0xe1, 0x66, - 0x41, 0xae, 0xf4, 0xec, 0x41, 0x93, 0xe0, 0x28, 0x41, 0xcf, 0x06, 0x47, 0x49, 0x00, 0x3f, 0x82, - 0xca, 0x61, 0x18, 0xfb, 0x07, 0x83, 0x20, 0xc9, 0xd1, 0xab, 0x24, 0xe1, 0x66, 0x53, 0x2c, 0x1b, - 0x6e, 0x36, 0xc5, 0xea, 0x7f, 0x2b, 0x70, 0xfd, 0xb4, 0x89, 0xf0, 0xbb, 0x50, 0x96, 0x65, 0xac, - 0xa3, 0x6c, 0xc5, 0xad, 0x04, 0xc9, 0x1e, 0x7e, 0x82, 0xe0, 0x87, 0x70, 0x35, 0x7e, 0x7e, 0xf0, - 0x65, 0xd0, 0x9b, 0xa4, 0xe7, 0x2e, 0x9d, 0x25, 0x85, 0xb2, 0xc1, 0x3d, 0x29, 0x84, 0x3f, 0x81, - 0x95, 0x38, 0x88, 0x42, 0x7f, 0xe0, 0x0d, 0x9f, 0x3f, 0x3b, 0x48, 0x3f, 0xb0, 0xd2, 0x90, 0xbb, - 0x9c, 0x20, 0x1b, 0x72, 0x97, 0x08, 0x2c, 0x89, 0x63, 0x0a, 0xab, 0xb3, 0xa8, 0x61, 0xaf, 0xef, - 0xc7, 0xfd, 0x34, 0x94, 0x49, 0xbe, 0x28, 0x61, 0x4e, 0x94, 0x0d, 0xe9, 0x1a, 0x4f, 0x43, 0x8a, - 0x5b, 0x7e, 0xdc, 0xaf, 0x7f, 0x57, 0x80, 0xb5, 0x66, 0xe0, 0x3f, 0x8b, 0x2f, 0xfc, 0xc3, 0xdf, - 0xb9, 0x9d, 0xb8, 0xc0, 0x4f, 0x7f, 0xe7, 0x76, 0xe2, 0x02, 0x3f, 0xfe, 0xfd, 0xb1, 0x08, 0x48, - 0x1d, 0xf8, 0x71, 0x1c, 0x3e, 0x09, 0x2f, 0x9f, 0xfc, 0xbe, 0x82, 0x3f, 0x44, 0xf3, 0x07, 0x74, - 0x79, 0xa7, 0xf0, 0xca, 0x1d, 0x90, 0x7c, 0xff, 0xce, 0xc9, 0x01, 0x5d, 0xde, 0x25, 0xfc, 0xa7, - 0xbb, 0x84, 0x5f, 0x0a, 0xb0, 0x2c, 0x33, 0x2f, 0xf4, 0x51, 0xf4, 0xc2, 0x8f, 0xfe, 0xef, 0xa5, - 0x63, 0x67, 0x9b, 0xb0, 0x74, 0xbe, 0xe0, 0xef, 0x7f, 0x5f, 0x2f, 0xb6, 0xfe, 0x6d, 0x09, 0x6e, - 0x92, 0xf1, 0xb4, 0x7e, 0x8a, 0xeb, 0x47, 0x4f, 0x83, 0x89, 0x16, 0xfa, 0x03, 0x2d, 0x18, 0x86, - 0xc1, 0x85, 0x2c, 0x3c, 0x92, 0x66, 0xe0, 0x97, 0xce, 0x95, 0x81, 0xff, 0x10, 0x20, 0xd9, 0xbb, - 0xd6, 0x28, 0x9e, 0xa4, 0x01, 0xed, 0xab, 0x7f, 0xfe, 0x76, 0xa7, 0x3a, 0x91, 0xa8, 0xd7, 0x1f, - 0xc5, 0x13, 0x27, 0x43, 0x99, 0x29, 0xb4, 0x47, 0xd1, 0x24, 0x4d, 0x7f, 0xcb, 0x2a, 0x8c, 0x47, - 0xd1, 0x89, 0x82, 0xa0, 0xe0, 0x8f, 0xa1, 0x2a, 0xae, 0xd7, 0xc1, 0x51, 0x70, 0xc8, 0xda, 0xf1, - 0x66, 0x65, 0x16, 0x9e, 0x1d, 0xa5, 0xb0, 0x17, 0xe6, 0xaa, 0x4b, 0x66, 0xe9, 0xb8, 0x0e, 0xe5, - 0xe4, 0x2f, 0xd3, 0x34, 0x75, 0x52, 0x26, 0x07, 0x8f, 0x25, 0xe2, 0xa4, 0x12, 0xfc, 0x21, 0x5c, - 0x6b, 0x0e, 0x46, 0xbd, 0xaf, 0x84, 0x46, 0x52, 0xf0, 0x26, 0xc9, 0x7a, 0x3a, 0x48, 0x40, 0x2f, - 0x1c, 0x67, 0xb3, 0x9e, 0x4e, 0xa8, 0xb8, 0x09, 0x2b, 0x69, 0xa7, 0x1d, 0x05, 0x4f, 0xc2, 0xaf, - 0x93, 0x02, 0x38, 0x49, 0x26, 0xc5, 0x54, 0x77, 0x2c, 0x25, 0xd9, 0x9f, 0xdd, 0x39, 0x95, 0x46, - 0x2f, 0x79, 0xdf, 0xd0, 0x6e, 0x38, 0x3c, 0xc4, 0x5b, 0xb0, 0xd1, 0xe1, 0xd4, 0xf1, 0x76, 0x99, - 0xa5, 0x79, 0x1d, 0x8b, 0xb7, 0xa9, 0xca, 0x74, 0x46, 0x35, 0x74, 0x05, 0xaf, 0xc3, 0xea, 0x4c, - 0xd4, 0xea, 0x98, 0xc4, 0x42, 0x0a, 0x5e, 0x83, 0x95, 0x19, 0xd8, 0xb4, 0x5d, 0x54, 0xc0, 0xd7, - 0x01, 0xcd, 0x20, 0xde, 0xe5, 0x2e, 0x35, 0x51, 0xb1, 0xf1, 0x83, 0x02, 0x20, 0x66, 0x49, 0x72, - 0xcb, 0xf0, 0x2d, 0xb8, 0x21, 0x49, 0xb6, 0xc3, 0x76, 0x98, 0x35, 0x37, 0xd3, 0x06, 0xac, 0x65, - 0x85, 0x86, 0xad, 0x12, 0x03, 0x29, 0x27, 0x06, 0xa4, 0x30, 0xe7, 0x76, 0x66, 0xb6, 0x14, 0xb4, - 0x77, 0x5d, 0x82, 0x8a, 0xf3, 0x28, 0x57, 0x99, 0x89, 0x96, 0xf0, 0x0d, 0x58, 0xcf, 0xa2, 0xd4, - 0x72, 0x1d, 0xc2, 0x34, 0x54, 0x6a, 0xbc, 0x05, 0x55, 0xf9, 0xdf, 0x46, 0x5a, 0xb3, 0x78, 0x19, - 0x2a, 0x76, 0x93, 0x53, 0x67, 0x4f, 0x5a, 0x03, 0x50, 0xd6, 0xa8, 0x25, 0x2c, 0x53, 0x1a, 0x11, - 0x54, 0x4d, 0x9d, 0xe8, 0x83, 0xd1, 0x0b, 0x79, 0x9f, 0xbd, 0x0d, 0x5b, 0xa6, 0x4e, 0x3c, 0xdd, - 0xb0, 0xf7, 0x3d, 0xb7, 0xdb, 0xa6, 0x73, 0xeb, 0x78, 0x07, 0x1e, 0xe4, 0xc5, 0x6d, 0xea, 0x78, - 0x9c, 0x72, 0xce, 0x6c, 0xcb, 0x53, 0xa9, 0xe3, 0x32, 0x9d, 0xa9, 0xc4, 0xa5, 0x48, 0x11, 0xfb, - 0x9e, 0xa7, 0x32, 0xcb, 0x6b, 0x12, 0x4b, 0x43, 0x85, 0xc6, 0x5f, 0x0a, 0x00, 0xd7, 0xdd, 0x76, - 0x6a, 0x5c, 0x15, 0xae, 0x32, 0x6b, 0x8f, 0x18, 0x4c, 0xcc, 0x50, 0x81, 0x25, 0xbb, 0x4d, 0xc5, - 0x41, 0x5c, 0x83, 0x92, 0x6a, 0xd8, 0x9c, 0xa2, 0x82, 0x00, 0x1d, 0x4a, 0x34, 0x54, 0x14, 0xe0, - 0xbe, 0xc3, 0x5c, 0x8a, 0x96, 0x44, 0xd3, 0xe0, 0x2e, 0x71, 0x51, 0x49, 0x34, 0x75, 0xd9, 0x2c, - 0x8b, 0xc1, 0x38, 0x75, 0x65, 0xe7, 0xaa, 0x58, 0xb6, 0x3e, 0xed, 0x55, 0x84, 0x48, 0x0c, 0xad, - 0x31, 0x07, 0x5d, 0x13, 0x1d, 0x31, 0xa4, 0xe8, 0x80, 0xd8, 0x10, 0x87, 0x9a, 0xf6, 0x1e, 0x45, - 0x55, 0x31, 0x96, 0xb9, 0x2b, 0xe0, 0x65, 0xd1, 0x74, 0x4c, 0xd1, 0x5c, 0x11, 0x23, 0x39, 0x94, - 0x18, 0x6d, 0xe2, 0xb6, 0xd0, 0x6b, 0xc2, 0x1e, 0x39, 0xe6, 0x6a, 0xa2, 0x69, 0x11, 0x93, 0x22, - 0x94, 0x72, 0x34, 0x83, 0x59, 0xbb, 0x68, 0x4d, 0x1a, 0xd2, 0x35, 0x65, 0x07, 0x0b, 0x05, 0xd9, - 0x5a, 0x6f, 0x7c, 0x01, 0x65, 0x9b, 0xcb, 0xad, 0xbe, 0x01, 0xeb, 0x36, 0x3f, 0x6d, 0x93, 0xd7, - 0x60, 0x65, 0x2a, 0x30, 0x98, 0xd5, 0x79, 0x9c, 0x38, 0xe5, 0x14, 0x32, 0x89, 0x6a, 0x73, 0x54, - 0x10, 0xbe, 0x33, 0x85, 0xf6, 0x99, 0xa5, 0xd9, 0xfb, 0x1c, 0x15, 0x1b, 0x47, 0xb0, 0xac, 0x05, - 0x47, 0x61, 0x2f, 0x48, 0x9d, 0x72, 0x1b, 0xb6, 0x34, 0xba, 0xc7, 0x54, 0xba, 0xd0, 0x2d, 0xf3, - 0x62, 0xd2, 0x66, 0x48, 0xc1, 0xaf, 0x03, 0xce, 0xc3, 0x9f, 0x12, 0x53, 0x47, 0x05, 0xbc, 0x09, - 0xd7, 0xf3, 0x38, 0xb3, 0xdc, 0x8e, 0x45, 0x51, 0xb1, 0xf1, 0x93, 0x02, 0x1b, 0x74, 0xe0, 0xc7, - 0x93, 0xb0, 0x17, 0x07, 0x7e, 0xd4, 0xeb, 0xab, 0xfe, 0x24, 0x78, 0x3a, 0x8a, 0xbe, 0xc1, 0x75, - 0xa8, 0x51, 0x83, 0x70, 0x97, 0xa9, 0x9c, 0x12, 0x47, 0x6d, 0x79, 0xc2, 0x3d, 0x76, 0x6c, 0xa7, - 0xeb, 0xed, 0x50, 0x8b, 0x3a, 0xc4, 0x40, 0x57, 0xf0, 0x1b, 0x70, 0x67, 0x01, 0x87, 0x53, 0xb5, - 0xe3, 0x30, 0xb7, 0x8b, 0x14, 0x7c, 0x0f, 0xb6, 0x17, 0x92, 0x44, 0x1f, 0x15, 0x70, 0x0d, 0x6e, - 0x2e, 0xa2, 0x7c, 0x66, 0xa0, 0x62, 0xe3, 0x7b, 0x05, 0xb0, 0x3d, 0x0e, 0x86, 0x3c, 0x6f, 0xe2, - 0x5d, 0xb8, 0x2d, 0xfc, 0xc2, 0x5b, 0x6c, 0xe0, 0x3d, 0xd8, 0x3e, 0x95, 0x91, 0x31, 0xef, 0x0e, - 0xdc, 0x5a, 0x40, 0x49, 0x8d, 0xbb, 0x0d, 0x9b, 0xa7, 0x13, 0xa4, 0x69, 0x3f, 0x2b, 0xb0, 0x41, - 0x0e, 0x9f, 0x85, 0xc3, 0xe4, 0x9a, 0x88, 0x4d, 0x9d, 0xa4, 0x5f, 0xc8, 0x6f, 0xc3, 0x7d, 0xa2, - 0x99, 0xe2, 0x6c, 0x54, 0x97, 0xd9, 0x16, 0xf7, 0xc4, 0x55, 0x25, 0xbc, 0xaf, 0xc3, 0xe7, 0x4e, - 0xf3, 0x4d, 0xa8, 0x9f, 0xc1, 0x54, 0x5b, 0xc4, 0xda, 0x11, 0x97, 0x3c, 0xbe, 0x0f, 0x77, 0x17, - 0xf2, 0xa8, 0x45, 0x9a, 0x06, 0xd5, 0x50, 0x01, 0x3f, 0x80, 0x7b, 0x0b, 0x59, 0x1a, 0xe3, 0x09, - 0xad, 0xd8, 0xf0, 0x73, 0x2f, 0xf4, 0x11, 0xab, 0x54, 0x6d, 0xcb, 0x25, 0xaa, 0x7b, 0x9a, 0x67, - 0x6f, 0xc1, 0x46, 0x4e, 0xda, 0xec, 0x70, 0x66, 0x51, 0xce, 0x93, 0x8f, 0x8b, 0x9c, 0xe8, 0x64, - 0x6b, 0x0b, 0x4d, 0xed, 0xe5, 0x1f, 0xb5, 0x2b, 0x2f, 0x8f, 0x6b, 0xca, 0xaf, 0xc7, 0x35, 0xe5, - 0xf7, 0xe3, 0x9a, 0xf2, 0xf9, 0xa3, 0xf3, 0x54, 0x7f, 0x48, 0xbe, 0x72, 0x0f, 0xca, 0xf2, 0x57, - 0xf6, 0xfb, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x12, 0x31, 0x72, 0x1f, 0x2a, 0x41, 0x02, 0x00, + 0xea, 0xc9, 0x7a, 0x92, 0x83, 0xa7, 0xfd, 0xf1, 0xff, 0xcf, 0xde, 0xd5, 0xec, 0xb6, 0x6d, 0x3c, + 0xf1, 0x50, 0xb2, 0x14, 0x65, 0x64, 0xc7, 0xeb, 0x75, 0xfc, 0x8f, 0x9d, 0xc4, 0x4a, 0xa2, 0x7f, + 0xd2, 0x0f, 0xa1, 0x40, 0x8a, 0x14, 0x2d, 0x50, 0xa0, 0x87, 0xae, 0xc8, 0xa5, 0xb5, 0x35, 0x3f, + 0x54, 0x2e, 0x65, 0x47, 0xbd, 0x10, 0xb4, 0xcc, 0x44, 0x6c, 0x15, 0x49, 0x20, 0x95, 0xa4, 0x3d, + 0x16, 0x28, 0xd0, 0x57, 0x28, 0x7a, 0xe8, 0xa5, 0xef, 0xd0, 0x73, 0x8f, 0x39, 0xf6, 0x09, 0x8a, + 0x36, 0xc7, 0xbe, 0x41, 0x2f, 0x45, 0xb1, 0x4b, 0x4a, 0x22, 0x05, 0xcb, 0x80, 0xdb, 0x4b, 0x0a, + 0xfb, 0xb6, 0xfb, 0x9b, 0xdf, 0x2c, 0x67, 0x77, 0x87, 0x14, 0x29, 0xce, 0x0c, 0x15, 0xb8, 0x79, + 0xca, 0xd0, 0xb8, 0x0b, 0xd5, 0x8c, 0x5e, 0xea, 0x83, 0xb7, 0xa6, 0x46, 0x3d, 0x7a, 0xff, 0xdd, + 0x0f, 0x97, 0xec, 0xc4, 0x5c, 0x29, 0x37, 0xf3, 0x39, 0x8c, 0xf7, 0xa0, 0xd4, 0xeb, 0xfb, 0xe1, + 0x70, 0xbb, 0x20, 0x67, 0x7a, 0xfa, 0xa0, 0x49, 0x70, 0x94, 0xa0, 0x67, 0x83, 0xa3, 0x24, 0x80, + 0x1f, 0x42, 0xe5, 0x38, 0x8c, 0xfd, 0xa3, 0x41, 0x90, 0xe4, 0xe8, 0x55, 0x92, 0x70, 0xb3, 0x29, + 0x96, 0x0d, 0x37, 0x9b, 0x62, 0xf5, 0xbf, 0x14, 0xb8, 0x76, 0xd2, 0x81, 0xf0, 0x3b, 0x50, 0x96, + 0x65, 0xac, 0xa3, 0x6c, 0xc5, 0xad, 0x04, 0xc9, 0x6e, 0x7e, 0x82, 0xe0, 0x07, 0x70, 0x39, 0x7e, + 0x76, 0xf4, 0x79, 0xd0, 0x9b, 0xa4, 0xfb, 0x2e, 0x9d, 0x25, 0x85, 0xb2, 0xc1, 0x3d, 0x29, 0x84, + 0x3f, 0x86, 0xb5, 0x38, 0x88, 0x42, 0x7f, 0xe0, 0x0d, 0x9f, 0x3d, 0x3d, 0x4a, 0x2f, 0x58, 0x69, + 0xc8, 0x5d, 0x4e, 0x90, 0x0d, 0xb9, 0x4b, 0x04, 0x96, 0xc4, 0x31, 0x85, 0xf5, 0x79, 0xd4, 0xb0, + 0xd7, 0xf7, 0xe3, 0x7e, 0x1a, 0xca, 0x24, 0x3f, 0x94, 0xb0, 0x20, 0xca, 0x86, 0x74, 0x8d, 0xa7, + 0x21, 0xc5, 0x2d, 0x3f, 0xee, 0xd7, 0xbf, 0x2d, 0xc0, 0x46, 0x33, 0xf0, 0x9f, 0xc6, 0xe7, 0xfe, + 0xe5, 0xef, 0xc2, 0x4a, 0x9c, 0xe3, 0xb7, 0xbf, 0x0b, 0x2b, 0x71, 0x8e, 0x5f, 0xff, 0xfe, 0x50, + 0x04, 0xa4, 0x0e, 0xfc, 0x38, 0x0e, 0x1f, 0x87, 0x17, 0x6f, 0x7e, 0x5f, 0xc3, 0x1b, 0xd1, 0xfc, + 0x06, 0x5d, 0x3c, 0x29, 0xbc, 0x76, 0x1b, 0x24, 0xbf, 0xbf, 0x33, 0xdb, 0xa0, 0x8b, 0xa7, 0x84, + 0x7f, 0xf5, 0x94, 0xf0, 0x73, 0x01, 0x56, 0x65, 0xe6, 0x85, 0x3e, 0x8a, 0x5e, 0xf8, 0xd1, 0x7f, + 0xbd, 0x74, 0xec, 0x7c, 0x11, 0x56, 0xce, 0x16, 0xfc, 0xfd, 0xcf, 0xeb, 0xc5, 0xd6, 0xbf, 0x29, + 0xc1, 0x0d, 0x32, 0x9e, 0xd6, 0x4f, 0x71, 0xfd, 0xe8, 0x49, 0x30, 0xd1, 0x42, 0x7f, 0xa0, 0x05, + 0xc3, 0x30, 0x38, 0x97, 0x85, 0x47, 0xd2, 0x0c, 0xfc, 0xd2, 0x99, 0x32, 0xf0, 0x1f, 0x00, 0x24, + 0x6b, 0xd7, 0x1a, 0xc5, 0x93, 0x34, 0xa0, 0x7d, 0xfd, 0x8f, 0x5f, 0x6f, 0x57, 0x27, 0x12, 0xf5, + 0xfa, 0xa3, 0x78, 0xe2, 0x64, 0x28, 0x73, 0x85, 0xf6, 0x28, 0x9a, 0xa4, 0xe9, 0x6f, 0x59, 0x85, + 0xf1, 0x28, 0x9a, 0x29, 0x08, 0x0a, 0xfe, 0x08, 0xaa, 0xe2, 0x7c, 0x1d, 0x3c, 0x0f, 0x8e, 0x59, + 0x3b, 0xde, 0xae, 0xcc, 0xc3, 0xb3, 0xa3, 0x14, 0xf6, 0xc2, 0x5c, 0x75, 0xc9, 0x2c, 0x1d, 0xd7, + 0xa1, 0x9c, 0xfc, 0x65, 0x9a, 0xa6, 0x4e, 0xca, 0xe4, 0xe0, 0xb1, 0x44, 0x9c, 0x54, 0x82, 0x3f, + 0x80, 0x2b, 0xcd, 0xc1, 0xa8, 0xf7, 0x85, 0xd0, 0x48, 0x0a, 0xde, 0x24, 0x59, 0x4f, 0x47, 0x09, + 0xe8, 0x85, 0xe3, 0x6c, 0xd6, 0xd3, 0x8c, 0x8a, 0x9b, 0xb0, 0x96, 0x76, 0xda, 0x51, 0xf0, 0x38, + 0xfc, 0x32, 0x29, 0x80, 0x93, 0x64, 0x52, 0x4c, 0x75, 0xc7, 0x52, 0x92, 0xbd, 0xed, 0xce, 0xa9, + 0x34, 0x7a, 0xc9, 0xf7, 0x86, 0xf6, 0xc3, 0xe1, 0x31, 0xde, 0x81, 0xad, 0x0e, 0xa7, 0x8e, 0xb7, + 0xcf, 0x2c, 0xcd, 0xeb, 0x58, 0xbc, 0x4d, 0x55, 0xa6, 0x33, 0xaa, 0xa1, 0x4b, 0x78, 0x13, 0xd6, + 0xe7, 0xa2, 0x56, 0xc7, 0x24, 0x16, 0x52, 0xf0, 0x06, 0xac, 0xcd, 0xc1, 0xa6, 0xed, 0xa2, 0x02, + 0xbe, 0x06, 0x68, 0x0e, 0xf1, 0x2e, 0x77, 0xa9, 0x89, 0x8a, 0x8d, 0xef, 0x15, 0x00, 0x71, 0x94, + 0x24, 0xb7, 0x0c, 0xdf, 0x84, 0xeb, 0x92, 0x64, 0x3b, 0x6c, 0x8f, 0x59, 0x0b, 0x47, 0xda, 0x82, + 0x8d, 0xac, 0xd0, 0xb0, 0x55, 0x62, 0x20, 0x65, 0x66, 0x40, 0x0a, 0x73, 0x6e, 0x67, 0x8e, 0x96, + 0x82, 0xf6, 0xbe, 0x4b, 0x50, 0x71, 0x11, 0xe5, 0x2a, 0x33, 0xd1, 0x0a, 0xbe, 0x0e, 0x9b, 0x59, + 0x94, 0x5a, 0xae, 0x43, 0x98, 0x86, 0x4a, 0x8d, 0x37, 0xa1, 0x2a, 0xff, 0xdb, 0x48, 0x6b, 0x16, + 0xaf, 0x42, 0xc5, 0x6e, 0x72, 0xea, 0x1c, 0x48, 0x6b, 0x00, 0xca, 0x1a, 0xb5, 0x84, 0x65, 0x4a, + 0x23, 0x82, 0xaa, 0xa9, 0x13, 0x7d, 0x30, 0x7a, 0x21, 0x9f, 0xb3, 0x77, 0x61, 0xc7, 0xd4, 0x89, + 0xa7, 0x1b, 0xf6, 0xa1, 0xe7, 0x76, 0xdb, 0x74, 0x61, 0x1e, 0x6f, 0xc3, 0xfd, 0xbc, 0xb8, 0x4d, + 0x1d, 0x8f, 0x53, 0xce, 0x99, 0x6d, 0x79, 0x2a, 0x75, 0x5c, 0xa6, 0x33, 0x95, 0xb8, 0x14, 0x29, + 0x62, 0xdd, 0xf3, 0x54, 0x66, 0x79, 0x4d, 0x62, 0x69, 0xa8, 0xd0, 0xf8, 0x53, 0x01, 0xe0, 0xba, + 0xdb, 0x4e, 0x8d, 0xab, 0xc2, 0x65, 0x66, 0x1d, 0x10, 0x83, 0x89, 0x23, 0x54, 0x60, 0xc5, 0x6e, + 0x53, 0xb1, 0x11, 0x57, 0xa0, 0xa4, 0x1a, 0x36, 0xa7, 0xa8, 0x20, 0x40, 0x87, 0x12, 0x0d, 0x15, + 0x05, 0x78, 0xe8, 0x30, 0x97, 0xa2, 0x15, 0xd1, 0x34, 0xb8, 0x4b, 0x5c, 0x54, 0x12, 0x4d, 0x5d, + 0x36, 0xcb, 0x62, 0x30, 0x4e, 0x5d, 0xd9, 0xb9, 0x2c, 0xa6, 0xad, 0x4f, 0x7b, 0x15, 0x21, 0x12, + 0x43, 0x6b, 0xcc, 0x41, 0x57, 0x44, 0x47, 0x0c, 0x29, 0x3a, 0x20, 0x16, 0xc4, 0xa1, 0xa6, 0x7d, + 0x40, 0x51, 0x55, 0x8c, 0x65, 0xee, 0x0b, 0x78, 0x55, 0x34, 0x1d, 0x53, 0x34, 0xd7, 0xc4, 0x48, + 0x0e, 0x25, 0x46, 0x9b, 0xb8, 0x2d, 0x74, 0x55, 0xd8, 0x23, 0xc7, 0x5c, 0x4f, 0x34, 0x2d, 0x62, + 0x52, 0x84, 0x52, 0x8e, 0x66, 0x30, 0x6b, 0x1f, 0x6d, 0x48, 0x43, 0xba, 0xa6, 0xec, 0x60, 0xa1, + 0x20, 0x5b, 0x9b, 0x8d, 0xaf, 0x15, 0x28, 0xdb, 0x5c, 0xae, 0xf5, 0x75, 0xd8, 0xb4, 0xf9, 0x49, + 0xab, 0xbc, 0x01, 0x6b, 0x53, 0x81, 0xc1, 0xac, 0xce, 0xa3, 0xc4, 0x2b, 0xa7, 0x90, 0x49, 0x54, + 0x9b, 0xa3, 0x82, 0x70, 0x9e, 0x29, 0x74, 0xc8, 0x2c, 0xcd, 0x3e, 0xe4, 0xa8, 0x88, 0xd7, 0xa1, + 0x3a, 0x05, 0x99, 0xcd, 0xd1, 0x0a, 0xc6, 0x70, 0x75, 0x06, 0xb4, 0x89, 0x66, 0x73, 0x54, 0x6a, + 0x3c, 0x87, 0x55, 0x2d, 0x78, 0x1e, 0xf6, 0x82, 0xd4, 0x75, 0x77, 0x61, 0x47, 0xa3, 0x07, 0x4c, + 0xa5, 0x4b, 0x9d, 0x37, 0x2f, 0x26, 0x6d, 0x86, 0x14, 0xfc, 0x3f, 0xc0, 0x79, 0xf8, 0x13, 0x62, + 0xea, 0xa8, 0x80, 0xb7, 0xe1, 0x5a, 0x1e, 0x67, 0x96, 0xdb, 0xb1, 0x28, 0x2a, 0x36, 0x7e, 0x54, + 0x60, 0x8b, 0x0e, 0xfc, 0x78, 0x12, 0xf6, 0xe2, 0xc0, 0x8f, 0x7a, 0x7d, 0xd5, 0x9f, 0x04, 0x4f, + 0x46, 0xd1, 0x57, 0xb8, 0x0e, 0x35, 0x6a, 0x10, 0xee, 0x32, 0x95, 0x53, 0xe2, 0xa8, 0x2d, 0x4f, + 0x38, 0xd1, 0x9e, 0xed, 0x74, 0xbd, 0x3d, 0x6a, 0x51, 0x87, 0x18, 0xe8, 0x12, 0xfe, 0x3f, 0xdc, + 0x5e, 0xc2, 0xe1, 0x54, 0xed, 0x38, 0xcc, 0xed, 0x22, 0x05, 0xdf, 0x85, 0xdd, 0xa5, 0x24, 0xd1, + 0x47, 0x05, 0x5c, 0x83, 0x1b, 0xcb, 0x28, 0x9f, 0x1a, 0xa8, 0xd8, 0xf8, 0x4e, 0x01, 0x6c, 0x8f, + 0x83, 0x21, 0xcf, 0x9b, 0x78, 0x07, 0x6e, 0x09, 0xef, 0xf1, 0x96, 0x1b, 0x78, 0x17, 0x76, 0x4f, + 0x64, 0x64, 0xcc, 0xbb, 0x0d, 0x37, 0x97, 0x50, 0x52, 0xe3, 0x6e, 0xc1, 0xf6, 0xc9, 0x04, 0x69, + 0xda, 0x4f, 0x0a, 0x6c, 0x91, 0xe3, 0xa7, 0xe1, 0x30, 0x39, 0x73, 0x62, 0x53, 0x27, 0xe9, 0xcf, + 0xf6, 0x5b, 0x70, 0x8f, 0x68, 0xa6, 0xd8, 0x1b, 0xd5, 0x65, 0xb6, 0xc5, 0x3d, 0x71, 0xee, 0x09, + 0x1f, 0xed, 0xf0, 0x85, 0xdd, 0x7c, 0x03, 0xea, 0xa7, 0x30, 0xd5, 0x16, 0xb1, 0xf6, 0xc4, 0x85, + 0x01, 0xdf, 0x83, 0x3b, 0x4b, 0x79, 0xd4, 0x22, 0x4d, 0x83, 0x6a, 0xa8, 0x80, 0xef, 0xc3, 0xdd, + 0xa5, 0x2c, 0x8d, 0xf1, 0x84, 0x56, 0x6c, 0xf8, 0xb9, 0xcf, 0xfe, 0x88, 0x59, 0xaa, 0xb6, 0xe5, + 0x12, 0xd5, 0x3d, 0xc9, 0xfd, 0x77, 0x60, 0x2b, 0x27, 0x6d, 0x76, 0x38, 0xb3, 0x28, 0xe7, 0xc9, + 0x45, 0x25, 0x27, 0x9a, 0x2d, 0x6d, 0xa1, 0xa9, 0xbd, 0xfc, 0xbd, 0x76, 0xe9, 0xe5, 0xab, 0x9a, + 0xf2, 0xcb, 0xab, 0x9a, 0xf2, 0xdb, 0xab, 0x9a, 0xf2, 0xd9, 0xc3, 0xb3, 0xd4, 0x88, 0x48, 0x7e, + 0x98, 0x8f, 0xca, 0xf2, 0x5e, 0xfc, 0xbd, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x50, 0x91, 0xe1, + 0x27, 0x50, 0x41, 0x02, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { diff --git a/lib/devicetrust/proto_test.go b/lib/devicetrust/proto_test.go new file mode 100644 index 000000000000..0eec003addf6 --- /dev/null +++ b/lib/devicetrust/proto_test.go @@ -0,0 +1,36 @@ +// Teleport +// Copyright (C) 2026 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 devicetrust_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" + apievents "github.com/gravitational/teleport/api/types/events" +) + +// events.OSType duplicates teleport.devicetrust.v1.OSType because gogo doesn't +// play well with protoc-gen-go. Audit event emitters convert between the two by +// numeric value, so a name or number present in only one of them silently +// mislabels the device in the audit log. +func TestOSTypeMirrorsEventsOSType(t *testing.T) { + assert.Equal(t, devicepb.OSType_name, apievents.OSType_name, + "teleport.devicetrust.v1.OSType and events.OSType drifted, "+ + "update api/proto/teleport/legacy/types/events/events.proto and run `make grpc`") +} From a33ce3c02775bfafae8ded752fc3e8507026d65a Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 27 Jul 2026 16:40:01 +0100 Subject: [PATCH 007/347] Dedupe scoped resource name validation (#69053) * scopes: dedupe scoped resource name validation #68721 added the private strongValidateName plus nameRegexp for scope qualified names, and #68489 then added the exported StrongValidateResourceName used by ScopedRole, ScopedRoleAssignment, ScopedToken and WorkloadIdentity. Both implement the same rule over the same regexp, so the package ended up carrying two spellings of it. Point QualifiedName.StrongValidate at StrongValidateResourceName, drop strongValidateName, and move nameRegexp beside its one remaining user. The two functions accept and reject exactly the same set of strings. The weak validation backstop that strongValidateName inherited from strongValidateFormat cannot fire on a name, because nameRegexp only admits [a-z0-9._-], all of which are non-space printable ASCII and none of which appear in breakingChars. A differential test over 70647 inputs found zero accept/reject disagreements between the two. The one observable change is the noun in the error message. A scope qualified name with a malformed name used to report: scope-qualified name "/staging::my@role" has invalid name: segment "my@role" is malformed and now reports: scope-qualified name "/staging::my@role" has invalid name: name "my@role" is malformed which is more accurate, since the value being rejected is a name rather than a scope segment. * scopes: StrongValidateResourceName delegates to strongValidateFormat The previous commit removed the duplicate wrapper, but left the body duplicated: the uppercase loop and the regexp match still appeared verbatim in both strongValidateFormat and StrongValidateResourceName. strongValidateFormat could not simply be reused because it hardcoded "segment" in all three of its messages, which would have described a name as a segment. It now takes the noun to use, so StrongValidateResourceName reduces to an empty check plus a delegation. Two things fall out of this. The uppercase and shape checks now exist in exactly one place, so a future change to the rules cannot apply to segments and miss names. Names also regain the weak validation backstop that every other strong validator in this package ends with, which means the invariant is enforced by construction rather than by the comment on nameRegexp that this removes. No behaviour change, error text included. A differential test over 70647 inputs comparing the delegating implementation against the previous one found zero verdict disagreements and zero message disagreements, and segment validation still reports "segment". --- lib/scopes/qualified.go | 17 +---------------- lib/scopes/scopes.go | 37 +++++++++++++++---------------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/lib/scopes/qualified.go b/lib/scopes/qualified.go index fb68ef6839c3..5aa9f98bf66c 100644 --- a/lib/scopes/qualified.go +++ b/lib/scopes/qualified.go @@ -19,7 +19,6 @@ package scopes import ( - "regexp" "strings" "github.com/gravitational/trace" @@ -84,7 +83,7 @@ func (q QualifiedName) StrongValidate() error { return trace.BadParameter("scope-qualified name %q has invalid scope: %v", q, err) } - if err := strongValidateName(q.Name); err != nil { + if err := StrongValidateResourceName(q.Name); err != nil { return trace.BadParameter("scope-qualified name %q has invalid name: %v", q, err) } @@ -113,20 +112,6 @@ func (q QualifiedName) WeakValidate() error { return nil } -// nameRegexp is the regular expression used to validate scoped resource names. It enforces -// the same character rules as segmentRegexp, but allows for single character names. -var nameRegexp = regexp.MustCompile(`^[a-z0-9]([a-z0-9\-\_\.]*[a-z0-9])?$`) - -// strongValidateName checks if a scoped resource name is valid according to scope character -// formatting rules. Unlike scope segments, names carry no length constraints. -func strongValidateName(name string) error { - if name == "" { - return trace.BadParameter("name is empty") - } - - return trace.Wrap(strongValidateFormat(name, nameRegexp)) -} - // ParseQualifiedName parses a scope-qualified name string into its scope and name // components by splitting on the first occurrence of "::". Returns an error if the // separator is absent or either component is empty. This function does not validate diff --git a/lib/scopes/scopes.go b/lib/scopes/scopes.go index f191a601664d..7e517fb8b481 100644 --- a/lib/scopes/scopes.go +++ b/lib/scopes/scopes.go @@ -144,7 +144,7 @@ func StrongValidateSegment(segment string) error { return trace.BadParameter("segment %q is too short (min characters %d)", segment, minSegmentSize) } - if err := strongValidateFormat(segment, segmentRegexp); err != nil { + if err := strongValidateFormat("segment", segment, segmentRegexp); err != nil { return trace.Wrap(err) } @@ -155,29 +155,30 @@ func StrongValidateSegment(segment string) error { return nil } -// strongValidateFormat applies the strong formatting checks: +// strongValidateFormat applies the strong formatting checks to value, describing it as noun +// (e.g. "segment" or "name") in any error it returns: // - no uppercase characters // - the provided shape regexp // - weak checks as a defensive backstop -func strongValidateFormat(segment string, shape *regexp.Regexp) error { +func strongValidateFormat(noun, value string, shape *regexp.Regexp) error { // check for uppercase characters separately. this would be caught by the regex, but its better // UX to call out uppercase characters specifically since its a common mistake. - for _, r := range segment { + for _, r := range value { if unicode.IsUpper(r) { - return trace.BadParameter("segment %q contains uppercase character(s)", segment) + return trace.BadParameter("%s %q contains uppercase character(s)", noun, value) } } - if !shape.MatchString(segment) { - return trace.BadParameter("segment %q is malformed", segment) + if !shape.MatchString(value) { + return trace.BadParameter("%s %q is malformed", noun, value) } // as an extra precaution, also run all weak checks just to be certain we didn't accidentally // construct a weak check that rejects something that would otherwise pass a strong check. strong // validation is not used in perf-critical paths, so there isn't any real downside to a little // defensiveness here. - if err := WeakValidateSegment(segment); err != nil { - return trace.BadParameter("segment would not pass weak validation: %v", err) + if err := WeakValidateSegment(value); err != nil { + return trace.BadParameter("%s would not pass weak validation: %v", noun, err) } return nil @@ -203,6 +204,10 @@ func WeakValidateSegment(segment string) error { return nil } +// nameRegexp is the regular expression used to validate scoped resource names. It enforces +// the same character rules as segmentRegexp, but allows for single character names. +var nameRegexp = regexp.MustCompile(`^[a-z0-9]([a-z0-9\-\_\.]*[a-z0-9])?$`) + // StrongValidateResourceName checks if a scoped resource name is valid according to all resource name // formatting rules. Scoped resource names follow the same character restrictions as scope segments, but // are not subject to the maximum segment length limit. This function *must* be called on all scoped @@ -214,19 +219,7 @@ func StrongValidateResourceName(name string) error { return trace.BadParameter("name is empty") } - // check for uppercase characters separately. this would be caught by the regex, but its better - // UX to call out uppercase characters specifically since its a common mistake. - for _, r := range name { - if unicode.IsUpper(r) { - return trace.BadParameter("name %q contains uppercase character(s)", name) - } - } - - if !nameRegexp.MatchString(name) { - return trace.BadParameter("name %q is malformed", name) - } - - return nil + return trace.Wrap(strongValidateFormat("name", name, nameRegexp)) } // isNonSpacePrintableASCII checks if a byte is a non-space printable ASCII character (i.e. a byte in the range From 733b77555d4b4b407ff59f2089195d82e2bd81d5 Mon Sep 17 00:00:00 2001 From: Ghassan Gedeon Achi Date: Mon, 27 Jul 2026 10:56:51 -0600 Subject: [PATCH 008/347] Escape terminal control sequences in `tctl` access-changes and detections output (#68901) * fix: add missing escape control to new tctl commands * fix: escape json diff operation * feat: use escape whitespace where appropriate --- .../accessgraph/access_changes_command.go | 26 +++++---- .../access_changes_command_test.go | 57 +++++++++++++++++++ .../accessgraph/access_review_command.go | 2 +- .../common/accessgraph/detections_command.go | 34 ++++++----- .../accessgraph/detections_command_test.go | 50 ++++++++++++++++ 5 files changed, 142 insertions(+), 27 deletions(-) diff --git a/tool/tctl/common/accessgraph/access_changes_command.go b/tool/tctl/common/accessgraph/access_changes_command.go index 0b706dade04d..98fbf421b0bc 100644 --- a/tool/tctl/common/accessgraph/access_changes_command.go +++ b/tool/tctl/common/accessgraph/access_changes_command.go @@ -36,6 +36,7 @@ import ( graphmodels "github.com/gravitational/teleport/lib/accessgraph/apiclient/models/graph" diffmodels "github.com/gravitational/teleport/lib/accessgraph/apiclient/models/jsondiff" "github.com/gravitational/teleport/lib/asciitable" + "github.com/gravitational/teleport/lib/utils" ) type accessChangesArgs struct { @@ -294,12 +295,12 @@ func displayAccessChangesText(out io.Writer, changes []accessgraph.AccessPathSum for _, change := range changes { table.AddRow([]string{ - change.Id, - change.AffectedNode.Kind, - change.AffectedNode.Name, - change.AffectedNode.Source, - change.AffectedNode.OriginType, - change.AffectedNode.Alias, + utils.EscapeControl(change.Id), + utils.EscapeControl(change.AffectedNode.Kind), + utils.EscapeControl(change.AffectedNode.Name), + utils.EscapeControl(change.AffectedNode.Source), + utils.EscapeControl(change.AffectedNode.OriginType), + utils.EscapeControl(change.AffectedNode.Alias), change.CreatedAt.Format(time.RFC3339), }) } @@ -314,17 +315,18 @@ func displayAccessChange(out io.Writer, change *accessgraph.AccessPathDiff, form } func displayAccessChangeText(out io.Writer, change *accessgraph.AccessPathDiff) error { + // UUID fields need no escaping; string fields below are escaped. fmt.Fprintf(out, "Change ID: %s\n\n", change.ChangeId) fmt.Fprintln(out, "Affected Node:") nodeTable := asciitable.MakeTable([]string{"ID", "Kind", "Name", "Source", "Origin Type", "Alias"}) nodeTable.AddRow([]string{ change.AffectedNode.Id.String(), - change.AffectedNode.Kind, - change.AffectedNode.Name, - change.AffectedNode.Source, - change.AffectedNode.OriginType, - change.AffectedNode.Alias, + utils.EscapeControl(change.AffectedNode.Kind), + utils.EscapeControl(change.AffectedNode.Name), + utils.EscapeControl(change.AffectedNode.Source), + utils.EscapeControl(change.AffectedNode.OriginType), + utils.EscapeControl(change.AffectedNode.Alias), }) fmt.Fprintln(out, nodeTable.AsBuffer().String()) @@ -390,7 +392,7 @@ func displayAccessChangeText(out io.Writer, change *accessgraph.AccessPathDiff) if name == "" { name = id } - rows = append(rows, []string{string(op.Op), entityType, name, kind, originType}) + rows = append(rows, []string{utils.EscapeControl(string(op.Op)), entityType, utils.EscapeControl(name), utils.EscapeControl(kind), utils.EscapeControl(originType)}) } diffTable := asciitable.MakeTableWithTruncatedColumn([]string{"Operation", "Type", "Name", "Kind", "Origin Type"}, rows, "Name") _, err := fmt.Fprintln(out, diffTable.AsBuffer().String()) diff --git a/tool/tctl/common/accessgraph/access_changes_command_test.go b/tool/tctl/common/accessgraph/access_changes_command_test.go index 018fb6979617..fe7bcfd8ad62 100644 --- a/tool/tctl/common/accessgraph/access_changes_command_test.go +++ b/tool/tctl/common/accessgraph/access_changes_command_test.go @@ -632,3 +632,60 @@ func TestConstructAccessChangesListQuery(t *testing.T) { } }) } + +func TestAccessChangesTextEscapesControlSequences(t *testing.T) { + // OSC 52 clipboard-write + screen clear. + const inject = "evil\x1b]52;c;AAAA\a\x1b[2Jspoofed" + + assertEscaped := func(t *testing.T, out string) { + require.NotContains(t, out, "\x1b", "raw escape byte leaked to terminal") + require.NotContains(t, out, "\a", "raw BEL byte leaked to terminal") + require.Contains(t, out, `\x1b`, "control sequence should be rendered as a visible escape") + } + + injectedNode := accessgraph.AccessPathSummaryItemNode{ + Id: fixtureAffectedID, + Kind: inject, + Name: inject, + Source: inject, + OriginType: inject, + Alias: inject, + } + + t.Run("summary list", func(t *testing.T) { + item := accessgraph.AccessPathSummaryItem{ + Id: fixtureChangeUUID.String(), + CreatedAt: fixtureChangeTime, + AffectedNode: injectedNode, + } + var buf bytes.Buffer + require.NoError(t, displayAccessChangesText(&buf, []accessgraph.AccessPathSummaryItem{item})) + assertEscaped(t, buf.String()) + }) + + t.Run("detail with diff row", func(t *testing.T) { + strRef := func(s string) *string { return &s } + anyRef := func(v any) *any { return &v } + newNodeID := "dddddddd-4444-4444-4444-444444444444" + addNode := map[string]any{ + "id": newNodeID, + "kind": inject, + "name": inject, + "origin_type": inject, + } + // A clean AffectedNode keeps the injected payload confined to the diff + // row, so the escape assertions can only pass via the diff-row path. The + // Op value is also injected to cover the Operation column, which is a + // bare string enum the API can return unvalidated. + change := &accessgraph.AccessPathDiff{ + ChangeId: fixtureChangeUUID, + AffectedNode: accessgraph.AccessPathSummaryItemNode{Id: fixtureAffectedID, Kind: "resource", Name: "prod-db"}, + Diff: []diffmodels.Operation{ + {Op: diffmodels.OperationOp(inject), Path: strRef("/nodes/" + newNodeID), Value: anyRef(addNode)}, + }, + } + var buf bytes.Buffer + require.NoError(t, displayAccessChangeText(&buf, change)) + assertEscaped(t, buf.String()) + }) +} diff --git a/tool/tctl/common/accessgraph/access_review_command.go b/tool/tctl/common/accessgraph/access_review_command.go index 32dc741b131c..bd09d95826c6 100644 --- a/tool/tctl/common/accessgraph/access_review_command.go +++ b/tool/tctl/common/accessgraph/access_review_command.go @@ -122,7 +122,7 @@ func (c *AccessGraphCommand) AccessReview(ctx context.Context, client *accessgra // The activity lookup couldn't run: hide the columns and surface the // backend's message as a note. showActivity = false - output.ActivityUnavailable = utils.EscapeControl(*resp.IacError) + output.ActivityUnavailable = utils.AllowWhitespace(*resp.IacError) } if truncated { output.Warnings = append(output.Warnings, fmt.Sprintf("results truncated at %d identities; narrow --query for the full set", args.limit)) diff --git a/tool/tctl/common/accessgraph/detections_command.go b/tool/tctl/common/accessgraph/detections_command.go index 567df6026812..79dcf96045ae 100644 --- a/tool/tctl/common/accessgraph/detections_command.go +++ b/tool/tctl/common/accessgraph/detections_command.go @@ -35,6 +35,7 @@ import ( accessgraph "github.com/gravitational/teleport/lib/accessgraph/apiclient" logmodels "github.com/gravitational/teleport/lib/accessgraph/apiclient/models/logs" "github.com/gravitational/teleport/lib/asciitable" + "github.com/gravitational/teleport/lib/utils" ) // descriptionTextMaxLen caps Description in the detailed text table so a long @@ -220,11 +221,11 @@ func detectionRowHeaders(detailed bool) []string { func detectionRow(a accessgraph.SecurityAlert, detailed bool) []string { row := []string{ a.Id.String(), - string(a.Status), + utils.EscapeControl(string(a.Status)), a.CreatedAt.Format(time.RFC3339), - string(a.Source), - a.Title, - string(a.Severity), + utils.EscapeControl(string(a.Source)), + utils.EscapeControl(a.Title), + utils.EscapeControl(string(a.Severity)), } if !detailed { return row @@ -243,11 +244,11 @@ func detectionRow(a accessgraph.SecurityAlert, detailed bool) []string { updated = a.UpdatedAt.Format(time.RFC3339) } row = append(row, - strPtrToStr(a.ReportedBy), - a.Type, - formatAffectedEntity(a), - tags, - description, + utils.EscapeControl(strPtrToStr(a.ReportedBy)), + utils.EscapeControl(a.Type), + utils.EscapeControl(formatAffectedEntity(a)), + utils.EscapeControl(tags), + utils.EscapeControl(description), a.StartTime.Format(time.RFC3339), a.EndTime.Format(time.RFC3339), updated, @@ -358,7 +359,7 @@ func fetchAlertEvents(ctx context.Context, client *accessgraph.ClientWithRespons // displayDetectionText renders the human-readable detail view for one alert. func displayDetectionText(out io.Writer, a accessgraph.SecurityAlert, events []logmodels.AccessgraphStorageV1alphaEvent, eventsErr error) error { field := func(label, value string) { - fmt.Fprintf(out, "%-19s%s\n", label+":", value) + fmt.Fprintf(out, "%-19s%s\n", label+":", utils.EscapeControl(value)) } field("ID", a.Id.String()) field("Title", a.Title) @@ -381,19 +382,19 @@ func displayDetectionText(out io.Writer, a accessgraph.SecurityAlert, events []l field("Updated", a.UpdatedAt.Format(time.RFC3339)) } if a.Description != nil && *a.Description != "" { - fmt.Fprintf(out, "\nDescription:\n%s\n", *a.Description) + fmt.Fprintf(out, "\nDescription:\n%s\n", utils.AllowWhitespace(*a.Description)) } if a.MitigationSteps != nil && len(*a.MitigationSteps) > 0 { fmt.Fprintln(out, "\nMitigation Steps:") for _, step := range *a.MitigationSteps { - fmt.Fprintf(out, " - %s\n", step) + fmt.Fprintf(out, " - %s\n", utils.AllowWhitespace(step)) } } if eventsErr != nil || len(events) > 0 || (a.LogEntries != nil && len(*a.LogEntries) > 0) { fmt.Fprintln(out, "\nLog Entries:") switch { case eventsErr != nil: - fmt.Fprintln(out, warningStyle.Render(eventsErr.Error())) + fmt.Fprintln(out, warningStyle.Render(utils.AllowWhitespace(eventsErr.Error()))) case len(events) == 0: fmt.Fprintln(out, "Not found.") default: @@ -410,7 +411,12 @@ func displayDetectionText(out io.Writer, a accessgraph.SecurityAlert, events []l if log.Reason != nil { reason = *log.Reason } - changes.AddRow([]string{log.CreatedAt.Format(time.RFC3339), string(log.Status), log.User, reason}) + changes.AddRow([]string{ + log.CreatedAt.Format(time.RFC3339), + utils.EscapeControl(string(log.Status)), + utils.EscapeControl(log.User), + utils.EscapeControl(reason), + }) } fmt.Fprintln(out, changes.String()) } diff --git a/tool/tctl/common/accessgraph/detections_command_test.go b/tool/tctl/common/accessgraph/detections_command_test.go index 6db07b63b1ca..0ce0a18c5fa3 100644 --- a/tool/tctl/common/accessgraph/detections_command_test.go +++ b/tool/tctl/common/accessgraph/detections_command_test.go @@ -564,6 +564,56 @@ func TestDisplayDetectionTextAffectedEntity(t *testing.T) { } } +func TestDetectionTextEscapesControlSequences(t *testing.T) { + // OSC 52 clipboard-write + screen clear + inject := "evil\x1b]52;c;AAAA\a\x1b[2Jspoofed" + + assertEscaped := func(t *testing.T, out string) { + require.NotContains(t, out, "\x1b", "raw escape byte leaked to terminal") + require.NotContains(t, out, "\a", "raw BEL byte leaked to terminal") + require.Contains(t, out, `\x1b`, "control sequence should be rendered as a visible escape") + } + + // Payload in every field detectionRow / displayDetectionText escapes. + alert := accessgraph.SecurityAlert{ + Id: fixtureAlertID, + Title: inject, + Type: inject, + Severity: accessgraph.SecurityAlertSeverity(inject), + Status: accessgraph.AlertStatus(inject), + Source: logmodels.EventSource(inject), + StartTime: fixtureStart, + EndTime: fixtureEnd, + CreatedAt: fixtureCreated, + AffectedEntity: &struct { + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + }{Name: &inject}, + Tags: &[]string{inject}, + Description: &inject, + ReportedBy: &inject, + MitigationSteps: &[]string{inject}, + StatusChangeLogs: []accessgraph.AlertStatusChangeLog{{ + CreatedAt: fixtureCreated, + Status: accessgraph.AlertStatus(inject), + User: inject, + Reason: &inject, + }}, + } + + t.Run("row detailed", func(t *testing.T) { + out := strings.Join(detectionRow(alert, true), "\n") + assertEscaped(t, out) + }) + + t.Run("detail view", func(t *testing.T) { + var buf bytes.Buffer + // Non-nil eventsErr covers the escaped-error branch. + require.NoError(t, displayDetectionText(&buf, alert, nil, trace.BadParameter("%s", inject))) + assertEscaped(t, buf.String()) + }) +} + // TestInitDetectionsFlags exercises the kingpin wiring (defaults, enum // validation, --from/--to via timeValue) without going through TryRun or // touching the network. From 1d7740af22e71e4172e376d6fcb2993425661701 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 27 Jul 2026 19:01:08 +0200 Subject: [PATCH 009/347] Fix Windows webassets build (#69029) * Fix Windows webassets build * Remove qoiz related features --- Makefile | 36 ++++++++++++++----- build.assets/Dockerfile-node | 2 ++ lib/srv/desktop/rdp/rdpclient/Cargo.toml | 6 ++-- lib/srv/desktop/rdp/rdpclient/client_qoiz.go | 15 ++++---- .../desktop/rdp/rdpclient/client_qoiz_stub.go | 2 +- .../desktop/rdp/rdpclient/client_qoiz_test.go | 2 +- lib/srv/desktop/rdp/rdpclient/src/lib.rs | 1 - web/packages/shared/libs/ironrdp/Cargo.toml | 5 +-- 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index a646ea1229c8..e1c05c9c23ba 100644 --- a/Makefile +++ b/Makefile @@ -1972,23 +1972,43 @@ ensure-js-deps: ifeq ($(WEBASSETS_SKIP_BUILD),1) ensure-wasm-deps: else -ensure-wasm-deps: ensure-llvm-macos rustup-toolchain-warning ensure-wasm-bindgen ensure-wasm-opt +ensure-wasm-deps: ensure-llvm rustup-toolchain-warning ensure-wasm-bindgen ensure-wasm-opt -.PHONY: ensure-llvm-macos -ifeq ("$(OS)-$(ARCH)","darwin-arm64") +.PHONY: ensure-llvm +ifeq ("$(OS)","darwin") BREW_DIR = $(shell brew --prefix) LLVM_PREFIX = $(shell brew list | grep llvm | head -n 1) LLVM_DIR = $(shell brew --prefix $(LLVM_PREFIX)) -CC = $(LLVM_DIR)/bin/clang -AR = $(LLVM_DIR)/bin/llvm-ar -ensure-llvm-macos: - @if [[ "${BREW_DIR}" = "${LLVM_DIR}" ]]; then \ +# Prevent these from being exported and expanded for every recipe. +unexport BREW_DIR LLVM_PREFIX LLVM_DIR + +# The ironrdp WASM build needs clang/llvm-ar. +# These are applied as target-specific variables so +# brew is only invoked when necessary and so that +# CC and AR are only overwritten for WASM compilation. +build-ironrdp-wasm: CC = $(LLVM_DIR)/bin/clang +build-ironrdp-wasm: AR = $(LLVM_DIR)/bin/llvm-ar + +ensure-llvm: + @if [[ "$(BREW_DIR)" = "$(LLVM_DIR)" ]]; then \ echo "llvm is required, please run 'brew install llvm' and add '/opt/homebrew/opt/llvm/bin' at the start of PATH variable"; \ exit 1; \ fi +else ifeq ("$(OS)","windows") +LLVM_DIR=$(shell vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Llvm.Clang -property installationPath) +unexport LLVM_DIR +build-ironrdp-wasm: CC = $(LLVM_DIR)/VC/Tools/Llvm/x64/bin/clang +build-ironrdp-wasm: AR = $(LLVM_DIR)/VC/Tools/Llvm/x64/bin/llvm-ar + +ensure-llvm: + @if [[ "x" = "x$(LLVM_DIR)" ]]; then \ + echo "llvm is required, please install Visual Studio with LLVM component"; \ + exit 1; \ + fi + else -ensure-llvm-macos: +ensure-llvm: endif WASM_BINDGEN_VERSION = $(shell awk ' \ diff --git a/build.assets/Dockerfile-node b/build.assets/Dockerfile-node index 3aea917065e1..3ea1bc4249e6 100644 --- a/build.assets/Dockerfile-node +++ b/build.assets/Dockerfile-node @@ -36,10 +36,12 @@ RUN apt-get -y update && \ apt-get install -q -y --no-install-recommends \ build-essential \ ca-certificates \ + clang \ git \ golang \ libc6-dev \ libssl-dev \ + llvm \ locales \ openssh-client \ pkg-config \ diff --git a/lib/srv/desktop/rdp/rdpclient/Cargo.toml b/lib/srv/desktop/rdp/rdpclient/Cargo.toml index 02d85060462a..2780001a0f2c 100644 --- a/lib/srv/desktop/rdp/rdpclient/Cargo.toml +++ b/lib/srv/desktop/rdp/rdpclient/Cargo.toml @@ -45,12 +45,13 @@ reqwest = { version = "0.13", default-features = false } rustls = { version = "0.23.40", default-features = false, features = [ "aws-lc-rs", ] } -qoi = { version = "0.4.1", optional = true } -zstd = { version = "0.13.3", optional = true } +qoi = "0.4.1" +zstd = "0.13.3" [dev-dependencies] ironrdp-graphics.workspace = true ironrdp-session.workspace = true +ironrdp-session.features = ["qoiz"] base64 = "0.22.1" [build-dependencies] @@ -59,4 +60,3 @@ tempfile = "3.27.0" [features] fips = ["tokio-boring/fips", "boring/fips"] -desktop-encoder = ["dep:qoi", "dep:zstd", "ironrdp-session/qoiz"] diff --git a/lib/srv/desktop/rdp/rdpclient/client_qoiz.go b/lib/srv/desktop/rdp/rdpclient/client_qoiz.go index 001ad34ed9a0..4422da54b259 100644 --- a/lib/srv/desktop/rdp/rdpclient/client_qoiz.go +++ b/lib/srv/desktop/rdp/rdpclient/client_qoiz.go @@ -1,4 +1,4 @@ -//go:build desktop_access_rdp && desktop_encoder +//go:build desktop_access_rdp // Teleport // Copyright (C) 2026 Gravitational, Inc. @@ -18,17 +18,18 @@ package rdpclient +/* +#include +*/ +import "C" + import ( "unsafe" - "github.com/gravitational/teleport/lib/srv/desktop/tdp/protocol/tdpb" "github.com/gravitational/trace" -) -/* -#include -*/ -import "C" + "github.com/gravitational/teleport/lib/srv/desktop/tdp/protocol/tdpb" +) // EncodeQOIZ encodes changed frame to series of FastPath SetSurface PDUs using QOIZ codec. // Resulting frames can be consumed directly by the FastPath processor from IronRDP if qoiz diff --git a/lib/srv/desktop/rdp/rdpclient/client_qoiz_stub.go b/lib/srv/desktop/rdp/rdpclient/client_qoiz_stub.go index 7021527bb47f..4dfb254914ba 100644 --- a/lib/srv/desktop/rdp/rdpclient/client_qoiz_stub.go +++ b/lib/srv/desktop/rdp/rdpclient/client_qoiz_stub.go @@ -1,4 +1,4 @@ -//go:build !(desktop_access_rdp && desktop_encoder) +//go:build !desktop_access_rdp // Teleport // Copyright (C) 2026 Gravitational, Inc. diff --git a/lib/srv/desktop/rdp/rdpclient/client_qoiz_test.go b/lib/srv/desktop/rdp/rdpclient/client_qoiz_test.go index 0d6cd39c4afb..0b59d8c270aa 100644 --- a/lib/srv/desktop/rdp/rdpclient/client_qoiz_test.go +++ b/lib/srv/desktop/rdp/rdpclient/client_qoiz_test.go @@ -1,4 +1,4 @@ -//go:build desktop_access_rdp && desktop_encoder +//go:build desktop_access_rdp /* * * diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index b6761cbca557..a68970b2ab65 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -49,7 +49,6 @@ use util::{from_c_string, from_go_array}; pub mod client; mod cliprdr; mod license; -#[cfg(feature = "desktop-encoder")] mod linux_desktop_encoder; mod network_client; mod piv; diff --git a/web/packages/shared/libs/ironrdp/Cargo.toml b/web/packages/shared/libs/ironrdp/Cargo.toml index 84ea641abc6c..4095b597d62a 100644 --- a/web/packages/shared/libs/ironrdp/Cargo.toml +++ b/web/packages/shared/libs/ironrdp/Cargo.toml @@ -15,6 +15,7 @@ console_error_panic_hook = "0.1.7" getrandom1 = { package = "getrandom", version = "0.2", features = ["js"] } getrandom2 = { package = "getrandom", version = "0.4.2", features = ["wasm_js"] } ironrdp-session.workspace = true +ironrdp-session.features = ["qoiz"] ironrdp-pdu.workspace = true ironrdp-core.workspace = true ironrdp-graphics.workspace = true @@ -27,7 +28,3 @@ tracing-web = "0.1.2" uuid = { version = ">=1.16, <1.24", features = ["js"] } # Pinned below 1.21: uuid 1.21+ needs getrandom 0.4 -> rand_core 0.10 (stable), which conflicts with picky rc.22's pinned rand_core = "=0.10.0-rc-3". Remove this pin when picky ships with stable RustCrypto deps. wasm-bindgen = "0.2.95" web-sys = { version = "0.3.95", features = ["ImageData"] } - -[features] -default = [] -qoiz = ["ironrdp-session/qoiz"] \ No newline at end of file From 0e69d15b4c59cd1e55a2c04a2d7022e13ce64e95 Mon Sep 17 00:00:00 2001 From: Alan Parra <12500300+codingllama@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:16:08 -0300 Subject: [PATCH 010/347] chore: Bump oras.land/oras-go/v2 to v2.6.2 (#69060) --- build.assets/tooling/go.sum | 4 ++-- e2e/runner/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integrations/event-handler/go.mod | 2 +- integrations/event-handler/go.sum | 4 ++-- integrations/terraform-mwi/go.mod | 2 +- integrations/terraform-mwi/go.sum | 4 ++-- integrations/terraform/go.mod | 2 +- integrations/terraform/go.sum | 4 ++-- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index f6c059315d58..18c5756d8df4 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -1051,8 +1051,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= pluginrpc.com/pluginrpc v0.5.0 h1:tOQj2D35hOmvHyPu8e7ohW2/QvAnEtKscy2IJYWQ2yo= diff --git a/e2e/runner/go.sum b/e2e/runner/go.sum index 09409b86cb84..ecb32fd81e91 100644 --- a/e2e/runner/go.sum +++ b/e2e/runner/go.sum @@ -1059,8 +1059,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak= diff --git a/go.mod b/go.mod index 98b78c37ad32..f0eaceda513c 100644 --- a/go.mod +++ b/go.mod @@ -614,7 +614,7 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect - oras.land/oras-go/v2 v2.6.1 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kustomize/v5 v5.8.1 // indirect diff --git a/go.sum b/go.sum index 8d0c157a0aea..e9f634842326 100644 --- a/go.sum +++ b/go.sum @@ -1806,8 +1806,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak= diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index f7f0aa134f1f..9933ffee1029 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -379,7 +379,7 @@ require ( modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.53.0 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect - oras.land/oras-go/v2 v2.6.1 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect rsc.io/ordered v1.1.1 // indirect sigs.k8s.io/controller-runtime v0.24.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index e8dbc974597d..2adf11aa8448 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -1188,8 +1188,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak= diff --git a/integrations/terraform-mwi/go.mod b/integrations/terraform-mwi/go.mod index 35ed9175f766..cec41d9c361e 100644 --- a/integrations/terraform-mwi/go.mod +++ b/integrations/terraform-mwi/go.mod @@ -508,7 +508,7 @@ require ( modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.53.0 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect - oras.land/oras-go/v2 v2.6.1 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect rsc.io/ordered v1.1.1 // indirect sigs.k8s.io/controller-runtime v0.24.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/integrations/terraform-mwi/go.sum b/integrations/terraform-mwi/go.sum index 362e59765032..d114a0f26c30 100644 --- a/integrations/terraform-mwi/go.sum +++ b/integrations/terraform-mwi/go.sum @@ -1687,8 +1687,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak= diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index 23f9cdfd4e6e..fd2e760f51f6 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -510,7 +510,7 @@ require ( modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.53.0 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect - oras.land/oras-go/v2 v2.6.1 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect rsc.io/ordered v1.1.1 // indirect sigs.k8s.io/controller-runtime v0.24.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index e4a33e79edf0..bfeee8e0ffa2 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -2065,8 +2065,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 7357ae3f0ca97c6485f7d45d83d7a6964f35b4b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:46:28 -0300 Subject: [PATCH 011/347] Bump github.com/google/cel-go from 0.27.0 to 0.29.0 (#69023) * Bump github.com/google/cel-go from 0.27.0 to 0.29.0 Bumps [github.com/google/cel-go](https://github.com/google/cel-go) from 0.27.0 to 0.29.0. - [Release notes](https://github.com/google/cel-go/releases) - [Commits](https://github.com/google/cel-go/compare/v0.27.0...v0.29.0) --- updated-dependencies: - dependency-name: github.com/google/cel-go dependency-version: 0.29.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] * tidy --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alan Parra --- build.assets/tooling/go.mod | 2 +- build.assets/tooling/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index 36fc97ef53e6..0409073a638c 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -151,7 +151,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.27.0 // indirect + github.com/google/cel-go v0.29.0 // indirect github.com/google/certificate-transparency-go v1.3.3 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-attestation v0.6.1 // indirect diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index 18c5756d8df4..f11e509dd174 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -411,8 +411,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= +github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= diff --git a/go.mod b/go.mod index f0eaceda513c..cd0afe7ad6ba 100644 --- a/go.mod +++ b/go.mod @@ -432,7 +432,7 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/google/cel-go v0.27.0 // indirect + github.com/google/cel-go v0.29.0 // indirect github.com/google/certificate-transparency-go v1.3.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.7.1 // indirect diff --git a/go.sum b/go.sum index e9f634842326..07a8b2ec4294 100644 --- a/go.sum +++ b/go.sum @@ -695,8 +695,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= +github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= From 8d643b29671aa70b56e81ea2a3d9f020a27d55f9 Mon Sep 17 00:00:00 2001 From: Lion Chen Date: Mon, 27 Jul 2026 10:55:35 -0700 Subject: [PATCH 012/347] Search Access Requests by Requester Display Values (#68120) * search access requests by requester display values * Refactor ListUsers request and response handling in user search functions * Refactor user search functions to use ListUsers API and improve test coverage * Enhance user search functionality by adding display traits and refining search logic * Refactor user search functionality by removing deprecated access request search files and updating related tests * reexport function * Refactor access request and user search functionality to normalize search keywords * update user search logging and add pagination handling in findUsernamesBySearchKeywords * use Set --- api/types/access_request.go | 7 +- lib/auth/access_request_test.go | 296 +++++++++++++++++++++++++++++++ lib/auth/auth.go | 39 +++- lib/services/user_search.go | 131 ++++++++++++++ lib/services/user_search_test.go | 295 ++++++++++++++++++++++++++++++ 5 files changed, 765 insertions(+), 3 deletions(-) create mode 100644 lib/services/user_search.go create mode 100644 lib/services/user_search_test.go diff --git a/api/types/access_request.go b/api/types/access_request.go index 5101659ba98b..6199388c4f5a 100644 --- a/api/types/access_request.go +++ b/api/types/access_request.go @@ -757,12 +757,17 @@ func (r *AccessRequestV3) GetAllLabels() map[string]string { // MatchSearch goes through select field values and tries to // match against the list of search values. func (r *AccessRequestV3) MatchSearch(values []string) bool { + return MatchSearch(r.SearchableFields(), values, nil) +} + +// SearchableFields returns the stored access request values used for search. +func (r *AccessRequestV3) SearchableFields() []string { fieldVals := append(utils.MapToStrings(r.GetAllLabels()), r.GetName(), r.GetUser()) fieldVals = append(fieldVals, r.GetRoles()...) for _, resource := range r.GetRequestedResourceIDs() { fieldVals = append(fieldVals, resource.Name) } - return MatchSearch(fieldVals, values, nil) + return fieldVals } // Origin returns the origin value of the resource. diff --git a/lib/auth/access_request_test.go b/lib/auth/access_request_test.go index 30d51d15458a..126efc8be1d8 100644 --- a/lib/auth/access_request_test.go +++ b/lib/auth/access_request_test.go @@ -40,6 +40,7 @@ import ( "github.com/gravitational/teleport/api/defaults" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" "github.com/gravitational/teleport/api/types/header" @@ -268,6 +269,72 @@ func waitForAccessRequests(t *testing.T, ctx context.Context, getter services.Ac } } +func upsertUser(t *testing.T, ctx context.Context, authServer *auth.Server, name string, roles []string, traits map[string][]string) { + t.Helper() + + user, err := types.NewUser(name) + require.NoError(t, err) + user.SetRoles(roles) + user.SetTraits(traits) + _, err = authServer.UpsertUser(ctx, user) + require.NoError(t, err) +} + +func upsertAccessRequest(t *testing.T, ctx context.Context, authServer *auth.Server, name string, user string, roles []string, mutate func(types.AccessRequest)) { + t.Helper() + + req, err := types.NewAccessRequest(name, user, roles...) + require.NoError(t, err) + if mutate != nil { + mutate(req) + } + require.NoError(t, authServer.UpsertAccessRequest(ctx, req)) +} + +func waitForUserSearchMatch(t *testing.T, ctx context.Context, users services.UserSearchLister, searchKeywords []string, username string) { + t.Helper() + + timeout := time.After(time.Second * 30) + for { + rsp, err := users.ListUsers(ctx, userspb.ListUsersRequest_builder{ + PageSize: defaults.DefaultChunkSize, + Filter: &types.UserFilter{ + SearchKeywords: searchKeywords, + SkipSystemUsers: true, + }, + }.Build()) + require.NoError(t, err) + for _, user := range rsp.GetUsers() { + if user.GetName() == username { + return + } + } + + select { + case <-time.After(time.Millisecond * 150): + case <-timeout: + require.FailNow(t, "timeout waiting for user search match") + } + } +} + +func collectAccessRequestNames(t *testing.T, ctx context.Context, getter services.AccessRequestGetter, req proto.ListAccessRequestsRequest) []string { + t.Helper() + + var names []string + for { + rsp, err := getter.ListAccessRequests(ctx, &req) + require.NoError(t, err) + for _, accessRequest := range rsp.AccessRequests { + names = append(names, accessRequest.GetName()) + } + req.StartKey = rsp.NextKey + if req.StartKey == "" { + return names + } + } +} + // TestAccessRequestResourceRBACLimits verifies the special constraint conditions put on resource-level access // request permissions (create/update) to mitigate their power. func TestAccessRequestResourceRBACLimits(t *testing.T) { @@ -642,6 +709,235 @@ func TestListAccessRequests(t *testing.T) { })) } +func TestListAccessRequestsRequesterDisplaySearch(t *testing.T) { + t.Parallel() + + const ( + existingRequestID = "00000000-0000-0000-0000-000000000001" + displayRequestID = "00000000-0000-0000-0000-000000000002" + displayRoleName = "display-role" + storedRoleName = "stored-role" + displayUserName = "123456" + storedUserName = "stored-user" + ) + + authServer, err := authtest.NewAuthServer(authtest.AuthServerConfig{ + Dir: t.TempDir(), + }) + require.NoError(t, err) + defer authServer.Close() + + tlsServer, err := authServer.NewTestTLSServer() + require.NoError(t, err) + defer tlsServer.Close() + + ctx := t.Context() + authServerClient := tlsServer.Auth() + + upsertUser(t, ctx, authServerClient, displayUserName, nil, map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + "okta/email": {"jane.garcia@example.com"}, + }) + upsertUser(t, ctx, authServerClient, storedUserName, nil, nil) + waitForUserSearchMatch(t, ctx, authServerClient, []string{"Jane"}, displayUserName) + + upsertAccessRequest(t, ctx, authServerClient, existingRequestID, storedUserName, []string{storedRoleName}, func(req types.AccessRequest) { + req.SetStaticLabels(map[string]string{"owner": "Jane"}) + }) + upsertAccessRequest(t, ctx, authServerClient, displayRequestID, displayUserName, []string{displayRoleName}, func(req types.AccessRequest) { + req.SetState(types.RequestState_APPROVED) + }) + + waitForAccessRequests(t, ctx, authServerClient, func(reqs []*types.AccessRequestV3) bool { + return len(reqs) == 2 + }) + + for _, tc := range []struct { + name string + filter *types.AccessRequestFilter + limit int32 + expected []string + }{ + { + name: "display primary partial first name", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane"}, + }, + // existingRequestID has a label with "Jane" in it, + // displayRequestID has a requester whose display name contains "Jane", so both should match + expected: []string{existingRequestID, displayRequestID}, + }, + { + name: "search keywords are normalized", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{" Jane "}, + }, + expected: []string{existingRequestID, displayRequestID}, + }, + { + name: "blank search keywords are ignored", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"", " "}, + }, + expected: []string{existingRequestID, displayRequestID}, + }, + { + name: "display secondary", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"jane.garcia@example.com"}, + }, + // displayRequestID has a requester whose email is "jane.garcia@example.com", so it should match + expected: []string{displayRequestID}, + }, + { + name: "display primary multi token", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane", "Garcia"}, + }, + // displayRequestID has a requester whose display name contains both "Jane" and "Garcia", so it should match + expected: []string{displayRequestID}, + }, + { + name: "requester username search still works", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{displayUserName}, + }, + expected: []string{displayRequestID}, + }, + { + name: "mixed display and role terms", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane", displayRoleName}, + }, + expected: []string{displayRequestID}, + }, + { + name: "existing role search still works", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{storedRoleName}, + }, + expected: []string{existingRequestID}, + }, + { + name: "state filter still ANDs with display search", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane", "Garcia"}, + State: types.RequestState_PENDING, + }, + // No requests are set up to be PENDING so this should return no results + expected: nil, + }, + { + name: "approved state filter still returns display search match", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane", "Garcia"}, + State: types.RequestState_APPROVED, + }, + // displayRequestID is set up to be APPROVED and has a requester whose display name contains both "Jane" and "Garcia", so it should match + expected: []string{displayRequestID}, + }, + { + name: "no match", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"NoSuchPerson"}, + }, + expected: nil, + }, + { + name: "mixed existing and display pagination", + filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane"}, + }, + // force pagination to be exercised with search + limit: 1, + expected: []string{existingRequestID, displayRequestID}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + req := proto.ListAccessRequestsRequest{ + Filter: tc.filter, + Limit: tc.limit, + } + require.Equal(t, tc.expected, collectAccessRequestNames(t, ctx, authServerClient, req)) + }) + } +} + +func TestListAccessRequestsRequesterDisplaySearchPreservesRBAC(t *testing.T) { + t.Parallel() + + const ( + reviewableRequestID = "00000000-0000-0000-0000-000000000011" + displayUserName = "123456" + reviewerUserName = "reviewer" + reviewableRole = "reviewable-role" + unreviewableRole = "unreviewable-role" + reviewerRole = "reviewer-role" + ) + + authServer, err := authtest.NewAuthServer(authtest.AuthServerConfig{ + Dir: t.TempDir(), + }) + require.NoError(t, err) + defer authServer.Close() + + tlsServer, err := authServer.NewTestTLSServer() + require.NoError(t, err) + defer tlsServer.Close() + + ctx := t.Context() + authServerClient := tlsServer.Auth() + + for roleName, roleSpec := range map[string]types.RoleSpecV6{ + reviewableRole: {}, + unreviewableRole: {}, + reviewerRole: { + Allow: types.RoleConditions{ + ReviewRequests: &types.AccessReviewConditions{ + Roles: []string{reviewableRole}, + }, + }, + }, + } { + role, err := types.NewRole(roleName, roleSpec) + require.NoError(t, err) + _, err = authServerClient.UpsertRole(ctx, role) + require.NoError(t, err) + } + + upsertUser(t, ctx, authServerClient, displayUserName, nil, map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + "department": {"classified-department"}, + }) + upsertUser(t, ctx, authServerClient, reviewerUserName, []string{reviewerRole}, nil) + waitForUserSearchMatch(t, ctx, authServerClient, []string{"Jane"}, displayUserName) + + upsertAccessRequest(t, ctx, authServerClient, reviewableRequestID, displayUserName, []string{reviewableRole}, nil) + upsertAccessRequest(t, ctx, authServerClient, "00000000-0000-0000-0000-000000000012", displayUserName, []string{unreviewableRole}, nil) + + waitForAccessRequests(t, ctx, authServerClient, func(reqs []*types.AccessRequestV3) bool { + return len(reqs) == 2 + }) + + reviewerClient, err := tlsServer.NewClient(authtest.TestUser(reviewerUserName)) + require.NoError(t, err) + defer reviewerClient.Close() + + names := collectAccessRequestNames(t, ctx, reviewerClient, proto.ListAccessRequestsRequest{ + Filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"Jane"}, + }, + }) + require.Equal(t, []string{reviewableRequestID}, names) + + names = collectAccessRequestNames(t, ctx, reviewerClient, proto.ListAccessRequestsRequest{ + Filter: &types.AccessRequestFilter{ + SearchKeywords: []string{"classified-department"}, + }, + }) + require.Empty(t, names) +} + func TestListAccessRequestsUserDisplays(t *testing.T) { t.Parallel() diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 9c52866a5ca9..c31df854d26a 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -6052,7 +6052,9 @@ func (a *Server) ListAccessRequests(ctx context.Context, req *proto.ListAccessRe // immediately after writing, but listing requires support for custom sort orders so we route it to // a special cache. note that the access request cache will still end up forwarding single-request // reads to the real backend due to the read after write issue. - return a.AccessRequestCache.ListAccessRequests(ctx, req) + return a.listMatchingAccessRequests(ctx, req, func(_ *types.AccessRequestV3) bool { + return true + }) } // ListMatchingAccessRequests is equivalent to ListAccessRequests except that it adds the ability to provide an arbitrary matcher function. This method @@ -6062,7 +6064,40 @@ func (a *Server) ListMatchingAccessRequests(ctx context.Context, req *proto.List // immediately after writing, but listing requires support for custom sort orders so we route it to // a special cache. note that the access request cache will still end up forwarding single-request // reads to the real backend due to the read after write issue. - return a.AccessRequestCache.ListMatchingAccessRequests(ctx, req, match) + return a.listMatchingAccessRequests(ctx, req, match) +} + +func (a *Server) listMatchingAccessRequests(ctx context.Context, req *proto.ListAccessRequestsRequest, match func(*types.AccessRequestV3) bool) (*proto.ListAccessRequestsResponse, error) { + cacheReq, searchKeywords := splitAccessRequestSearchKeywords(req) + searchMatcher := services.NewAccessRequestSearchMatcher(searchKeywords, a) + + return a.AccessRequestCache.ListMatchingAccessRequests(ctx, cacheReq, func(accessRequest *types.AccessRequestV3) bool { + // searchMatcher decides whether the request matches the query. match + // preserves the caller-specific visibility filter, such as RBAC. + return searchMatcher(ctx, accessRequest) && match(accessRequest) + }) +} + +func splitAccessRequestSearchKeywords(req *proto.ListAccessRequestsRequest) (*proto.ListAccessRequestsRequest, []string) { + if req.Filter == nil || len(req.Filter.SearchKeywords) == 0 { + return req, nil + } + + searchKeywords := make([]string, 0, len(req.Filter.SearchKeywords)) + for _, keyword := range req.Filter.SearchKeywords { + keyword = strings.TrimSpace(keyword) + if keyword != "" { + searchKeywords = append(searchKeywords, keyword) + } + } + + reqCopy := *req + filterCopy := *req.Filter + // Search keywords can contain user display values that are not stored in the + // backend to result in false negatives, so the custom matcher handles all keyword filtering instead. + filterCopy.SearchKeywords = nil + reqCopy.Filter = &filterCopy + return &reqCopy, searchKeywords } func (a *Server) CreateAccessRequestV2(ctx context.Context, req types.AccessRequest, identity tlsca.Identity) (types.AccessRequest, error) { diff --git a/lib/services/user_search.go b/lib/services/user_search.go new file mode 100644 index 000000000000..0a63dbd803d6 --- /dev/null +++ b/lib/services/user_search.go @@ -0,0 +1,131 @@ +/* + * Teleport + * Copyright (C) 2026 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 services + +import ( + "context" + "strings" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" + apidefaults "github.com/gravitational/teleport/api/defaults" + userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" + "github.com/gravitational/teleport/api/types" + logutils "github.com/gravitational/teleport/lib/utils/log" + "github.com/gravitational/teleport/lib/utils/set" +) + +var userSearchLogger = logutils.NewPackageLogger(teleport.ComponentKey, teleport.Component("user", "search")) + +// UserSearchLister lists users for display-value search. +type UserSearchLister interface { + ListUsers(ctx context.Context, req *userspb.ListUsersRequest) (*userspb.ListUsersResponse, error) +} + +// findUsernamesBySearchKeywords returns usernames whose resolved display values match the keywords. +func findUsernamesBySearchKeywords(ctx context.Context, users UserSearchLister, searchKeywords []string) (set.Set[string], error) { + if len(searchKeywords) == 0 { + return nil, nil + } + + usernames := set.NewWithCapacity[string](apidefaults.DefaultChunkSize) + var pageToken string + for { + rsp, err := users.ListUsers(ctx, userspb.ListUsersRequest_builder{ + PageSize: apidefaults.DefaultChunkSize, + PageToken: pageToken, + Filter: &types.UserFilter{ + SearchKeywords: searchKeywords, + SkipSystemUsers: true, + }, + }.Build()) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, user := range rsp.GetUsers() { + display := user.GetDisplay() + // Exclude non-display trait matches. + if !types.MatchSearch([]string{display.Primary, display.Secondary}, searchKeywords, nil) { + continue + } + usernames.Add(user.GetName()) + if usernames.Len() == apidefaults.DefaultChunkSize { + // Cap resolved usernames to avoid excessive paging. + return usernames, nil + } + } + + pageToken = rsp.GetNextPageToken() + if pageToken == "" { + return usernames, nil + } + } +} + +type searchKeywordUsernameResolver struct { + users UserSearchLister + // usernamesBySearchKeyword caches the resolved usernames for each keyword. + usernamesBySearchKeyword map[string]set.Set[string] +} + +// NewSearchKeywordUsernameResolver returns a memoizing resolver for search-keyword username matches. +func NewSearchKeywordUsernameResolver(users UserSearchLister) func(context.Context, string) set.Set[string] { + resolver := &searchKeywordUsernameResolver{ + users: users, + usernamesBySearchKeyword: make(map[string]set.Set[string]), + } + return resolver.resolveUsernames +} + +func (r *searchKeywordUsernameResolver) resolveUsernames(ctx context.Context, searchKeyword string) set.Set[string] { + searchKeyword = strings.TrimSpace(searchKeyword) + if searchKeyword == "" { + return nil + } + + if usernames, ok := r.usernamesBySearchKeyword[searchKeyword]; ok { + return usernames + } + + usernames, err := findUsernamesBySearchKeywords(ctx, r.users, []string{searchKeyword}) + if err != nil { + userSearchLogger.WarnContext(ctx, "Failed to resolve search keyword to users", + "search_keywords", []string{searchKeyword}, + "error", err, + ) + r.usernamesBySearchKeyword[searchKeyword] = nil + return nil + } + + r.usernamesBySearchKeyword[searchKeyword] = usernames + return usernames +} + +// NewAccessRequestSearchMatcher returns a matcher that checks stored request fields and requester user-search matches. +func NewAccessRequestSearchMatcher(searchKeywords []string, users UserSearchLister) func(context.Context, *types.AccessRequestV3) bool { + resolveToUsernames := NewSearchKeywordUsernameResolver(users) + + return func(ctx context.Context, accessRequest *types.AccessRequestV3) bool { + return types.MatchSearch(accessRequest.SearchableFields(), searchKeywords, func(searchKeyword string) bool { + return resolveToUsernames(ctx, searchKeyword).Contains(accessRequest.GetUser()) + }) + } +} diff --git a/lib/services/user_search_test.go b/lib/services/user_search_test.go new file mode 100644 index 000000000000..9247152daf0a --- /dev/null +++ b/lib/services/user_search_test.go @@ -0,0 +1,295 @@ +/* + * Teleport + * Copyright (C) 2026 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 services + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + apidefaults "github.com/gravitational/teleport/api/defaults" + userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/utils/set" +) + +type testUserSearchLister struct { + users []*types.UserV2 + responsesByPageToken map[string]*userspb.ListUsersResponse + err error + calls int + ctx context.Context + request *userspb.ListUsersRequest + requests []*userspb.ListUsersRequest +} + +func (l *testUserSearchLister) ListUsers(ctx context.Context, req *userspb.ListUsersRequest) (*userspb.ListUsersResponse, error) { + l.calls++ + l.ctx = ctx + l.request = req + l.requests = append(l.requests, req) + if l.err != nil { + return nil, l.err + } + + if l.responsesByPageToken != nil { + response, ok := l.responsesByPageToken[req.GetPageToken()] + if !ok { + return nil, fmt.Errorf("unexpected page token %q", req.GetPageToken()) + } + return response, nil + } + + return userspb.ListUsersResponse_builder{ + Users: l.users, + }.Build(), nil +} + +func TestFindUsernamesBySearchKeywords(t *testing.T) { + t.Parallel() + + const janeUsername = "123456" + + jane, err := types.NewUser(janeUsername) + require.NoError(t, err) + jane.SetTraits(map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + }) + + lister := &testUserSearchLister{ + users: []*types.UserV2{jane.(*types.UserV2)}, + } + + usernames, err := findUsernamesBySearchKeywords(t.Context(), lister, []string{"Jane", "Garcia"}) + require.NoError(t, err) + require.Equal(t, set.New(janeUsername), usernames) + + // Verify that only one page of users was requested with the expected filter. + require.Equal(t, 1, lister.calls) + require.Equal(t, int32(apidefaults.DefaultChunkSize), lister.request.GetPageSize()) + require.Equal(t, []string{"Jane", "Garcia"}, lister.request.GetFilter().SearchKeywords) + require.True(t, lister.request.GetFilter().SkipSystemUsers) + + _, err = findUsernamesBySearchKeywords(t.Context(), &testUserSearchLister{err: errors.New("backend unavailable")}, []string{"Jane"}) + require.Error(t, err) +} + +func TestFindUsernamesBySearchKeywordsPaginatesPastNonDisplayMatches(t *testing.T) { + t.Parallel() + + nonDisplayMatch, err := types.NewUser("non-display-match") + require.NoError(t, err) + nonDisplayMatch.SetRoles([]string{"dev"}) + + displayMatch, err := types.NewUser("display-match") + require.NoError(t, err) + displayMatch.SetTraits(map[string][]string{ + "okta/displayName": {"Devon Garcia"}, + }) + + lister := &testUserSearchLister{ + responsesByPageToken: map[string]*userspb.ListUsersResponse{ + "": userspb.ListUsersResponse_builder{ + Users: []*types.UserV2{nonDisplayMatch.(*types.UserV2)}, + NextPageToken: "second-page", + }.Build(), + "second-page": userspb.ListUsersResponse_builder{ + Users: []*types.UserV2{displayMatch.(*types.UserV2)}, + }.Build(), + }, + } + + usernames, err := findUsernamesBySearchKeywords(t.Context(), lister, []string{"dev"}) + require.NoError(t, err) + require.Equal(t, set.New("display-match"), usernames) + require.Len(t, lister.requests, 2) + require.Empty(t, lister.requests[0].GetPageToken()) + require.Equal(t, "second-page", lister.requests[1].GetPageToken()) +} + +func TestFindUsernamesBySearchKeywordsStopsAtDisplayMatchLimit(t *testing.T) { + t.Parallel() + + users := make([]*types.UserV2, 0, apidefaults.DefaultChunkSize) + for i := range apidefaults.DefaultChunkSize { + user, err := types.NewUser(fmt.Sprintf("display-match-%04d", i)) + require.NoError(t, err) + user.SetTraits(map[string][]string{ + "okta/displayName": {"Devon Garcia"}, + }) + users = append(users, user.(*types.UserV2)) + } + + lister := &testUserSearchLister{ + responsesByPageToken: map[string]*userspb.ListUsersResponse{ + "": userspb.ListUsersResponse_builder{ + Users: users, + NextPageToken: "second-page", + }.Build(), + "second-page": userspb.ListUsersResponse_builder{ + NextPageToken: "unexpected-third-page", + }.Build(), + }, + } + + usernames, err := findUsernamesBySearchKeywords(t.Context(), lister, []string{"dev"}) + require.NoError(t, err) + require.Len(t, usernames, apidefaults.DefaultChunkSize) + require.Len(t, lister.requests, 1) +} + +func TestFindUsernamesBySearchKeywordsMatchesOnlyDisplayValues(t *testing.T) { + t.Parallel() + + const janeUsername = "123456" + + jane, err := types.NewUser(janeUsername) + require.NoError(t, err) + jane.SetRoles([]string{"finance-role"}) + jane.SetStaticLabels(map[string]string{ + "cost-center": "secret-cost-center", + }) + jane.SetTraits(map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + "okta/email": {"jane.garcia@example.com"}, + "department": {"classified-department"}, + }) + janeV2 := jane.(*types.UserV2) + + tests := []struct { + name string + keyword string + expected set.Set[string] + }{ + { + name: "primary display", + keyword: "Jane", + expected: set.New(janeUsername), + }, + { + name: "secondary display", + keyword: "jane.garcia@example.com", + expected: set.New(janeUsername), + }, + {name: "role", keyword: "finance-role"}, + {name: "label key", keyword: "cost-center"}, + {name: "label value", keyword: "secret-cost-center"}, + {name: "trait key", keyword: "department"}, + {name: "trait value", keyword: "classified-department"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + require.True(t, janeV2.MatchSearch([]string{test.keyword})) + + usernames, err := findUsernamesBySearchKeywords(t.Context(), &testUserSearchLister{ + users: []*types.UserV2{janeV2}, + }, []string{test.keyword}) + require.NoError(t, err) + if test.expected == nil { + require.Empty(t, usernames) + return + } + require.Equal(t, test.expected, usernames) + }) + } +} + +func TestSearchKeywordUsernameResolver(t *testing.T) { + t.Parallel() + + const janeUsername = "123456" + + jane, err := types.NewUser(janeUsername) + require.NoError(t, err) + jane.SetTraits(map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + }) + + type contextKey struct{} + ctx := context.WithValue(t.Context(), contextKey{}, "test-value") + lister := &testUserSearchLister{users: []*types.UserV2{jane.(*types.UserV2)}} + resolveUsernames := NewSearchKeywordUsernameResolver(lister) + + require.Equal(t, set.New(janeUsername), resolveUsernames(ctx, " Jane ")) + require.Equal(t, set.New(janeUsername), resolveUsernames(ctx, "Jane")) + require.Equal(t, 1, lister.calls) + require.Equal(t, ctx, lister.ctx) + require.Nil(t, resolveUsernames(ctx, " ")) + require.Equal(t, 1, lister.calls) +} + +func TestNewAccessRequestSearchMatcherResolvesLazilyAndMemoizes(t *testing.T) { + t.Parallel() + + const janeUsername = "123456" + + jane, err := types.NewUser(janeUsername) + require.NoError(t, err) + jane.SetTraits(map[string][]string{ + "okta/displayName": {"Jane Garcia"}, + }) + + accessRequest, err := types.NewAccessRequest("request", janeUsername, "db-access") + require.NoError(t, err) + accessRequestV3 := accessRequest.(*types.AccessRequestV3) + lister := &testUserSearchLister{users: []*types.UserV2{jane.(*types.UserV2)}} + + storedFieldMatcher := NewAccessRequestSearchMatcher([]string{"db-access"}, lister) + require.True(t, storedFieldMatcher(t.Context(), accessRequestV3)) + require.Zero(t, lister.calls) + + displayMatcher := NewAccessRequestSearchMatcher([]string{"Jane"}, lister) + require.True(t, displayMatcher(t.Context(), accessRequestV3)) + require.True(t, displayMatcher(t.Context(), accessRequestV3)) + require.Equal(t, 1, lister.calls) +} + +func TestNewAccessRequestSearchMatcherDegradesGracefullyOnUserLookupFailure(t *testing.T) { + t.Parallel() + + const displayOnlyUsername = "123456" + + displayOnly, err := types.NewAccessRequest("display-only", displayOnlyUsername, "role") + require.NoError(t, err) + + resolvedUser, err := types.NewUser(displayOnlyUsername) + require.NoError(t, err) + resolvedUser.SetTraits(map[string][]string{ + "okta/displayName": {"Jane"}, + }) + resolvedUsers := []*types.UserV2{resolvedUser.(*types.UserV2)} + + successLister := &testUserSearchLister{users: resolvedUsers} + successMatcher := NewAccessRequestSearchMatcher([]string{"Jane"}, successLister) + require.True(t, successMatcher(t.Context(), displayOnly.(*types.AccessRequestV3)), "display-only match should pass when user lookup resolves the requester") + + failedLister := &testUserSearchLister{ + users: resolvedUsers, + err: errors.New("backend unavailable"), + } + failedMatcher := NewAccessRequestSearchMatcher([]string{"Jane"}, failedLister) + // Gracefully degrade to return false when user lookup fails. + require.False(t, failedMatcher(t.Context(), displayOnly.(*types.AccessRequestV3)), "same display-only match should be skipped when user lookup fails") +} From ae3ede8e1c6c666fd922f4f82db505f966feb752 Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Mon, 27 Jul 2026 14:49:43 -0400 Subject: [PATCH 013/347] adding scope to keepalive proto (#69046) --- api/proto/teleport/legacy/types/types.proto | 2 + api/types/types.pb.go | 4725 ++++++++++--------- lib/inventory/controller.go | 7 +- lib/inventory/controller_test.go | 21 +- lib/inventory/inventory.go | 2 +- lib/services/local/presence.go | 7 +- lib/services/local/presence_test.go | 120 + 7 files changed, 2535 insertions(+), 2349 deletions(-) diff --git a/api/proto/teleport/legacy/types/types.proto b/api/proto/teleport/legacy/types/types.proto index cb75c3adc034..0ade1fdf82f1 100644 --- a/api/proto/teleport/legacy/types/types.proto +++ b/api/proto/teleport/legacy/types/types.proto @@ -74,6 +74,8 @@ message KeepAlive { KeepAliveType Type = 9 [(gogoproto.jsontag) = "type"]; // HostID is an optional UUID of the host the resource belongs to. string HostID = 10 [(gogoproto.jsontag) = "host_id,omitempty"]; + // The scope the resource being kept alive belongs to. + string scope = 11; } // Rotation is a status of the rotation of the certificate authority diff --git a/api/types/types.pb.go b/api/types/types.pb.go index 5c7c58232204..4ded5933ec9f 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -1526,7 +1526,9 @@ type KeepAlive struct { // Type is the type (or kind) of the resource that's being kept alive. Type KeepAlive_KeepAliveType `protobuf:"varint,9,opt,name=Type,proto3,enum=types.KeepAlive_KeepAliveType" json:"type"` // HostID is an optional UUID of the host the resource belongs to. - HostID string `protobuf:"bytes,10,opt,name=HostID,proto3" json:"host_id,omitempty"` + HostID string `protobuf:"bytes,10,opt,name=HostID,proto3" json:"host_id,omitempty"` + // The scope the resource being kept alive belongs to. + Scope string `protobuf:"bytes,11,opt,name=scope,proto3" json:"scope,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -26446,2345 +26448,2345 @@ func init() { func init() { proto.RegisterFile("teleport/legacy/types/types.proto", fileDescriptor_9198ee693835762e) } var fileDescriptor_9198ee693835762e = []byte{ - // 37393 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xfd, 0x7d, 0x90, 0x1c, 0x49, - 0x76, 0x18, 0x86, 0x5f, 0x77, 0xcf, 0x47, 0xcf, 0x9b, 0xaf, 0x9e, 0xc4, 0xd7, 0x60, 0x76, 0xb1, - 0x83, 0xad, 0xdd, 0xc5, 0x02, 0x7b, 0xbb, 0xc0, 0x2e, 0x70, 0x8b, 0xbb, 0xfd, 0xb8, 0xdb, 0xed, - 0xf9, 0x00, 0x30, 0xc0, 0x00, 0xe8, 0xad, 0x1e, 0x60, 0xee, 0x78, 0x1f, 0xb5, 0x35, 0xdd, 0x39, - 0x33, 0xb5, 0xe8, 0xae, 0xea, 0xad, 0xaa, 0x1e, 0x60, 0x74, 0x94, 0xa8, 0x1f, 0x75, 0x27, 0x89, - 0xe2, 0x8f, 0xe6, 0x87, 0x48, 0x91, 0x72, 0x98, 0x0c, 0x85, 0x23, 0x64, 0x7d, 0xd0, 0x74, 0x84, - 0x29, 0xc9, 0xa6, 0x43, 0x16, 0x4d, 0x3a, 0xf4, 0x61, 0x2b, 0x6c, 0x4a, 0x11, 0x94, 0x6d, 0x99, - 0x71, 0x1e, 0x86, 0x4d, 0xda, 0x21, 0xc3, 0x11, 0x0a, 0xd9, 0x7f, 0x30, 0xc2, 0xa7, 0x70, 0xd8, - 0x91, 0x2f, 0x5f, 0x56, 0x65, 0x56, 0x55, 0xcf, 0xc7, 0x2e, 0x96, 0x24, 0x2e, 0xf8, 0x0f, 0x30, - 0xfd, 0xf2, 0xbd, 0x97, 0x59, 0xf9, 0xf1, 0xf2, 0xe5, 0xcb, 0xf7, 0x5e, 0xc2, 0xf3, 0x31, 0xef, - 0xf0, 0x5e, 0x10, 0xc6, 0x97, 0x3a, 0x7c, 0xcb, 0x6d, 0xed, 0x5e, 0x8a, 0x77, 0x7b, 0x3c, 0x92, - 0xff, 0x5e, 0xec, 0x85, 0x41, 0x1c, 0xb0, 0x61, 0xfc, 0x31, 0x77, 0x7c, 0x2b, 0xd8, 0x0a, 0x10, - 0x72, 0x49, 0xfc, 0x25, 0x0b, 0xe7, 0x9e, 0xdb, 0x0a, 0x82, 0xad, 0x0e, 0xbf, 0x84, 0xbf, 0x36, - 0xfa, 0x9b, 0x97, 0xda, 0xfd, 0xd0, 0x8d, 0xbd, 0xc0, 0xa7, 0xf2, 0x67, 0xb3, 0xe5, 0x51, 0x1c, - 0xf6, 0x5b, 0x31, 0x95, 0xce, 0x67, 0x4b, 0x63, 0xaf, 0xcb, 0xa3, 0xd8, 0xed, 0xf6, 0x08, 0xe1, - 0x42, 0xd2, 0x3c, 0x37, 0x8e, 0x45, 0x89, 0x60, 0x7d, 0x69, 0xe7, 0x0d, 0xfd, 0x27, 0xa1, 0x5e, - 0x4d, 0x50, 0x5b, 0x41, 0xb7, 0x17, 0xf8, 0xdc, 0x8f, 0x37, 0xb9, 0x1b, 0xf7, 0x43, 0x1e, 0x09, - 0x82, 0x04, 0xe8, 0x28, 0x28, 0xd1, 0xbd, 0x58, 0xdc, 0x03, 0x5d, 0x1e, 0xbb, 0x6d, 0x37, 0x76, - 0x09, 0xeb, 0xdc, 0x00, 0xac, 0x4d, 0xd7, 0x69, 0xf3, 0x1d, 0xaf, 0xc5, 0x09, 0xef, 0xa5, 0x62, - 0xbc, 0x90, 0x47, 0x41, 0x3f, 0x6c, 0x25, 0x95, 0x5e, 0x1d, 0xd0, 0xed, 0x61, 0x3f, 0x8a, 0x79, - 0x9b, 0x58, 0x3a, 0x21, 0xff, 0xb8, 0xef, 0x85, 0xbc, 0xcb, 0x7d, 0xd5, 0x61, 0xaf, 0x15, 0xd3, - 0x3d, 0x0c, 0xdd, 0x5e, 0x8f, 0x87, 0xe9, 0x1f, 0xaa, 0x7f, 0x13, 0xf4, 0xa8, 0x15, 0xf4, 0x64, - 0x47, 0xc8, 0xbf, 0x24, 0x82, 0xf5, 0x4b, 0x15, 0x18, 0xbb, 0xc5, 0x79, 0xaf, 0xde, 0xf1, 0x76, - 0x38, 0x7b, 0x01, 0x86, 0xee, 0xb8, 0x5d, 0x3e, 0x5b, 0x3a, 0x5b, 0x3a, 0x3f, 0xb6, 0x30, 0xfd, - 0x78, 0x6f, 0x7e, 0x3c, 0xe2, 0xe1, 0x0e, 0x0f, 0x1d, 0xdf, 0xed, 0x72, 0x1b, 0x0b, 0xd9, 0xe7, - 0x61, 0x4c, 0xfc, 0x1f, 0xf5, 0xdc, 0x16, 0x9f, 0x2d, 0x23, 0xe6, 0xe4, 0xe3, 0xbd, 0xf9, 0x31, - 0x5f, 0x01, 0xed, 0xb4, 0x9c, 0xad, 0xc0, 0xe8, 0xf2, 0xa3, 0x9e, 0x17, 0xf2, 0x68, 0x76, 0xe8, - 0x6c, 0xe9, 0xfc, 0xf8, 0xe5, 0xb9, 0x8b, 0x72, 0xc8, 0x2f, 0xaa, 0x21, 0xbf, 0xb8, 0xa6, 0x86, - 0x7c, 0xe1, 0xd8, 0x7f, 0xb5, 0x37, 0xff, 0xb9, 0xc7, 0x7b, 0xf3, 0xa3, 0x5c, 0x92, 0xfc, 0xd4, - 0xef, 0xce, 0x97, 0x6c, 0x45, 0xcf, 0xde, 0x85, 0xa1, 0xb5, 0xdd, 0x1e, 0x9f, 0x1d, 0x3b, 0x5b, - 0x3a, 0x3f, 0x75, 0xf9, 0xb9, 0x8b, 0x72, 0x8a, 0x26, 0x8d, 0x4f, 0xff, 0x12, 0x58, 0x0b, 0xd5, - 0xc7, 0x7b, 0xf3, 0x43, 0x02, 0xc5, 0x46, 0x2a, 0xf6, 0x1a, 0x8c, 0xdc, 0x08, 0xa2, 0x78, 0x65, - 0x69, 0x16, 0xb0, 0xc9, 0x27, 0x1e, 0xef, 0xcd, 0xcf, 0x6c, 0x07, 0x51, 0xec, 0x78, 0xed, 0x57, - 0x83, 0xae, 0x17, 0xf3, 0x6e, 0x2f, 0xde, 0xb5, 0x09, 0xc9, 0x7a, 0x04, 0x93, 0x06, 0x3f, 0x36, - 0x0e, 0xa3, 0xf7, 0xee, 0xdc, 0xba, 0x73, 0x77, 0xfd, 0x4e, 0xed, 0x73, 0xac, 0x0a, 0x43, 0x77, - 0xee, 0x2e, 0x2d, 0xd7, 0x4a, 0x6c, 0x14, 0x2a, 0xf5, 0x46, 0xa3, 0x56, 0x66, 0x13, 0x50, 0x5d, - 0xaa, 0xaf, 0xd5, 0x17, 0xea, 0xcd, 0xe5, 0x5a, 0x85, 0x1d, 0x83, 0xe9, 0xf5, 0x95, 0x3b, 0x4b, - 0x77, 0xd7, 0x9b, 0xce, 0xd2, 0x72, 0xf3, 0xd6, 0xda, 0xdd, 0x46, 0x6d, 0x88, 0x4d, 0x01, 0xdc, - 0xba, 0xb7, 0xb0, 0x6c, 0xdf, 0x59, 0x5e, 0x5b, 0x6e, 0xd6, 0x86, 0xd9, 0x71, 0xa8, 0x29, 0x12, - 0xa7, 0xb9, 0x6c, 0xdf, 0x5f, 0x59, 0x5c, 0xae, 0x8d, 0xdc, 0x1c, 0xaa, 0x56, 0x6a, 0x43, 0xf6, - 0xe8, 0x2a, 0x77, 0x23, 0xbe, 0xb2, 0x64, 0xfd, 0xcc, 0x10, 0x54, 0xed, 0x40, 0x4e, 0x74, 0x76, - 0x01, 0x86, 0x9b, 0xb1, 0x1b, 0xab, 0x01, 0x3a, 0xf6, 0x78, 0x6f, 0x7e, 0x5a, 0x2c, 0x02, 0xae, - 0x7d, 0x81, 0xc4, 0x10, 0xa8, 0x8d, 0x6d, 0x37, 0x52, 0x23, 0x84, 0xa8, 0x3d, 0x01, 0xd0, 0x51, - 0x11, 0x83, 0x9d, 0x83, 0xa1, 0xdb, 0x41, 0x9b, 0xcf, 0x56, 0x10, 0x93, 0x3d, 0xde, 0x9b, 0x9f, - 0xea, 0x06, 0x6d, 0x1d, 0x11, 0xcb, 0xd9, 0xab, 0x30, 0xb6, 0xd8, 0x0f, 0x43, 0xee, 0x8b, 0x5e, - 0x1c, 0x42, 0xe4, 0xa9, 0xc7, 0x7b, 0xf3, 0xd0, 0x92, 0x40, 0xc7, 0x6b, 0xdb, 0x29, 0x02, 0x6b, - 0xc2, 0x68, 0x33, 0x76, 0xc3, 0x98, 0xb7, 0x67, 0x87, 0x0f, 0x1c, 0xf9, 0x33, 0x34, 0xf2, 0x33, - 0x91, 0x24, 0x49, 0xeb, 0x96, 0x73, 0x80, 0x38, 0xb1, 0x1b, 0x30, 0x7e, 0x3d, 0x74, 0x5b, 0xbc, - 0xc1, 0x43, 0x2f, 0x68, 0xcf, 0x8e, 0x9c, 0x2d, 0x9d, 0xaf, 0x2c, 0x9c, 0x7b, 0xbc, 0x37, 0x7f, - 0x72, 0x4b, 0x80, 0x9d, 0x1e, 0xc2, 0x53, 0xea, 0xef, 0xef, 0xcd, 0x57, 0x97, 0x48, 0x20, 0xd9, - 0x3a, 0x29, 0xfb, 0x10, 0xc6, 0x57, 0xdd, 0x28, 0xc6, 0xae, 0xe5, 0xed, 0xd9, 0xd1, 0x03, 0x9b, - 0x68, 0x51, 0x13, 0x4f, 0x76, 0xdc, 0x28, 0x76, 0x42, 0x49, 0x97, 0x69, 0xa7, 0xce, 0x92, 0xdd, - 0x85, 0x6a, 0xb3, 0xb5, 0xcd, 0xdb, 0xfd, 0x0e, 0x9f, 0xad, 0x22, 0xfb, 0x53, 0x34, 0x67, 0xd5, - 0x78, 0xaa, 0xe2, 0x85, 0x39, 0xe2, 0xcd, 0x22, 0x82, 0x68, 0x7d, 0x9f, 0x30, 0x79, 0xbb, 0xfa, - 0x0b, 0x7f, 0x6d, 0xfe, 0x73, 0x7f, 0xf6, 0x7b, 0x67, 0x3f, 0x67, 0xfd, 0xa7, 0x65, 0xa8, 0x65, - 0x99, 0xb0, 0x4d, 0x98, 0xbc, 0xd7, 0x6b, 0xbb, 0x31, 0x5f, 0xec, 0x78, 0xdc, 0x8f, 0x23, 0x9c, - 0x24, 0xfb, 0x7f, 0xd3, 0x8b, 0x54, 0xef, 0x6c, 0x1f, 0x09, 0x9d, 0x96, 0xa4, 0xcc, 0x7c, 0x95, - 0xc9, 0x36, 0xad, 0xa7, 0x89, 0xa2, 0x21, 0xc2, 0x19, 0x76, 0xb4, 0x7a, 0xa4, 0x50, 0x19, 0x50, - 0x0f, 0xb1, 0xa5, 0x09, 0xe4, 0xb7, 0x37, 0x76, 0x71, 0x66, 0x1e, 0x7e, 0x02, 0x09, 0x92, 0x82, - 0x09, 0x24, 0xc0, 0xd6, 0xef, 0x97, 0x60, 0xca, 0x26, 0x59, 0x7c, 0x83, 0xbb, 0x6d, 0x1e, 0x8a, - 0xe9, 0x7f, 0xcb, 0xf3, 0xdb, 0xb4, 0xa6, 0x70, 0xfa, 0x3f, 0xf0, 0x7c, 0x5d, 0x28, 0x60, 0x39, - 0x7b, 0x1d, 0x46, 0x9b, 0xfd, 0x0d, 0x44, 0x95, 0x6b, 0xea, 0x24, 0x8e, 0x58, 0x7f, 0xc3, 0xc9, - 0xa0, 0x2b, 0x34, 0x76, 0x09, 0x46, 0xef, 0xf3, 0x30, 0xf2, 0x02, 0x9f, 0xd6, 0x16, 0x0a, 0x9d, - 0x1d, 0x09, 0xd2, 0x09, 0x08, 0x8b, 0x5d, 0x87, 0xea, 0x6d, 0xda, 0x76, 0x48, 0x5c, 0x4e, 0xd3, - 0x94, 0x51, 0xe0, 0x74, 0xaa, 0xa8, 0xfd, 0x49, 0x9f, 0x2a, 0x0a, 0xcb, 0xfa, 0xd7, 0x65, 0xa8, - 0x2d, 0xb9, 0xb1, 0xbb, 0xe1, 0x46, 0xd4, 0x9f, 0xf7, 0xaf, 0xb0, 0x67, 0x8d, 0x0f, 0x45, 0x01, - 0x29, 0x5a, 0xfe, 0x89, 0x3f, 0xef, 0xa5, 0xec, 0xe7, 0x8d, 0x0b, 0xd9, 0x4d, 0x9f, 0x97, 0x7e, - 0xd4, 0x97, 0x0f, 0xfe, 0xa8, 0x1a, 0x7d, 0x54, 0x55, 0x7d, 0x54, 0xfa, 0x29, 0xec, 0xcb, 0x30, - 0xd4, 0xec, 0xf1, 0x16, 0x09, 0x91, 0x67, 0x88, 0xd4, 0xfc, 0x38, 0x81, 0x70, 0xff, 0xca, 0xc2, - 0x04, 0xb1, 0x19, 0x8a, 0x7a, 0xbc, 0x65, 0x23, 0x19, 0x5b, 0x86, 0x11, 0x21, 0x10, 0xfb, 0x11, - 0x0a, 0x8b, 0xf1, 0xcb, 0x67, 0x8a, 0x19, 0x20, 0xca, 0xfd, 0x2b, 0x0b, 0x53, 0xc4, 0x62, 0x24, - 0x42, 0x88, 0x4d, 0xc4, 0xec, 0x38, 0x0c, 0xe3, 0xbe, 0x89, 0x82, 0x62, 0xcc, 0x96, 0x3f, 0xb4, - 0x15, 0xf9, 0x07, 0x15, 0x38, 0x5e, 0xd4, 0x26, 0xbd, 0x93, 0x46, 0xf6, 0xe9, 0xa4, 0xf3, 0x50, - 0x15, 0x3b, 0x8f, 0xd8, 0x43, 0x65, 0x15, 0x0b, 0x13, 0xa2, 0x3f, 0xb6, 0x09, 0x66, 0x27, 0xa5, - 0xec, 0x85, 0x64, 0x23, 0xab, 0xa6, 0xfc, 0x68, 0x23, 0x53, 0xdb, 0x97, 0x98, 0x48, 0x4a, 0x3e, - 0xe0, 0x7e, 0x97, 0xf6, 0xb9, 0x02, 0xa7, 0x13, 0x29, 0x24, 0x88, 0x3e, 0x91, 0x92, 0x1d, 0x67, - 0x19, 0xaa, 0xea, 0xb3, 0x66, 0x27, 0x90, 0xd1, 0x4c, 0xa6, 0x03, 0xef, 0x5f, 0x91, 0x33, 0xa5, - 0x4d, 0xbf, 0x75, 0x36, 0x0a, 0x87, 0x5d, 0x81, 0x6a, 0x23, 0x0c, 0x1e, 0xed, 0xae, 0x2c, 0x45, - 0xb3, 0x93, 0x67, 0x2b, 0xe7, 0xc7, 0x16, 0x4e, 0x3d, 0xde, 0x9b, 0x3f, 0xd6, 0x13, 0x30, 0xc7, - 0x6b, 0x47, 0x3a, 0x91, 0x42, 0x64, 0xf3, 0x30, 0x1e, 0xf2, 0x8e, 0xbb, 0xeb, 0x6c, 0x85, 0x41, - 0xbf, 0x37, 0x3b, 0x85, 0x3d, 0x0f, 0x08, 0xba, 0x2e, 0x20, 0xec, 0x19, 0x18, 0x93, 0x08, 0x5e, - 0x3b, 0x9a, 0x9d, 0x16, 0x6c, 0xed, 0x2a, 0x02, 0x56, 0xda, 0xd1, 0xcd, 0xa1, 0x6a, 0xa9, 0x56, - 0xbe, 0x39, 0x54, 0x2d, 0xd7, 0x2a, 0x72, 0x4f, 0xbd, 0x39, 0x54, 0x1d, 0xaa, 0x0d, 0xdf, 0x1c, - 0xaa, 0x0e, 0xe3, 0x2e, 0x3b, 0x56, 0x83, 0x9b, 0x43, 0xd5, 0xf1, 0xda, 0x84, 0x3d, 0xbe, 0xc4, - 0xa3, 0x56, 0xe8, 0xf5, 0x70, 0x93, 0x10, 0xd5, 0xc7, 0x41, 0x2b, 0xe8, 0xd8, 0x95, 0x7b, 0xf6, - 0x8a, 0x3d, 0xb2, 0x58, 0x5f, 0xe4, 0x61, 0x6c, 0x57, 0xea, 0xeb, 0x4d, 0x7b, 0x72, 0x69, 0xd7, - 0x77, 0xbb, 0x5e, 0x6b, 0xd5, 0xdd, 0xe0, 0x9d, 0xc8, 0xae, 0x5c, 0x5f, 0x6c, 0x58, 0x3e, 0x9c, - 0x2c, 0x9e, 0x4a, 0x6c, 0x0d, 0x26, 0xd6, 0xdc, 0x70, 0x8b, 0xc7, 0x37, 0xb8, 0xdb, 0x89, 0xb7, - 0xb1, 0xfd, 0xe3, 0x97, 0x8f, 0x51, 0xf7, 0xe9, 0x45, 0x0b, 0xcf, 0x3c, 0xde, 0x9b, 0x3f, 0x15, - 0x23, 0xc4, 0xd9, 0x46, 0x90, 0xd6, 0x21, 0x06, 0x17, 0xab, 0x0e, 0x53, 0x69, 0xcf, 0xaf, 0x7a, - 0x51, 0xcc, 0x2e, 0xc1, 0x98, 0x82, 0x08, 0x99, 0x5f, 0x29, 0x1c, 0x23, 0x3b, 0xc5, 0xb1, 0xfe, - 0x71, 0x19, 0x20, 0x2d, 0x79, 0x4a, 0xc5, 0xc2, 0x17, 0x0d, 0xb1, 0x70, 0x22, 0xbb, 0xaa, 0x07, - 0x0b, 0x84, 0xf7, 0x32, 0x02, 0xe1, 0x54, 0x96, 0xf4, 0x00, 0x51, 0xa0, 0x2d, 0xfa, 0x5f, 0x1a, - 0x4d, 0x07, 0x83, 0x96, 0xfb, 0x79, 0x48, 0x26, 0x10, 0x75, 0x28, 0xae, 0xe3, 0x9e, 0x9a, 0x54, - 0x49, 0x29, 0x3b, 0x0d, 0x62, 0x82, 0x51, 0xa7, 0x8e, 0x3e, 0xde, 0x9b, 0xaf, 0xf4, 0x43, 0x0f, - 0x27, 0x1d, 0xbb, 0x04, 0x34, 0xed, 0xa8, 0x03, 0xc5, 0x5a, 0x99, 0x69, 0xb9, 0x4e, 0x8b, 0x87, - 0x71, 0xda, 0xe3, 0xb3, 0x25, 0x35, 0x3b, 0x59, 0x0f, 0xcc, 0xa9, 0x39, 0x3b, 0x84, 0xd3, 0xe0, - 0x7c, 0x61, 0xaf, 0x5c, 0x34, 0x50, 0x97, 0xfd, 0x38, 0xdc, 0x5d, 0x38, 0xab, 0x36, 0xe8, 0xb6, - 0x2c, 0x73, 0x3a, 0x58, 0xa8, 0x0d, 0xaf, 0x59, 0x01, 0xbb, 0x02, 0x62, 0x45, 0x50, 0xef, 0x03, - 0xd5, 0x53, 0x5f, 0x6f, 0x2e, 0x9c, 0x20, 0x4e, 0x93, 0xee, 0x43, 0x9d, 0x5c, 0x60, 0xb3, 0x77, - 0x40, 0x2c, 0x19, 0xea, 0x77, 0x46, 0x44, 0xd7, 0x17, 0x1b, 0x8b, 0x9d, 0xa0, 0xdf, 0x6e, 0x7e, - 0xb0, 0x9a, 0x12, 0x6f, 0xb5, 0x7a, 0x3a, 0xf1, 0xf5, 0xc5, 0x06, 0x7b, 0x07, 0x86, 0xeb, 0x7f, - 0xaa, 0x1f, 0x72, 0x52, 0xd5, 0x26, 0x54, 0x9d, 0x02, 0xb6, 0x70, 0x8a, 0x08, 0xa7, 0x5d, 0xf1, - 0x53, 0x57, 0x71, 0xb1, 0x5c, 0xd4, 0xbc, 0xb6, 0xda, 0x24, 0x35, 0x8c, 0x65, 0xba, 0x65, 0x6d, - 0x55, 0x6b, 0x76, 0x6c, 0x7c, 0xb5, 0xa0, 0x62, 0x97, 0xa0, 0x5c, 0x5f, 0xc2, 0x63, 0xc7, 0xf8, - 0xe5, 0x31, 0x55, 0xed, 0xd2, 0xc2, 0x71, 0x22, 0x99, 0x70, 0xf5, 0x65, 0x50, 0xae, 0x2f, 0xb1, - 0x05, 0x18, 0xbe, 0xbd, 0xdb, 0xfc, 0x60, 0x95, 0x44, 0xaf, 0x5a, 0xf2, 0x08, 0xbb, 0x8b, 0x62, - 0x26, 0x4a, 0x5b, 0xdc, 0xdd, 0x8d, 0x3e, 0xee, 0xe8, 0x2d, 0x46, 0x34, 0xd6, 0x80, 0xb1, 0x7a, - 0xbb, 0xeb, 0xf9, 0xf7, 0x22, 0x1e, 0xce, 0x8e, 0x23, 0x9f, 0xd9, 0x4c, 0xbb, 0x93, 0xf2, 0x85, - 0xd9, 0xc7, 0x7b, 0xf3, 0xc7, 0x5d, 0xf1, 0xd3, 0xe9, 0x47, 0x3c, 0xd4, 0xb8, 0xa5, 0x4c, 0x58, - 0x03, 0xe0, 0x76, 0xe0, 0x6f, 0x05, 0xf5, 0xb8, 0xe3, 0x46, 0x19, 0x61, 0x9e, 0x16, 0x24, 0x9a, - 0xd4, 0x89, 0xae, 0x80, 0x39, 0xae, 0x00, 0x6a, 0x0c, 0x35, 0x1e, 0xec, 0x1a, 0x8c, 0xdc, 0x0d, - 0xdd, 0x56, 0x87, 0xcf, 0x4e, 0x22, 0xb7, 0xe3, 0xc4, 0x4d, 0x02, 0xd5, 0x97, 0xce, 0x12, 0xc3, - 0x5a, 0x80, 0x60, 0xfd, 0xb0, 0x25, 0x11, 0xe7, 0xd6, 0x81, 0xe5, 0xe7, 0x24, 0xab, 0x41, 0xe5, - 0x01, 0xdf, 0x95, 0xab, 0xc8, 0x16, 0x7f, 0xb2, 0xcf, 0xc3, 0xf0, 0x8e, 0xdb, 0xe9, 0x73, 0xd2, - 0x38, 0xd5, 0xa2, 0x5f, 0x0c, 0xba, 0x5d, 0xd7, 0x6f, 0x23, 0xed, 0xfd, 0xcb, 0xb6, 0xc4, 0x79, - 0xbb, 0xfc, 0xa5, 0x92, 0xf5, 0x31, 0xcc, 0xe4, 0x3a, 0x4b, 0xc8, 0x3b, 0xed, 0x90, 0x8b, 0xf2, - 0x4e, 0x3b, 0xdd, 0x7e, 0x05, 0xa6, 0x97, 0xf8, 0xa6, 0xdb, 0xef, 0xc4, 0xc9, 0xbe, 0x27, 0x97, - 0xe8, 0x71, 0xf1, 0x09, 0x6d, 0x59, 0xe4, 0xa8, 0xcd, 0xce, 0xce, 0x22, 0x5b, 0xff, 0xac, 0x04, - 0x93, 0xc6, 0xf7, 0xb3, 0xab, 0x30, 0x56, 0xef, 0xb7, 0xbd, 0x18, 0x47, 0x52, 0x56, 0x2a, 0xc7, - 0x4b, 0x00, 0xf3, 0xe3, 0xa5, 0x50, 0xd9, 0x5b, 0x00, 0x36, 0x8f, 0xc3, 0xdd, 0xc5, 0xa0, 0xef, - 0xc7, 0xd8, 0x88, 0xe1, 0x85, 0xd3, 0x62, 0x60, 0x42, 0x01, 0x75, 0x5a, 0x02, 0xac, 0x0f, 0x4c, - 0x8a, 0xcc, 0x6e, 0x41, 0xad, 0xb9, 0xdd, 0xdf, 0xdc, 0xec, 0x70, 0xa5, 0x36, 0x44, 0x28, 0x4a, - 0xaa, 0x0b, 0xf3, 0x8f, 0xf7, 0xe6, 0x9f, 0x89, 0x64, 0x99, 0xa3, 0xb4, 0x0b, 0x7d, 0x7c, 0x73, - 0x84, 0xd6, 0xef, 0xe9, 0xba, 0xa4, 0xda, 0xdc, 0x5e, 0x4b, 0x44, 0x54, 0x29, 0xd5, 0x6c, 0x73, - 0x22, 0x2a, 0x11, 0x50, 0x17, 0xa4, 0xb8, 0x28, 0xe7, 0xc4, 0xc5, 0x38, 0x4d, 0x8e, 0x8a, 0xfb, - 0x30, 0x92, 0x42, 0x22, 0x59, 0x3c, 0x95, 0x4f, 0xbe, 0x78, 0xde, 0x83, 0x89, 0xdb, 0xae, 0xef, - 0x6e, 0xf1, 0xb6, 0xe8, 0x49, 0x29, 0x0e, 0xc7, 0xe4, 0x2e, 0xdb, 0x95, 0x70, 0xec, 0x77, 0xfd, - 0xbb, 0x0d, 0x02, 0xf6, 0x86, 0x12, 0x36, 0xc3, 0x05, 0xc2, 0x66, 0x92, 0x6a, 0x1f, 0x46, 0x61, - 0xa3, 0x44, 0xcc, 0x97, 0x61, 0xfc, 0xfe, 0x1d, 0x1e, 0x2f, 0xdd, 0x69, 0xe2, 0xec, 0x92, 0xca, - 0x1e, 0x56, 0xb9, 0xe3, 0xf3, 0xd8, 0x69, 0xfb, 0x11, 0x1a, 0x51, 0xb4, 0x2a, 0x75, 0x7c, 0xeb, - 0xd7, 0x01, 0xbb, 0x88, 0xbd, 0x0a, 0x23, 0x36, 0xdf, 0x12, 0x9b, 0x67, 0x29, 0x9d, 0x76, 0x21, - 0x42, 0xf4, 0x7e, 0x95, 0x38, 0xa8, 0xe7, 0xf1, 0x76, 0xb4, 0xed, 0x6d, 0xc6, 0xd4, 0xb9, 0x89, - 0x9e, 0x47, 0x60, 0x4d, 0xcf, 0x23, 0x88, 0xa1, 0xe7, 0x11, 0x4c, 0xc8, 0x73, 0x7b, 0xa9, 0x49, - 0x7d, 0xae, 0x06, 0xc8, 0x5e, 0xd2, 0x04, 0x63, 0x68, 0x68, 0x69, 0x02, 0x1b, 0x67, 0x76, 0x0b, - 0xe7, 0x61, 0x62, 0x10, 0x90, 0x33, 0x5b, 0x02, 0x4d, 0xcb, 0x4a, 0x8a, 0xca, 0x9a, 0x30, 0xbe, - 0x2c, 0x4e, 0xd1, 0xde, 0xa2, 0xdb, 0xda, 0x56, 0x7d, 0xac, 0xa4, 0xb2, 0x56, 0x92, 0xca, 0x22, - 0x8e, 0xc0, 0x96, 0x00, 0xea, 0x1d, 0xa8, 0xe1, 0xb2, 0x35, 0x18, 0x6f, 0xf2, 0x56, 0xc8, 0xe3, - 0x66, 0x1c, 0x84, 0x3c, 0xb3, 0xc9, 0x68, 0x25, 0x0b, 0xcf, 0xa9, 0x83, 0x7c, 0x84, 0x40, 0x27, - 0x12, 0x50, 0x9d, 0xab, 0x86, 0x2c, 0x4f, 0x64, 0xdd, 0x20, 0xdc, 0x5d, 0x5a, 0xa0, 0x8d, 0x27, - 0xd5, 0x52, 0x24, 0x58, 0x3f, 0x91, 0x09, 0x48, 0x7b, 0xc3, 0x3c, 0x91, 0x49, 0x2c, 0x1c, 0xa9, - 0xa5, 0x26, 0xea, 0xb6, 0xb4, 0x0d, 0x4d, 0xa7, 0xbd, 0x8c, 0x60, 0x6d, 0xa4, 0xda, 0x11, 0x6a, - 0xc6, 0xc6, 0x48, 0x11, 0x16, 0xeb, 0x01, 0x53, 0xa3, 0x26, 0x15, 0xce, 0x0e, 0x8f, 0x22, 0xda, - 0x9d, 0x4e, 0x67, 0x06, 0x3f, 0x45, 0x58, 0x78, 0x89, 0x98, 0x9f, 0x51, 0xd3, 0x80, 0x0e, 0xe1, - 0xa2, 0x50, 0xab, 0xa7, 0x80, 0xb7, 0x10, 0x44, 0xcb, 0x8f, 0x62, 0x1e, 0xfa, 0x6e, 0x27, 0x31, - 0x9f, 0xa1, 0x20, 0xe2, 0x04, 0x35, 0x07, 0x5a, 0x43, 0x66, 0x8b, 0x30, 0x59, 0x8f, 0xa2, 0x7e, - 0x97, 0xdb, 0x41, 0x87, 0xd7, 0xed, 0x3b, 0xb8, 0x93, 0x8d, 0x2d, 0x9c, 0x79, 0xbc, 0x37, 0x7f, - 0xda, 0xc5, 0x02, 0x27, 0x0c, 0x3a, 0xdc, 0x71, 0x43, 0x7d, 0x76, 0x9b, 0x34, 0xec, 0x2e, 0xc0, - 0xdd, 0x1e, 0xf7, 0x9b, 0xdc, 0x0d, 0x5b, 0xdb, 0x99, 0x8d, 0x2b, 0x2d, 0x58, 0x78, 0x96, 0xbe, - 0xf0, 0x78, 0xd0, 0xe3, 0x7e, 0x84, 0x30, 0xbd, 0x55, 0x29, 0x26, 0x5b, 0x87, 0xe9, 0x95, 0xfa, - 0xed, 0x46, 0xd0, 0xf1, 0x5a, 0xbb, 0xa4, 0x0b, 0x4e, 0xa1, 0x51, 0xf1, 0x24, 0x71, 0xcd, 0x94, - 0x4a, 0xe9, 0xe6, 0xb9, 0x5d, 0xa7, 0x87, 0x50, 0x87, 0x34, 0xc2, 0x2c, 0x17, 0xf6, 0x35, 0x31, - 0x07, 0x23, 0xa1, 0xde, 0xae, 0xb9, 0x5b, 0xf2, 0x48, 0x92, 0x1e, 0x59, 0xeb, 0xeb, 0xcd, 0x8b, - 0x5a, 0xa9, 0x54, 0xbc, 0xe6, 0xe4, 0x44, 0x44, 0xa8, 0x13, 0xbb, 0x5b, 0x91, 0x39, 0x11, 0x13, - 0x6c, 0x76, 0x13, 0x60, 0x29, 0x68, 0xf5, 0xbb, 0xdc, 0x8f, 0x97, 0x16, 0x66, 0x6b, 0xe6, 0x51, - 0x2c, 0x29, 0x48, 0x25, 0x63, 0x3b, 0x68, 0x19, 0x33, 0x51, 0xa3, 0x66, 0x3f, 0x0c, 0x27, 0xb4, - 0x95, 0xa3, 0xcd, 0xa2, 0x19, 0x64, 0xfb, 0x6c, 0x7e, 0x25, 0x6a, 0x13, 0xe9, 0x3c, 0xd5, 0x70, - 0x56, 0x5b, 0x93, 0xc5, 0x73, 0xa9, 0xb8, 0x92, 0xb9, 0xaf, 0x40, 0x2d, 0xdb, 0x0d, 0x05, 0x7b, - 0xfd, 0x71, 0x7d, 0xaf, 0x1f, 0xd3, 0x36, 0xf5, 0x9b, 0x43, 0xd5, 0xc9, 0xda, 0x94, 0xd6, 0xf7, - 0xcb, 0x8f, 0xbc, 0x28, 0x8e, 0xac, 0x6f, 0x1b, 0xeb, 0x5f, 0xc8, 0xa6, 0x5b, 0x7c, 0xb7, 0x11, - 0xf2, 0x4d, 0xef, 0x91, 0xbe, 0xeb, 0x3e, 0xe0, 0xbb, 0x4e, 0x0f, 0xa1, 0xba, 0x6c, 0x4a, 0x50, - 0xd9, 0x17, 0xa0, 0x7a, 0xeb, 0x76, 0xf3, 0x16, 0xdf, 0x5d, 0x59, 0xa2, 0x8d, 0x5f, 0x92, 0x75, - 0x23, 0x47, 0x90, 0x1a, 0x33, 0x3d, 0xc1, 0xb4, 0x16, 0x52, 0x39, 0x2c, 0x6a, 0x5e, 0xec, 0xf4, - 0xa3, 0x98, 0x87, 0x2b, 0x4b, 0x7a, 0xcd, 0x2d, 0x09, 0xcc, 0x48, 0xc5, 0x04, 0xd5, 0xfa, 0x7f, - 0xcb, 0x28, 0x83, 0xc5, 0x72, 0x5b, 0xf1, 0xa3, 0xd8, 0xf5, 0x5b, 0x3c, 0x61, 0x80, 0xcb, 0xcd, - 0x23, 0x68, 0x66, 0xb9, 0xa5, 0xc8, 0x66, 0xd5, 0xe5, 0x43, 0x57, 0x2d, 0x55, 0x0d, 0x69, 0x14, - 0x5b, 0x59, 0xa2, 0x43, 0x07, 0xa9, 0x1a, 0x12, 0x9a, 0xa9, 0x32, 0x45, 0x66, 0xe7, 0x60, 0x74, - 0xa5, 0x7e, 0xbb, 0xde, 0x8f, 0xb7, 0x71, 0x07, 0xa8, 0xca, 0xf3, 0x8e, 0x58, 0x2b, 0x6e, 0x3f, - 0xde, 0xb6, 0x55, 0x21, 0xbb, 0x84, 0xe7, 0x48, 0x9f, 0xc7, 0xd1, 0xec, 0x30, 0xee, 0xc6, 0xb8, - 0xa6, 0x22, 0x09, 0xca, 0x1c, 0x23, 0x05, 0x88, 0xbd, 0x02, 0xc3, 0xf7, 0x1b, 0x8b, 0x2b, 0x4b, - 0xb4, 0x93, 0xe2, 0x3e, 0xb8, 0xd3, 0x6b, 0x99, 0x2d, 0x91, 0x28, 0x6c, 0x19, 0xa6, 0x9a, 0xbc, - 0xd5, 0x0f, 0xbd, 0x58, 0x5a, 0x06, 0xa2, 0xd9, 0x51, 0xac, 0x03, 0xe5, 0x4c, 0x44, 0x25, 0xd2, - 0x8c, 0xa0, 0xd7, 0x95, 0x21, 0xb2, 0x7e, 0xb3, 0x94, 0x0a, 0x69, 0x76, 0xce, 0x50, 0x13, 0xd1, - 0x2c, 0x98, 0xd9, 0xbf, 0xa5, 0xc2, 0x68, 0x03, 0x5b, 0xec, 0x47, 0x71, 0xd0, 0x5d, 0xf6, 0xdb, - 0xbd, 0xc0, 0xf3, 0x63, 0xa4, 0x92, 0x9d, 0x6f, 0x3d, 0xde, 0x9b, 0x7f, 0xae, 0x85, 0xa5, 0x0e, - 0xa7, 0xe2, 0xac, 0x16, 0x50, 0x40, 0xfd, 0x29, 0xc6, 0xc3, 0xfa, 0xa7, 0x65, 0x63, 0x73, 0x15, - 0xcd, 0xb3, 0x79, 0xaf, 0xe3, 0xb5, 0xd0, 0x9e, 0x83, 0x1f, 0x9a, 0xcc, 0x2a, 0x6c, 0x5e, 0x98, - 0x96, 0xca, 0x1e, 0x32, 0x79, 0x17, 0x50, 0xb3, 0xf7, 0x61, 0x42, 0xa8, 0x49, 0xf4, 0x33, 0x9a, - 0x2d, 0x63, 0x67, 0x3f, 0x8b, 0x06, 0xde, 0x88, 0x87, 0x09, 0x1b, 0x43, 0xbf, 0xd2, 0x29, 0x58, - 0x1b, 0x66, 0xd7, 0x42, 0xd7, 0x8f, 0xbc, 0x78, 0xd9, 0x6f, 0x85, 0xbb, 0xa8, 0xd6, 0x2d, 0xfb, - 0xee, 0x46, 0x87, 0xb7, 0x49, 0x51, 0x3d, 0xff, 0x78, 0x6f, 0xfe, 0xc5, 0x58, 0xe2, 0x38, 0x3c, - 0x41, 0x72, 0xb8, 0xc4, 0xd2, 0x38, 0x0f, 0xe4, 0x24, 0xd4, 0x40, 0xd5, 0xad, 0x78, 0x73, 0x34, - 0x94, 0xea, 0x64, 0xc9, 0x68, 0x08, 0x51, 0xa7, 0x37, 0x53, 0x27, 0xb0, 0xee, 0x0e, 0x10, 0x94, - 0xb8, 0xd0, 0x04, 0x48, 0x9b, 0x21, 0x72, 0xa1, 0xa1, 0x34, 0xcc, 0x8c, 0x70, 0x8a, 0x6a, 0xfd, - 0x41, 0x29, 0xd5, 0x27, 0xd8, 0xbb, 0x30, 0x4e, 0x4b, 0x50, 0x63, 0x83, 0x1b, 0x82, 0x5a, 0xaf, - 0x59, 0x85, 0x51, 0x43, 0x67, 0xaf, 0xc3, 0x68, 0x7d, 0x71, 0x55, 0x9b, 0x6c, 0x68, 0x98, 0x71, - 0x5b, 0x9d, 0x2c, 0x95, 0x42, 0x13, 0xb3, 0x6a, 0x6d, 0xb5, 0x69, 0x76, 0x33, 0xce, 0xaa, 0xb8, - 0x13, 0x15, 0xf4, 0xab, 0x86, 0xfc, 0xe9, 0x7b, 0xf2, 0x7f, 0x28, 0x15, 0xa9, 0x2d, 0x6c, 0x01, - 0x26, 0xd7, 0x83, 0xf0, 0x01, 0x4e, 0x18, 0xad, 0x13, 0x70, 0x2a, 0x3d, 0x54, 0x05, 0xd9, 0x0f, - 0x32, 0x49, 0xf4, 0xb6, 0x69, 0xbd, 0x61, 0xb6, 0x2d, 0xc3, 0xc1, 0x20, 0x10, 0xe3, 0x90, 0x70, - 0x4c, 0x96, 0x1b, 0x8e, 0x43, 0xda, 0x04, 0x63, 0x4d, 0xe8, 0xe8, 0xd6, 0x3f, 0x28, 0xe9, 0xea, - 0x89, 0xe8, 0xe4, 0xa5, 0xa0, 0xeb, 0x7a, 0xbe, 0xf6, 0x39, 0xd8, 0xc9, 0x6d, 0x84, 0x66, 0x5b, - 0xa2, 0x21, 0xb3, 0x2b, 0x50, 0x95, 0xbf, 0x12, 0xe1, 0x8d, 0x46, 0x52, 0x22, 0x34, 0x77, 0x1e, - 0x85, 0x98, 0x1b, 0x99, 0xca, 0x51, 0x47, 0xe6, 0x37, 0x4a, 0xba, 0x66, 0xf1, 0x49, 0x77, 0xaf, - 0xcc, 0xae, 0x55, 0x3e, 0xca, 0xae, 0xf5, 0xa9, 0x3f, 0xe1, 0x1f, 0x95, 0x60, 0x5c, 0x33, 0x23, - 0x89, 0x6f, 0x68, 0x84, 0xc1, 0x47, 0xbc, 0x15, 0x9b, 0xdf, 0xd0, 0x93, 0xc0, 0xcc, 0x37, 0x24, - 0xa8, 0x9f, 0xe6, 0x1b, 0x16, 0x61, 0xb4, 0xde, 0xe9, 0x04, 0xe2, 0x98, 0x20, 0xcf, 0x50, 0x53, - 0x4a, 0xeb, 0x93, 0xd0, 0x85, 0xd3, 0xea, 0x82, 0xca, 0x15, 0x00, 0x43, 0x35, 0x53, 0x94, 0xd6, - 0xcf, 0x97, 0x12, 0x2e, 0xb9, 0x4e, 0x29, 0x1d, 0xb1, 0x53, 0xd8, 0x2d, 0xa8, 0xa9, 0xdf, 0x77, - 0x77, 0x78, 0x18, 0x7a, 0x6d, 0xb5, 0x34, 0xd0, 0x06, 0x90, 0x30, 0x09, 0xa8, 0x50, 0xb7, 0x01, - 0x64, 0x09, 0xad, 0xff, 0xb3, 0x44, 0x07, 0xe2, 0x43, 0x6f, 0x8b, 0xe6, 0x16, 0x56, 0x3e, 0x8a, - 0x4a, 0xf1, 0x3e, 0x0c, 0xdb, 0xbc, 0xed, 0x45, 0xd4, 0x93, 0x33, 0xfa, 0xe1, 0x1b, 0x0b, 0x52, - 0x2d, 0x37, 0x14, 0x3f, 0x75, 0x7d, 0x00, 0xcb, 0xc5, 0xb1, 0x63, 0x25, 0xba, 0xd6, 0xe1, 0x8f, - 0x3c, 0x29, 0x6b, 0x48, 0x35, 0x41, 0x75, 0xc0, 0x8b, 0x9c, 0x4d, 0x51, 0x42, 0x3a, 0xab, 0x2e, - 0x57, 0x0c, 0x1a, 0xeb, 0x6b, 0x00, 0x69, 0x95, 0xa2, 0x3b, 0x69, 0xb2, 0x7b, 0xfe, 0x96, 0x54, - 0x3c, 0xa9, 0x0f, 0xb0, 0x3b, 0x5b, 0x49, 0x19, 0x9d, 0x11, 0xf4, 0xee, 0xcc, 0x12, 0x5a, 0xff, - 0x5b, 0x05, 0xca, 0x75, 0x9c, 0x6f, 0xb7, 0xf8, 0x6e, 0xec, 0x6e, 0x5c, 0xf3, 0x3a, 0x86, 0xac, - 0x78, 0x80, 0x50, 0x67, 0xd3, 0x33, 0xcc, 0x65, 0x1a, 0xb2, 0x90, 0x15, 0xb7, 0xc2, 0x8d, 0x37, - 0x91, 0x50, 0x93, 0x15, 0x0f, 0xc2, 0x8d, 0x37, 0xb3, 0x64, 0x09, 0x22, 0xb3, 0x60, 0x44, 0xca, - 0x0d, 0x5a, 0x62, 0xf0, 0x78, 0x6f, 0x7e, 0x44, 0x8a, 0x17, 0x9b, 0x4a, 0xd8, 0x69, 0xa8, 0x34, - 0x1b, 0x77, 0x48, 0xc0, 0xa3, 0x59, 0x3a, 0xea, 0xf9, 0xb6, 0x80, 0x89, 0x3a, 0x57, 0x97, 0xea, - 0x0d, 0xb4, 0xfa, 0x0c, 0xa7, 0x75, 0x76, 0xda, 0x6e, 0x2f, 0x6b, 0xf7, 0x49, 0x10, 0xd9, 0x97, - 0x61, 0xfc, 0xd6, 0xd2, 0xe2, 0x8d, 0x20, 0x8a, 0xb3, 0x66, 0x91, 0x07, 0xed, 0x16, 0x5a, 0xa0, - 0x72, 0xbb, 0x9c, 0x86, 0xcf, 0x1c, 0x38, 0x29, 0x58, 0x89, 0x21, 0xf1, 0x5a, 0x9c, 0x4c, 0x08, - 0x77, 0xd2, 0x5b, 0xb2, 0x97, 0x1f, 0xef, 0xcd, 0xbf, 0x80, 0x2d, 0x88, 0x24, 0x8a, 0xa3, 0x8c, - 0x0f, 0x19, 0xae, 0x03, 0xd8, 0xb0, 0x6f, 0xc0, 0x89, 0x7c, 0x49, 0x33, 0xb9, 0x5d, 0x3b, 0xf7, - 0x78, 0x6f, 0xde, 0x2a, 0xe4, 0x1f, 0x19, 0xf3, 0xb7, 0x98, 0x89, 0xf5, 0xdd, 0x32, 0x8c, 0x6b, - 0x66, 0x66, 0xf6, 0x05, 0x72, 0xb5, 0x28, 0x19, 0xc7, 0x4d, 0x0d, 0x43, 0x94, 0x4a, 0x9b, 0x64, - 0x37, 0x68, 0x73, 0x72, 0xbc, 0x48, 0x8d, 0x6d, 0xe5, 0xc3, 0x18, 0xdb, 0xde, 0x02, 0x90, 0x53, - 0x18, 0xfb, 0x49, 0xd3, 0x1e, 0x35, 0x5f, 0x1e, 0x7d, 0x5a, 0xa5, 0xc8, 0xec, 0x3e, 0x1c, 0x5b, - 0x0b, 0xfb, 0x51, 0xdc, 0xdc, 0x8d, 0x62, 0xde, 0x15, 0xdc, 0x1a, 0x41, 0xd0, 0xa1, 0xe5, 0xf3, - 0xa2, 0x38, 0xf5, 0xa1, 0x87, 0x92, 0x13, 0x61, 0x39, 0x36, 0xc0, 0xe9, 0x05, 0x81, 0x6e, 0x82, - 0x2b, 0x62, 0x60, 0xd9, 0x30, 0xa1, 0x1b, 0xf0, 0xc4, 0xbe, 0x4f, 0xd7, 0xd2, 0x74, 0x53, 0xa4, - 0xed, 0xfb, 0xd4, 0xca, 0xfc, 0x35, 0xb9, 0x49, 0x62, 0x7d, 0x41, 0xb7, 0x67, 0x1f, 0x56, 0x2e, - 0x59, 0x3f, 0x5a, 0x4a, 0x85, 0xfc, 0xfd, 0x37, 0xd8, 0x3b, 0x30, 0x22, 0xdd, 0x00, 0xc8, 0x5b, - 0xe2, 0x44, 0x62, 0x41, 0xd1, 0x7d, 0x04, 0xe4, 0x45, 0xd2, 0x3f, 0xdf, 0x9b, 0x2f, 0x3d, 0xde, - 0x9b, 0xff, 0x9c, 0x4d, 0x24, 0xc9, 0x1d, 0x94, 0x69, 0x8e, 0x56, 0xdc, 0xf1, 0xb6, 0xe5, 0x8d, - 0xa2, 0x3b, 0x28, 0xeb, 0x1f, 0x0d, 0xc3, 0x94, 0x89, 0xa6, 0xfb, 0x0a, 0x94, 0x0e, 0xe5, 0x2b, - 0xf0, 0x3e, 0x54, 0x69, 0xbe, 0x29, 0x05, 0xfc, 0x45, 0xbc, 0x99, 0x23, 0x98, 0xe1, 0x03, 0x03, - 0x72, 0x38, 0xec, 0xa0, 0xc3, 0xed, 0x84, 0x8a, 0x5d, 0xd6, 0xee, 0x9c, 0x2b, 0xa9, 0x0a, 0xa9, - 0xac, 0xc2, 0xfa, 0x72, 0x4e, 0x6e, 0x9f, 0x5f, 0x83, 0x11, 0x71, 0x9c, 0x4b, 0xec, 0x7d, 0xd8, - 0x4a, 0x71, 0xd2, 0xcb, 0xb8, 0x51, 0x49, 0x24, 0xb6, 0x0e, 0xd5, 0x55, 0x37, 0x8a, 0x9b, 0x9c, - 0xfb, 0x87, 0xf0, 0x02, 0x9a, 0xa7, 0xae, 0x3a, 0x86, 0x2e, 0x36, 0x11, 0xe7, 0x7e, 0xc6, 0x8d, - 0x23, 0x61, 0xc6, 0xbe, 0x09, 0xb0, 0x18, 0xf8, 0x71, 0x18, 0x74, 0x56, 0x83, 0xad, 0xd9, 0x11, - 0x34, 0xb4, 0x3c, 0x97, 0x19, 0x80, 0x14, 0x41, 0xda, 0x5a, 0x12, 0x6b, 0x62, 0x4b, 0x16, 0x38, - 0x9d, 0x60, 0x4b, 0x5f, 0x07, 0x29, 0x3e, 0xbb, 0x06, 0x35, 0x65, 0xc5, 0xba, 0xd7, 0xdb, 0x0a, - 0x71, 0x82, 0x8c, 0xa6, 0x7a, 0x21, 0x7f, 0x14, 0x3b, 0x7d, 0x82, 0x1b, 0xfb, 0x66, 0x86, 0x86, - 0x7d, 0x03, 0x4e, 0x65, 0x61, 0x6a, 0x94, 0xab, 0xe9, 0x11, 0x4c, 0x67, 0x57, 0x30, 0xef, 0x07, - 0xb1, 0x60, 0xd7, 0x61, 0x5a, 0x74, 0xc8, 0x6d, 0xee, 0x46, 0x7d, 0xe9, 0x25, 0x48, 0x76, 0x40, - 0xe5, 0xe4, 0x40, 0xab, 0xb0, 0x13, 0xb4, 0x1e, 0x68, 0x48, 0x76, 0x96, 0x8a, 0x5d, 0x85, 0x71, - 0xe9, 0x7b, 0x13, 0xae, 0xf8, 0x9b, 0x01, 0x5d, 0x5b, 0xa9, 0xdb, 0x1c, 0x2a, 0xb9, 0x7f, 0x59, - 0x94, 0xd9, 0x3a, 0xa2, 0xb5, 0x57, 0x86, 0x93, 0xc5, 0x75, 0xb0, 0x1f, 0x81, 0x13, 0xd4, 0x9f, - 0x1d, 0x1e, 0x6a, 0x38, 0x87, 0xf0, 0x4a, 0x7a, 0x8d, 0xc6, 0xe9, 0xf9, 0x56, 0xc2, 0x20, 0x11, - 0x38, 0x82, 0x45, 0x66, 0x52, 0x14, 0xd7, 0xc3, 0x3e, 0x84, 0x71, 0xbd, 0xda, 0xf2, 0xe1, 0x1d, - 0xbc, 0xf6, 0xa9, 0x4b, 0x67, 0xc9, 0x5c, 0x98, 0xb6, 0xf9, 0xc7, 0x7d, 0x1e, 0xc5, 0xca, 0xc5, - 0x8c, 0x34, 0x96, 0xd3, 0xb9, 0x5a, 0x14, 0x42, 0x62, 0xa4, 0xac, 0x85, 0x92, 0xd2, 0x51, 0xee, - 0xb2, 0xbf, 0x20, 0xd8, 0x67, 0xf9, 0x59, 0xdf, 0x2f, 0xc3, 0xa9, 0x01, 0xd3, 0x59, 0x48, 0x3c, - 0x4d, 0x33, 0x44, 0x89, 0x97, 0x51, 0x08, 0xa5, 0xe7, 0xe3, 0x59, 0x28, 0x93, 0x06, 0x36, 0xb4, - 0x50, 0x7b, 0xbc, 0x37, 0x3f, 0x61, 0xac, 0xd4, 0xf2, 0xca, 0x12, 0xbb, 0x09, 0x43, 0xa2, 0x1b, - 0x0e, 0xe1, 0x66, 0xa5, 0x4c, 0xd4, 0x53, 0xb1, 0xa7, 0x0b, 0x08, 0xec, 0x1b, 0xe4, 0xc1, 0xbe, - 0x00, 0x95, 0xb5, 0xb5, 0x55, 0x94, 0x0e, 0x15, 0x9c, 0xdd, 0x93, 0x71, 0xdc, 0x31, 0x84, 0xd1, - 0xa4, 0xa0, 0x4d, 0x7a, 0xc4, 0x16, 0xe8, 0xec, 0xab, 0x30, 0x42, 0x37, 0xd7, 0xc3, 0xb8, 0x94, - 0x5f, 0xd9, 0x7f, 0x29, 0x5f, 0xd4, 0xef, 0xae, 0xd1, 0x32, 0x94, 0xbb, 0xaf, 0x26, 0x7e, 0x73, - 0x6f, 0xc1, 0xf8, 0xfe, 0x97, 0x8a, 0x03, 0x0d, 0x8d, 0xd6, 0x4f, 0x94, 0x94, 0xa7, 0x1b, 0x4d, - 0x7e, 0x76, 0x56, 0xad, 0x13, 0x34, 0x64, 0x10, 0x17, 0x1d, 0xc4, 0x9e, 0x03, 0x90, 0x3f, 0xef, - 0xdd, 0xa3, 0x4e, 0x9f, 0xb0, 0x35, 0x08, 0x7b, 0x3b, 0x61, 0x49, 0x86, 0xe7, 0x0a, 0x6a, 0x02, - 0x99, 0xb5, 0x26, 0xcb, 0x6c, 0x13, 0xd5, 0xfa, 0xf5, 0x72, 0xba, 0x6b, 0x5c, 0xf3, 0x3a, 0x31, - 0x0f, 0xd9, 0x9c, 0xdc, 0x04, 0xd2, 0xc3, 0x9a, 0x9d, 0xfc, 0x66, 0xb3, 0xe9, 0x8e, 0x22, 0x3f, - 0x2d, 0xd9, 0x3a, 0x5e, 0xd1, 0xb6, 0x8e, 0x0a, 0x6e, 0x1d, 0x53, 0x03, 0x37, 0x89, 0x57, 0x0a, - 0x24, 0x21, 0x8a, 0xfe, 0x02, 0x69, 0xf7, 0x22, 0x4c, 0xde, 0x09, 0x96, 0x1f, 0xc5, 0x09, 0xa2, - 0x10, 0xf9, 0x55, 0xdb, 0x04, 0x0a, 0x8e, 0x77, 0x3b, 0x6d, 0x1e, 0xae, 0x6d, 0xbb, 0xbe, 0xe1, - 0x1a, 0x65, 0xe7, 0xe0, 0x02, 0xf7, 0x0e, 0x7f, 0x68, 0xe2, 0x4a, 0x0f, 0xac, 0x1c, 0x3c, 0x3b, - 0x38, 0xd5, 0xdc, 0xe0, 0x58, 0x3f, 0x5f, 0x56, 0xdd, 0x75, 0xff, 0xf2, 0x53, 0xea, 0xf6, 0xf2, - 0xa6, 0xe1, 0xf6, 0x72, 0x2c, 0xb9, 0xde, 0x4a, 0x3c, 0xce, 0x2e, 0x17, 0x3a, 0xbd, 0x24, 0xee, - 0x6b, 0x23, 0xc5, 0xee, 0x6b, 0xdf, 0xab, 0xc2, 0x84, 0xce, 0x44, 0xf4, 0x4e, 0xbd, 0xdd, 0x0e, - 0xf5, 0xde, 0x71, 0xdb, 0xed, 0xd0, 0x46, 0xa8, 0xe1, 0xad, 0x56, 0xd9, 0xd7, 0x5b, 0xed, 0x5b, - 0x30, 0xb6, 0xd8, 0x6d, 0x1b, 0x5e, 0x29, 0x56, 0x41, 0xa3, 0x2f, 0x26, 0x48, 0x72, 0x4d, 0x27, - 0x77, 0x39, 0xad, 0x6e, 0x3b, 0xef, 0x8b, 0x92, 0xb2, 0x34, 0x1c, 0xdd, 0x86, 0x3f, 0x8d, 0xa3, - 0xdb, 0x55, 0x18, 0xbb, 0x17, 0xf1, 0xb5, 0xbe, 0xef, 0xf3, 0x0e, 0xf6, 0x52, 0x55, 0xda, 0x0c, - 0xfa, 0x11, 0x77, 0x62, 0x84, 0xea, 0x0d, 0x48, 0x50, 0xf5, 0x61, 0x1f, 0xdd, 0x67, 0xd8, 0xaf, - 0x40, 0xb5, 0xc1, 0x79, 0x88, 0x7d, 0x3a, 0x9e, 0x9e, 0x9d, 0x7a, 0x9c, 0x87, 0x8e, 0xe8, 0x58, - 0xc3, 0x01, 0x8e, 0x10, 0x0d, 0xaf, 0xb9, 0x89, 0xc3, 0x7a, 0xcd, 0x3d, 0x0f, 0x13, 0xbd, 0xfe, - 0x46, 0xc7, 0x6b, 0x21, 0x5f, 0x72, 0xb7, 0xb3, 0xc7, 0x25, 0x4c, 0xb0, 0x8d, 0xd8, 0x57, 0x61, - 0x12, 0x6d, 0x25, 0xc9, 0x44, 0x9c, 0x32, 0x36, 0x7c, 0xa3, 0x4c, 0xea, 0xe4, 0x2d, 0x01, 0x72, - 0x0a, 0x5c, 0x4e, 0x4d, 0x46, 0xec, 0x26, 0x8c, 0x6e, 0x79, 0xb1, 0xb3, 0xdd, 0xdf, 0x98, 0x9d, - 0x36, 0xfc, 0x35, 0xaf, 0x7b, 0xf1, 0x8d, 0xfe, 0x86, 0x1c, 0xf2, 0x84, 0x35, 0x4a, 0xee, 0x2d, - 0x2f, 0xde, 0xee, 0xeb, 0xe6, 0x90, 0x91, 0x2d, 0xc4, 0xcd, 0xba, 0xff, 0xd5, 0xf6, 0x77, 0xff, - 0x9b, 0x31, 0xdd, 0xff, 0x98, 0x03, 0x2c, 0x1f, 0xf1, 0x31, 0xcb, 0xb0, 0x51, 0xaf, 0x5f, 0x54, - 0x61, 0x11, 0x17, 0x73, 0xa1, 0x22, 0x17, 0x77, 0xde, 0xb8, 0xb8, 0xa8, 0x80, 0xd7, 0x08, 0x68, - 0xcf, 0xb4, 0xb2, 0x20, 0xd6, 0x84, 0x9a, 0xd7, 0xed, 0xf6, 0x63, 0x77, 0xa3, 0xc3, 0x69, 0x8a, - 0xce, 0x1e, 0x33, 0xdc, 0xae, 0x8c, 0x09, 0xbe, 0xa2, 0x70, 0xb5, 0x69, 0x6e, 0x4f, 0x7b, 0x26, - 0x74, 0xae, 0x09, 0x53, 0xe6, 0x4a, 0x78, 0x02, 0x5e, 0x30, 0x73, 0x0b, 0x70, 0xbc, 0xa8, 0xf6, - 0x23, 0x5e, 0xba, 0x49, 0x3f, 0xca, 0x6a, 0x6d, 0xec, 0xe6, 0x50, 0x15, 0x6a, 0xe3, 0xd2, 0x83, - 0xd2, 0x86, 0x46, 0x32, 0xaf, 0x6c, 0x76, 0xab, 0xbf, 0xc1, 0x43, 0x9f, 0xc7, 0x3c, 0x22, 0x8b, - 0x47, 0x64, 0x0f, 0xd5, 0x7b, 0xbd, 0xc8, 0xfa, 0xbb, 0x65, 0x18, 0xad, 0xaf, 0x37, 0x71, 0x0f, - 0x7d, 0x55, 0x77, 0x1a, 0x28, 0xa5, 0x51, 0x04, 0xa9, 0xd3, 0x80, 0xee, 0x2a, 0x70, 0xa9, 0xc0, - 0x24, 0x87, 0x71, 0x29, 0x9a, 0x49, 0xce, 0x30, 0xc4, 0xa5, 0xfe, 0x13, 0x95, 0x43, 0xf8, 0x4f, - 0x24, 0x97, 0x4c, 0x43, 0x07, 0x5f, 0x32, 0xbd, 0x03, 0xe3, 0x2b, 0x7e, 0xcc, 0xb7, 0xc2, 0x54, - 0xda, 0x24, 0xe6, 0xc1, 0x04, 0xac, 0xdb, 0x31, 0x34, 0x6c, 0xb1, 0x94, 0xe5, 0xc5, 0x56, 0x72, - 0xa1, 0x85, 0x4b, 0x59, 0xde, 0x7f, 0x65, 0x6c, 0xbb, 0x0a, 0xd1, 0x5a, 0xca, 0xac, 0x53, 0xe5, - 0x75, 0x57, 0x32, 0x2d, 0x8c, 0xb2, 0x63, 0x17, 0x66, 0x8a, 0xbd, 0xee, 0xac, 0xbf, 0x5c, 0x82, - 0xe3, 0x45, 0xcb, 0x8f, 0x7d, 0x05, 0x26, 0x82, 0x70, 0xcb, 0xf5, 0xbd, 0x3f, 0x25, 0xbf, 0x48, - 0xbb, 0x80, 0xd0, 0xe1, 0xba, 0x85, 0x51, 0x87, 0x8b, 0x0e, 0xd1, 0xbe, 0xdc, 0xb4, 0x97, 0x16, - 0x76, 0x88, 0x06, 0xb6, 0x7e, 0xb1, 0x0c, 0xe3, 0xf5, 0x5e, 0xef, 0x29, 0x77, 0x4e, 0xff, 0x92, - 0xb1, 0x1d, 0x2b, 0x7b, 0x4e, 0xf2, 0x5d, 0xfb, 0xb8, 0xa1, 0x1e, 0xb4, 0x23, 0xff, 0xb7, 0x15, - 0x98, 0xce, 0xf0, 0xd1, 0xbf, 0xa9, 0x74, 0x48, 0x5f, 0xf2, 0xf2, 0x21, 0x7d, 0xc9, 0x2b, 0x87, - 0xf3, 0x25, 0x1f, 0xfa, 0x34, 0x5b, 0xec, 0xcb, 0x50, 0xa9, 0xf7, 0x7a, 0x59, 0x97, 0xaa, 0x5e, - 0xef, 0xfe, 0x15, 0x69, 0x68, 0x74, 0x7b, 0x3d, 0x5b, 0x60, 0x18, 0xfb, 0xde, 0xc8, 0x27, 0xf4, - 0x16, 0x1f, 0xdd, 0x7f, 0xbb, 0xa8, 0x1e, 0x6a, 0xbb, 0x18, 0x7b, 0x62, 0xdb, 0x85, 0xf5, 0x1a, - 0x8c, 0xe1, 0xa7, 0xa2, 0xcb, 0xf6, 0x59, 0x40, 0xb9, 0x48, 0xde, 0xda, 0x46, 0x57, 0x90, 0xc4, - 0xfc, 0xe9, 0x32, 0x0c, 0xe3, 0xef, 0xa7, 0x74, 0x61, 0x5c, 0x36, 0x16, 0x46, 0x4d, 0x5b, 0x18, - 0x9f, 0x7c, 0x49, 0xfc, 0xed, 0x0a, 0xc0, 0xe2, 0x5d, 0xbb, 0x29, 0x8d, 0xe8, 0xec, 0x1a, 0x4c, - 0xbb, 0x9d, 0x4e, 0xf0, 0x90, 0xb7, 0x9d, 0x20, 0xf4, 0xb6, 0x3c, 0x5f, 0xf6, 0xa7, 0xf2, 0x2e, - 0x32, 0x8b, 0xf4, 0x5b, 0x7f, 0x2a, 0xba, 0x2b, 0x4b, 0x74, 0x3e, 0x5d, 0x1e, 0x6f, 0x07, 0x6d, - 0x65, 0x4f, 0x33, 0xf8, 0x50, 0x51, 0x01, 0x9f, 0xdb, 0xb2, 0x44, 0xe7, 0xb3, 0x8d, 0xf6, 0x41, - 0x75, 0xb8, 0x32, 0xf8, 0x50, 0x51, 0x01, 0x1f, 0x69, 0x54, 0x8c, 0xd8, 0x2a, 0xe0, 0x1d, 0xd1, - 0x43, 0xa7, 0x15, 0xf2, 0x36, 0xf7, 0x63, 0xcf, 0xed, 0x44, 0x64, 0x81, 0xc5, 0xab, 0x86, 0x5c, - 0xa1, 0x6e, 0x81, 0xc2, 0xc2, 0xc5, 0xb4, 0x8c, 0x5d, 0x84, 0xd1, 0xae, 0xfb, 0xc8, 0x71, 0xb7, - 0xa4, 0x9f, 0xdd, 0xa4, 0xb4, 0xd8, 0x11, 0x48, 0xdf, 0x11, 0xbb, 0xee, 0xa3, 0xfa, 0x16, 0x17, - 0x5f, 0xc1, 0x1f, 0xf5, 0x82, 0x48, 0xfb, 0x8a, 0x91, 0xf4, 0x2b, 0x32, 0x45, 0xfa, 0x57, 0x50, - 0x11, 0x7d, 0x85, 0xf5, 0x2b, 0x25, 0x78, 0x66, 0x05, 0x5b, 0x11, 0xef, 0x2e, 0x72, 0x3f, 0xe6, - 0x61, 0x83, 0x87, 0x5d, 0x0f, 0xfd, 0x7e, 0x9a, 0x3c, 0x66, 0x2f, 0x40, 0xa5, 0x6e, 0xdf, 0xa1, - 0x59, 0x2d, 0xb7, 0x2e, 0xc3, 0x07, 0x4c, 0x94, 0x26, 0x46, 0xdd, 0xf2, 0x01, 0x97, 0x4d, 0x75, - 0x98, 0xa8, 0x47, 0x91, 0xb7, 0xe5, 0x77, 0x65, 0x70, 0x62, 0xc5, 0xf0, 0x32, 0x23, 0x78, 0xee, - 0x12, 0x58, 0x27, 0xb1, 0xfe, 0x93, 0x12, 0xcc, 0xd4, 0x7b, 0x3d, 0xb3, 0xc9, 0xa6, 0x87, 0x63, - 0xe9, 0xf0, 0x1e, 0x8e, 0x1e, 0x4c, 0x19, 0x9f, 0x2b, 0xa7, 0x54, 0x7a, 0xf6, 0xd9, 0xa7, 0x67, - 0x64, 0xb3, 0x7b, 0x09, 0xc8, 0x89, 0x4c, 0x07, 0x99, 0x0c, 0x63, 0xeb, 0xdf, 0x1d, 0x43, 0xc9, - 0x42, 0x5b, 0x04, 0x45, 0x15, 0x94, 0x0a, 0xa2, 0x0a, 0xde, 0x02, 0x4d, 0x59, 0xd3, 0x77, 0x6b, - 0xed, 0xb8, 0xa0, 0x9b, 0x43, 0x53, 0x64, 0xf6, 0x20, 0x1b, 0x5f, 0x50, 0xc1, 0xaf, 0x79, 0x21, - 0xbb, 0xac, 0x9f, 0x48, 0x68, 0xc1, 0x0d, 0x60, 0x2b, 0x3e, 0x3a, 0xed, 0xf0, 0xe6, 0x03, 0xaf, - 0x77, 0x9f, 0x87, 0xde, 0xe6, 0x2e, 0x2d, 0x00, 0xec, 0x7c, 0x8f, 0x4a, 0x9d, 0xe8, 0x81, 0xd7, - 0x73, 0x76, 0xb0, 0xdc, 0x2e, 0xa0, 0x61, 0xef, 0xc1, 0xa8, 0xcd, 0x1f, 0x86, 0x5e, 0xac, 0x7c, - 0x4c, 0xa7, 0x12, 0xeb, 0x3e, 0x42, 0xe5, 0x5a, 0x08, 0xe5, 0x0f, 0x5d, 0x56, 0x52, 0x39, 0xbb, - 0x2c, 0xf5, 0x2d, 0xe9, 0x4b, 0x3a, 0x99, 0x7e, 0x6d, 0x7d, 0xbd, 0x39, 0x48, 0xdd, 0x62, 0x17, - 0x60, 0x18, 0x95, 0x36, 0x3a, 0x0e, 0x62, 0xe0, 0x2d, 0x1e, 0x9f, 0x74, 0x8d, 0x12, 0x31, 0xd0, - 0x58, 0xa4, 0xbc, 0x62, 0xd4, 0x96, 0xa4, 0x41, 0xb2, 0x1a, 0xe7, 0xd8, 0x91, 0x34, 0xce, 0x55, - 0xa8, 0xd9, 0x32, 0x7c, 0xbc, 0x5d, 0xef, 0xa1, 0xa7, 0x44, 0x34, 0x0b, 0xb8, 0x92, 0xcf, 0x3e, - 0xde, 0x9b, 0x7f, 0x96, 0x42, 0xcb, 0xdb, 0x8e, 0xdb, 0x93, 0x0e, 0x16, 0x86, 0x18, 0xc9, 0x52, - 0xb2, 0xb7, 0x60, 0x48, 0x88, 0x5e, 0x8a, 0x44, 0x50, 0x57, 0xb2, 0xa9, 0x34, 0x96, 0x8b, 0xb3, - 0x15, 0x18, 0x32, 0x01, 0x49, 0x98, 0x03, 0x53, 0xe6, 0x74, 0x27, 0x17, 0xce, 0xd9, 0xb4, 0x3f, - 0xcd, 0x72, 0xba, 0xa7, 0x25, 0x98, 0xd3, 0x42, 0xa0, 0xbe, 0x02, 0x32, 0x8b, 0x74, 0x19, 0xaa, - 0x6b, 0x8b, 0x8d, 0x46, 0x10, 0xc6, 0xf2, 0xb4, 0x9b, 0xee, 0x37, 0x02, 0x66, 0xbb, 0xfe, 0x16, - 0x97, 0x0a, 0x44, 0xdc, 0xea, 0x39, 0x62, 0x1b, 0x37, 0x14, 0x08, 0x45, 0xca, 0xbe, 0x09, 0x27, - 0xee, 0x45, 0xbc, 0xee, 0xef, 0xa2, 0x4a, 0xa1, 0x2d, 0x95, 0x29, 0x9c, 0x7a, 0x78, 0xd3, 0xd8, - 0x8f, 0xb8, 0xe3, 0xfa, 0xbb, 0x8e, 0x54, 0x45, 0x8a, 0x17, 0x4e, 0x31, 0x17, 0x76, 0x09, 0x2a, - 0xb7, 0x17, 0x1b, 0x74, 0x2c, 0x56, 0x1e, 0xd6, 0xb7, 0x17, 0x1b, 0x72, 0x22, 0x75, 0xcd, 0x80, - 0x97, 0xdb, 0x8b, 0x0d, 0xf6, 0x26, 0x54, 0x56, 0x57, 0x6f, 0x93, 0xab, 0xa7, 0x22, 0x58, 0x5d, - 0xbd, 0x2d, 0xbf, 0xc5, 0xf3, 0x37, 0x79, 0xc8, 0xfd, 0x96, 0x3e, 0x6f, 0x05, 0xbe, 0x98, 0xb3, - 0x6b, 0xab, 0x4d, 0x72, 0xe5, 0xd4, 0xe6, 0xec, 0xda, 0x2a, 0xcd, 0xd9, 0x7c, 0x84, 0xcb, 0x67, - 0x17, 0x80, 0xf1, 0x0d, 0xfc, 0x68, 0x36, 0x0b, 0xa3, 0x2d, 0x89, 0x43, 0xdc, 0xd4, 0x4f, 0xc6, - 0x60, 0xc8, 0x0d, 0xb7, 0x68, 0xc7, 0xb5, 0xf1, 0x6f, 0xf6, 0x32, 0xd4, 0xc2, 0xbe, 0xef, 0xb8, - 0x91, 0xbc, 0x1e, 0xee, 0x47, 0x3c, 0x94, 0x12, 0xdd, 0x9e, 0x0c, 0xfb, 0x7e, 0x3d, 0x12, 0x7a, - 0xa9, 0x58, 0x25, 0xd6, 0xdf, 0x2b, 0x81, 0xb6, 0x54, 0xab, 0x36, 0x6f, 0x7b, 0x21, 0x6f, 0xc5, - 0xa4, 0x06, 0x9c, 0x24, 0xa7, 0x77, 0x84, 0x65, 0x9c, 0xde, 0x11, 0xc6, 0xbe, 0x02, 0xa3, 0xb4, - 0x5d, 0x91, 0x78, 0x56, 0xdd, 0x45, 0xb7, 0x7e, 0x32, 0x47, 0x40, 0x6e, 0xab, 0x53, 0x44, 0x62, - 0x77, 0xb8, 0xb9, 0xbe, 0xb6, 0xd8, 0x71, 0xbd, 0x6e, 0x44, 0x7b, 0x0e, 0x0a, 0xa8, 0x8f, 0x1e, - 0xc6, 0x4e, 0x0b, 0xa1, 0xfa, 0xee, 0x90, 0xa0, 0x5a, 0xd7, 0xd5, 0xa5, 0xe3, 0x01, 0xb1, 0x28, - 0xf3, 0x30, 0x7c, 0x3f, 0x3d, 0x8e, 0x2f, 0x8c, 0x3d, 0xde, 0x9b, 0x97, 0x7d, 0x6b, 0x4b, 0xb8, - 0xc5, 0x61, 0x2c, 0x99, 0xe2, 0x82, 0x97, 0xf8, 0x81, 0xbc, 0x26, 0x25, 0x2f, 0x31, 0xd9, 0x6d, - 0x84, 0x0a, 0x45, 0x71, 0xd9, 0x6f, 0x23, 0x42, 0x19, 0x11, 0xb0, 0x7b, 0xb8, 0xdf, 0xc6, 0x15, - 0xa1, 0x7f, 0x1d, 0xa1, 0x69, 0x8a, 0xd7, 0x7f, 0x53, 0xc2, 0xa9, 0xc8, 0x4e, 0xc2, 0xc8, 0x66, - 0x10, 0x76, 0x5d, 0x0a, 0xfa, 0xb0, 0xe9, 0x17, 0x9b, 0x83, 0x6a, 0x2f, 0x0c, 0x76, 0xbc, 0x36, - 0xa7, 0x7d, 0xc5, 0x4e, 0x7e, 0xb3, 0xf3, 0x30, 0xd2, 0x0d, 0xda, 0xe9, 0x9e, 0x51, 0x4b, 0x27, - 0xf2, 0xc5, 0xdb, 0xa2, 0xc0, 0xa6, 0x72, 0xf6, 0x12, 0x4c, 0x6d, 0xba, 0x9d, 0xce, 0x86, 0xdb, - 0x7a, 0xe0, 0x20, 0x88, 0xec, 0xcc, 0x93, 0x0a, 0x8a, 0xe8, 0x73, 0xef, 0xc3, 0x30, 0xfe, 0x21, - 0xa6, 0x8e, 0x9f, 0xf4, 0x9d, 0x3d, 0x44, 0x07, 0x9a, 0x49, 0x55, 0xb3, 0x93, 0x9e, 0x7f, 0xec, - 0x09, 0x05, 0xc4, 0x2b, 0xe0, 0x5f, 0x2d, 0xc1, 0x88, 0x5c, 0x10, 0x82, 0x47, 0x57, 0xdd, 0xc7, - 0x8f, 0xd9, 0xf8, 0xb7, 0x38, 0x48, 0x68, 0x17, 0xe5, 0xc4, 0x01, 0xa2, 0xf4, 0x92, 0xfc, 0x3c, - 0xd4, 0x08, 0x21, 0xea, 0x79, 0x9b, 0x9b, 0xdc, 0xf1, 0xda, 0x34, 0x3f, 0xa7, 0x22, 0x3a, 0xae, - 0x09, 0xf0, 0x4a, 0x5b, 0xb0, 0x52, 0x7a, 0x5f, 0xcb, 0xa5, 0x30, 0x14, 0x1b, 0x08, 0xb4, 0xe8, - 0x46, 0x82, 0x95, 0x8c, 0xb9, 0x97, 0x57, 0xe9, 0xd8, 0x96, 0x61, 0xc9, 0x4a, 0xc2, 0x17, 0x79, - 0x18, 0x8b, 0xcf, 0xb5, 0x7e, 0xbc, 0x04, 0x53, 0xe6, 0x3a, 0x63, 0x17, 0x61, 0x84, 0xf2, 0x20, - 0x94, 0xf0, 0xb6, 0x45, 0x8c, 0xe8, 0x88, 0xcc, 0x80, 0x60, 0xe4, 0x3d, 0x20, 0x2c, 0xa1, 0xf9, - 0x13, 0x07, 0x52, 0x70, 0x51, 0xf3, 0xa7, 0x95, 0x68, 0xab, 0x32, 0x66, 0xc1, 0x88, 0xcd, 0xa3, - 0x7e, 0x27, 0xd6, 0xbd, 0x44, 0x42, 0x84, 0xd8, 0x54, 0x62, 0xfd, 0x4b, 0xd9, 0x85, 0x62, 0xbf, - 0x33, 0xa3, 0x03, 0x4a, 0x47, 0x89, 0x0e, 0xf8, 0x36, 0x1c, 0xb7, 0x83, 0x0e, 0x8f, 0xea, 0xfe, - 0xee, 0xc3, 0x6d, 0x1e, 0xf2, 0x46, 0x18, 0x6c, 0x2a, 0x87, 0x96, 0xf1, 0xcb, 0xcf, 0x1b, 0xfb, - 0x6d, 0x11, 0xa2, 0xf4, 0x48, 0x08, 0x45, 0x89, 0x90, 0xca, 0x58, 0x24, 0x44, 0x73, 0xc6, 0x01, - 0xa6, 0xb0, 0x12, 0x31, 0x99, 0x43, 0xcd, 0x50, 0x64, 0xd3, 0x2f, 0xeb, 0x97, 0x4b, 0x30, 0x37, - 0xb8, 0x4a, 0xd4, 0xa2, 0xe4, 0x9f, 0xa9, 0xfa, 0x2a, 0xb5, 0x28, 0x09, 0xcd, 0x84, 0x32, 0x68, - 0xc8, 0xcc, 0x86, 0x13, 0xf5, 0x56, 0x8b, 0xf7, 0x62, 0xc1, 0x98, 0x5c, 0xe0, 0x13, 0xf5, 0xb6, - 0x2a, 0x0d, 0xad, 0x2e, 0x22, 0xc8, 0xa0, 0x08, 0x15, 0x16, 0x80, 0x12, 0xa1, 0x98, 0xd4, 0xfa, - 0xfd, 0x12, 0x40, 0xb3, 0x79, 0xe3, 0x16, 0xdf, 0x6d, 0xb8, 0x1e, 0xea, 0xab, 0x72, 0xcb, 0xb9, - 0x45, 0x82, 0x7b, 0x82, 0x3c, 0xdf, 0xe4, 0x4e, 0xf5, 0x80, 0xef, 0x1a, 0x9e, 0x6f, 0x0a, 0x55, - 0x7e, 0x95, 0xb7, 0xe3, 0xc6, 0x5c, 0x10, 0xe2, 0xb5, 0x95, 0xfa, 0x2a, 0x84, 0x66, 0x28, 0x35, - 0x64, 0xf6, 0x4d, 0x98, 0x4a, 0x7f, 0x25, 0xfe, 0x7b, 0x53, 0xc9, 0xe6, 0x60, 0x16, 0x2e, 0x3c, - 0xf7, 0x78, 0x6f, 0x7e, 0x4e, 0xe3, 0x9a, 0x75, 0x62, 0xcb, 0x30, 0x7b, 0x7b, 0xe8, 0x5f, 0xfd, - 0xb5, 0xf9, 0x12, 0x3a, 0x59, 0xae, 0xad, 0x36, 0xd5, 0x67, 0x9e, 0x83, 0xa1, 0x24, 0xf6, 0x6c, - 0x82, 0x54, 0x0f, 0xd3, 0x17, 0x06, 0xcb, 0xc5, 0x21, 0x23, 0xfd, 0x1e, 0xdc, 0xfc, 0xcc, 0xef, - 0x10, 0xa5, 0xec, 0x3a, 0x8c, 0x1e, 0xaa, 0xe5, 0x28, 0x30, 0x0b, 0x5a, 0xac, 0xa8, 0x85, 0xee, - 0xbd, 0x68, 0xcb, 0xab, 0xcf, 0x09, 0xa9, 0x7b, 0xb7, 0xc2, 0x8e, 0x2d, 0x60, 0xd6, 0x5e, 0x09, - 0xe0, 0xe6, 0xfa, 0xda, 0x0f, 0xec, 0x30, 0x59, 0x3f, 0x5d, 0x86, 0x99, 0xd4, 0x01, 0x5b, 0x7d, - 0xe7, 0x17, 0x01, 0xd2, 0x4f, 0x3a, 0xf8, 0x43, 0x7b, 0xc9, 0x87, 0xbe, 0x0d, 0xe3, 0x5a, 0xe5, - 0x87, 0xf8, 0xd2, 0x5e, 0xfa, 0xa5, 0x0e, 0xd4, 0xb2, 0x0d, 0xff, 0x94, 0xdf, 0xda, 0x33, 0xf0, - 0xc5, 0xec, 0xdb, 0x76, 0x23, 0x19, 0xef, 0x30, 0x29, 0x67, 0x9f, 0xf8, 0xad, 0xcf, 0x3e, 0xf1, - 0xdb, 0xaa, 0x43, 0xad, 0xbe, 0xc5, 0x8d, 0x5e, 0x61, 0xaf, 0x15, 0xf4, 0x08, 0x9a, 0xbf, 0x53, - 0xa8, 0xd6, 0x0f, 0xd6, 0x4f, 0x96, 0x61, 0x5a, 0xcc, 0xe4, 0x7a, 0x3f, 0xde, 0x0e, 0x42, 0x2f, - 0xde, 0x7d, 0x6a, 0x2f, 0x2e, 0xdf, 0x35, 0x0c, 0x42, 0x73, 0x4a, 0x73, 0xd4, 0xbf, 0x6d, 0xf0, - 0xfd, 0xa5, 0xae, 0x8b, 0x0c, 0xc3, 0xb1, 0x02, 0x2a, 0xf6, 0xaa, 0xe1, 0x0d, 0x31, 0xab, 0xb2, - 0x3f, 0x7d, 0x7f, 0x6f, 0x7e, 0x42, 0xa1, 0xaf, 0xa5, 0xd9, 0xa0, 0x2e, 0x9b, 0xae, 0xf7, 0xb2, - 0xa7, 0xd0, 0x39, 0x42, 0x77, 0xbd, 0x37, 0x1d, 0xee, 0x2f, 0xc0, 0x30, 0xee, 0x08, 0x14, 0xbf, - 0x82, 0x07, 0x3b, 0xdc, 0x7b, 0x0c, 0xff, 0x53, 0x01, 0x60, 0x37, 0x60, 0x54, 0xfc, 0x71, 0xdb, - 0xed, 0x91, 0x6b, 0x12, 0x4b, 0x2c, 0xa6, 0x08, 0xed, 0x79, 0xfe, 0x96, 0x6e, 0x34, 0xed, 0x70, - 0xa7, 0xeb, 0xf6, 0x8c, 0x13, 0xa8, 0x44, 0x34, 0x8c, 0xaf, 0xd5, 0xc1, 0xc6, 0xd7, 0xd2, 0x81, - 0xc6, 0xd7, 0x4d, 0x80, 0xa6, 0xb7, 0xe5, 0x7b, 0xfe, 0x56, 0xbd, 0xb3, 0x45, 0x39, 0xb4, 0x2e, - 0x0c, 0x1e, 0x85, 0x8b, 0x29, 0x32, 0xae, 0x91, 0x67, 0xd0, 0x7f, 0x50, 0xc2, 0x1c, 0xb7, 0xb3, - 0x65, 0xa4, 0x21, 0xd0, 0x38, 0xb3, 0x3b, 0x00, 0xf5, 0x56, 0xec, 0xed, 0x88, 0xd5, 0x12, 0xd1, - 0x71, 0x51, 0x35, 0x79, 0xb1, 0x7e, 0x8b, 0xef, 0xa2, 0x89, 0x43, 0x79, 0x62, 0xb9, 0x88, 0x2a, - 0x66, 0xbd, 0x11, 0x63, 0x9e, 0x72, 0x60, 0x3d, 0x38, 0x51, 0x6f, 0xb7, 0x3d, 0xf1, 0x0d, 0x6e, - 0x67, 0x4d, 0xa6, 0x47, 0x43, 0xd6, 0x13, 0xc5, 0xac, 0x2f, 0x28, 0xe7, 0x21, 0x37, 0xa1, 0x72, - 0x54, 0x56, 0xb5, 0x4c, 0x35, 0xc5, 0x8c, 0xad, 0x26, 0x4c, 0x99, 0x1f, 0x6f, 0xe6, 0xfe, 0x9a, - 0x80, 0xaa, 0xdd, 0xac, 0x3b, 0xcd, 0x1b, 0xf5, 0x37, 0x6a, 0x25, 0x56, 0x83, 0x09, 0xfa, 0x75, - 0xd9, 0xb9, 0xfc, 0xe6, 0xd5, 0x5a, 0xd9, 0x80, 0xbc, 0xf9, 0xc6, 0xe5, 0x5a, 0x65, 0xae, 0x3c, - 0x5b, 0xca, 0x64, 0x20, 0x19, 0xad, 0x55, 0xe5, 0x2d, 0x9a, 0xd0, 0x45, 0xab, 0xaa, 0xed, 0xec, - 0x2a, 0x54, 0x9a, 0xcd, 0x1b, 0x99, 0x1c, 0x1e, 0xe9, 0xee, 0x2e, 0x77, 0xb0, 0xc8, 0x90, 0x34, - 0x82, 0x40, 0xd0, 0x89, 0x23, 0x5f, 0xd9, 0xa0, 0x4b, 0xb7, 0xcb, 0x41, 0xc7, 0x3e, 0x41, 0x77, - 0x73, 0x7d, 0x8d, 0x14, 0x73, 0x45, 0x97, 0x6e, 0x53, 0x92, 0xee, 0xa3, 0x87, 0xfa, 0xbe, 0x2a, - 0x08, 0x2c, 0x1b, 0xc6, 0xb5, 0x89, 0x2c, 0x15, 0xc6, 0x6e, 0x90, 0xa4, 0x25, 0x23, 0x85, 0x51, - 0x40, 0x6c, 0x2a, 0x11, 0x47, 0x99, 0xd5, 0xa0, 0xe5, 0x76, 0x48, 0xf3, 0xc4, 0xa3, 0x4c, 0x47, - 0x00, 0x6c, 0x09, 0xb7, 0x7e, 0xb3, 0x04, 0xb5, 0x86, 0xd0, 0xd2, 0x31, 0x30, 0x30, 0x78, 0xc0, - 0xfd, 0xfb, 0x6f, 0xb0, 0xd7, 0xd4, 0x92, 0x2b, 0x25, 0xb7, 0x00, 0xc3, 0xb8, 0xe4, 0x32, 0xee, - 0x2a, 0xb4, 0xec, 0x9a, 0x69, 0xb2, 0xb9, 0xf2, 0xe1, 0x33, 0x46, 0x51, 0xb2, 0xb9, 0x6c, 0xc6, - 0x28, 0x95, 0x76, 0x6e, 0x1e, 0x86, 0xb1, 0x39, 0x24, 0x1c, 0xb1, 0xe5, 0xb1, 0x00, 0xd8, 0x12, - 0xae, 0xc9, 0xa6, 0xbd, 0x72, 0xee, 0x1b, 0x2e, 0xff, 0x40, 0x65, 0x5d, 0x32, 0x3f, 0x6e, 0x1f, - 0x7f, 0x93, 0x5b, 0x03, 0xb2, 0x2e, 0x65, 0x18, 0xc8, 0x6c, 0x02, 0x97, 0xe5, 0xbd, 0xab, 0x0c, - 0xaa, 0xd5, 0x4d, 0xd2, 0xb9, 0x84, 0x2b, 0x5f, 0x83, 0xe3, 0xd9, 0xfe, 0xc5, 0xfb, 0x94, 0x3a, - 0x4c, 0x9b, 0x70, 0x75, 0xb5, 0x72, 0xaa, 0xb0, 0xde, 0xfb, 0x97, 0xed, 0x2c, 0xbe, 0xf5, 0x6b, - 0x15, 0x18, 0xc3, 0x3f, 0xed, 0xbe, 0xd4, 0xf2, 0xeb, 0xeb, 0x4d, 0xb2, 0xe7, 0xea, 0x5a, 0xbe, - 0xfb, 0x30, 0x52, 0x1e, 0xe0, 0x86, 0xc0, 0x4a, 0x90, 0x89, 0x54, 0xde, 0x2f, 0xab, 0x9b, 0x84, - 0x84, 0x54, 0x9e, 0x31, 0xa2, 0x0c, 0x29, 0x21, 0x63, 0x48, 0x97, 0x3c, 0x76, 0xe8, 0xfe, 0xb8, - 0x48, 0x17, 0x74, 0xcc, 0x90, 0x2e, 0x89, 0x86, 0xee, 0xb8, 0xeb, 0x4d, 0x71, 0x12, 0xd1, 0xdd, - 0x71, 0x45, 0x1b, 0x8d, 0x53, 0x08, 0x21, 0xb1, 0xbb, 0x30, 0x53, 0x5f, 0x6f, 0xde, 0xd5, 0x2e, - 0x71, 0x57, 0x96, 0xe8, 0x22, 0xfb, 0xf9, 0xc7, 0x7b, 0xf3, 0x67, 0x04, 0xa5, 0x7e, 0xc5, 0x6b, - 0x1e, 0xdd, 0xf2, 0xb4, 0xec, 0x47, 0x4b, 0x70, 0x32, 0x03, 0x75, 0x3b, 0xf7, 0x7c, 0x2f, 0x56, - 0x03, 0x6f, 0xa5, 0x97, 0xd4, 0x3a, 0x12, 0xa2, 0xdc, 0x76, 0xe3, 0xd6, 0x36, 0x0f, 0xa5, 0x8f, - 0x7d, 0xb6, 0x6a, 0xb7, 0xe3, 0xf4, 0x05, 0x92, 0xee, 0xc2, 0x5f, 0x5c, 0x93, 0xf5, 0xb7, 0xa6, - 0xb3, 0xd3, 0x82, 0x74, 0x82, 0x23, 0x8a, 0x8f, 0x77, 0x60, 0xb8, 0x2e, 0x8e, 0xe6, 0x24, 0x48, - 0x95, 0xa5, 0x22, 0x99, 0x15, 0x72, 0xcb, 0xc7, 0xd3, 0xbb, 0x91, 0x61, 0x46, 0x00, 0xd8, 0x22, - 0x8c, 0xd5, 0xd7, 0x9b, 0x2b, 0x2b, 0x4b, 0x6b, 0x6b, 0x32, 0x75, 0x45, 0x65, 0xe1, 0x25, 0x35, - 0xea, 0x9e, 0xd7, 0x76, 0xb2, 0x5e, 0x90, 0xe9, 0xf1, 0x3c, 0xa5, 0x63, 0x5f, 0x06, 0xb8, 0x19, - 0x78, 0xbe, 0xbc, 0x51, 0xa2, 0x21, 0x3d, 0xf3, 0x78, 0x6f, 0x7e, 0xfc, 0xa3, 0xc0, 0xf3, 0xe9, - 0x0a, 0x4a, 0xb4, 0x3d, 0x45, 0xb2, 0xb5, 0xbf, 0xc5, 0xfc, 0x59, 0x08, 0x64, 0x74, 0xc4, 0x70, - 0x3a, 0x7f, 0x36, 0x82, 0xdc, 0xd5, 0x87, 0x42, 0x63, 0x5d, 0x98, 0x6e, 0xf6, 0xb7, 0xb6, 0xb8, - 0xd8, 0xfc, 0xc8, 0xb4, 0x3f, 0x42, 0xa6, 0xbd, 0x24, 0x93, 0xa8, 0x34, 0x37, 0xb8, 0x9d, 0x3e, - 0x8f, 0x16, 0x5e, 0x15, 0x6b, 0xfd, 0x77, 0xf6, 0xe6, 0xc9, 0xbb, 0x52, 0x28, 0xce, 0x91, 0xa2, - 0xcf, 0x1b, 0xf6, 0xb3, 0xbc, 0xd9, 0x5d, 0x18, 0x91, 0x9e, 0x08, 0x94, 0x4b, 0xe1, 0xf9, 0x7d, - 0xe4, 0x8a, 0x44, 0x1c, 0xe4, 0x23, 0x24, 0x4b, 0xd9, 0x3a, 0x54, 0x17, 0xbd, 0xb0, 0xd5, 0xe1, - 0x8b, 0x2b, 0xa4, 0x1e, 0xbd, 0xb0, 0x0f, 0x4b, 0x85, 0x2a, 0xfb, 0xa5, 0x85, 0xbf, 0x5a, 0x9e, - 0xae, 0x2e, 0x29, 0x0c, 0xf6, 0x97, 0x4b, 0xf0, 0x4c, 0xd2, 0xfa, 0xfa, 0x16, 0xf7, 0x63, 0x9a, - 0xad, 0xd4, 0x4b, 0x63, 0xfb, 0xf5, 0xd2, 0xdb, 0xb9, 0x5e, 0x3a, 0x9f, 0xf6, 0x92, 0x2b, 0x98, - 0x39, 0x5d, 0xc9, 0x2d, 0xdf, 0x67, 0xfb, 0xd5, 0xca, 0x1c, 0x80, 0xd4, 0xc7, 0x86, 0xdc, 0xb4, - 0x5f, 0xda, 0xe7, 0x83, 0x53, 0x64, 0x8a, 0x62, 0x4f, 0x7e, 0x1b, 0x61, 0x45, 0x09, 0x94, 0xdd, - 0x52, 0x79, 0x4f, 0xa4, 0xe2, 0x76, 0x76, 0x1f, 0xde, 0x32, 0x17, 0xca, 0xb1, 0x7d, 0x92, 0x2e, - 0xc9, 0xd1, 0x5e, 0x75, 0x37, 0x48, 0x57, 0x3b, 0x60, 0xb4, 0x57, 0xdd, 0x74, 0xb4, 0x3b, 0x6e, - 0x76, 0xb4, 0x57, 0xdd, 0x0d, 0xb6, 0x28, 0xf3, 0x47, 0xc9, 0x64, 0x43, 0xcf, 0xed, 0xc7, 0x4d, - 0x99, 0xd5, 0x0b, 0xf2, 0x48, 0x7d, 0x1d, 0xc6, 0x9a, 0x3d, 0xb7, 0xc5, 0x3b, 0xde, 0x66, 0x4c, - 0x8e, 0x6f, 0x2f, 0xee, 0xc3, 0x2a, 0xc1, 0x25, 0x87, 0x1d, 0xf5, 0x53, 0x3f, 0xb7, 0x26, 0x38, - 0xa2, 0x85, 0x6b, 0x8d, 0xdb, 0x64, 0xe4, 0xdf, 0xaf, 0x85, 0x6b, 0x8d, 0xdb, 0xa4, 0x96, 0xf5, - 0xba, 0x86, 0x5a, 0xd6, 0xb8, 0xcd, 0x7a, 0x30, 0xb5, 0xc6, 0xc3, 0xd0, 0xdd, 0x0c, 0xc2, 0xae, - 0xbc, 0x4a, 0x92, 0x77, 0x00, 0x17, 0xf6, 0xe3, 0x67, 0x10, 0xc8, 0x1b, 0x94, 0x58, 0xc1, 0x9c, - 0xec, 0xfd, 0x53, 0x86, 0xbf, 0xe8, 0x93, 0x05, 0x2f, 0xde, 0xe8, 0xb7, 0x1e, 0xf0, 0x98, 0x6e, - 0x0e, 0xf6, 0xeb, 0x93, 0x04, 0x57, 0xf6, 0xc9, 0x86, 0xfa, 0xa9, 0xf7, 0x49, 0x82, 0x23, 0xa6, - 0x01, 0x65, 0x89, 0x62, 0x07, 0x4e, 0x03, 0x89, 0x28, 0xa7, 0xc1, 0xa0, 0x74, 0x51, 0x6c, 0x1b, - 0x26, 0x16, 0x82, 0xbe, 0x2f, 0xb4, 0xf5, 0x9e, 0xeb, 0x85, 0xb3, 0xc7, 0x90, 0xed, 0xcb, 0xfb, - 0x35, 0x58, 0x43, 0x97, 0xc1, 0x67, 0x1b, 0x02, 0x22, 0x0e, 0x04, 0x02, 0xa4, 0x5f, 0x0a, 0xeb, - 0xa8, 0xac, 0x0d, 0xe3, 0x38, 0x95, 0x97, 0xf8, 0x4e, 0xd0, 0x8b, 0x66, 0x8f, 0x63, 0x45, 0xe7, - 0x0e, 0x5a, 0x14, 0x12, 0x5b, 0x3a, 0x52, 0xe1, 0xd2, 0x70, 0xda, 0x08, 0xd1, 0x6f, 0xea, 0x34, - 0x44, 0x76, 0x1d, 0x86, 0x96, 0xfd, 0x9d, 0xd7, 0x67, 0x4f, 0x20, 0xfb, 0xf9, 0x7d, 0xd8, 0x0b, - 0x34, 0x69, 0x70, 0xe0, 0xfe, 0xce, 0xeb, 0xba, 0xc1, 0x41, 0x94, 0x64, 0xef, 0x0b, 0x4f, 0x1e, - 0xe9, 0xbe, 0xb0, 0x0d, 0xe3, 0xd7, 0xb9, 0xcf, 0x43, 0xaf, 0x75, 0x77, 0x65, 0x69, 0x71, 0xf6, - 0xd4, 0x81, 0xdf, 0xaa, 0x61, 0xcb, 0x6f, 0xdd, 0x92, 0x00, 0x27, 0xf0, 0xda, 0x2d, 0xbd, 0x16, - 0x0d, 0xd1, 0xfa, 0xbb, 0xc3, 0x30, 0x7f, 0x40, 0xc7, 0xb1, 0xfb, 0x6a, 0x1f, 0x96, 0x3a, 0xdc, - 0xe7, 0x0f, 0xd7, 0xdf, 0x17, 0x0f, 0xdc, 0xa2, 0xdf, 0x81, 0xa9, 0x8c, 0xea, 0xa3, 0xe5, 0x46, - 0xce, 0xa8, 0x3d, 0x76, 0x06, 0x75, 0xee, 0xdf, 0x56, 0x60, 0x08, 0x55, 0xc3, 0x17, 0xa0, 0xd2, - 0xec, 0x6f, 0xe8, 0x8e, 0x0b, 0x91, 0xb1, 0x35, 0x89, 0x52, 0xf6, 0x2e, 0x8c, 0x53, 0x58, 0xb1, - 0x66, 0x5f, 0xc0, 0x4e, 0x52, 0x31, 0xc8, 0xd9, 0xa0, 0x47, 0x0d, 0x9d, 0xbd, 0x07, 0x13, 0x0d, - 0xaf, 0xc7, 0x3b, 0x9e, 0xcf, 0xb5, 0x10, 0x3e, 0x9c, 0xb7, 0x3d, 0x82, 0xe7, 0x9c, 0x19, 0x74, - 0x02, 0x33, 0x00, 0x7a, 0xe8, 0xf0, 0x01, 0xd0, 0xef, 0xc1, 0xc4, 0x12, 0xdf, 0xf4, 0x7c, 0xcf, - 0x50, 0x0d, 0xb1, 0xe2, 0x76, 0x02, 0x37, 0xa9, 0x0d, 0x02, 0xb6, 0x00, 0x93, 0x36, 0xef, 0x05, - 0x91, 0x17, 0x07, 0xe1, 0xee, 0x3d, 0x7b, 0x85, 0x7c, 0x1d, 0xd1, 0xb4, 0x1d, 0x26, 0x05, 0x4e, - 0x3f, 0xd4, 0x77, 0x5d, 0x93, 0x84, 0xdd, 0x81, 0x99, 0x14, 0x60, 0xfa, 0x56, 0xd3, 0xcd, 0x75, - 0xc2, 0x27, 0x1f, 0x2b, 0x95, 0x27, 0x35, 0xdb, 0x64, 0xf3, 0x4d, 0x8a, 0xbc, 0xca, 0xb6, 0x29, - 0xe4, 0x9b, 0xc5, 0x6d, 0xb2, 0xf9, 0xa6, 0xf5, 0xf7, 0x2b, 0x70, 0x6a, 0x80, 0x18, 0x67, 0x77, - 0xcc, 0xe9, 0xfa, 0xc2, 0xfe, 0x52, 0xff, 0xe0, 0x69, 0xba, 0x0a, 0xb5, 0xe5, 0x5b, 0x68, 0x92, - 0xa1, 0x7b, 0xa2, 0xba, 0x3a, 0x46, 0xe0, 0xe7, 0xf3, 0x07, 0x78, 0x55, 0xa4, 0xdd, 0x2b, 0x19, - 0x11, 0x68, 0x19, 0xca, 0xb9, 0xff, 0x5f, 0x99, 0xe6, 0xed, 0x3b, 0xa0, 0xe7, 0x39, 0x35, 0x32, - 0x13, 0xa4, 0x60, 0x7d, 0x46, 0x6a, 0xd8, 0xec, 0x7d, 0x98, 0x58, 0xbe, 0x25, 0xed, 0xd0, 0x37, - 0x94, 0xe9, 0x93, 0xba, 0x90, 0x3f, 0x50, 0xf7, 0xe0, 0x19, 0x23, 0xa8, 0x41, 0xc1, 0xee, 0xc1, - 0x31, 0xd9, 0x36, 0x6f, 0xd3, 0x6b, 0xc9, 0x2c, 0xcf, 0x9e, 0xdb, 0xa1, 0x19, 0xf6, 0xc2, 0xe3, - 0xbd, 0xf9, 0x79, 0xfe, 0x00, 0x2f, 0xc1, 0xa8, 0xdc, 0x89, 0x10, 0x41, 0x0f, 0x2c, 0x2d, 0xa0, - 0xd7, 0xf3, 0xbb, 0xda, 0x63, 0x58, 0xa1, 0xa8, 0x4d, 0xd4, 0x2d, 0x70, 0x25, 0x92, 0xf5, 0x7f, - 0x8c, 0xc0, 0xdc, 0x60, 0x15, 0x93, 0x7d, 0x60, 0x0e, 0xe0, 0xb9, 0x03, 0x95, 0xd2, 0x83, 0xc7, - 0xf0, 0xab, 0x70, 0x7c, 0xd9, 0x8f, 0x79, 0xd8, 0x0b, 0x3d, 0x95, 0x17, 0xf6, 0x46, 0x10, 0xa9, - 0xf8, 0x5d, 0xbc, 0xb6, 0xe2, 0x49, 0x39, 0x45, 0xa2, 0xe3, 0x6d, 0xb7, 0x7e, 0x6d, 0x55, 0xc4, - 0x81, 0x2d, 0xc3, 0x94, 0x06, 0xef, 0xf4, 0xb7, 0x74, 0x67, 0x27, 0x9d, 0x67, 0xa7, 0xaf, 0x07, - 0x37, 0x66, 0x88, 0x30, 0x46, 0x38, 0x76, 0x63, 0xaf, 0x75, 0x73, 0xfd, 0x56, 0x93, 0x86, 0x53, - 0xc6, 0x08, 0x23, 0xd4, 0xf9, 0xe8, 0xe1, 0x03, 0x43, 0x47, 0x4c, 0x91, 0xe7, 0x7e, 0x71, 0xe8, - 0x28, 0x92, 0xf0, 0x4b, 0x00, 0xe9, 0x52, 0xd2, 0x73, 0x12, 0xa5, 0x4b, 0xcf, 0x4c, 0x03, 0xa0, - 0xa0, 0xec, 0x06, 0x4c, 0xa7, 0xbf, 0xee, 0x3e, 0xf4, 0x95, 0x17, 0x80, 0xb4, 0xd7, 0x6b, 0x2b, - 0x37, 0x10, 0x65, 0xfa, 0xb1, 0x23, 0x43, 0xc6, 0x2e, 0x43, 0x75, 0x3d, 0x08, 0x1f, 0x6c, 0x8a, - 0x31, 0x1e, 0x4a, 0x0f, 0x46, 0x0f, 0x09, 0xa6, 0x1f, 0x00, 0x14, 0x9e, 0x58, 0x2e, 0xcb, 0xfe, - 0x8e, 0x17, 0x06, 0xe8, 0x20, 0xa6, 0x7b, 0x7b, 0xf3, 0x14, 0x6c, 0xe4, 0xa2, 0x4b, 0xc1, 0xec, - 0x02, 0x0c, 0xd7, 0x5b, 0x71, 0x10, 0x92, 0xf8, 0x93, 0x33, 0x45, 0x00, 0x8c, 0x99, 0x22, 0x00, - 0xa2, 0x13, 0x85, 0x4c, 0x1a, 0x4d, 0x3b, 0xd1, 0x14, 0x44, 0xa2, 0x54, 0x1c, 0xec, 0x6c, 0xbe, - 0x89, 0xf6, 0xed, 0x6a, 0xda, 0xfe, 0x90, 0x6f, 0xe6, 0xee, 0xa2, 0x08, 0x4d, 0x74, 0x7b, 0x3a, - 0xe2, 0xe4, 0x39, 0x84, 0xdd, 0x9e, 0x4e, 0x11, 0xe3, 0x52, 0x36, 0x81, 0xca, 0xc4, 0x13, 0xea, - 0x57, 0x92, 0xef, 0x8d, 0x12, 0x4f, 0x24, 0xd3, 0xcb, 0xdc, 0x03, 0x74, 0x02, 0xeb, 0xcf, 0xc1, - 0xc0, 0xd5, 0x26, 0x94, 0xf8, 0xa3, 0xad, 0xb6, 0x55, 0xf7, 0x10, 0xab, 0xed, 0xd5, 0x24, 0xaf, - 0x81, 0x9e, 0xaa, 0x13, 0x21, 0xba, 0xfa, 0x48, 0x19, 0x0e, 0xcc, 0xa9, 0x5f, 0x39, 0xca, 0xd4, - 0xff, 0x9b, 0xd5, 0xa3, 0x4c, 0x7d, 0x1a, 0xda, 0xf2, 0x61, 0x87, 0xb6, 0x72, 0xb8, 0xa1, 0x5d, - 0x80, 0xc9, 0xe4, 0x7d, 0x8d, 0x86, 0x1b, 0x1b, 0xc2, 0x38, 0x79, 0x83, 0xc3, 0xe9, 0xb9, 0x46, - 0x12, 0x69, 0x93, 0x44, 0xd3, 0x4f, 0x90, 0xc3, 0x70, 0x5e, 0x3f, 0xc9, 0xd0, 0xeb, 0xe8, 0x42, - 0x06, 0x29, 0x75, 0xa3, 0x89, 0x51, 0xf2, 0x34, 0xcf, 0xa5, 0xe7, 0xa2, 0xd2, 0x50, 0x64, 0x00, - 0xbd, 0x71, 0x67, 0x68, 0x10, 0x65, 0x97, 0xd8, 0xe8, 0x91, 0x96, 0x98, 0x8c, 0xd7, 0x0a, 0x57, - 0x83, 0x2d, 0x4f, 0xc5, 0x52, 0xab, 0x78, 0xad, 0xd0, 0xe9, 0x08, 0x68, 0x26, 0x5e, 0x4b, 0xa2, - 0xb2, 0xd7, 0x60, 0x44, 0xfc, 0x58, 0x59, 0xa2, 0x45, 0x81, 0x16, 0x33, 0x24, 0x32, 0x03, 0xd8, - 0x25, 0x92, 0xaa, 0x66, 0xb9, 0xeb, 0x7a, 0x1d, 0x5a, 0x0a, 0x69, 0x35, 0x5c, 0x40, 0xb3, 0xd5, - 0x20, 0x2a, 0x6b, 0xc1, 0x84, 0xcd, 0x37, 0x1b, 0x61, 0x10, 0xf3, 0x56, 0xcc, 0xdb, 0x74, 0x9e, - 0x56, 0x26, 0xa5, 0x85, 0x20, 0x90, 0xb6, 0x02, 0x8c, 0x75, 0x2e, 0xfd, 0xce, 0xde, 0x3c, 0x08, - 0x90, 0xcc, 0x8e, 0x20, 0x56, 0x9a, 0x18, 0xff, 0x9e, 0x22, 0xd6, 0x57, 0x9a, 0xce, 0x94, 0x7d, - 0x5b, 0xec, 0x32, 0x49, 0x97, 0xa4, 0x95, 0x4d, 0x0c, 0xa8, 0xec, 0xcd, 0xc2, 0xca, 0xe6, 0xb5, - 0xde, 0x2e, 0xac, 0xb4, 0xb0, 0x12, 0xf6, 0x65, 0x18, 0x5f, 0x5c, 0x59, 0x0c, 0xfc, 0x4d, 0x6f, - 0xab, 0x79, 0xa3, 0x8e, 0x87, 0x72, 0x12, 0x13, 0x2d, 0xcf, 0x69, 0x21, 0xdc, 0x89, 0xb6, 0x5d, - 0x23, 0x7d, 0x55, 0x8a, 0xcf, 0xae, 0xc3, 0x94, 0xfa, 0x69, 0xf3, 0x4d, 0xa1, 0x2a, 0x4e, 0x69, - 0xd9, 0x54, 0x12, 0x0e, 0xa2, 0x23, 0x4c, 0x6d, 0x31, 0x43, 0x26, 0x26, 0xe3, 0x12, 0xef, 0x75, - 0x82, 0x5d, 0xd1, 0xbc, 0x35, 0x8f, 0x87, 0x78, 0xfa, 0xa6, 0xc9, 0xd8, 0x4e, 0x4a, 0x9c, 0xd8, - 0x33, 0x9d, 0x08, 0x4d, 0x22, 0xa1, 0x75, 0xd2, 0x14, 0xbf, 0xef, 0x45, 0xde, 0x86, 0xd7, 0xf1, - 0xe2, 0x5d, 0x19, 0x73, 0x26, 0xd5, 0x2e, 0xb5, 0x2e, 0x76, 0x92, 0x52, 0x5d, 0xeb, 0xcc, 0x91, - 0x5a, 0xbf, 0x5a, 0x86, 0x67, 0xf7, 0xb3, 0x41, 0xb1, 0xa6, 0x29, 0x07, 0xcf, 0x1f, 0xc2, 0x6e, - 0x75, 0xb0, 0x24, 0x5c, 0x1e, 0x70, 0xc4, 0xc1, 0xce, 0x18, 0x6c, 0xd9, 0xcd, 0x1e, 0x76, 0x76, - 0x48, 0xcc, 0x7d, 0xd2, 0x44, 0x4a, 0x57, 0x61, 0x6c, 0x31, 0xf0, 0x63, 0xfe, 0x28, 0xce, 0xe4, - 0x21, 0x94, 0xc0, 0x6c, 0x12, 0x29, 0x85, 0x6a, 0xfd, 0x76, 0x05, 0xce, 0xec, 0x6b, 0x84, 0x61, - 0x6b, 0x66, 0xaf, 0x5d, 0x38, 0x8c, 0xe5, 0xe6, 0xe0, 0x6e, 0xbb, 0x9c, 0x8b, 0x98, 0x39, 0x38, - 0x13, 0x86, 0x0d, 0x4c, 0x66, 0x47, 0xbb, 0xde, 0x09, 0x36, 0xd0, 0x4c, 0xe7, 0xf9, 0x5b, 0x94, - 0x55, 0x4d, 0x66, 0x75, 0xc0, 0x52, 0x67, 0xab, 0x13, 0x6c, 0x48, 0x73, 0x9f, 0xe7, 0xeb, 0x1a, - 0x59, 0x01, 0xf5, 0xdc, 0x6f, 0x97, 0xa8, 0xe3, 0x5f, 0x87, 0x51, 0x6c, 0x7e, 0xd2, 0xed, 0xf2, - 0x5a, 0x08, 0x25, 0xbb, 0x67, 0x5e, 0x0b, 0x49, 0x34, 0x76, 0x05, 0xaa, 0x8b, 0x6e, 0xa7, 0xa3, - 0x65, 0x7e, 0x44, 0xdb, 0x4c, 0x0b, 0x61, 0x99, 0x00, 0x33, 0x85, 0x28, 0xb6, 0x42, 0xf9, 0xb7, - 0xb6, 0xff, 0xa0, 0x00, 0x26, 0xb2, 0xcc, 0x16, 0xa4, 0x21, 0xe3, 0x53, 0x44, 0x18, 0xac, 0x31, - 0xa4, 0x3d, 0x45, 0x24, 0x00, 0xc6, 0x53, 0x44, 0x02, 0x60, 0x7d, 0x6f, 0x04, 0x9e, 0xdb, 0xdf, - 0x3a, 0xc9, 0xee, 0x99, 0xc3, 0xfa, 0xca, 0xa1, 0x6c, 0x9a, 0x07, 0x8f, 0xab, 0x7a, 0x32, 0x4a, - 0x76, 0xc8, 0xf9, 0x7c, 0x8a, 0x84, 0xef, 0xef, 0xcd, 0x6b, 0x51, 0x8b, 0x37, 0x03, 0xcf, 0xd7, - 0x9c, 0x04, 0x3e, 0xce, 0x29, 0x0a, 0xe3, 0x97, 0xaf, 0x1e, 0xae, 0x65, 0x29, 0x9d, 0x94, 0x55, - 0x87, 0x54, 0x30, 0xd8, 0x57, 0x61, 0x08, 0xad, 0x2f, 0x43, 0x2a, 0xf0, 0xe8, 0x30, 0x95, 0x09, - 0x0a, 0xaa, 0x06, 0x6d, 0x43, 0x19, 0xfb, 0x0b, 0x72, 0x9c, 0x7b, 0x1b, 0x6a, 0xd9, 0x46, 0xb1, - 0x73, 0x30, 0x84, 0x9f, 0xa6, 0x65, 0x90, 0xc8, 0xb4, 0x0d, 0xcb, 0xe7, 0x7e, 0xb6, 0x04, 0x90, - 0x56, 0x22, 0xd4, 0xad, 0x95, 0x28, 0xea, 0x27, 0xd9, 0xcc, 0x51, 0xdd, 0xf2, 0x10, 0xa2, 0xef, - 0xa0, 0x12, 0x87, 0x7d, 0x15, 0x33, 0x58, 0xa0, 0x6b, 0x3e, 0x0e, 0xca, 0x8d, 0xb5, 0xb5, 0x06, - 0x91, 0x4b, 0xbf, 0x37, 0x54, 0xe7, 0x13, 0x9f, 0x7e, 0x19, 0xdd, 0xb2, 0x1d, 0xc7, 0x3d, 0x47, - 0xb2, 0xb4, 0x07, 0x91, 0xcf, 0x7d, 0x47, 0x1d, 0x6d, 0x31, 0xfd, 0xa7, 0x9e, 0x7a, 0x89, 0x1a, - 0x46, 0xe9, 0x3f, 0x8d, 0xbc, 0x4d, 0x66, 0xfa, 0x4f, 0x9d, 0x48, 0xac, 0xe8, 0x82, 0x3c, 0x53, - 0x5a, 0x26, 0xcf, 0x03, 0x52, 0x4c, 0x15, 0x50, 0x33, 0x17, 0x4e, 0xe5, 0xa1, 0xf2, 0xe9, 0xb4, - 0x4a, 0x9a, 0xc0, 0xaa, 0x88, 0x31, 0x22, 0xe9, 0x59, 0x60, 0x06, 0xf0, 0xb1, 0x7e, 0xb9, 0x0c, - 0xa7, 0x07, 0xda, 0xc6, 0x58, 0xc3, 0x5c, 0x59, 0x2f, 0x1d, 0x64, 0x4c, 0x3b, 0x70, 0x51, 0xcd, - 0xfd, 0x7d, 0x25, 0xa4, 0xbe, 0x02, 0x13, 0xcd, 0xfe, 0x46, 0xd6, 0xa4, 0x20, 0x33, 0x1a, 0x6b, - 0x70, 0x5d, 0x7d, 0xd1, 0xf1, 0xc5, 0xb0, 0xa9, 0x34, 0x4b, 0x14, 0x02, 0xa1, 0xc5, 0x5d, 0x25, - 0x69, 0xe2, 0xf2, 0x59, 0x5b, 0x4d, 0x22, 0x31, 0x1d, 0x63, 0xee, 0xbb, 0x7e, 0xac, 0x47, 0xfc, - 0x4a, 0x88, 0x3e, 0x1d, 0x25, 0xc4, 0xfa, 0x8f, 0xca, 0xc5, 0x96, 0x9c, 0xeb, 0x8b, 0x8d, 0xa3, - 0x58, 0x72, 0xae, 0x2f, 0x36, 0x0e, 0xee, 0xa9, 0xff, 0x52, 0xf5, 0x94, 0x74, 0x1a, 0x95, 0x9b, - 0xa3, 0xba, 0x8d, 0x54, 0x4e, 0xa3, 0xb4, 0x91, 0x46, 0x19, 0xa7, 0x51, 0x42, 0x66, 0x6f, 0xc2, - 0xd8, 0x6a, 0x20, 0xd3, 0xaf, 0xaa, 0xfe, 0x91, 0x59, 0xd7, 0x14, 0x50, 0xdf, 0x49, 0x13, 0x4c, - 0x71, 0x78, 0x36, 0xe7, 0x8b, 0x0a, 0x46, 0xc3, 0xd5, 0x96, 0x99, 0x6f, 0xe6, 0x9d, 0x9d, 0x49, - 0x66, 0xfd, 0xc7, 0xc3, 0x60, 0x1d, 0x7c, 0xe3, 0xc0, 0xbe, 0x66, 0xf6, 0xdd, 0xc5, 0x43, 0xdf, - 0x55, 0x1c, 0x6a, 0x77, 0xae, 0xf7, 0xdb, 0x1e, 0xf7, 0x5b, 0x66, 0xaa, 0x53, 0x82, 0xe9, 0x3b, - 0x9b, 0xc2, 0xfb, 0x24, 0xb9, 0xad, 0xe6, 0xfe, 0x49, 0x25, 0x95, 0x27, 0x19, 0x2d, 0xaa, 0xf4, - 0x09, 0xb4, 0x28, 0x76, 0x0b, 0x6a, 0x3a, 0x44, 0x93, 0x26, 0xa8, 0xe4, 0x1a, 0x8c, 0x32, 0x8d, - 0xca, 0x11, 0x9a, 0xaa, 0x58, 0xe5, 0xf0, 0xaa, 0x58, 0xc6, 0x12, 0x3d, 0x74, 0x34, 0x4b, 0x34, - 0xa5, 0x46, 0x8d, 0x48, 0x17, 0x19, 0x36, 0x53, 0xa3, 0x16, 0xe8, 0x23, 0x3a, 0xba, 0xca, 0xee, - 0x8a, 0x3f, 0xb5, 0xec, 0x7f, 0x49, 0x76, 0x57, 0x49, 0x5f, 0x94, 0xdd, 0x35, 0x21, 0x11, 0x7a, - 0x8d, 0xdd, 0xf7, 0xe5, 0x53, 0x86, 0xa3, 0xa9, 0x5e, 0x13, 0xf6, 0x7d, 0x27, 0xfb, 0x9c, 0x61, - 0x82, 0x68, 0xfd, 0xda, 0x50, 0xb1, 0x1e, 0x99, 0x5e, 0x4a, 0x1d, 0x41, 0x8f, 0x4c, 0x88, 0x3e, - 0x9b, 0x99, 0x7a, 0x0f, 0x8e, 0xa9, 0x78, 0xa6, 0x06, 0x45, 0x29, 0xdc, 0xb3, 0x57, 0x69, 0x88, - 0xd1, 0x30, 0x9a, 0x44, 0x42, 0x25, 0xa1, 0x0d, 0xfd, 0xd0, 0x30, 0x8c, 0x16, 0xd0, 0xcf, 0xfd, - 0x3d, 0xb5, 0x39, 0xea, 0x83, 0x80, 0x49, 0x89, 0x4a, 0x45, 0x83, 0xd0, 0xef, 0x1b, 0xc3, 0x68, - 0x92, 0x48, 0x49, 0x9d, 0xd8, 0xe8, 0xef, 0x99, 0xc7, 0x0a, 0xdd, 0xae, 0x6f, 0x72, 0xc9, 0x10, - 0xb1, 0x2d, 0x38, 0x9d, 0x9e, 0xba, 0xb4, 0x43, 0x25, 0x72, 0x94, 0x1f, 0x7c, 0xe1, 0xf1, 0xde, - 0xfc, 0x4b, 0xda, 0xa9, 0x4d, 0x3f, 0x9b, 0x66, 0xb8, 0x0f, 0xe6, 0x25, 0xe4, 0xed, 0x42, 0xe8, - 0xfa, 0xad, 0x6d, 0x6d, 0xce, 0xa3, 0xbc, 0xdd, 0x40, 0x68, 0x2e, 0x03, 0x62, 0x8a, 0x6c, 0xfd, - 0x4e, 0xb9, 0xd8, 0x7a, 0x45, 0x77, 0x8f, 0x47, 0xb0, 0x5e, 0x49, 0x8a, 0x83, 0x77, 0x89, 0x7f, - 0xa5, 0x76, 0x89, 0x97, 0x60, 0x74, 0x4d, 0x6c, 0x52, 0x49, 0x66, 0x51, 0xf4, 0xec, 0x8a, 0x25, - 0xc8, 0x56, 0x65, 0xec, 0x03, 0x60, 0x0d, 0x37, 0xe4, 0x7e, 0xbc, 0x18, 0x74, 0x7b, 0x6e, 0x18, - 0x77, 0xf1, 0xb1, 0x47, 0xb9, 0x35, 0xa0, 0x17, 0x4f, 0x0f, 0x4b, 0x9d, 0x96, 0x56, 0xac, 0x6b, - 0x29, 0x79, 0x62, 0x76, 0x09, 0x46, 0x95, 0xc3, 0x52, 0x25, 0x4d, 0xce, 0x9e, 0x77, 0x56, 0x52, - 0x58, 0x62, 0x57, 0x52, 0x39, 0x37, 0xd4, 0xeb, 0x2a, 0x14, 0x95, 0x46, 0x40, 0x5d, 0x18, 0x25, - 0x98, 0xd6, 0x7f, 0x3e, 0x02, 0xb3, 0x83, 0xee, 0x35, 0xd9, 0x5d, 0xb3, 0x6b, 0x5f, 0x3c, 0xe0, - 0x1e, 0xf4, 0xe0, 0x8e, 0xfd, 0xad, 0xe1, 0x27, 0x2b, 0xcf, 0x0d, 0x11, 0x5c, 0xfe, 0xc4, 0x22, - 0xb8, 0x72, 0x34, 0x11, 0xfc, 0x16, 0xc0, 0x1a, 0xef, 0xf6, 0x3a, 0x6e, 0xcc, 0x93, 0xcb, 0x3c, - 0x99, 0xb5, 0x9b, 0xa0, 0x99, 0xf8, 0x9a, 0x14, 0x99, 0xbd, 0x07, 0x13, 0xea, 0x97, 0xe6, 0x14, - 0x24, 0x1f, 0x9b, 0x53, 0xc4, 0xd9, 0x7b, 0x44, 0x9d, 0x40, 0xc8, 0x0e, 0x6d, 0x69, 0x25, 0xa9, - 0x4b, 0xe4, 0xbd, 0x8f, 0xb6, 0x32, 0x4d, 0xd9, 0x61, 0x90, 0x08, 0x4d, 0x44, 0x03, 0x68, 0xa9, - 0x5b, 0x51, 0x13, 0xd1, 0xb9, 0x64, 0x9a, 0x92, 0x25, 0xcb, 0x6f, 0x27, 0xd5, 0xa3, 0x6f, 0x27, - 0xa6, 0xb5, 0x48, 0x3d, 0x27, 0x5c, 0x60, 0x2d, 0xca, 0x84, 0x00, 0x98, 0x44, 0xa2, 0x29, 0x12, - 0x62, 0x9a, 0x06, 0xb1, 0x29, 0x6d, 0x2a, 0xc8, 0x99, 0x07, 0x4d, 0x12, 0x19, 0x91, 0xb7, 0xf3, - 0xfa, 0x9a, 0xbb, 0x45, 0x19, 0xa1, 0x28, 0x22, 0x6f, 0xe7, 0x75, 0x27, 0x76, 0xb7, 0xcc, 0x88, - 0x3c, 0x44, 0xb3, 0xfe, 0x60, 0xac, 0xf8, 0xf2, 0x5c, 0xbb, 0x60, 0x67, 0x56, 0xe6, 0x70, 0x86, - 0xce, 0xb8, 0x74, 0x92, 0xfa, 0xcc, 0x8f, 0x64, 0xc6, 0x06, 0x58, 0x39, 0xe4, 0x06, 0xf8, 0xc9, - 0xaf, 0xa2, 0xd8, 0x2b, 0x30, 0xbc, 0xb6, 0xda, 0x5c, 0xac, 0xd3, 0xcc, 0x96, 0x9a, 0x7f, 0x27, - 0x72, 0x5a, 0xba, 0xd9, 0x51, 0xa2, 0xb0, 0x0d, 0x98, 0xbe, 0xdd, 0x8f, 0xa4, 0x43, 0xd5, 0x35, - 0x8f, 0x77, 0xda, 0xe9, 0x03, 0x80, 0x59, 0x27, 0xe1, 0x26, 0x3e, 0x51, 0x8e, 0x5a, 0xda, 0x4c, - 0xb7, 0x1f, 0x91, 0xb7, 0x96, 0xb3, 0x89, 0x54, 0xdf, 0xdf, 0x9b, 0x1f, 0x91, 0x08, 0x76, 0x96, - 0x21, 0xbb, 0x0f, 0x55, 0xec, 0x91, 0xba, 0xbf, 0x8b, 0x2f, 0x50, 0xec, 0xef, 0xbc, 0xa0, 0x0d, - 0x9b, 0x14, 0x66, 0xf8, 0x8c, 0xb6, 0xec, 0x6d, 0xd7, 0x17, 0x5d, 0x44, 0xbc, 0xe6, 0x2e, 0xc2, - 0xf8, 0x62, 0xe0, 0x4b, 0x67, 0xf5, 0xe5, 0x8f, 0xd3, 0xb8, 0xd0, 0x52, 0x71, 0x5c, 0xe8, 0xdc, - 0x1b, 0x30, 0x95, 0xe0, 0xdf, 0x09, 0xe2, 0xc3, 0x91, 0xa4, 0x55, 0xac, 0xf8, 0x62, 0x1a, 0x49, - 0x67, 0x36, 0x3a, 0xad, 0xe0, 0x34, 0x42, 0x82, 0xc8, 0xa6, 0x92, 0xb9, 0x2f, 0x98, 0xb5, 0x1c, - 0x92, 0xea, 0x47, 0x2b, 0x68, 0x1c, 0x94, 0x64, 0xec, 0xf3, 0x30, 0x56, 0x8f, 0xe3, 0xd0, 0xdb, - 0xe8, 0x27, 0xee, 0xe3, 0xb2, 0x17, 0x14, 0xd0, 0x4e, 0xcb, 0xd9, 0x4d, 0x28, 0x2f, 0x7f, 0x4c, - 0xae, 0xdd, 0x97, 0x0f, 0xd9, 0xb1, 0x5a, 0xbf, 0x2d, 0x8c, 0x3c, 0xde, 0x9b, 0x2f, 0xf3, 0x8f, - 0xed, 0xf2, 0xf2, 0xc7, 0x6c, 0x1d, 0x86, 0xb1, 0x67, 0xc8, 0xae, 0xf3, 0xe6, 0x51, 0xd9, 0x21, - 0xb1, 0xfc, 0x40, 0x3f, 0x88, 0x1d, 0xfe, 0xb1, 0x2d, 0xf9, 0x89, 0x46, 0xae, 0xa8, 0x44, 0x39, - 0x47, 0x6e, 0xe4, 0x8a, 0x2f, 0x1b, 0xe9, 0xf9, 0x76, 0x79, 0xc5, 0xa7, 0x46, 0xae, 0xa8, 0xd4, - 0x76, 0x9f, 0xa4, 0x91, 0x2b, 0x7e, 0xda, 0x48, 0xcf, 0xb7, 0x25, 0xbf, 0xb9, 0xff, 0x50, 0xe9, - 0x1c, 0x1f, 0x61, 0x1e, 0x5d, 0x89, 0xad, 0x9c, 0xa6, 0x5f, 0x3f, 0x6a, 0x35, 0x89, 0x71, 0x97, - 0xf8, 0x64, 0x92, 0xea, 0x12, 0x14, 0xef, 0x24, 0x1f, 0xf5, 0x42, 0x19, 0xbd, 0xa8, 0x6f, 0xa0, - 0x3c, 0x81, 0x9a, 0x81, 0xa2, 0x0a, 0x6a, 0xfd, 0x93, 0x61, 0x38, 0x7b, 0x90, 0x5f, 0x17, 0xdb, - 0x04, 0xb8, 0xeb, 0x6f, 0x04, 0x6e, 0xd8, 0xf6, 0xfc, 0x2d, 0xca, 0x91, 0x75, 0xf5, 0x90, 0x4e, - 0x61, 0x17, 0x53, 0x4a, 0x51, 0x28, 0x43, 0xb2, 0x82, 0x04, 0x66, 0x6b, 0x9c, 0xd9, 0xb7, 0xa0, - 0x6a, 0xf3, 0x56, 0xb0, 0xc3, 0xe9, 0x3e, 0x7b, 0xfc, 0xf2, 0x17, 0x0e, 0x5b, 0x8b, 0xa2, 0xc3, - 0x3a, 0x30, 0x29, 0x53, 0x48, 0x10, 0x3b, 0xe1, 0xc9, 0x3e, 0x84, 0x71, 0xf9, 0x84, 0x78, 0x7d, - 0x33, 0xa6, 0x3b, 0xef, 0x83, 0x13, 0xd7, 0x96, 0x84, 0x3a, 0x21, 0x1f, 0x25, 0x77, 0xdc, 0x4d, - 0x23, 0x5f, 0x82, 0x4c, 0x5c, 0xab, 0xb1, 0x9c, 0xfb, 0x5b, 0x65, 0x98, 0x32, 0x3f, 0x98, 0xad, - 0x42, 0x6d, 0xc5, 0xf7, 0x62, 0xcf, 0xed, 0x98, 0x81, 0x89, 0x74, 0xfb, 0xe1, 0xc9, 0x32, 0xa7, - 0x30, 0x6e, 0x2f, 0x47, 0x29, 0x54, 0x54, 0xa1, 0x29, 0x46, 0xb1, 0xf4, 0x2d, 0x93, 0xef, 0x32, - 0xd1, 0x88, 0x3f, 0x2f, 0x1f, 0x21, 0x4b, 0x4b, 0x1d, 0xf9, 0x0e, 0x9b, 0xf9, 0xe6, 0x4c, 0x96, - 0x98, 0xed, 0x00, 0x13, 0xd2, 0x56, 0x96, 0xf0, 0x70, 0x81, 0x6f, 0x06, 0xe1, 0x61, 0x32, 0xd6, - 0xbe, 0x42, 0x9d, 0xf3, 0x1c, 0x0a, 0xf2, 0x90, 0xc8, 0x9d, 0x0d, 0xa4, 0xcf, 0x74, 0x52, 0x41, - 0x0d, 0x73, 0xb7, 0x61, 0x42, 0x1f, 0x35, 0x0c, 0x64, 0xf1, 0xba, 0x9e, 0x0a, 0xb3, 0x97, 0x81, - 0x2c, 0x02, 0x60, 0x4b, 0x38, 0x7b, 0x96, 0x52, 0xbc, 0x97, 0xd3, 0x78, 0x8f, 0x34, 0x95, 0xbb, - 0xf5, 0x9d, 0x12, 0x9c, 0x2c, 0x0e, 0x82, 0x60, 0x1f, 0x65, 0xdc, 0x1a, 0x4b, 0xfb, 0x39, 0x7d, - 0xaa, 0xc8, 0x89, 0x4f, 0xe6, 0xd8, 0x68, 0xfd, 0x83, 0xa1, 0x9c, 0x51, 0xa7, 0x80, 0x23, 0xbb, - 0x5e, 0x38, 0x8e, 0x25, 0xed, 0x18, 0x9e, 0x1f, 0xc7, 0xc2, 0xd1, 0x7b, 0x17, 0xa6, 0x90, 0x71, - 0x3a, 0xb9, 0xb4, 0x9b, 0x7a, 0xd9, 0x64, 0x2d, 0x00, 0x32, 0x83, 0xcb, 0x56, 0x80, 0x21, 0x64, - 0x41, 0xc8, 0xae, 0x24, 0x19, 0xa0, 0x76, 0x5d, 0x21, 0x39, 0x6c, 0xa0, 0x88, 0x4b, 0xd3, 0x02, - 0x16, 0x10, 0xb1, 0x2f, 0xc1, 0xa4, 0x1a, 0x4e, 0xf9, 0xae, 0xa6, 0x16, 0xc4, 0xa9, 0xd6, 0xa2, - 0x7c, 0x5a, 0xd3, 0x36, 0x11, 0x59, 0x57, 0x26, 0xdb, 0x26, 0x20, 0x6f, 0xd7, 0xe3, 0x43, 0x64, - 0x34, 0x7f, 0x99, 0x66, 0xdf, 0x33, 0x98, 0xd1, 0x3c, 0x54, 0xb4, 0x8e, 0x1b, 0x67, 0xa6, 0x5e, - 0x96, 0x37, 0xdb, 0x82, 0x49, 0x04, 0xe1, 0xb2, 0x15, 0x95, 0x8d, 0x1c, 0x58, 0xd9, 0x4b, 0x54, - 0xd9, 0x69, 0x59, 0x99, 0xa4, 0xcc, 0x57, 0x65, 0xf2, 0x65, 0x57, 0x60, 0x1c, 0xfb, 0x89, 0x12, - 0xc1, 0x69, 0x8e, 0x28, 0xb2, 0x57, 0x55, 0x3a, 0x38, 0x1d, 0xcb, 0xfa, 0x89, 0x32, 0x4c, 0x49, - 0x3d, 0x4c, 0x86, 0xcf, 0x3c, 0xb5, 0x71, 0x4e, 0xef, 0x18, 0x71, 0x4e, 0xea, 0xfd, 0x44, 0xfd, - 0xd3, 0x0e, 0x15, 0x95, 0xba, 0x0d, 0x2c, 0x4f, 0xc3, 0x6c, 0x98, 0xd0, 0xa1, 0xfb, 0xc7, 0x24, - 0xbd, 0x91, 0x3e, 0xb5, 0x49, 0x8a, 0x2f, 0x46, 0x99, 0x45, 0xb6, 0xc1, 0xc3, 0xfa, 0xf1, 0x32, - 0x4c, 0x6a, 0x51, 0xa9, 0x4f, 0x6d, 0xc7, 0xbf, 0x6d, 0x74, 0xfc, 0x6c, 0x92, 0x7a, 0x36, 0xf9, - 0xb2, 0x43, 0xf5, 0x7b, 0x1f, 0x66, 0x72, 0x24, 0xd9, 0xe0, 0xde, 0xd2, 0x61, 0x82, 0x7b, 0x5f, - 0xcd, 0xbf, 0x9c, 0x87, 0x3a, 0x40, 0xfa, 0xec, 0x91, 0xfe, 0x54, 0xdf, 0x4f, 0x96, 0xe1, 0x38, - 0xfd, 0xc2, 0xf7, 0x7a, 0xe5, 0xc5, 0xd8, 0x53, 0x3b, 0x16, 0x75, 0x63, 0x2c, 0xe6, 0xcd, 0xb1, - 0xd0, 0x3e, 0x70, 0xf0, 0x90, 0x58, 0xdf, 0x01, 0x98, 0x1d, 0x44, 0x70, 0xe8, 0x4c, 0xf5, 0x69, - 0xee, 0xd6, 0xf2, 0x21, 0x72, 0xb7, 0xae, 0x42, 0x0d, 0xab, 0xa2, 0x74, 0x18, 0xd1, 0x3d, 0x7b, - 0x85, 0x3a, 0x09, 0x55, 0x16, 0xf9, 0xbc, 0x32, 0xe5, 0xd0, 0x88, 0x32, 0x4e, 0x24, 0x39, 0x4a, - 0xf6, 0xef, 0x97, 0x60, 0x0a, 0x81, 0xcb, 0x3b, 0xdc, 0x8f, 0x91, 0xd9, 0x10, 0x25, 0xf5, 0x4c, - 0x62, 0x7c, 0x9a, 0x71, 0xe8, 0xf9, 0x5b, 0x14, 0xe4, 0xb3, 0x41, 0x41, 0x3e, 0xef, 0xca, 0xe0, - 0xa4, 0x8b, 0xad, 0xa0, 0x7b, 0x69, 0x2b, 0x74, 0x77, 0xbc, 0x98, 0x82, 0xd3, 0x2e, 0xa9, 0xac, - 0x91, 0x97, 0xdc, 0x9e, 0x77, 0x09, 0xfb, 0xf4, 0x52, 0x86, 0x15, 0x06, 0x50, 0xc9, 0x86, 0x72, - 0xac, 0x36, 0xeb, 0xeb, 0x62, 0xb6, 0x88, 0xfd, 0x10, 0x9c, 0x92, 0x5e, 0x03, 0x8b, 0x81, 0x1f, - 0x7b, 0x7e, 0x3f, 0xe8, 0x47, 0x0b, 0x6e, 0xeb, 0x41, 0xbf, 0x17, 0x51, 0xca, 0x6a, 0xfc, 0xf2, - 0x56, 0x52, 0xe8, 0x6c, 0xc8, 0x52, 0xe3, 0x31, 0x89, 0x62, 0x06, 0xec, 0x06, 0xcc, 0xc8, 0xa2, - 0x7a, 0x3f, 0x0e, 0x9a, 0x2d, 0xb7, 0x23, 0xb4, 0xe8, 0x51, 0xe4, 0x2a, 0x23, 0x19, 0xfa, 0x71, - 0xe0, 0x44, 0x12, 0xae, 0xbb, 0xbe, 0xe4, 0x88, 0xd8, 0x0a, 0x4c, 0xdb, 0xdc, 0x6d, 0xdf, 0x76, - 0x1f, 0x2d, 0xba, 0x3d, 0xb7, 0xe5, 0xc5, 0xf2, 0xc5, 0xdb, 0x8a, 0xbc, 0xf6, 0x08, 0xb9, 0xdb, - 0x76, 0xba, 0xee, 0x23, 0xa7, 0x45, 0x85, 0xa6, 0xe7, 0xa6, 0x41, 0x97, 0xb0, 0xf2, 0xfc, 0x84, - 0xd5, 0x58, 0x96, 0x95, 0xe7, 0x0f, 0x66, 0x95, 0xd2, 0x29, 0x56, 0x6b, 0x6e, 0xb8, 0xc5, 0x63, - 0x79, 0x16, 0x86, 0xb3, 0xa5, 0xf3, 0x25, 0x8d, 0x55, 0x8c, 0x65, 0x0e, 0x1e, 0x58, 0xb3, 0xac, - 0x34, 0x3a, 0x31, 0xf3, 0xd6, 0x43, 0x2f, 0xe6, 0xfa, 0x17, 0x8e, 0x63, 0xb3, 0xb0, 0xff, 0x31, - 0x21, 0xd5, 0xa0, 0x4f, 0xcc, 0x51, 0xa6, 0xdc, 0xb4, 0x8f, 0x9c, 0xc8, 0x71, 0x2b, 0xfe, 0xca, - 0x1c, 0x65, 0xc2, 0x4d, 0xff, 0xce, 0x49, 0xfc, 0x4e, 0x8d, 0xdb, 0x80, 0x0f, 0xcd, 0x51, 0xb2, - 0x3b, 0xa2, 0xd3, 0x62, 0xee, 0x8b, 0x19, 0x4d, 0xb9, 0x86, 0xa6, 0xb0, 0x69, 0x2f, 0x92, 0xae, - 0x51, 0x0b, 0x55, 0xb1, 0x53, 0x90, 0x79, 0x28, 0x4b, 0xcc, 0x7e, 0x18, 0xa6, 0xef, 0x45, 0xfc, - 0xda, 0x4a, 0xa3, 0xa9, 0x9e, 0x18, 0x43, 0x6f, 0xad, 0xa9, 0xcb, 0x6f, 0x1c, 0x20, 0x74, 0x2e, - 0xea, 0x34, 0x62, 0x43, 0xe4, 0x72, 0xdc, 0xfa, 0x11, 0x77, 0x36, 0xbd, 0x5e, 0x94, 0x3c, 0xaf, - 0xa9, 0x8f, 0x5b, 0xa6, 0x2a, 0xeb, 0x06, 0xcc, 0xe4, 0xd8, 0xb0, 0x29, 0x00, 0x01, 0x74, 0xee, - 0xdd, 0x69, 0x2e, 0xaf, 0xd5, 0x3e, 0xc7, 0x6a, 0x30, 0x81, 0xbf, 0x97, 0xef, 0xd4, 0x17, 0x56, - 0x97, 0x97, 0x6a, 0x25, 0x36, 0x03, 0x93, 0x08, 0x59, 0x5a, 0x69, 0x4a, 0x50, 0xf9, 0xe6, 0x50, - 0x75, 0xb8, 0x36, 0x62, 0xd7, 0xe4, 0xd2, 0x95, 0x29, 0xc0, 0xc5, 0x9e, 0x62, 0xfd, 0xd5, 0x32, - 0x9c, 0x56, 0xdb, 0x0a, 0x8f, 0x1f, 0x06, 0xe1, 0x03, 0xcf, 0xdf, 0x7a, 0xca, 0x77, 0x87, 0x6b, - 0xc6, 0xee, 0xf0, 0x62, 0x66, 0xa7, 0xce, 0x7c, 0xe5, 0x3e, 0x5b, 0xc4, 0x6f, 0x8c, 0xc1, 0x99, - 0x7d, 0xa9, 0xd8, 0x07, 0x62, 0x37, 0xf7, 0xb8, 0x1f, 0xaf, 0xb4, 0x3b, 0x5c, 0xe8, 0xb5, 0x41, - 0x3f, 0xa6, 0xdc, 0x56, 0x2f, 0xa0, 0x33, 0x93, 0x4c, 0x95, 0xe5, 0xb5, 0x3b, 0xdc, 0x89, 0x65, - 0xb1, 0x31, 0xdd, 0xf2, 0xd4, 0x82, 0xe5, 0x2d, 0xce, 0x7b, 0xf5, 0x8e, 0xb7, 0xc3, 0x57, 0xfc, - 0x98, 0x87, 0x3b, 0x98, 0x83, 0x20, 0x61, 0xf9, 0x80, 0xf3, 0x9e, 0xe3, 0x8a, 0x52, 0xc7, 0xa3, - 0x62, 0x93, 0x65, 0x8e, 0x9a, 0x5d, 0xd3, 0x58, 0xe2, 0xd1, 0xe0, 0xb6, 0xfb, 0x88, 0x22, 0x7e, - 0xe9, 0x85, 0xe1, 0x84, 0xa5, 0xf4, 0xe6, 0xe8, 0xba, 0x8f, 0xec, 0x3c, 0x09, 0xfb, 0x26, 0x9c, - 0xa0, 0x0d, 0x88, 0x5e, 0x37, 0x51, 0x5f, 0x2c, 0xdf, 0x4e, 0x79, 0x59, 0x9c, 0xe6, 0x54, 0x0a, - 0x28, 0xf5, 0x62, 0x51, 0xd1, 0x57, 0x17, 0x73, 0x61, 0x6b, 0x62, 0x43, 0xce, 0x74, 0xc7, 0x6d, - 0x1e, 0x45, 0x2a, 0x13, 0x2c, 0xd9, 0x5e, 0xf4, 0xce, 0x74, 0xba, 0xb2, 0xdc, 0x1e, 0x48, 0xc9, - 0x6e, 0xc0, 0xd4, 0x3a, 0xdf, 0xd0, 0xc7, 0x67, 0x24, 0x11, 0x55, 0xb5, 0x87, 0x7c, 0x63, 0xf0, - 0xe0, 0x64, 0xe8, 0x98, 0x87, 0x0e, 0x97, 0x8f, 0x76, 0x57, 0xc5, 0x69, 0xdb, 0xe7, 0x21, 0x1e, - 0x9a, 0x47, 0x51, 0x18, 0xcc, 0xa6, 0x1a, 0xb2, 0x59, 0x4e, 0xf7, 0x5b, 0x98, 0x7c, 0xb1, 0x43, - 0x70, 0x4c, 0x9a, 0x96, 0xf1, 0xc5, 0x34, 0xa9, 0xd8, 0x87, 0x30, 0x6d, 0x07, 0xfd, 0xd8, 0xf3, - 0xb7, 0x9a, 0xe2, 0x58, 0xca, 0xb7, 0xe4, 0x86, 0x94, 0x3e, 0xc0, 0x96, 0x29, 0xa5, 0x08, 0x03, - 0x09, 0x74, 0x22, 0x82, 0x1a, 0x3b, 0x82, 0x49, 0xc0, 0xbe, 0x05, 0x53, 0xf2, 0x3d, 0x88, 0xa4, - 0x82, 0x31, 0xb2, 0x2d, 0x53, 0x0c, 0xb9, 0x51, 0x78, 0xff, 0x0d, 0x0a, 0xd0, 0x44, 0x68, 0x51, - 0x05, 0x19, 0x6e, 0xec, 0xeb, 0xd4, 0x59, 0x0d, 0xcf, 0xdf, 0x4a, 0xa6, 0x31, 0x60, 0xcf, 0xbf, - 0x96, 0x76, 0x49, 0x4f, 0x34, 0x57, 0x4d, 0xe3, 0x01, 0xd1, 0xe6, 0x79, 0x3e, 0x2c, 0x86, 0x33, - 0xf5, 0x28, 0xf2, 0xa2, 0x98, 0x32, 0xc0, 0x2d, 0x3f, 0xe2, 0xad, 0xbe, 0x40, 0x5e, 0x0f, 0xc2, - 0x07, 0x3c, 0x94, 0x59, 0x5c, 0x86, 0x17, 0x2e, 0x3e, 0xde, 0x9b, 0x7f, 0xc5, 0x45, 0x44, 0x87, - 0x92, 0xc6, 0x39, 0x5c, 0xa1, 0x3a, 0x0f, 0x25, 0xae, 0xf6, 0x0d, 0xfb, 0x33, 0x65, 0xdf, 0x82, - 0x93, 0x8b, 0x6e, 0xc4, 0x57, 0xfc, 0x88, 0xfb, 0x91, 0x17, 0x7b, 0x3b, 0x9c, 0x3a, 0x15, 0x37, - 0xbf, 0xaa, 0xcc, 0x0a, 0xd0, 0x72, 0x23, 0xb1, 0x30, 0x13, 0x14, 0x87, 0x06, 0x45, 0xcf, 0x0a, - 0x50, 0xcc, 0x85, 0xd9, 0x30, 0xd5, 0x6c, 0xde, 0x58, 0xf2, 0xdc, 0x64, 0x5d, 0x4d, 0x62, 0x7f, - 0xbd, 0x82, 0x0e, 0x30, 0xd1, 0xb6, 0xd3, 0xf6, 0xdc, 0x64, 0x41, 0x0d, 0xe8, 0xac, 0x0c, 0x07, - 0x6b, 0xaf, 0x04, 0xb5, 0xec, 0x50, 0xb2, 0xaf, 0xc2, 0x98, 0x0c, 0xd5, 0xe6, 0xd1, 0x36, 0x19, - 0x6d, 0x54, 0xe4, 0x6f, 0x02, 0x37, 0x89, 0x28, 0x87, 0xb0, 0x0c, 0x04, 0xe7, 0x7a, 0xd0, 0xd4, - 0x8d, 0xcf, 0xd9, 0x29, 0x33, 0xd6, 0x86, 0x09, 0x39, 0x5a, 0x1c, 0x1f, 0x8f, 0xcc, 0xe4, 0xc5, - 0xd3, 0x8b, 0x32, 0xfc, 0x65, 0xa8, 0xa0, 0x9c, 0x13, 0x12, 0xc1, 0xa8, 0xc2, 0xe0, 0xba, 0x00, - 0x50, 0x55, 0x84, 0xd6, 0x69, 0x38, 0x35, 0xa0, 0xcd, 0xd6, 0xaf, 0x95, 0xf0, 0x62, 0x7c, 0x40, - 0x95, 0xec, 0xab, 0x70, 0x1c, 0x29, 0x17, 0x03, 0xdf, 0xe7, 0xad, 0x18, 0xe5, 0x91, 0xf2, 0xc4, - 0xab, 0xc8, 0x88, 0x27, 0xf9, 0xc1, 0xad, 0x04, 0xc1, 0xc9, 0x3a, 0xe4, 0x15, 0x72, 0x60, 0xef, - 0xc3, 0xb3, 0x6d, 0x2f, 0x22, 0x32, 0x27, 0xde, 0x0e, 0x79, 0xb4, 0x1d, 0x74, 0xda, 0x4e, 0xc4, - 0x5b, 0x81, 0xdf, 0x96, 0xa9, 0x5e, 0x26, 0xed, 0xb9, 0x14, 0x67, 0x4d, 0xa1, 0x34, 0x25, 0x86, - 0xf5, 0xaf, 0xcb, 0x30, 0x4b, 0x42, 0xd2, 0xe6, 0xad, 0x00, 0xad, 0x9e, 0x4f, 0xf9, 0xa6, 0xbc, - 0x6c, 0x6c, 0xca, 0x2f, 0x24, 0x2f, 0x8e, 0x14, 0x7d, 0xe4, 0x3e, 0x79, 0x5a, 0xee, 0x65, 0xf2, - 0xb4, 0x1c, 0xc0, 0x08, 0x51, 0x17, 0x66, 0x95, 0xee, 0x37, 0x28, 0x63, 0x8b, 0xb5, 0x02, 0xd5, - 0x5b, 0x7c, 0x17, 0x93, 0x16, 0x88, 0xfe, 0x8d, 0xd3, 0xc3, 0x5f, 0x55, 0x25, 0xe6, 0xb2, 0xf1, - 0x5f, 0x36, 0x0f, 0xc3, 0x98, 0x02, 0x41, 0x4f, 0x71, 0x8a, 0x00, 0x5b, 0xfe, 0x67, 0xfd, 0xef, - 0x25, 0x38, 0x7d, 0xdb, 0xf5, 0xfb, 0x6e, 0xe7, 0x16, 0xdf, 0xbd, 0xed, 0xfa, 0xee, 0x16, 0xbe, - 0x2f, 0x47, 0xae, 0xa8, 0x97, 0x60, 0x94, 0x1e, 0xae, 0x46, 0xfe, 0x55, 0xe9, 0xb8, 0x90, 0x7f, - 0xcb, 0x5a, 0x61, 0xb1, 0x5b, 0x30, 0xae, 0xe5, 0x9c, 0xa2, 0x4c, 0x1f, 0xaa, 0xe7, 0x55, 0x9b, - 0x29, 0x5f, 0x4b, 0x71, 0x6e, 0x2a, 0x37, 0xcd, 0x4d, 0x75, 0x07, 0x26, 0x94, 0x95, 0x0d, 0xb9, - 0x55, 0x8a, 0xb9, 0xcd, 0xa5, 0xb6, 0xf9, 0x5c, 0x0e, 0xaa, 0x71, 0x82, 0x63, 0xe6, 0xa9, 0xdf, - 0x2a, 0xc1, 0xd9, 0x6c, 0xcf, 0xa7, 0xe9, 0xe3, 0x3e, 0xe9, 0x27, 0xef, 0xc2, 0x89, 0x2e, 0x76, - 0x20, 0xe6, 0xb6, 0xeb, 0x26, 0x5d, 0x48, 0xe2, 0x44, 0xe5, 0x78, 0x18, 0xd8, 0xc9, 0xd2, 0x09, - 0xa9, 0x90, 0x85, 0xee, 0x84, 0xd4, 0xcd, 0xd3, 0x5b, 0x3f, 0x57, 0x86, 0x67, 0xf7, 0x9b, 0x93, - 0x89, 0xb1, 0xbc, 0x54, 0x64, 0x2c, 0x67, 0x3d, 0x38, 0x86, 0x12, 0x67, 0x71, 0x9b, 0xb7, 0x1e, - 0x60, 0xda, 0xdf, 0x5b, 0x72, 0xd0, 0x8a, 0xc3, 0x5b, 0x5e, 0x2d, 0x0c, 0x6f, 0x39, 0x29, 0xe5, - 0x60, 0x0b, 0x79, 0x48, 0x6b, 0xa6, 0x18, 0x03, 0xbb, 0x88, 0x35, 0xe3, 0x00, 0xe9, 0x5b, 0xf3, - 0x74, 0xbb, 0xf0, 0xf2, 0x80, 0x35, 0x91, 0x1d, 0x19, 0x7c, 0xb4, 0xaa, 0x24, 0x63, 0xec, 0x54, - 0x89, 0x3e, 0x71, 0x52, 0xa8, 0xd5, 0x1f, 0xd8, 0x2d, 0xb8, 0x7e, 0xd8, 0x3d, 0x98, 0xd6, 0x9e, - 0xbc, 0xc7, 0xb9, 0x65, 0xda, 0x0e, 0xb3, 0x79, 0x05, 0xa5, 0x1f, 0x4c, 0x86, 0xc6, 0x9e, 0xe2, - 0x3a, 0x0e, 0x3e, 0x2a, 0x51, 0xab, 0xf7, 0xe3, 0xed, 0x46, 0xc8, 0x29, 0x79, 0xf4, 0x0f, 0x58, - 0x7e, 0x2a, 0xf3, 0xe3, 0x0e, 0x65, 0x41, 0xfc, 0xcd, 0x69, 0x38, 0x5e, 0x44, 0x26, 0xfa, 0x65, - 0xad, 0x50, 0x6e, 0xa1, 0xa9, 0xea, 0xbb, 0x25, 0x98, 0x90, 0xdb, 0xcb, 0x35, 0x0c, 0xec, 0xa4, - 0xde, 0x71, 0xa5, 0xd2, 0x2e, 0xe0, 0xce, 0x66, 0x26, 0xe2, 0xf3, 0xfb, 0x7b, 0xf3, 0xef, 0x1f, - 0xce, 0x56, 0xd4, 0x0a, 0xf0, 0xc2, 0x22, 0x16, 0x73, 0x2e, 0xad, 0x42, 0xb6, 0xc6, 0x36, 0xaa, - 0x65, 0x0b, 0x30, 0x49, 0xfb, 0x65, 0xa0, 0x3f, 0xf7, 0x2b, 0x1f, 0xed, 0x52, 0x05, 0x39, 0x9f, - 0x18, 0x83, 0x84, 0x5d, 0x81, 0xca, 0xbd, 0xcb, 0xd7, 0x68, 0x14, 0x54, 0xa2, 0xf1, 0x7b, 0x97, - 0xaf, 0xa1, 0x41, 0x5a, 0x4c, 0xe8, 0xc9, 0xfe, 0x65, 0x23, 0x6e, 0xf1, 0xde, 0xe5, 0x6b, 0xec, - 0x47, 0xe0, 0xc4, 0x52, 0xb2, 0xe3, 0xca, 0x5c, 0x69, 0x6d, 0xcc, 0xc9, 0x3a, 0x32, 0x60, 0x75, - 0x7e, 0xb1, 0x70, 0x75, 0x3e, 0xaf, 0x6d, 0xed, 0x32, 0x11, 0x5b, 0x3b, 0xfb, 0xac, 0x71, 0x71, - 0x3d, 0xec, 0x23, 0x98, 0x42, 0xff, 0x08, 0x4c, 0x1f, 0x27, 0x46, 0x90, 0x72, 0x0a, 0xe5, 0x6b, - 0x7e, 0xbd, 0xb0, 0xe6, 0x39, 0xe9, 0x68, 0x81, 0x49, 0xe8, 0x1c, 0xb7, 0x6f, 0x44, 0x3b, 0x66, - 0x38, 0xb3, 0x9b, 0x30, 0x4d, 0x07, 0xa0, 0xbb, 0x9b, 0x6b, 0xdb, 0x7c, 0xc9, 0xdd, 0x25, 0xdf, - 0x23, 0xb4, 0xa9, 0xd0, 0xa9, 0xc9, 0x09, 0x36, 0x9d, 0x78, 0x9b, 0x3b, 0x6d, 0xd7, 0x38, 0x2a, - 0x64, 0x08, 0xd9, 0xb7, 0x61, 0x7c, 0x35, 0x68, 0x89, 0xb3, 0x2f, 0x4a, 0x3e, 0xe9, 0x7e, 0xf4, - 0x35, 0x21, 0xa8, 0x3a, 0x12, 0x9c, 0x39, 0xd0, 0x7c, 0x7f, 0x6f, 0xfe, 0x9d, 0xa3, 0x4e, 0x1b, - 0xad, 0x02, 0x5b, 0xaf, 0x8d, 0x2d, 0x42, 0x75, 0x9d, 0x6f, 0x88, 0xaf, 0xf5, 0x29, 0x7d, 0x90, - 0x5a, 0x75, 0x0a, 0x4c, 0xa1, 0xd1, 0xf4, 0xcb, 0x08, 0x8d, 0x26, 0x18, 0x0b, 0x61, 0x06, 0xfb, - 0xa7, 0xe1, 0x46, 0xd1, 0xc3, 0x20, 0x6c, 0x77, 0x78, 0x14, 0x0d, 0x0c, 0x70, 0xbc, 0x5c, 0xd8, - 0xf9, 0xcf, 0xca, 0xce, 0xef, 0x69, 0x1c, 0x8c, 0x44, 0x63, 0x59, 0xf6, 0xec, 0x43, 0x98, 0xa2, - 0x37, 0x09, 0x6e, 0x5f, 0xab, 0xe3, 0x4a, 0x98, 0x30, 0x52, 0xba, 0x9a, 0x85, 0xea, 0x92, 0x19, - 0x61, 0x49, 0x1e, 0xe5, 0xee, 0xa6, 0x6b, 0x3a, 0xa7, 0xea, 0x24, 0xac, 0x01, 0xe3, 0x4b, 0x7c, - 0xc7, 0x6b, 0x71, 0xcc, 0x05, 0x49, 0x49, 0x86, 0x54, 0xde, 0x4e, 0xad, 0x44, 0x6e, 0xe7, 0x6d, - 0x04, 0xc8, 0xcc, 0x92, 0x66, 0xd2, 0x84, 0x04, 0x91, 0x5d, 0x85, 0xca, 0xca, 0x52, 0x83, 0x72, - 0x0c, 0xcd, 0x24, 0x2f, 0x7f, 0x34, 0xe8, 0x11, 0x6c, 0x79, 0xc9, 0xe6, 0xb5, 0x8d, 0x0c, 0x45, - 0x2b, 0x4b, 0x0d, 0xb6, 0x09, 0x93, 0xd2, 0xaf, 0x8a, 0xbb, 0xb2, 0x6f, 0xa7, 0x07, 0xf4, 0xed, - 0xc5, 0xc2, 0xbe, 0x9d, 0x25, 0x7f, 0x2d, 0xa2, 0xd6, 0xd7, 0xbd, 0xc1, 0x56, 0x1c, 0x2a, 0x97, - 0xf8, 0xa6, 0xdb, 0xef, 0x28, 0x43, 0xf8, 0xda, 0xda, 0x2a, 0x86, 0x3c, 0xd2, 0xa1, 0xb2, 0x2d, - 0x0b, 0x93, 0xfe, 0x1b, 0x9c, 0xc2, 0x2c, 0xcf, 0x87, 0xbd, 0x0d, 0x43, 0x77, 0x1f, 0xc4, 0x2e, - 0x65, 0x13, 0x52, 0xfd, 0x28, 0x40, 0xea, 0xf3, 0x65, 0xac, 0xd2, 0x03, 0xe3, 0x39, 0x41, 0xa4, - 0x11, 0x43, 0x71, 0xc3, 0x0d, 0xdb, 0x0f, 0xdd, 0x10, 0xf3, 0x1c, 0x1f, 0x33, 0x58, 0x68, 0x25, - 0x72, 0x28, 0xb6, 0x09, 0x90, 0xf1, 0x4a, 0xd0, 0x59, 0xb0, 0x1f, 0x82, 0xd3, 0x91, 0xb7, 0xe5, - 0xe3, 0x5b, 0x4c, 0x8e, 0xdb, 0xd9, 0x0a, 0x42, 0x2f, 0xde, 0xee, 0x3a, 0x51, 0xdf, 0x8b, 0x39, - 0xa6, 0xf5, 0x99, 0x4a, 0xce, 0x6c, 0x4d, 0x85, 0x57, 0x57, 0x68, 0x4d, 0x81, 0x65, 0x9f, 0x8a, - 0x8a, 0x0b, 0xd8, 0xd7, 0x61, 0x52, 0x17, 0xc9, 0xd1, 0xec, 0x89, 0xb3, 0x95, 0xf3, 0x53, 0xc9, - 0x56, 0x9d, 0x15, 0xe1, 0xea, 0x91, 0x73, 0x6d, 0x8f, 0x88, 0xcc, 0x47, 0xce, 0x35, 0x5e, 0xcc, - 0x86, 0x53, 0x91, 0xb4, 0x2f, 0xf6, 0x7d, 0xef, 0x11, 0xbe, 0x85, 0x40, 0xa1, 0xb1, 0x98, 0xde, - 0x27, 0xdd, 0xfa, 0x9a, 0x88, 0x75, 0xef, 0xce, 0xca, 0x57, 0xef, 0x45, 0x3c, 0xa4, 0x08, 0xd9, - 0xe3, 0x92, 0xf6, 0x9e, 0xef, 0x3d, 0x4a, 0xa1, 0xec, 0x3b, 0x25, 0x38, 0x89, 0x33, 0x61, 0x71, - 0x75, 0x45, 0x88, 0xb7, 0xfb, 0x9e, 0xbb, 0x10, 0x06, 0x0f, 0x23, 0x1e, 0xce, 0xce, 0x0e, 0x98, - 0x69, 0x6f, 0x17, 0xce, 0xb4, 0x17, 0xe9, 0x29, 0xa2, 0x8e, 0x87, 0x02, 0xd4, 0xd9, 0xf1, 0x5c, - 0x67, 0x43, 0x72, 0x32, 0xd2, 0xf6, 0x15, 0xd6, 0x25, 0x6d, 0xa8, 0x37, 0x87, 0xaa, 0xac, 0x76, - 0xec, 0xe6, 0x50, 0xf5, 0x54, 0x6d, 0xd6, 0x9e, 0xa1, 0x25, 0x49, 0xd3, 0xe8, 0xf6, 0xb5, 0xba, - 0x3d, 0xda, 0x58, 0xb9, 0xdf, 0xec, 0x04, 0xb1, 0x3d, 0x87, 0x5c, 0x88, 0x56, 0x70, 0xe2, 0x7e, - 0xec, 0xc9, 0x68, 0x17, 0x6b, 0x1b, 0x8e, 0x17, 0x7d, 0x3e, 0x9b, 0xcd, 0xa8, 0xca, 0xa9, 0x4e, - 0xfc, 0x0c, 0x8c, 0x6d, 0x7a, 0x61, 0x14, 0x3b, 0x7d, 0x4f, 0x2a, 0x36, 0xc3, 0x76, 0x15, 0x01, - 0xf7, 0xbc, 0x36, 0x3b, 0x0d, 0x55, 0xbc, 0x41, 0xef, 0x53, 0x5e, 0xff, 0x61, 0x7b, 0x54, 0xfc, - 0xbe, 0xe7, 0xb5, 0xad, 0xff, 0xac, 0x84, 0x7b, 0x25, 0x7b, 0x05, 0x5f, 0xe7, 0x4a, 0x1c, 0x89, - 0xf1, 0xa2, 0xca, 0xed, 0xf5, 0x32, 0xef, 0x06, 0x22, 0x0a, 0x7b, 0x15, 0x46, 0xae, 0xb9, 0x2d, - 0x9e, 0xf8, 0x68, 0x23, 0xf2, 0x26, 0x42, 0xf4, 0xa3, 0x93, 0xc4, 0x11, 0xe7, 0x68, 0x29, 0x43, - 0xea, 0x71, 0xcc, 0x23, 0x29, 0xe8, 0x17, 0xeb, 0xca, 0x2f, 0x1b, 0xcf, 0xd1, 0x24, 0x7b, 0xdc, - 0x14, 0x21, 0x93, 0x05, 0xa6, 0x90, 0x83, 0xf5, 0x6f, 0x4a, 0xa9, 0xf0, 0x67, 0x2f, 0xc3, 0x90, - 0xdd, 0x48, 0xda, 0x2f, 0x73, 0x19, 0x67, 0x9a, 0x8f, 0x08, 0xec, 0xeb, 0x70, 0x42, 0xe3, 0x93, - 0x4b, 0x49, 0xf3, 0x12, 0xa6, 0xda, 0xd5, 0x5a, 0x52, 0x9c, 0x97, 0xa6, 0x98, 0x07, 0x1a, 0x0d, - 0xd2, 0x82, 0x25, 0xee, 0x7b, 0x92, 0xb7, 0xf6, 0xb1, 0x3a, 0xef, 0x36, 0x22, 0x64, 0x3f, 0xb6, - 0x88, 0x83, 0xcc, 0xb4, 0x6b, 0xfd, 0x7a, 0xc9, 0x10, 0xea, 0xec, 0x9c, 0x71, 0xdc, 0x40, 0x01, - 0x94, 0xb1, 0x1e, 0xca, 0x83, 0xc7, 0x5b, 0x00, 0xf5, 0x7e, 0x1c, 0x2c, 0xfb, 0x61, 0xd0, 0xe9, - 0x90, 0x4f, 0xac, 0x3c, 0x13, 0xf6, 0xe3, 0xc0, 0xe1, 0x08, 0x36, 0x72, 0x78, 0x26, 0xc8, 0x85, - 0xd9, 0x7b, 0x2a, 0x9f, 0x34, 0x7b, 0x8f, 0x38, 0x40, 0x19, 0x72, 0xec, 0x75, 0x50, 0x0b, 0x42, - 0x8f, 0x0b, 0xee, 0x79, 0x3b, 0x4e, 0xd4, 0x09, 0x8c, 0x57, 0x3c, 0x08, 0x8d, 0xfd, 0xc5, 0x12, - 0x9c, 0x94, 0x69, 0x70, 0xee, 0xf4, 0xbb, 0x1b, 0x3c, 0xbc, 0xef, 0x76, 0xbc, 0x76, 0xfa, 0x60, - 0x63, 0x1a, 0x78, 0xae, 0x55, 0x53, 0x8c, 0x2f, 0x2d, 0x72, 0x32, 0x2d, 0x8f, 0xe3, 0x63, 0xa1, - 0xb3, 0x93, 0x94, 0xea, 0x0b, 0xbe, 0x98, 0x9e, 0xad, 0xc0, 0x78, 0xc3, 0xf3, 0x17, 0xdd, 0xd6, - 0x36, 0x4f, 0x93, 0x64, 0xbe, 0x2c, 0xb3, 0x5a, 0x89, 0x29, 0xdc, 0xda, 0xe6, 0xfb, 0xec, 0x31, - 0x3a, 0xad, 0xf5, 0xab, 0x25, 0x78, 0xfe, 0xc0, 0x06, 0x8b, 0xa3, 0xf2, 0xf2, 0xa1, 0x8e, 0xca, - 0x84, 0xc5, 0xbe, 0x01, 0x27, 0x74, 0x56, 0x6b, 0xa1, 0xeb, 0xe9, 0x41, 0xa0, 0x05, 0x1d, 0x10, - 0x0b, 0x94, 0xac, 0x7e, 0x5d, 0xcc, 0xc4, 0xfa, 0xbf, 0x4b, 0x30, 0x96, 0x84, 0x6d, 0x3e, 0xa5, - 0xe7, 0xae, 0xab, 0xc6, 0xb9, 0x4b, 0xbd, 0x18, 0x9c, 0x7c, 0x95, 0x74, 0x6c, 0x2c, 0xb8, 0xfc, - 0x99, 0xd6, 0x92, 0x96, 0x20, 0xe0, 0x2f, 0x94, 0x61, 0x5c, 0x88, 0x6a, 0xe9, 0xfc, 0xf2, 0x83, - 0xf5, 0xfe, 0x67, 0xf2, 0x5d, 0x83, 0x1f, 0x3b, 0xd4, 0x4e, 0xa0, 0xff, 0x73, 0x09, 0x2f, 0x45, - 0x75, 0x0a, 0xd1, 0x1b, 0x02, 0xa4, 0xf7, 0x86, 0xd8, 0xfa, 0x6d, 0x84, 0xca, 0x47, 0xde, 0x56, - 0xa9, 0x27, 0xe8, 0x91, 0xb7, 0x8e, 0x2d, 0x60, 0xec, 0x7d, 0x18, 0xbe, 0x87, 0x57, 0x3c, 0xe6, - 0x8b, 0x07, 0x09, 0x7f, 0x2c, 0x94, 0xf2, 0xbe, 0x1f, 0x99, 0x2f, 0x00, 0x4a, 0x42, 0xd6, 0x84, - 0xd1, 0xc5, 0x90, 0xbb, 0x31, 0x6f, 0x53, 0x87, 0x1c, 0x2a, 0x89, 0x76, 0x4b, 0x92, 0x64, 0x93, - 0x68, 0x13, 0x27, 0x21, 0xc7, 0x58, 0xfa, 0x8d, 0xe8, 0x13, 0x18, 0x3d, 0xb5, 0x83, 0xfe, 0x9e, - 0x31, 0xe8, 0x67, 0x72, 0x83, 0x2e, 0x3f, 0xef, 0x50, 0x63, 0xff, 0x9b, 0x25, 0x38, 0x59, 0x4c, - 0xc8, 0x5e, 0x80, 0x91, 0xbb, 0x6b, 0x8d, 0xd4, 0x0f, 0x17, 0x3f, 0x25, 0xe8, 0xa1, 0xf5, 0xca, - 0xa6, 0x22, 0xf6, 0x1a, 0x8c, 0x7c, 0x60, 0x2f, 0xa6, 0xee, 0xa6, 0x28, 0xe0, 0x3e, 0x16, 0x2a, - 0xa2, 0xb1, 0xab, 0x11, 0x92, 0x3e, 0xb6, 0x95, 0x27, 0x36, 0xb6, 0x3f, 0x59, 0x86, 0xe9, 0x7a, - 0xab, 0xc5, 0xa3, 0x48, 0xa8, 0x6f, 0x3c, 0x8a, 0x9f, 0xda, 0x81, 0x2d, 0x7e, 0xa3, 0xc2, 0xf8, - 0xb6, 0x43, 0x8d, 0xea, 0x3f, 0x2e, 0xc9, 0x97, 0x7e, 0x04, 0xd5, 0x8e, 0xc7, 0x1f, 0x26, 0x17, - 0x12, 0xc9, 0x83, 0x96, 0xa5, 0x03, 0x1e, 0xb4, 0x14, 0x3a, 0xa3, 0xd7, 0x89, 0xd5, 0x7b, 0x5a, - 0xa4, 0x33, 0x22, 0xc4, 0xd0, 0x19, 0x11, 0x22, 0xf6, 0xb9, 0x7a, 0xaf, 0x17, 0x06, 0x3b, 0x72, - 0xd9, 0xd3, 0x1b, 0x9f, 0xae, 0x04, 0x19, 0x69, 0xc3, 0x25, 0x48, 0x34, 0x63, 0x89, 0xfb, 0xbb, - 0xba, 0xf3, 0x6b, 0x9b, 0xfb, 0xfa, 0xe9, 0x09, 0xcb, 0xad, 0x26, 0xb0, 0x46, 0x18, 0x74, 0x83, - 0x98, 0xb7, 0xe5, 0xf7, 0x60, 0xb6, 0xf5, 0x03, 0x1f, 0x2d, 0x5b, 0xf3, 0xe2, 0x8e, 0xf1, 0x68, - 0x59, 0x2c, 0x00, 0xb6, 0x84, 0x5b, 0x3f, 0x5d, 0x81, 0x33, 0x46, 0x9f, 0x2e, 0x85, 0xbb, 0x76, - 0xdf, 0x5f, 0xf6, 0x43, 0xaf, 0xb5, 0x8d, 0xb9, 0x9d, 0xee, 0x00, 0xd8, 0xdc, 0x8d, 0x02, 0x5f, - 0x53, 0xd6, 0x2e, 0x0a, 0xf5, 0x2b, 0x44, 0x68, 0xde, 0x40, 0x32, 0x43, 0x9c, 0x52, 0x2a, 0x5b, - 0xe3, 0xc0, 0xea, 0x30, 0x29, 0x7f, 0x89, 0x8f, 0xe9, 0x25, 0x8a, 0xf8, 0x33, 0x32, 0xd3, 0x12, - 0xb2, 0xec, 0x61, 0x89, 0x99, 0x00, 0x52, 0xa3, 0x60, 0xdf, 0x86, 0x09, 0xb1, 0x4e, 0x97, 0xbc, - 0xa8, 0xd7, 0x71, 0x13, 0x53, 0xff, 0xd5, 0xa2, 0x29, 0x92, 0xfd, 0x9c, 0x8b, 0x3a, 0xa1, 0x7c, - 0x13, 0x73, 0x9e, 0xa6, 0xcf, 0x29, 0x3c, 0xcf, 0xb5, 0xa9, 0x4c, 0xf7, 0xd6, 0xd6, 0x69, 0xe6, - 0x3e, 0x84, 0x99, 0x1c, 0x8f, 0x82, 0xd7, 0xf9, 0xde, 0x34, 0x5f, 0xe7, 0x9b, 0x2f, 0x6a, 0x9c, - 0xc6, 0x47, 0x7f, 0xa7, 0xef, 0x47, 0x4b, 0x30, 0x3b, 0x08, 0x4f, 0x4c, 0xaf, 0x46, 0xe8, 0x75, - 0xdd, 0x50, 0x89, 0x22, 0x9c, 0x5e, 0x3d, 0x09, 0x32, 0x74, 0x4e, 0x09, 0x62, 0x6f, 0xc2, 0x98, - 0x3c, 0xc5, 0xba, 0x49, 0xca, 0x3f, 0x99, 0x3c, 0x59, 0x01, 0x8d, 0xe4, 0xc9, 0x0a, 0x68, 0xfd, - 0x5f, 0x23, 0x30, 0xa1, 0x2f, 0x1b, 0x66, 0xc1, 0x88, 0x7c, 0x96, 0x44, 0x8f, 0x65, 0x73, 0x11, - 0x62, 0x53, 0x49, 0xfa, 0x2a, 0x4b, 0xf9, 0xc0, 0x57, 0x59, 0xd6, 0x61, 0xb2, 0x11, 0x06, 0xf8, - 0x4a, 0x2d, 0xba, 0xfe, 0xd0, 0x1e, 0x79, 0x4c, 0x33, 0x21, 0x89, 0x15, 0x8e, 0xce, 0x45, 0x78, - 0x48, 0xef, 0x11, 0xb6, 0x23, 0x8e, 0x17, 0x86, 0x01, 0xd5, 0xe0, 0x23, 0xfd, 0x16, 0xc5, 0x6c, - 0xd1, 0x9f, 0x91, 0x97, 0x13, 0xcb, 0xf4, 0x5b, 0x14, 0x10, 0x5d, 0x08, 0x0f, 0x3f, 0x29, 0x21, - 0xcc, 0x7e, 0xae, 0x04, 0xe3, 0x75, 0xdf, 0xa7, 0xd7, 0x5e, 0x0e, 0xc8, 0xe2, 0xfe, 0x0d, 0x72, - 0x5d, 0x7c, 0xe7, 0x13, 0xb9, 0x2e, 0xa2, 0x42, 0x1b, 0xe1, 0x69, 0x28, 0xad, 0xd0, 0xc8, 0x6d, - 0x9c, 0x82, 0xd9, 0x3b, 0x50, 0x4b, 0xa4, 0xdf, 0x8a, 0xdf, 0xe6, 0x8f, 0x78, 0x84, 0x91, 0x71, - 0x93, 0xf4, 0x8a, 0xbf, 0x7e, 0xfa, 0xc9, 0x22, 0xb2, 0x35, 0x00, 0x37, 0x11, 0x3b, 0xe4, 0x51, - 0x71, 0x3a, 0xbd, 0xfd, 0xce, 0xc8, 0x25, 0x75, 0x6b, 0x27, 0x7e, 0xa3, 0x77, 0x88, 0x79, 0x6b, - 0x97, 0x88, 0xaf, 0x2e, 0x4c, 0xd7, 0xa3, 0xa8, 0xdf, 0xe5, 0xcd, 0xd8, 0x0d, 0x63, 0xd1, 0xd5, - 0x64, 0x0a, 0x3d, 0x94, 0x23, 0xbf, 0x8b, 0xa4, 0x62, 0x46, 0x84, 0x31, 0x7a, 0x0f, 0x64, 0x1d, - 0xf9, 0x33, 0xbc, 0xd9, 0xbb, 0x30, 0xde, 0xec, 0x6f, 0x74, 0xbd, 0x38, 0xe6, 0xed, 0x85, 0x5d, - 0x0a, 0xf5, 0x54, 0x49, 0x52, 0x24, 0xd8, 0xd9, 0x30, 0x0c, 0x57, 0x1a, 0x3a, 0x7b, 0x1f, 0xce, - 0xa4, 0x68, 0x81, 0xef, 0x6c, 0xf0, 0x6d, 0xb7, 0xb3, 0xe9, 0x04, 0x9b, 0x8e, 0x5c, 0x07, 0xd2, - 0x59, 0xc2, 0x3e, 0x9d, 0x20, 0xdd, 0xf5, 0x17, 0x10, 0xe5, 0xee, 0xa6, 0x5c, 0x20, 0x37, 0x87, - 0xaa, 0xd5, 0xda, 0x98, 0x7d, 0x2a, 0xdf, 0x5f, 0x52, 0x1a, 0xff, 0x64, 0x09, 0x4e, 0xea, 0x8b, - 0x0e, 0x2b, 0x47, 0xf3, 0x0b, 0xbb, 0x08, 0x63, 0xb4, 0x26, 0x12, 0x43, 0x01, 0x7a, 0x51, 0x9b, - 0x71, 0xce, 0x09, 0x0a, 0x5b, 0x16, 0xcb, 0x40, 0xf0, 0x20, 0x01, 0x74, 0x2c, 0x23, 0x80, 0x44, - 0x11, 0x5e, 0x1e, 0x7f, 0x4e, 0xae, 0x0f, 0xf1, 0xdb, 0x5c, 0x1f, 0x02, 0x62, 0x7d, 0x05, 0x66, - 0xcc, 0x99, 0xd0, 0xe4, 0x31, 0xbb, 0x00, 0xa3, 0x6a, 0xfa, 0x94, 0x8a, 0xa7, 0x8f, 0x2a, 0xb7, - 0xd6, 0x81, 0xe5, 0xe8, 0x23, 0xf4, 0x71, 0xe6, 0xb1, 0xba, 0x47, 0x53, 0x1e, 0x46, 0x39, 0xc4, - 0x85, 0x63, 0xd4, 0xbe, 0x71, 0x23, 0x0d, 0x12, 0xbe, 0xb4, 0xfc, 0x13, 0xc7, 0xe0, 0x58, 0x81, - 0x32, 0x70, 0x80, 0xb2, 0x3e, 0x6f, 0x0a, 0xa8, 0xb1, 0xe4, 0x11, 0x0a, 0x25, 0x96, 0xbe, 0x02, - 0xc3, 0x07, 0x8a, 0x23, 0x99, 0x5e, 0x2b, 0x23, 0x85, 0x24, 0xd9, 0x67, 0xa2, 0xb0, 0xeb, 0x4f, - 0xe9, 0x0c, 0x3f, 0xb1, 0xa7, 0x74, 0x30, 0x13, 0xb3, 0xb6, 0x51, 0x9b, 0xd9, 0xa1, 0xb1, 0xc0, - 0xc9, 0x89, 0x4d, 0x93, 0x44, 0xf2, 0x88, 0x82, 0xce, 0x0e, 0x27, 0x1e, 0xa3, 0x3a, 0x0f, 0x2c, - 0x28, 0xe4, 0xa1, 0x91, 0xb0, 0x5f, 0x2e, 0x01, 0x23, 0x88, 0x2e, 0x33, 0xab, 0xfb, 0xc9, 0xcc, - 0xf6, 0x93, 0x91, 0x99, 0x67, 0x54, 0x1b, 0x8b, 0x65, 0x67, 0x41, 0xb3, 0xd8, 0xdf, 0x2c, 0xc1, - 0x8c, 0x7c, 0xac, 0x44, 0x6f, 0xec, 0xbe, 0x0f, 0x50, 0xb4, 0x9e, 0x4c, 0x63, 0x9f, 0x8d, 0xb0, - 0xda, 0x01, 0x6d, 0xcd, 0x37, 0x8a, 0xfd, 0x10, 0x40, 0xb2, 0xa2, 0xe4, 0x6b, 0xd3, 0xe3, 0x97, - 0x9f, 0x2d, 0x90, 0x02, 0x09, 0x12, 0xde, 0x99, 0x8b, 0xa9, 0x73, 0x3c, 0xf1, 0xeb, 0x31, 0x9c, - 0x2d, 0x52, 0x6e, 0xec, 0x47, 0xe4, 0x63, 0xa1, 0x09, 0x84, 0x5e, 0x9f, 0x9a, 0x1d, 0xc7, 0x5a, - 0xbe, 0x30, 0x58, 0x59, 0xbf, 0x58, 0x44, 0x26, 0xf5, 0xb0, 0x24, 0xe2, 0x26, 0x8c, 0xbb, 0xd9, - 0x07, 0x43, 0xb3, 0x14, 0xf8, 0xa8, 0x1b, 0xb6, 0x3e, 0x9a, 0x9d, 0xc0, 0x3a, 0x0b, 0xe5, 0xdb, - 0x69, 0xb5, 0x16, 0xa4, 0x7c, 0xcb, 0x64, 0xcf, 0x40, 0x10, 0xfb, 0x00, 0x58, 0xf2, 0xca, 0x87, - 0x84, 0xf1, 0x50, 0x3e, 0x5d, 0x4d, 0xe1, 0x91, 0xe9, 0x6b, 0x21, 0xa1, 0x2a, 0x36, 0xf2, 0x8c, - 0xe5, 0x88, 0x19, 0x87, 0xe3, 0xf4, 0xd1, 0x02, 0x2a, 0xf3, 0x63, 0xad, 0x2c, 0x45, 0xb3, 0x53, - 0xc6, 0xdb, 0x5e, 0x69, 0xc9, 0xc2, 0x73, 0xd4, 0xce, 0x93, 0x49, 0xa2, 0x2d, 0x33, 0x07, 0x55, - 0x21, 0x3b, 0x76, 0x15, 0xc6, 0x30, 0x8d, 0xea, 0x0d, 0xe5, 0xb9, 0x4d, 0x5e, 0xa4, 0x98, 0x70, - 0xd5, 0xd9, 0x36, 0xfd, 0xaf, 0x53, 0x54, 0x71, 0x4e, 0x95, 0x6a, 0x31, 0xde, 0x2f, 0x91, 0x21, - 0xae, 0x1d, 0xee, 0x3a, 0x61, 0xdf, 0x4c, 0xd1, 0x8b, 0x48, 0xec, 0x43, 0x18, 0xbf, 0xed, 0x3e, - 0x52, 0xa6, 0x3f, 0xba, 0x43, 0x3a, 0x28, 0x04, 0x16, 0xbf, 0xa6, 0xeb, 0x3e, 0x72, 0xda, 0xfd, - 0xec, 0x4b, 0x07, 0x32, 0x04, 0x56, 0x63, 0xc9, 0xbe, 0x09, 0xa0, 0x5d, 0x7a, 0xb1, 0x03, 0x2b, - 0x78, 0x5e, 0xbd, 0x58, 0x57, 0x78, 0x19, 0x86, 0xfc, 0x35, 0x86, 0x19, 0xcd, 0xe5, 0xf8, 0x67, - 0xa7, 0xb9, 0x9c, 0xf8, 0x0c, 0x35, 0x97, 0x0f, 0xd3, 0xfc, 0x6c, 0x28, 0xc1, 0x77, 0xe9, 0xca, - 0x69, 0xbf, 0xda, 0x94, 0x0f, 0x4c, 0x0d, 0xb7, 0x82, 0xdd, 0x4c, 0x15, 0x19, 0x7e, 0x2c, 0x84, - 0x5a, 0xf6, 0xb4, 0x44, 0x0f, 0x4f, 0xbc, 0x78, 0x98, 0x93, 0x95, 0x34, 0xa9, 0xd3, 0x34, 0x72, - 0x78, 0x02, 0xd7, 0x95, 0xca, 0xdc, 0xe1, 0xf2, 0x3e, 0x8c, 0x13, 0x3b, 0x34, 0x40, 0xcc, 0x1a, - 0x1e, 0xc7, 0x46, 0x75, 0xa2, 0x9c, 0x9c, 0xb7, 0x68, 0x73, 0xca, 0x98, 0x27, 0x74, 0x46, 0xac, - 0x0b, 0xb5, 0xd5, 0xc0, 0xdf, 0x5a, 0xe3, 0x61, 0x17, 0x13, 0xd3, 0x09, 0xd9, 0x74, 0xda, 0x38, - 0x88, 0xa9, 0x62, 0x23, 0x7f, 0x9d, 0xe7, 0x6f, 0xc9, 0xcf, 0xe8, 0x04, 0xfe, 0x96, 0x13, 0xf3, - 0xb0, 0x2b, 0x33, 0xde, 0x99, 0x4e, 0xae, 0x39, 0xd6, 0xec, 0xcf, 0xc0, 0x5c, 0x6e, 0x85, 0xca, - 0xd6, 0x8b, 0x65, 0x3f, 0x67, 0x78, 0x0b, 0x65, 0xcb, 0x31, 0x52, 0xf4, 0x73, 0x6a, 0x37, 0xc2, - 0xc5, 0x4f, 0x93, 0xcf, 0x94, 0x01, 0xfb, 0xd4, 0x30, 0xb7, 0x01, 0xa7, 0x07, 0xca, 0xd5, 0x82, - 0xb3, 0xe9, 0x25, 0xf3, 0x6c, 0x7a, 0x7a, 0x90, 0xfe, 0x15, 0x69, 0xa7, 0xd2, 0x9b, 0x43, 0xd5, - 0x63, 0xb5, 0xe3, 0x83, 0x55, 0xd7, 0xdf, 0x29, 0x67, 0xf4, 0x31, 0x32, 0x87, 0x9c, 0x85, 0xf2, - 0x3e, 0x0a, 0x6b, 0x79, 0x65, 0x89, 0x9d, 0x23, 0x8d, 0xad, 0x9c, 0x9a, 0x61, 0xfa, 0xe6, 0xa5, - 0xa5, 0xd4, 0xdd, 0x3e, 0xad, 0x6a, 0xf6, 0x2e, 0x4c, 0x35, 0xb9, 0x1b, 0xb6, 0xb6, 0x6f, 0xf1, - 0xdd, 0x87, 0x41, 0xd8, 0x56, 0xb9, 0x92, 0xe4, 0x7b, 0x77, 0x58, 0x62, 0xe6, 0xa3, 0xd4, 0x71, - 0xd9, 0x92, 0x4a, 0xb1, 0x3a, 0x8c, 0xb5, 0x9f, 0x2e, 0xdc, 0xe2, 0x04, 0xc2, 0x7e, 0xd9, 0x57, - 0xc5, 0x61, 0x5c, 0x0d, 0xa3, 0xca, 0x47, 0x4f, 0xa1, 0xdb, 0x04, 0x2c, 0x50, 0xe6, 0x79, 0x68, - 0x7d, 0xaf, 0x02, 0x4c, 0xd6, 0xb4, 0xe8, 0xf6, 0x5c, 0x4c, 0x6a, 0xec, 0xe1, 0x03, 0x51, 0x35, - 0xc2, 0x71, 0x37, 0x3a, 0x5c, 0x7f, 0x5d, 0x8d, 0xc2, 0xa8, 0x92, 0x32, 0x27, 0x7b, 0x0a, 0xcf, - 0x11, 0x0e, 0xd8, 0x07, 0xcb, 0x9f, 0x66, 0x1f, 0xfc, 0x10, 0x9e, 0xa9, 0xf7, 0x7a, 0x1d, 0xaf, - 0x95, 0xd4, 0x72, 0x2d, 0x08, 0xd5, 0xec, 0x35, 0x72, 0x20, 0xba, 0x09, 0x5a, 0xae, 0xa5, 0xfb, - 0xb1, 0xd0, 0x94, 0x58, 0x69, 0x1b, 0xd2, 0xd3, 0xaf, 0x2b, 0x39, 0x51, 0x64, 0x4d, 0xd2, 0x48, - 0x14, 0x0f, 0x2f, 0x54, 0x4a, 0xec, 0x70, 0xfa, 0x02, 0xb8, 0xf2, 0x5a, 0x29, 0x56, 0x84, 0x13, - 0x12, 0x71, 0xae, 0xac, 0xf7, 0xe3, 0x80, 0x18, 0x53, 0xfc, 0x5f, 0x1a, 0xa9, 0x47, 0x4d, 0x31, - 0xce, 0xe5, 0x29, 0xba, 0xf5, 0xdb, 0x43, 0x70, 0x3a, 0x3f, 0xbc, 0x54, 0x9a, 0xac, 0x8f, 0xd2, - 0x01, 0xeb, 0xa3, 0x68, 0x36, 0xc8, 0xdb, 0xd2, 0x27, 0x36, 0x1b, 0x64, 0x42, 0xe5, 0x4f, 0x38, - 0x1b, 0x9a, 0x42, 0xd6, 0xa7, 0xca, 0xd0, 0xd0, 0x27, 0x55, 0x86, 0x74, 0x2e, 0xec, 0x02, 0x0c, - 0xcb, 0xac, 0xf3, 0xc3, 0xe9, 0xdd, 0x79, 0x36, 0xe1, 0xbc, 0xc4, 0x60, 0x7f, 0x06, 0xce, 0x4a, - 0x99, 0x94, 0xfd, 0xd8, 0x85, 0x5d, 0xc5, 0x91, 0x06, 0xee, 0xf2, 0xe3, 0xbd, 0xf9, 0x8b, 0xd2, - 0xc0, 0xeb, 0xe4, 0xba, 0xcd, 0xd9, 0xd8, 0x75, 0x54, 0xcb, 0xb4, 0x4a, 0x0e, 0xe4, 0xcd, 0x7c, - 0x38, 0x56, 0x20, 0xe1, 0x29, 0x35, 0xd1, 0xa7, 0xdd, 0x1d, 0x66, 0x42, 0x93, 0xb0, 0x1d, 0x59, - 0x3f, 0x0c, 0xb3, 0xf2, 0xbd, 0xd7, 0x02, 0xc9, 0x71, 0xc0, 0xca, 0x2c, 0x7d, 0xea, 0x95, 0x29, - 0xf6, 0x83, 0xf9, 0x41, 0xd5, 0x1f, 0x75, 0x66, 0xdf, 0x84, 0x49, 0x29, 0x8d, 0xeb, 0x91, 0x7e, - 0x7a, 0x47, 0x97, 0x04, 0xf9, 0xb4, 0xad, 0x23, 0xe5, 0xb7, 0xe3, 0x46, 0xb9, 0x56, 0x9a, 0xa4, - 0xd9, 0x59, 0x58, 0x79, 0x22, 0xb3, 0x70, 0xc0, 0xd0, 0x0e, 0x7d, 0x56, 0x43, 0xfb, 0x08, 0x4e, - 0x2b, 0x6d, 0x27, 0xc9, 0x3c, 0x9d, 0xcc, 0xb3, 0x73, 0x80, 0xd7, 0x3b, 0x7a, 0xaf, 0x66, 0x54, - 0x25, 0x2c, 0x67, 0x57, 0xa0, 0x5a, 0x6f, 0xac, 0xa0, 0x0e, 0xa3, 0xdb, 0x85, 0xdd, 0x9e, 0x27, - 0x95, 0x1e, 0x23, 0x3f, 0x18, 0x21, 0x5a, 0x3f, 0x56, 0x86, 0x89, 0x46, 0xa7, 0xbf, 0xe5, 0xf9, - 0x4b, 0x6e, 0xec, 0x3e, 0xb5, 0x97, 0x4b, 0x6f, 0x19, 0x97, 0x4b, 0x49, 0x12, 0x88, 0xe4, 0xc3, - 0x0e, 0x75, 0xb3, 0xf4, 0xb3, 0x25, 0x98, 0x4e, 0x49, 0xa4, 0xb2, 0x75, 0x03, 0x86, 0xc4, 0x0f, - 0xb2, 0x6c, 0x9d, 0xcd, 0x31, 0x46, 0xac, 0x8b, 0xc9, 0x5f, 0x74, 0xdd, 0xe3, 0x9a, 0x7e, 0x77, - 0xa2, 0x78, 0xee, 0x8b, 0x30, 0x96, 0xb2, 0xcd, 0xeb, 0x70, 0xc7, 0x75, 0x1d, 0x6e, 0x4c, 0xbf, - 0x3e, 0xf8, 0x3b, 0x25, 0xa8, 0x65, 0xbf, 0x84, 0xdd, 0x82, 0x51, 0xc1, 0xc9, 0xe3, 0x51, 0x36, - 0xa3, 0x63, 0x06, 0xf3, 0x22, 0xa1, 0xc9, 0xe6, 0x61, 0xe7, 0x73, 0x09, 0xb1, 0x15, 0x87, 0x39, - 0x1b, 0x5f, 0xa5, 0x49, 0xb0, 0x0a, 0x5a, 0xf7, 0xaa, 0xa9, 0x61, 0x9e, 0x2c, 0xee, 0x07, 0xbd, - 0xd5, 0xff, 0x9e, 0xd1, 0x6a, 0x52, 0x1e, 0xcf, 0x19, 0x93, 0xab, 0x70, 0x2a, 0xe3, 0xa4, 0xb9, - 0x0c, 0xd5, 0x44, 0x84, 0x97, 0xf5, 0x87, 0x5b, 0x72, 0x62, 0x3a, 0xc1, 0x63, 0xaf, 0xc2, 0x88, - 0xac, 0x4f, 0xcf, 0x54, 0xdd, 0x43, 0x88, 0x7e, 0x08, 0x96, 0x38, 0xd6, 0x2f, 0x56, 0xe0, 0x64, - 0xda, 0xbc, 0x7b, 0xbd, 0xb6, 0x1b, 0xf3, 0x86, 0x1b, 0xba, 0xdd, 0xe8, 0x80, 0x15, 0x70, 0x3e, - 0xd7, 0x34, 0x4a, 0x35, 0x25, 0x61, 0x5a, 0x83, 0xac, 0x4c, 0x83, 0xf0, 0x82, 0x45, 0x36, 0x48, - 0x35, 0x83, 0xdd, 0x82, 0x4a, 0x93, 0xc7, 0x24, 0x58, 0xce, 0xe5, 0x7a, 0x55, 0x6f, 0xd7, 0xc5, - 0x26, 0x8f, 0xe5, 0x20, 0xca, 0x27, 0x71, 0x8c, 0xa4, 0x4e, 0x82, 0x0b, 0x5b, 0x87, 0x91, 0xe5, - 0x47, 0x3d, 0xde, 0x8a, 0xf1, 0x11, 0x7d, 0x2d, 0xbb, 0x51, 0x31, 0x3f, 0x89, 0x2b, 0x59, 0x1e, - 0xa7, 0x53, 0x25, 0x6f, 0x19, 0x69, 0xbd, 0x25, 0xca, 0xdc, 0x55, 0xa8, 0xaa, 0xca, 0x8f, 0x32, - 0x73, 0xe7, 0xde, 0x82, 0x71, 0xad, 0x92, 0x23, 0x4d, 0xfa, 0x5f, 0x2a, 0x01, 0x08, 0x01, 0x4f, - 0x13, 0x67, 0x39, 0xa7, 0xeb, 0x97, 0xd2, 0x6c, 0xe6, 0xb4, 0x57, 0x3c, 0xa0, 0xa2, 0x7d, 0x94, - 0xfe, 0x15, 0x98, 0x6e, 0x3e, 0xf0, 0x7a, 0xe9, 0xeb, 0xc4, 0x86, 0x46, 0x15, 0x3d, 0xf0, 0x7a, - 0x0e, 0x59, 0xe5, 0xb2, 0xbb, 0x4e, 0x96, 0xce, 0xfa, 0x7f, 0x4a, 0x30, 0x22, 0xfe, 0xba, 0x7f, - 0xf5, 0x29, 0x15, 0x99, 0x57, 0x0c, 0x91, 0x99, 0xec, 0x98, 0x41, 0x47, 0xc6, 0x68, 0x5c, 0x3d, - 0x40, 0x58, 0xee, 0xd1, 0x00, 0x49, 0x64, 0x76, 0x1d, 0x46, 0xc9, 0xf5, 0x98, 0xa2, 0x34, 0x99, - 0xc6, 0x50, 0x39, 0x25, 0x27, 0xe6, 0xbb, 0xa0, 0x97, 0xb5, 0x77, 0x2a, 0x6a, 0x71, 0x2e, 0x53, - 0xef, 0x44, 0x4b, 0x1b, 0x6c, 0xca, 0x26, 0xcd, 0x76, 0xb7, 0x70, 0x8a, 0x38, 0x0d, 0xca, 0x15, - 0x5d, 0xa7, 0x3b, 0xf8, 0xca, 0x7e, 0x4c, 0x4e, 0x12, 0x93, 0xe2, 0xeb, 0xf9, 0x2e, 0x9c, 0x6c, - 0x36, 0x6f, 0x60, 0x98, 0x42, 0x23, 0x08, 0xe3, 0x6b, 0x41, 0xf8, 0x90, 0x72, 0xd4, 0x35, 0x4d, - 0xcf, 0xb7, 0x22, 0x97, 0xde, 0x97, 0x0b, 0x5d, 0x7a, 0xf7, 0xf1, 0x8e, 0xb3, 0x7c, 0x38, 0xd5, - 0x6c, 0xde, 0x90, 0x1a, 0xd6, 0x1f, 0x46, 0x7d, 0x7f, 0xa7, 0x04, 0x33, 0xcd, 0xe6, 0x8d, 0x4c, - 0x55, 0xab, 0x42, 0xfd, 0x6e, 0xb9, 0x1d, 0xaa, 0x48, 0xb9, 0xdf, 0x14, 0x77, 0x04, 0x8e, 0x42, - 0x49, 0x6a, 0xe8, 0x2d, 0xe3, 0x35, 0x41, 0xc9, 0x84, 0x35, 0x60, 0x44, 0x7e, 0x10, 0x0d, 0xe6, - 0x73, 0x29, 0xbb, 0xa2, 0x0f, 0x4d, 0x63, 0x1f, 0xa5, 0x12, 0x68, 0x5e, 0x5f, 0x09, 0x88, 0xf5, - 0x5b, 0xa7, 0x60, 0x5c, 0x9b, 0x51, 0xec, 0xcb, 0x30, 0x41, 0xf4, 0x18, 0xdd, 0x4a, 0x9e, 0x88, - 0xa7, 0x85, 0x80, 0xdc, 0x94, 0x70, 0xf9, 0x24, 0xf4, 0xf7, 0xf7, 0xe6, 0x87, 0x44, 0xd7, 0xd8, - 0x06, 0x3a, 0xbb, 0x0b, 0x93, 0xb7, 0xdd, 0x47, 0x9a, 0xad, 0x52, 0x26, 0x2f, 0xb8, 0x20, 0xa4, - 0x4a, 0xd7, 0x7d, 0x74, 0x08, 0xe7, 0x7c, 0x93, 0x9e, 0x3d, 0x80, 0x29, 0xf3, 0x9b, 0x68, 0x06, - 0xe6, 0x47, 0xec, 0x8d, 0xc2, 0x11, 0x3b, 0xdd, 0x0b, 0xc2, 0xd8, 0xd9, 0x4c, 0xc8, 0xd3, 0x5a, - 0x67, 0x4b, 0x76, 0x86, 0x35, 0xfb, 0x32, 0xcc, 0x68, 0x6f, 0x36, 0x5e, 0x0b, 0xc2, 0xae, 0xab, - 0x0e, 0xcc, 0x78, 0x81, 0x87, 0xce, 0xb0, 0x9b, 0x08, 0xb6, 0xf3, 0x98, 0xec, 0xeb, 0x45, 0x09, - 0x21, 0x86, 0xd3, 0x08, 0x85, 0x82, 0x84, 0x10, 0x83, 0x22, 0x14, 0xf2, 0xa9, 0x21, 0xb6, 0xf6, - 0x8b, 0x60, 0xaa, 0xca, 0xaf, 0x3f, 0x54, 0x84, 0x52, 0x32, 0x72, 0x03, 0x22, 0x95, 0x2e, 0x43, - 0x65, 0xa1, 0x71, 0x0d, 0x4f, 0x5d, 0xca, 0x0b, 0xd8, 0xdf, 0x76, 0xfd, 0x16, 0x1e, 0x64, 0x29, - 0xfc, 0x4f, 0xdf, 0x28, 0x17, 0x1a, 0xd7, 0x98, 0x0b, 0xc7, 0x1a, 0x3c, 0xec, 0x7a, 0xf1, 0x57, - 0xdf, 0x78, 0x43, 0x1b, 0xaa, 0x2a, 0x36, 0xed, 0x12, 0x35, 0x6d, 0xbe, 0x87, 0x28, 0xce, 0xa3, - 0x37, 0xde, 0x28, 0x1c, 0x90, 0xa4, 0x61, 0x45, 0xbc, 0xc4, 0x86, 0x75, 0xdb, 0x7d, 0x94, 0x46, - 0x5b, 0x47, 0x94, 0x5a, 0xe7, 0x8c, 0x9a, 0x5a, 0x69, 0xa4, 0xb6, 0xb1, 0x61, 0x99, 0x44, 0xec, - 0x5d, 0xb4, 0xd5, 0xab, 0x54, 0x4a, 0x94, 0x94, 0x60, 0x4e, 0xd9, 0xe2, 0x55, 0xfe, 0x25, 0xfd, - 0x18, 0xa3, 0xa1, 0xb3, 0x7b, 0x89, 0x35, 0x45, 0x1e, 0x35, 0xe8, 0x7e, 0xfc, 0x92, 0x6e, 0x4d, - 0x91, 0x47, 0x14, 0xe3, 0xb3, 0xa6, 0x13, 0x13, 0x9c, 0x0c, 0x3f, 0xb7, 0x4d, 0x2e, 0x79, 0x23, - 0xcd, 0xc4, 0xd1, 0x8d, 0x34, 0x1c, 0x86, 0x56, 0x83, 0xd6, 0x03, 0x7a, 0x97, 0xec, 0x03, 0x21, - 0x85, 0x3b, 0x41, 0xeb, 0xc1, 0x93, 0x8b, 0xcc, 0x42, 0xf6, 0xec, 0x8e, 0xcc, 0x48, 0x18, 0xb6, - 0xa9, 0x4f, 0x28, 0xda, 0xe7, 0x78, 0x72, 0x84, 0xd3, 0xca, 0xd2, 0x3c, 0x85, 0x61, 0x5b, 0x75, - 0xad, 0x6d, 0x92, 0x33, 0x0e, 0xb5, 0x25, 0x1e, 0x3d, 0x88, 0x83, 0xde, 0x62, 0xc7, 0xeb, 0x61, - 0x92, 0x4f, 0x7a, 0x3f, 0xfc, 0xd0, 0x32, 0xb9, 0x2d, 0xe9, 0x9d, 0x96, 0x62, 0x60, 0xe7, 0x58, - 0xb2, 0xaf, 0xc3, 0x94, 0x98, 0xdc, 0xcb, 0x8f, 0x62, 0xee, 0xcb, 0x91, 0x9f, 0x41, 0x8d, 0x4e, - 0xb5, 0xdb, 0x28, 0x94, 0x73, 0x0a, 0x17, 0x3b, 0x4f, 0x08, 0x8c, 0x37, 0xdd, 0x0c, 0x56, 0xac, - 0x0d, 0xb3, 0xb7, 0xdd, 0x47, 0xe9, 0x41, 0x53, 0x9f, 0xa4, 0x0c, 0x27, 0xd8, 0xf9, 0xc7, 0x7b, - 0xf3, 0x2f, 0x8a, 0x09, 0x96, 0x3e, 0x69, 0x3f, 0x60, 0xbe, 0x0e, 0xe4, 0xc4, 0xbe, 0x0d, 0xa7, - 0xe8, 0xb3, 0x96, 0xbc, 0x10, 0xc3, 0x21, 0x77, 0x9b, 0xdb, 0x2e, 0x66, 0x5a, 0x38, 0x36, 0xa0, - 0xc3, 0x2e, 0x15, 0x8b, 0x44, 0xd5, 0x61, 0x6d, 0xc5, 0xc7, 0x89, 0x24, 0x23, 0x7b, 0x50, 0x0d, - 0xec, 0x23, 0x98, 0x92, 0x77, 0xed, 0x37, 0x02, 0xe9, 0x6d, 0x45, 0xb7, 0x44, 0x87, 0x0e, 0x5f, - 0x94, 0x17, 0xf8, 0x32, 0xa0, 0x39, 0x63, 0xc2, 0xc8, 0x70, 0x66, 0xef, 0xa0, 0xe3, 0xbd, 0x7c, - 0x75, 0x71, 0xa5, 0x81, 0x77, 0x46, 0xb4, 0x03, 0xf5, 0x3c, 0xdf, 0x51, 0xb6, 0x86, 0x5e, 0x22, - 0x2e, 0x74, 0x6c, 0xb6, 0x0e, 0xe3, 0xcd, 0xe6, 0x8d, 0x6b, 0x9e, 0xd0, 0x4b, 0x7a, 0xea, 0x0a, - 0x28, 0xdf, 0xca, 0x17, 0x0a, 0x5b, 0x39, 0x19, 0x45, 0xdb, 0xce, 0xa6, 0xd7, 0xe1, 0x4e, 0x2b, - 0xe8, 0xed, 0xda, 0x3a, 0xa7, 0x82, 0x90, 0xbe, 0x53, 0x4f, 0x38, 0xa4, 0x6f, 0x05, 0xa6, 0xb5, - 0xe8, 0x0f, 0x74, 0x26, 0x9c, 0x4d, 0xdf, 0x5f, 0xd1, 0x43, 0xf8, 0xb2, 0x49, 0x64, 0xb2, 0x74, - 0x2a, 0x96, 0xef, 0xf4, 0x51, 0x63, 0xf9, 0x3c, 0x98, 0x91, 0x83, 0x41, 0xf3, 0x00, 0x47, 0x7a, - 0x6e, 0x40, 0x1f, 0x5e, 0x28, 0xec, 0xc3, 0x63, 0x34, 0xd2, 0x6a, 0x92, 0xa1, 0x6f, 0x49, 0x9e, - 0x2b, 0xdb, 0x04, 0x46, 0x40, 0x37, 0x76, 0x37, 0xdc, 0x88, 0x63, 0x5d, 0xcf, 0x0c, 0xa8, 0xeb, - 0xc5, 0xc2, 0xba, 0xa6, 0x54, 0x5d, 0x1b, 0xb2, 0x9a, 0x02, 0x8e, 0xcc, 0x57, 0xf5, 0xa8, 0xf9, - 0x85, 0x1d, 0xfb, 0xac, 0x71, 0x47, 0x91, 0x47, 0x90, 0x59, 0x04, 0xb2, 0x93, 0x36, 0xdb, 0xef, - 0x05, 0x9c, 0xd9, 0x23, 0x38, 0x99, 0x6f, 0x05, 0xd6, 0x79, 0x06, 0xeb, 0x3c, 0x63, 0xd4, 0x99, - 0x45, 0x92, 0xf3, 0xc6, 0xfc, 0xac, 0x6c, 0xad, 0x03, 0xf8, 0xb3, 0xef, 0x94, 0xe0, 0xd4, 0xed, - 0x6b, 0xf5, 0xfb, 0x3c, 0x94, 0x6a, 0x09, 0x26, 0xe8, 0xa6, 0xe4, 0x3b, 0xcf, 0xd1, 0x3d, 0x56, - 0xf6, 0x2a, 0x54, 0x69, 0x1c, 0x28, 0x2a, 0x84, 0xea, 0xfe, 0x42, 0x77, 0xd3, 0x75, 0x76, 0x34, - 0x16, 0x05, 0x19, 0x7a, 0x7e, 0xe1, 0x77, 0xe7, 0x4b, 0xf6, 0xa0, 0xaa, 0x58, 0x07, 0xe6, 0xcc, - 0x6e, 0x51, 0xd1, 0x96, 0xdb, 0xbc, 0xd3, 0x99, 0x9d, 0xc7, 0x19, 0xfd, 0xea, 0xe3, 0xbd, 0xf9, - 0xf3, 0xb9, 0xde, 0x4d, 0x22, 0x38, 0x05, 0xa6, 0x7e, 0xb1, 0x37, 0x98, 0x1f, 0xeb, 0x16, 0x28, - 0xdd, 0xb3, 0x67, 0x8d, 0x2c, 0x9d, 0xb9, 0xf2, 0x24, 0xf5, 0xec, 0x19, 0xb1, 0xde, 0x07, 0x2a, - 0x88, 0x76, 0x81, 0x3a, 0xff, 0xe7, 0x4b, 0x30, 0xbb, 0xce, 0x37, 0xd6, 0x84, 0x82, 0xe2, 0xbb, - 0x9d, 0x64, 0x83, 0xc1, 0x01, 0x7e, 0x1e, 0x07, 0x78, 0x3e, 0x0d, 0x51, 0x2e, 0x44, 0x93, 0x8f, - 0xc6, 0x3c, 0xe4, 0x1b, 0x78, 0x7d, 0x2a, 0x8a, 0xd3, 0x9d, 0x2b, 0x3b, 0xd4, 0x03, 0xeb, 0xba, - 0x39, 0x54, 0x9d, 0xac, 0x4d, 0x15, 0x84, 0x20, 0x5a, 0xbf, 0x51, 0xce, 0xec, 0xd0, 0x6c, 0x05, - 0x46, 0x69, 0xe1, 0x0d, 0x3c, 0xed, 0x9c, 0x29, 0x5c, 0x5e, 0xa3, 0xb4, 0x86, 0x6d, 0x45, 0xcf, - 0x1e, 0x0a, 0x56, 0xd8, 0xfb, 0x74, 0xf4, 0xfe, 0xa6, 0xdc, 0x80, 0x11, 0x64, 0xa8, 0x1a, 0x4b, - 0x47, 0xcf, 0x1d, 0x60, 0x66, 0x98, 0x40, 0x9d, 0x43, 0xd5, 0xc6, 0x1e, 0x40, 0xa5, 0xd9, 0xbc, - 0x41, 0xa7, 0xf7, 0xaf, 0x91, 0xa8, 0xfe, 0x0c, 0x2a, 0x14, 0xb5, 0x58, 0xbf, 0x5e, 0x82, 0x49, - 0x63, 0x8b, 0x67, 0x57, 0xb5, 0xec, 0x0a, 0xe9, 0xf5, 0xbb, 0x81, 0x83, 0x52, 0x3f, 0x9b, 0x77, - 0xe1, 0xaa, 0x96, 0x5d, 0x7b, 0x00, 0x1d, 0x4e, 0x89, 0x6c, 0x2a, 0x11, 0xe5, 0xb3, 0x5e, 0x19, - 0xe4, 0xb3, 0x2e, 0x33, 0x25, 0x0e, 0x15, 0xbf, 0x8e, 0x60, 0xfd, 0x4f, 0x2f, 0xc0, 0x94, 0x79, - 0x34, 0x67, 0xaf, 0xc2, 0x08, 0x5e, 0xf2, 0x28, 0x3b, 0x0f, 0xda, 0xa7, 0xf0, 0x1e, 0xc8, 0x08, - 0xeb, 0x94, 0x38, 0xec, 0x25, 0x80, 0x24, 0xfe, 0x49, 0x5d, 0x26, 0x0c, 0x3f, 0xde, 0x9b, 0x2f, - 0xbd, 0x66, 0x6b, 0x05, 0xec, 0x5b, 0x00, 0x77, 0x82, 0x36, 0x47, 0x5f, 0xae, 0x28, 0x31, 0x0d, - 0x14, 0xfa, 0x78, 0xbd, 0x4c, 0x3e, 0x5e, 0x23, 0x12, 0xf9, 0xf1, 0xde, 0xfc, 0x09, 0x3f, 0x68, - 0x73, 0x07, 0xd3, 0xe6, 0x18, 0x4e, 0x54, 0x29, 0x47, 0xf6, 0x36, 0x0c, 0xdb, 0xfd, 0x0e, 0x57, - 0xf7, 0x04, 0xe3, 0x6a, 0xab, 0xed, 0x77, 0x78, 0x6a, 0xb0, 0x08, 0xfb, 0x59, 0xf7, 0x69, 0x01, - 0x60, 0xef, 0x01, 0x08, 0x3d, 0x8a, 0x9e, 0xea, 0x1b, 0x4e, 0x2f, 0x7d, 0x35, 0x15, 0x2c, 0xf7, - 0x58, 0x9f, 0x46, 0xc2, 0xee, 0xc2, 0xa8, 0x7e, 0x5b, 0xa9, 0xa5, 0xc9, 0xd2, 0x6f, 0xb4, 0x35, - 0xeb, 0x07, 0xbd, 0x42, 0x94, 0xbd, 0xc8, 0x54, 0x5c, 0xd8, 0xbb, 0x30, 0x26, 0xd8, 0x0b, 0x11, - 0x16, 0xd1, 0xf1, 0x0a, 0x2f, 0x90, 0xb4, 0x06, 0x09, 0x31, 0x68, 0x3c, 0x46, 0x94, 0x10, 0xb0, - 0xaf, 0xc3, 0x58, 0xbd, 0xd7, 0xa3, 0xae, 0xde, 0xd7, 0xf7, 0xef, 0x5c, 0xae, 0xab, 0x8f, 0xbb, - 0xbd, 0x5e, 0xbe, 0xa7, 0x53, 0x7e, 0x6c, 0x2b, 0xc9, 0xd2, 0x4c, 0x15, 0xec, 0xeb, 0xaf, 0xf7, - 0x4a, 0xae, 0x82, 0x59, 0x95, 0x78, 0x38, 0x57, 0x89, 0xc9, 0x97, 0xf5, 0xa0, 0x96, 0x6a, 0xb7, - 0x54, 0x17, 0xec, 0x57, 0xd7, 0x6b, 0xb9, 0xba, 0xf4, 0x01, 0xcc, 0x55, 0x97, 0xe3, 0xce, 0xda, - 0x30, 0xa5, 0x76, 0x4a, 0xaa, 0x6f, 0x7c, 0xbf, 0xfa, 0x5e, 0xca, 0xd5, 0x77, 0xac, 0xbd, 0x91, - 0xaf, 0x27, 0xc3, 0x93, 0xbd, 0x0b, 0x93, 0x0a, 0x82, 0xeb, 0x03, 0x7d, 0xee, 0xc8, 0x32, 0xd9, - 0xde, 0x90, 0x2f, 0x64, 0x1a, 0x6f, 0xeb, 0xe8, 0xc8, 0x3a, 0xb5, 0x9c, 0x1d, 0x93, 0x06, 0x75, - 0x76, 0x56, 0x98, 0xc8, 0xec, 0x6b, 0x30, 0xbe, 0xd2, 0x15, 0x1f, 0x12, 0xf8, 0x6e, 0xcc, 0x29, - 0x81, 0x83, 0xf2, 0x63, 0xd4, 0x4a, 0xb4, 0xa9, 0x8a, 0x1e, 0x5c, 0x5e, 0x5a, 0xa4, 0x9f, 0x77, - 0x35, 0x0a, 0xd1, 0x79, 0xf2, 0x7a, 0x9a, 0xe6, 0xb0, 0x4a, 0xee, 0x70, 0xa6, 0xc0, 0x97, 0x50, - 0x63, 0x4f, 0x0f, 0xad, 0x09, 0xa8, 0xba, 0x1e, 0xce, 0x3c, 0x89, 0xa9, 0xf3, 0x64, 0x5f, 0x86, - 0xf1, 0xfa, 0x7a, 0x53, 0x08, 0xac, 0xba, 0x7d, 0x27, 0x9a, 0xad, 0xa5, 0xe1, 0x2e, 0xee, 0x43, - 0x79, 0x5d, 0xe9, 0xb8, 0x61, 0xc6, 0x69, 0x3f, 0xc5, 0x67, 0x5f, 0x85, 0xe3, 0xeb, 0x9e, 0xdf, - 0x0e, 0x1e, 0x46, 0xb4, 0x4d, 0x91, 0xa0, 0x9b, 0x49, 0xef, 0x40, 0x1f, 0xca, 0xf2, 0x44, 0x29, - 0xcd, 0x09, 0xbe, 0x42, 0x0e, 0xec, 0x4f, 0xe7, 0x38, 0xcb, 0x19, 0xc4, 0xf6, 0x9b, 0x41, 0x97, - 0x73, 0x33, 0x28, 0x5f, 0x7d, 0x76, 0x3a, 0x15, 0x56, 0xc3, 0x02, 0x60, 0xe6, 0xfe, 0x7e, 0x33, - 0xf0, 0xfc, 0xd9, 0x63, 0x28, 0x0b, 0x9f, 0xc9, 0xa6, 0x79, 0x42, 0xbc, 0x46, 0xd0, 0xf1, 0x5a, - 0xbb, 0xf2, 0xf9, 0xd7, 0xec, 0xe1, 0xe3, 0xa3, 0xc0, 0xb8, 0xb7, 0x29, 0x60, 0xcd, 0xbe, 0x06, - 0x13, 0xe2, 0xff, 0xc4, 0x3a, 0x72, 0xdc, 0xf0, 0x3e, 0xd7, 0x30, 0xa9, 0x1e, 0x1c, 0x23, 0xc1, - 0xb7, 0xc8, 0x70, 0x62, 0xb0, 0x62, 0x6f, 0x01, 0x08, 0xfd, 0x8d, 0xc4, 0xf1, 0x89, 0xf4, 0x4d, - 0x51, 0x54, 0xff, 0xf2, 0x82, 0x38, 0x45, 0x66, 0xef, 0xc2, 0xb8, 0xf8, 0xd5, 0xec, 0xb7, 0x03, - 0xb1, 0x36, 0x4e, 0x22, 0xad, 0xcc, 0xa5, 0x21, 0x68, 0x23, 0x09, 0x37, 0x72, 0x69, 0xa4, 0xe8, - 0xec, 0x06, 0x4c, 0xe3, 0x4b, 0xb1, 0xf4, 0xea, 0xa0, 0xc7, 0xa3, 0xd9, 0x53, 0xda, 0xe5, 0xbd, - 0x28, 0x72, 0xbc, 0xa4, 0x4c, 0x3f, 0x54, 0x65, 0xc8, 0x58, 0x04, 0xc7, 0xf2, 0x97, 0xc9, 0xd1, - 0xec, 0x2c, 0x76, 0x92, 0x3a, 0x4a, 0xe4, 0x31, 0xa4, 0x3c, 0x16, 0x23, 0xa2, 0x09, 0x2e, 0x75, - 0xbb, 0xa5, 0x57, 0x58, 0xc4, 0x9d, 0xd9, 0xc0, 0xae, 0x2f, 0x36, 0xb2, 0x8f, 0xa3, 0x9e, 0xc6, - 0x2f, 0xc0, 0x61, 0xde, 0x6a, 0xf5, 0x9c, 0x7d, 0x1e, 0x48, 0x2d, 0xa0, 0x66, 0x7f, 0x0a, 0x4e, - 0x28, 0x09, 0x42, 0x45, 0x34, 0xaf, 0xe7, 0x8e, 0x28, 0x89, 0xdb, 0x1b, 0x49, 0xd5, 0xb9, 0x29, - 0x5d, 0x5c, 0x05, 0x73, 0x61, 0x1c, 0x87, 0x95, 0x6a, 0x7c, 0x66, 0xbf, 0x1a, 0xcf, 0xe7, 0x6a, - 0x3c, 0x89, 0x13, 0x25, 0x5f, 0x99, 0xce, 0x53, 0xbe, 0x76, 0x86, 0xeb, 0x88, 0x66, 0xdb, 0xb3, - 0xd8, 0x5b, 0xf4, 0xda, 0x99, 0x5c, 0x81, 0xb9, 0x09, 0x67, 0x92, 0xe8, 0x12, 0x59, 0xde, 0x6a, - 0x9d, 0x31, 0x24, 0x72, 0xce, 0x85, 0xc2, 0x40, 0x16, 0x12, 0x29, 0xd5, 0x62, 0xb4, 0x47, 0x84, - 0x9e, 0x43, 0x7d, 0x0d, 0x25, 0x92, 0xa6, 0xfc, 0x38, 0x85, 0x0f, 0x0a, 0x15, 0x72, 0x60, 0xf7, - 0xe0, 0x58, 0xb2, 0x6b, 0x6b, 0x8c, 0xe7, 0xd3, 0xe7, 0x37, 0xd3, 0xad, 0xbe, 0x98, 0x6f, 0x11, - 0x3d, 0x73, 0xe1, 0x94, 0xb1, 0x4f, 0x6b, 0xac, 0xcf, 0xa6, 0xef, 0x3e, 0x9b, 0x9b, 0x7c, 0x31, - 0xfb, 0x41, 0x7c, 0xd8, 0x47, 0x30, 0x97, 0xdd, 0x9b, 0xb5, 0x5a, 0x9e, 0xc7, 0x5a, 0x5e, 0x79, - 0xbc, 0x37, 0x7f, 0x2e, 0xb7, 0xbd, 0x17, 0x57, 0xb4, 0x0f, 0x37, 0xf6, 0x2d, 0x98, 0x35, 0xf7, - 0x67, 0xad, 0x26, 0x2b, 0x7d, 0x20, 0x3b, 0xd9, 0xd8, 0x8b, 0x6b, 0x18, 0xc8, 0x83, 0xc5, 0x30, - 0x5f, 0x38, 0xbb, 0xb5, 0x6a, 0x5e, 0x48, 0x3f, 0x28, 0xb7, 0x4a, 0x8a, 0xab, 0x3b, 0x88, 0x25, - 0x7b, 0x08, 0xcf, 0x15, 0x6d, 0x13, 0x5a, 0xa5, 0x2f, 0x26, 0xd6, 0xe8, 0xcf, 0x17, 0x6f, 0x39, - 0xc5, 0x35, 0x1f, 0xc0, 0x96, 0x7d, 0x1d, 0x4e, 0x68, 0xeb, 0x4b, 0xab, 0xef, 0x25, 0xac, 0x0f, - 0x93, 0xaa, 0xe8, 0x0b, 0xb3, 0xb8, 0x96, 0x62, 0x1e, 0xac, 0x0b, 0xc7, 0xd4, 0x87, 0xa3, 0xd9, - 0x9f, 0xb6, 0x9e, 0x73, 0x86, 0x54, 0xcd, 0x63, 0x2c, 0x9c, 0x25, 0xa9, 0x3a, 0xdb, 0xde, 0x70, - 0x7a, 0x29, 0xa1, 0x3e, 0xd3, 0x0b, 0xf8, 0xb2, 0x1b, 0x30, 0xd2, 0x6c, 0xac, 0x5c, 0xbb, 0xb6, - 0x3c, 0xfb, 0x32, 0xd6, 0xa0, 0xc2, 0xa6, 0x25, 0xd0, 0x38, 0x34, 0x91, 0xdf, 0x6b, 0xcf, 0xdb, - 0xdc, 0x34, 0x6e, 0xce, 0x24, 0x2a, 0xfb, 0xd3, 0xe8, 0x71, 0x2a, 0x24, 0x6a, 0x3d, 0x8a, 0xbc, - 0x2d, 0x5f, 0x3e, 0x6c, 0xfa, 0x8a, 0xe1, 0x78, 0xa0, 0x9e, 0xba, 0x5d, 0xe4, 0x7e, 0xcc, 0xc3, - 0x1c, 0x7a, 0xea, 0xe0, 0xa4, 0x9e, 0x52, 0x77, 0x53, 0x56, 0xba, 0x10, 0xcf, 0x57, 0x24, 0xfa, - 0x6d, 0xcb, 0x8b, 0x9d, 0xed, 0xbe, 0xf1, 0xf9, 0xb3, 0x9f, 0x37, 0x3c, 0xaa, 0xae, 0x7b, 0xf1, - 0x8d, 0xfe, 0x86, 0xd6, 0x6b, 0x2f, 0x52, 0x85, 0xcf, 0xca, 0xd3, 0xf2, 0x80, 0x9e, 0x9b, 0xd9, - 0xca, 0xd0, 0x45, 0xec, 0xcf, 0x95, 0xe0, 0xe4, 0x7a, 0x10, 0x3e, 0xe8, 0x04, 0x6e, 0x5b, 0x7d, - 0x15, 0xc9, 0xf0, 0x57, 0xf7, 0x93, 0xe1, 0x5f, 0xc8, 0xc9, 0x70, 0xeb, 0x21, 0xb1, 0x71, 0x92, - 0x97, 0x82, 0x73, 0xf2, 0x7c, 0x40, 0x55, 0xec, 0x4f, 0xc3, 0xd9, 0xe2, 0x12, 0x6d, 0x52, 0xbe, - 0x86, 0x93, 0xf2, 0x8d, 0xc7, 0x7b, 0xf3, 0xaf, 0x0d, 0xaa, 0xa9, 0x78, 0x82, 0x1e, 0xc8, 0x9a, - 0xbd, 0x0d, 0x95, 0xdb, 0x8b, 0x8d, 0xd9, 0x8b, 0xc6, 0x1d, 0xf8, 0xed, 0xc5, 0x86, 0xd6, 0x51, - 0xd2, 0xb4, 0xda, 0x6d, 0x19, 0xa6, 0xd5, 0xdb, 0x8b, 0x0d, 0xb1, 0x91, 0xaf, 0x7a, 0x7e, 0xff, - 0x91, 0xa9, 0xa3, 0x5e, 0x4a, 0x37, 0xf2, 0x8e, 0x28, 0x1d, 0xac, 0xa1, 0x16, 0x50, 0xb3, 0x87, - 0x19, 0x9e, 0x72, 0x3c, 0x5e, 0xdf, 0x6f, 0x3c, 0x5e, 0xcf, 0x8d, 0x47, 0xb6, 0xe2, 0xec, 0x58, - 0x14, 0x54, 0xc1, 0x3e, 0x86, 0x67, 0xf3, 0x50, 0x6d, 0x0c, 0xde, 0xc0, 0x31, 0x78, 0xed, 0xf1, - 0xde, 0xfc, 0x85, 0x22, 0xee, 0xc5, 0xfd, 0xbf, 0x2f, 0x4b, 0xf6, 0x2d, 0x80, 0x05, 0xee, 0x76, - 0xe9, 0x1b, 0x2f, 0x1f, 0xd1, 0xd6, 0xb0, 0xc1, 0xdd, 0x6e, 0x81, 0xad, 0x21, 0xe5, 0x28, 0xf6, - 0xec, 0xf4, 0x97, 0xf6, 0x29, 0x57, 0xd2, 0x3d, 0x5b, 0x63, 0x32, 0x60, 0xcf, 0x2e, 0xe2, 0x70, - 0x73, 0xa8, 0x7a, 0xbe, 0x76, 0xe1, 0xe6, 0x50, 0xf5, 0x42, 0xed, 0x15, 0xfb, 0xd9, 0x66, 0xfd, - 0xf6, 0xea, 0x4a, 0x5b, 0xa9, 0x64, 0xea, 0x09, 0x6c, 0x49, 0x63, 0x9f, 0xdb, 0xaf, 0x54, 0x7b, - 0x60, 0xf0, 0xaf, 0x94, 0x60, 0xfe, 0x00, 0xd1, 0x22, 0xb4, 0xa0, 0x74, 0x5a, 0x36, 0x93, 0x77, - 0xd0, 0x64, 0xa4, 0x78, 0x52, 0xe0, 0x98, 0x5e, 0x4f, 0x26, 0x09, 0x66, 0x6a, 0x90, 0x8c, 0xf5, - 0x84, 0x1d, 0x24, 0xab, 0x8c, 0x4c, 0x0d, 0x12, 0x64, 0xad, 0x42, 0x2d, 0x2b, 0x72, 0xd8, 0x97, - 0x60, 0x52, 0x7f, 0xb4, 0x58, 0x19, 0xa0, 0x64, 0x3e, 0xbd, 0x70, 0xcb, 0x50, 0xa3, 0x0c, 0x44, - 0xeb, 0x1c, 0x4c, 0x99, 0x8b, 0x8b, 0x1d, 0x87, 0xe1, 0x38, 0x08, 0x3a, 0xc4, 0xc3, 0x96, 0x3f, - 0xac, 0x5f, 0x2a, 0xc1, 0xb1, 0x02, 0xf9, 0xcd, 0xce, 0xc1, 0x50, 0xc3, 0x8d, 0xb7, 0x75, 0xe7, - 0xb8, 0x9e, 0x6b, 0x64, 0xce, 0xc4, 0x72, 0xf6, 0x3a, 0x8c, 0x2e, 0xdd, 0x69, 0x36, 0xeb, 0x77, - 0x94, 0xa9, 0x4b, 0xaa, 0x79, 0x7e, 0xe4, 0x44, 0xae, 0xe9, 0x53, 0x43, 0x68, 0xec, 0x35, 0x18, - 0x59, 0x69, 0x20, 0x81, 0xf6, 0x00, 0xb5, 0xd7, 0xcb, 0xe2, 0x13, 0x92, 0xf5, 0x13, 0x25, 0x60, - 0xf9, 0xcd, 0x88, 0xbd, 0x01, 0xe3, 0xfa, 0x96, 0x27, 0xfb, 0x05, 0x1d, 0x0d, 0x34, 0xb1, 0x6c, - 0xeb, 0x38, 0x6c, 0x09, 0x86, 0xf1, 0x05, 0xd7, 0xc4, 0x99, 0xa7, 0x70, 0x01, 0x9c, 0xca, 0x2d, - 0x80, 0x61, 0x7c, 0x13, 0xd6, 0x96, 0xc4, 0xd6, 0xbf, 0x2d, 0x01, 0x2b, 0xf6, 0x8b, 0x3d, 0x94, - 0x33, 0xe1, 0x9b, 0x5a, 0x62, 0x28, 0xdd, 0x31, 0xd6, 0x57, 0x40, 0xdd, 0xc8, 0x94, 0xa6, 0x90, - 0x3a, 0x67, 0x18, 0x35, 0x07, 0x67, 0x13, 0xb9, 0x00, 0xc3, 0xf7, 0x79, 0xb8, 0xa1, 0xa2, 0x4f, - 0xd0, 0x63, 0x7d, 0x47, 0x00, 0x74, 0x23, 0x1f, 0x62, 0x18, 0x1e, 0xba, 0xc3, 0x87, 0xf5, 0xd0, - 0xfd, 0xbd, 0x12, 0x1c, 0x2f, 0x3a, 0x36, 0x1f, 0x90, 0x29, 0xc4, 0xca, 0x24, 0x39, 0x41, 0xef, - 0x43, 0xe9, 0x03, 0x9f, 0xa4, 0x36, 0x99, 0x87, 0x61, 0xd1, 0x43, 0x6a, 0x5a, 0xa0, 0x65, 0x56, - 0x74, 0x61, 0x64, 0x4b, 0xb8, 0x40, 0x48, 0x1f, 0xf2, 0x1b, 0x96, 0x08, 0xf2, 0xfd, 0x3e, 0x09, - 0x17, 0x08, 0xb7, 0x83, 0x36, 0x57, 0x16, 0x4b, 0x44, 0xe8, 0x0a, 0x80, 0x2d, 0xe1, 0xec, 0x1c, - 0x8c, 0xde, 0xf5, 0x57, 0xb9, 0xbb, 0xc3, 0x29, 0x3c, 0x06, 0xbd, 0x25, 0x03, 0xdf, 0xe9, 0x08, - 0x98, 0xad, 0x0a, 0xad, 0x9f, 0x2d, 0xc1, 0x4c, 0xee, 0xc4, 0x7e, 0x70, 0x32, 0x94, 0xfd, 0x83, - 0xbf, 0x0f, 0xf3, 0x7d, 0xb2, 0xf9, 0x43, 0xc5, 0xcd, 0xb7, 0xfe, 0xeb, 0x11, 0x38, 0x35, 0xc0, - 0x80, 0x9a, 0x26, 0xc7, 0x28, 0x1d, 0x98, 0x1c, 0xe3, 0x1b, 0x30, 0xb9, 0xd8, 0x71, 0xbd, 0x6e, - 0xb4, 0x16, 0xa4, 0x2d, 0x4e, 0x63, 0x5c, 0xb1, 0x8c, 0x62, 0xbc, 0x92, 0x60, 0xc8, 0xd3, 0x2d, - 0xa4, 0x70, 0xe2, 0x20, 0x7f, 0x7e, 0x33, 0x98, 0xe5, 0xd2, 0x53, 0x54, 0xfe, 0x98, 0xa4, 0xa7, - 0x30, 0x03, 0x96, 0x87, 0x9e, 0x68, 0xc0, 0x72, 0x71, 0x3c, 0xcb, 0xf0, 0xa7, 0x89, 0x6e, 0x5a, - 0xcc, 0x46, 0x25, 0x8c, 0xe4, 0x5c, 0x4c, 0x0f, 0x0e, 0x47, 0xb8, 0x61, 0x06, 0xd7, 0x8e, 0xa2, - 0x3f, 0xc5, 0xb9, 0xc1, 0xc1, 0xb3, 0x66, 0x16, 0x3e, 0x3d, 0x88, 0xf6, 0xdb, 0x70, 0xbc, 0xc8, - 0x02, 0x33, 0x5b, 0x35, 0x3c, 0xd1, 0x07, 0x86, 0x0d, 0x1c, 0xde, 0x8e, 0xf3, 0xa0, 0xd0, 0x8e, - 0xa3, 0x92, 0xae, 0x8c, 0x0d, 0x8e, 0x18, 0x4d, 0xd7, 0x82, 0xc4, 0xdd, 0x3f, 0x35, 0x8b, 0xf5, - 0x97, 0x4a, 0x99, 0xd4, 0x44, 0x59, 0x7a, 0xf6, 0x8e, 0x91, 0x41, 0xf2, 0xe5, 0x7c, 0x06, 0xc9, - 0xe2, 0x6c, 0x44, 0xf2, 0x12, 0xea, 0x55, 0x18, 0x21, 0x77, 0x24, 0x2d, 0xab, 0x53, 0xce, 0x0d, - 0x89, 0x70, 0xac, 0xef, 0x95, 0xcd, 0xcc, 0x1c, 0x7f, 0x1c, 0xd7, 0xf5, 0x05, 0x18, 0x5e, 0xdf, - 0xe6, 0xa1, 0xda, 0x82, 0xb0, 0x21, 0x0f, 0x05, 0x40, 0x6f, 0x08, 0x62, 0xb0, 0x6b, 0x30, 0xd5, - 0x90, 0xf3, 0x5c, 0x4d, 0xde, 0xa1, 0xd4, 0x6a, 0xd8, 0x23, 0xdb, 0x76, 0xc1, 0xec, 0xcd, 0x50, - 0x09, 0x3e, 0x32, 0x41, 0xca, 0xb5, 0x20, 0x94, 0xb6, 0xfd, 0xe1, 0x94, 0x8f, 0x4c, 0x83, 0xe2, - 0x6c, 0x06, 0x61, 0xce, 0xc6, 0x9f, 0xa1, 0xb2, 0xae, 0x67, 0x86, 0x9a, 0xb2, 0x6e, 0xca, 0x60, - 0x53, 0xa9, 0xec, 0x4c, 0xa5, 0xb1, 0xd7, 0xa9, 0x8c, 0xb7, 0x33, 0x50, 0x6b, 0x13, 0x9e, 0xdb, - 0x97, 0x91, 0xd0, 0x31, 0xa0, 0x97, 0xfc, 0xca, 0x04, 0x43, 0xec, 0x4b, 0x6a, 0x6b, 0x74, 0xd6, - 0x6a, 0x1a, 0x8e, 0xbd, 0xb2, 0x84, 0xf1, 0xe0, 0x6f, 0xc3, 0x84, 0x1e, 0x2a, 0x44, 0x9c, 0x0b, - 0x22, 0x8b, 0x86, 0xc4, 0xc0, 0xda, 0xe3, 0x0a, 0x79, 0xa5, 0x1d, 0x59, 0xff, 0x4b, 0x05, 0x66, - 0x07, 0x05, 0x24, 0xb3, 0x9f, 0x4a, 0x12, 0x98, 0x61, 0xb4, 0x6d, 0x60, 0x86, 0x69, 0x8d, 0x5f, - 0x7e, 0xfb, 0x80, 0x88, 0xe6, 0x8b, 0x85, 0xc4, 0xd2, 0x8f, 0x3f, 0x89, 0x72, 0x42, 0x7d, 0x02, - 0x93, 0xdf, 0x38, 0x5a, 0xe8, 0xbb, 0x5d, 0x5c, 0x31, 0xfb, 0x00, 0x4e, 0xd8, 0xbc, 0x15, 0x74, - 0xbb, 0xdc, 0x6f, 0xeb, 0xa1, 0xc0, 0xb4, 0x94, 0x28, 0x97, 0x57, 0x82, 0x60, 0xb2, 0x2c, 0xa4, - 0x64, 0x77, 0x60, 0x26, 0x4d, 0x16, 0xaa, 0x9e, 0xc6, 0xd2, 0x5e, 0x90, 0x4c, 0x93, 0x9b, 0xaa, - 0x87, 0xb1, 0xf4, 0x13, 0x7d, 0x8e, 0x94, 0x5d, 0x02, 0x58, 0x74, 0xfd, 0x46, 0x18, 0xb4, 0x38, - 0xe5, 0x62, 0xa9, 0x92, 0x97, 0xab, 0x8b, 0x09, 0xc6, 0x04, 0xd8, 0xd6, 0x50, 0xe6, 0x1c, 0x98, - 0x1b, 0xdc, 0x51, 0x05, 0xb1, 0x08, 0x9f, 0x37, 0x43, 0x5c, 0x4e, 0xe4, 0x06, 0x5a, 0xf0, 0xd1, - 0x43, 0x14, 0xbe, 0x0d, 0x13, 0xfa, 0x02, 0x47, 0x65, 0x49, 0xfc, 0x26, 0xf1, 0x25, 0x95, 0x25, - 0x01, 0xb0, 0x25, 0x3c, 0xbd, 0x08, 0x2f, 0x17, 0x5f, 0x84, 0xa7, 0x92, 0xa7, 0x72, 0x90, 0xe4, - 0x11, 0x95, 0xe3, 0x5e, 0xac, 0x55, 0x8e, 0xbf, 0xf5, 0xca, 0x31, 0x7d, 0xaa, 0x2d, 0xe1, 0x4f, - 0xb4, 0xf2, 0x7f, 0xa8, 0x1e, 0xb8, 0xc7, 0xf0, 0x67, 0x33, 0xc8, 0x90, 0xc2, 0x9f, 0xf3, 0xfb, - 0x4c, 0x8a, 0x99, 0xaa, 0xcc, 0xe5, 0x03, 0x55, 0xe6, 0x23, 0xc8, 0x40, 0x3c, 0xfe, 0x49, 0x29, - 0x30, 0x94, 0x1e, 0x73, 0xdc, 0x9c, 0xcf, 0xa2, 0xc2, 0xb2, 0x7e, 0xa1, 0x04, 0x27, 0x0a, 0x2f, - 0x1c, 0x45, 0xad, 0x52, 0xfa, 0x69, 0x5b, 0x40, 0x56, 0xe4, 0x49, 0x8c, 0xa3, 0xa4, 0x48, 0x3b, - 0xfc, 0xb7, 0x58, 0xcf, 0xc3, 0x58, 0xe2, 0xee, 0x22, 0x8e, 0x91, 0x72, 0xe8, 0x64, 0x9e, 0x6d, - 0xf2, 0x9a, 0xf8, 0x1b, 0x65, 0x00, 0xd1, 0x84, 0xcf, 0x30, 0x42, 0x46, 0xf6, 0xc1, 0x80, 0x08, - 0x99, 0x6c, 0x7f, 0x64, 0xe9, 0xd8, 0x9f, 0x2f, 0xc1, 0x88, 0xd4, 0x17, 0xf7, 0xd7, 0x48, 0xd7, - 0x9f, 0x8c, 0x46, 0x5a, 0xc3, 0xe9, 0x6e, 0x9c, 0x67, 0x65, 0x99, 0xf5, 0x0f, 0xcb, 0x30, 0x22, - 0x9a, 0xf4, 0xd4, 0xbe, 0xc7, 0x52, 0x1c, 0xaa, 0x23, 0x3e, 0x69, 0x9f, 0xd7, 0xa7, 0x96, 0x33, - 0xaf, 0x4f, 0x1d, 0xd3, 0xc9, 0xd4, 0x7b, 0xf4, 0x49, 0xc2, 0xb0, 0x41, 0xaf, 0x4d, 0x69, 0x11, - 0x3f, 0xff, 0xac, 0x24, 0xd3, 0x34, 0x26, 0x6f, 0xea, 0x7f, 0x0d, 0xa6, 0xd4, 0x1b, 0x13, 0x32, - 0x89, 0x1f, 0x39, 0x1c, 0x29, 0x2f, 0x65, 0xf5, 0xc6, 0x84, 0x9e, 0xf4, 0xcf, 0xc0, 0xd7, 0x55, - 0x9f, 0x9e, 0x8e, 0xcc, 0xda, 0xc0, 0xba, 0x9b, 0xae, 0xf3, 0x90, 0xbb, 0x0f, 0x78, 0x14, 0x3b, - 0xd2, 0x9b, 0x94, 0xfc, 0x92, 0x14, 0xfb, 0xdb, 0xd7, 0xea, 0xd2, 0x91, 0x14, 0x53, 0x89, 0xc8, - 0xc7, 0x42, 0x72, 0x34, 0xba, 0xb3, 0x45, 0x77, 0xd3, 0x5d, 0x97, 0x85, 0x92, 0xce, 0xfa, 0x37, - 0x23, 0x72, 0x09, 0xd1, 0xa3, 0x34, 0x1b, 0x30, 0x75, 0x77, 0x65, 0x69, 0x51, 0xbb, 0x7a, 0x35, - 0xdf, 0x05, 0x5a, 0x7e, 0x14, 0xf3, 0xd0, 0x77, 0x3b, 0xca, 0x96, 0x95, 0xaa, 0x74, 0x81, 0xd7, - 0x6e, 0x15, 0x5f, 0xcb, 0x66, 0x38, 0x8a, 0x3a, 0xa4, 0xd5, 0x2c, 0xa9, 0xa3, 0x7c, 0xc8, 0x3a, - 0x22, 0xb7, 0xdb, 0x19, 0x50, 0x87, 0xc9, 0x91, 0x6d, 0xa3, 0x59, 0x6b, 0xbb, 0xbf, 0xa1, 0xd5, - 0x52, 0xd9, 0xbf, 0x96, 0x17, 0xa8, 0x96, 0x67, 0xc8, 0xd0, 0x5e, 0x58, 0x4f, 0x8e, 0x6b, 0x2a, - 0xfc, 0x86, 0x0e, 0x14, 0x7e, 0x9a, 0x34, 0x18, 0xfe, 0xa3, 0x94, 0x06, 0x6c, 0x29, 0xb3, 0x2e, - 0x58, 0xa2, 0x6e, 0x6d, 0x79, 0xbe, 0xf6, 0x08, 0xdb, 0xbe, 0xcb, 0x82, 0xad, 0xa4, 0x29, 0xdc, - 0x46, 0x0f, 0xcc, 0xdb, 0xa3, 0xd2, 0xde, 0x8d, 0x52, 0x0a, 0x37, 0x33, 0x71, 0xdb, 0x2a, 0x8c, - 0x51, 0x62, 0xb8, 0x85, 0x5d, 0x72, 0x95, 0xaa, 0x19, 0x5e, 0xb7, 0xed, 0x85, 0xdd, 0xf4, 0x64, - 0x4c, 0xa9, 0xe5, 0xcc, 0xac, 0x86, 0x29, 0x03, 0x76, 0x17, 0xc6, 0xd2, 0x47, 0x7b, 0xcc, 0x77, - 0x32, 0x13, 0x38, 0xe5, 0x42, 0x56, 0xd9, 0xa5, 0x0a, 0xde, 0xe8, 0x49, 0x79, 0xb0, 0x55, 0xa8, - 0xa1, 0xa7, 0x36, 0x6f, 0xcb, 0x55, 0xb3, 0xb2, 0x24, 0x93, 0x8f, 0x91, 0x1e, 0x17, 0xcb, 0x32, - 0x5a, 0x6e, 0x99, 0x50, 0xf7, 0x1c, 0xa5, 0xf5, 0x33, 0x65, 0xa8, 0x65, 0x67, 0x1f, 0x7b, 0x17, - 0xc6, 0x93, 0x47, 0x93, 0x92, 0xe4, 0x32, 0xe8, 0x32, 0x91, 0xbe, 0xb2, 0x64, 0xa4, 0x99, 0xd1, - 0xd1, 0xd9, 0x65, 0xa8, 0x8a, 0x45, 0xec, 0xa7, 0xa9, 0xe4, 0x51, 0x6c, 0xf7, 0x09, 0xa6, 0x1b, - 0xd5, 0x14, 0x1e, 0x6b, 0xc2, 0x31, 0xb1, 0x68, 0x9a, 0x9e, 0xbf, 0xd5, 0xe1, 0xab, 0xc1, 0x56, - 0xd0, 0x8f, 0xef, 0xd9, 0xab, 0x24, 0xc3, 0xa5, 0xfd, 0xc0, 0xed, 0x76, 0x8c, 0xe2, 0x50, 0xf7, - 0xf1, 0x2d, 0xa2, 0x66, 0xaf, 0xc9, 0x6d, 0x66, 0x65, 0x89, 0x3c, 0x1d, 0x51, 0xff, 0x40, 0x57, - 0x61, 0xa3, 0xf1, 0x84, 0xa4, 0x49, 0xd6, 0xdf, 0x2d, 0xc3, 0xb8, 0x36, 0xfd, 0xd8, 0x05, 0xa8, - 0xae, 0x44, 0xab, 0x41, 0xeb, 0x41, 0x92, 0x5b, 0x7f, 0xf2, 0xf1, 0xde, 0xfc, 0x98, 0x17, 0x39, - 0x1d, 0x04, 0xda, 0x49, 0x31, 0x5b, 0x80, 0x49, 0xf9, 0x97, 0x52, 0xb1, 0xcb, 0xa9, 0xdd, 0x5c, - 0x22, 0x17, 0xa8, 0xd7, 0x26, 0x09, 0xfb, 0x26, 0x80, 0x04, 0x60, 0xd2, 0xab, 0xca, 0xe1, 0xd3, - 0x75, 0x51, 0x05, 0x05, 0xe9, 0xae, 0x34, 0x86, 0xec, 0x43, 0xf9, 0x26, 0x93, 0x5a, 0x2e, 0x43, - 0x87, 0xcf, 0x37, 0x26, 0xf8, 0x3b, 0xc5, 0x69, 0x0f, 0x75, 0x96, 0xf4, 0x60, 0xf4, 0x9c, 0x38, - 0x88, 0xec, 0xf0, 0x70, 0xb7, 0x1e, 0x23, 0xa2, 0x86, 0x61, 0xfd, 0x8f, 0x25, 0x6d, 0x91, 0xb1, - 0x3b, 0x30, 0x96, 0x4c, 0x20, 0x72, 0x32, 0x4e, 0x8e, 0xec, 0x0a, 0x6e, 0xf3, 0xcd, 0x85, 0x67, - 0xc8, 0xf1, 0xfa, 0x58, 0x32, 0x0d, 0x8d, 0x35, 0xa7, 0x80, 0xec, 0x7d, 0x18, 0xc2, 0xae, 0x2b, - 0x1f, 0xf8, 0x69, 0x6a, 0x97, 0x1f, 0x12, 0x7d, 0x86, 0x1f, 0x82, 0x94, 0xec, 0x75, 0xca, 0x9c, - 0x21, 0x3b, 0x7f, 0x4a, 0xdb, 0xaa, 0x45, 0x3b, 0x92, 0xed, 0x3d, 0xcd, 0x7c, 0xa9, 0xcd, 0x9e, - 0xbf, 0x52, 0x86, 0x5a, 0x76, 0x69, 0xb3, 0xf7, 0x60, 0x42, 0x6d, 0xbf, 0x37, 0x5c, 0x7a, 0x3a, - 0x75, 0x82, 0x9e, 0x2e, 0x55, 0x7b, 0xf0, 0xb6, 0xab, 0x3b, 0x25, 0xdb, 0x06, 0x81, 0xd0, 0x85, - 0xd6, 0x28, 0x45, 0xba, 0xb6, 0xa8, 0xe2, 0x20, 0xee, 0x65, 0x9e, 0x02, 0x52, 0x68, 0xec, 0x4d, - 0xa8, 0xdc, 0xbe, 0x56, 0xa7, 0xd8, 0xf3, 0x5a, 0x76, 0x93, 0xa6, 0x9b, 0x46, 0x23, 0xa4, 0x44, - 0xe0, 0xb3, 0x55, 0xed, 0xd5, 0xac, 0x11, 0xc3, 0x13, 0x5e, 0x81, 0x93, 0x8f, 0x3b, 0xf8, 0xf9, - 0xac, 0x9b, 0x43, 0xd5, 0x4a, 0x6d, 0x88, 0x1e, 0x28, 0x59, 0x80, 0x99, 0x1c, 0x0b, 0x6d, 0x91, - 0xca, 0x2e, 0xd9, 0x7f, 0x91, 0x5a, 0x3e, 0x4c, 0xe8, 0x53, 0xe2, 0x80, 0x87, 0xeb, 0x4e, 0x62, - 0x72, 0x2c, 0xd9, 0x5f, 0x23, 0x8f, 0xf7, 0xe6, 0xcb, 0x5e, 0x1b, 0x53, 0x62, 0x9d, 0x87, 0xaa, - 0x12, 0x76, 0x24, 0x63, 0xd0, 0x0c, 0xae, 0xee, 0x79, 0xed, 0xa4, 0xd4, 0x7a, 0x19, 0x46, 0x69, - 0xd4, 0xf7, 0x37, 0x7e, 0x5b, 0xdf, 0x2d, 0xc3, 0xb4, 0xcd, 0x85, 0xa6, 0xc9, 0xe5, 0x63, 0xb1, - 0x4f, 0xad, 0x76, 0x5b, 0x9c, 0x18, 0xde, 0xf8, 0xb6, 0x7d, 0x1e, 0x3e, 0xff, 0x95, 0x12, 0x1c, - 0x2b, 0xc0, 0x65, 0x97, 0x61, 0x5c, 0xbd, 0x87, 0x9e, 0x76, 0x22, 0x26, 0x2c, 0x53, 0x7e, 0x41, - 0xd8, 0x99, 0x3a, 0x12, 0xbb, 0x0a, 0x63, 0x4b, 0x9e, 0xdb, 0xa9, 0xb7, 0xdb, 0x49, 0xa6, 0x2c, - 0xdc, 0x22, 0xf1, 0x31, 0x63, 0x57, 0x40, 0xf5, 0xf5, 0x9f, 0xa0, 0xb2, 0x57, 0x68, 0x52, 0x54, - 0x92, 0x6e, 0xc5, 0x49, 0xf1, 0xfd, 0xbd, 0x79, 0x90, 0x6d, 0x5a, 0x4b, 0xa6, 0x08, 0x3e, 0xd6, - 0x20, 0x81, 0x69, 0xb4, 0xdc, 0x53, 0x3b, 0x74, 0xc5, 0x8f, 0x35, 0x64, 0x3f, 0xef, 0x50, 0x4f, - 0x45, 0xfe, 0x3b, 0x65, 0x38, 0x59, 0x4c, 0xf8, 0x89, 0x86, 0xf2, 0x55, 0x18, 0xc3, 0x27, 0x48, - 0xb5, 0x07, 0x66, 0xa6, 0x1e, 0xef, 0xcd, 0x83, 0x7c, 0xaf, 0x14, 0xf1, 0x53, 0x04, 0xb6, 0x09, - 0x93, 0xab, 0x6e, 0x14, 0xdf, 0xe0, 0x6e, 0x18, 0x6f, 0x70, 0x37, 0x3e, 0xc4, 0x26, 0xa8, 0x5c, - 0x53, 0x66, 0xf1, 0x35, 0xab, 0x6d, 0x45, 0x99, 0xd9, 0xa6, 0x4c, 0xb6, 0xc9, 0x44, 0x19, 0x3a, - 0xc4, 0x44, 0xf9, 0x18, 0xa6, 0x9b, 0xbc, 0xeb, 0xf6, 0xb6, 0x83, 0x50, 0x65, 0xc1, 0xb8, 0x08, - 0x93, 0x09, 0xa8, 0x70, 0xb6, 0x98, 0xc5, 0x06, 0xbe, 0xd6, 0x11, 0xa9, 0x28, 0x31, 0x8b, 0xad, - 0xbf, 0x5a, 0x86, 0x53, 0xf5, 0x16, 0xf9, 0xd9, 0x52, 0x81, 0x0a, 0x07, 0xf8, 0x8c, 0xeb, 0x66, - 0x97, 0x60, 0xec, 0xb6, 0xfb, 0x68, 0x95, 0xbb, 0x11, 0x8f, 0xe8, 0xf5, 0x22, 0xb9, 0x63, 0xb8, - 0x8f, 0xd2, 0x6b, 0x0b, 0x3b, 0xc5, 0xd1, 0x35, 0xf0, 0xa1, 0x4f, 0xa9, 0x81, 0x5b, 0x30, 0x72, - 0x23, 0xe8, 0xb4, 0x79, 0x48, 0xb7, 0xb2, 0x78, 0x57, 0xba, 0x8d, 0x10, 0x9b, 0x4a, 0xac, 0xdf, - 0x2b, 0xc1, 0x54, 0xd2, 0x62, 0x6c, 0xc2, 0x67, 0xde, 0x25, 0xe7, 0x60, 0x14, 0x2b, 0x5a, 0x59, - 0xd2, 0x37, 0x8d, 0x0e, 0xc7, 0x67, 0xdb, 0xdb, 0xb6, 0x2a, 0xd4, 0x7b, 0x62, 0xf8, 0xd3, 0xf5, - 0x84, 0xf5, 0xb7, 0xf1, 0x1a, 0x56, 0xff, 0x4a, 0xb1, 0x13, 0x69, 0x0d, 0x29, 0x1d, 0xb2, 0x21, - 0xe5, 0x27, 0x36, 0x24, 0x95, 0x81, 0x43, 0xf2, 0x17, 0xca, 0x30, 0x9e, 0x34, 0xf6, 0x07, 0xec, - 0x95, 0xa3, 0xe4, 0xbb, 0x0e, 0x95, 0xb9, 0xaa, 0xa9, 0xc9, 0x0a, 0x4a, 0x10, 0xf5, 0x3e, 0x8c, - 0xd0, 0x62, 0x2a, 0x65, 0xdc, 0xe2, 0x33, 0xa3, 0xbb, 0x30, 0x45, 0xac, 0x47, 0x70, 0x40, 0x23, - 0x9b, 0xe8, 0x30, 0x35, 0xd8, 0x3a, 0xdf, 0xa0, 0x5b, 0xf9, 0xa7, 0x76, 0x8f, 0x2a, 0x4e, 0x0d, - 0x96, 0x7e, 0xd8, 0xa1, 0x76, 0xa7, 0xff, 0x7e, 0x0c, 0x6a, 0x59, 0x92, 0x83, 0xdf, 0x91, 0x6a, - 0xf4, 0x37, 0xb0, 0x2f, 0x26, 0xe4, 0x3b, 0x52, 0xbd, 0xfe, 0x86, 0x2d, 0x60, 0xe8, 0xe9, 0x13, - 0x7a, 0x3b, 0xf8, 0xd5, 0x13, 0xe4, 0xe9, 0x13, 0x7a, 0x3b, 0x86, 0xa7, 0x4f, 0xe8, 0xed, 0xa0, - 0x0e, 0xbe, 0xda, 0xc4, 0xb4, 0x19, 0x43, 0x88, 0x2a, 0x75, 0xf0, 0x4e, 0x94, 0x7d, 0xc5, 0x57, - 0xa1, 0x89, 0xad, 0x72, 0x81, 0xbb, 0x21, 0xbd, 0x79, 0x44, 0xe2, 0x0c, 0xb7, 0xca, 0x0d, 0x04, - 0x3b, 0xb1, 0x80, 0xdb, 0x3a, 0x12, 0xeb, 0x00, 0xd3, 0x7e, 0xaa, 0x05, 0x3c, 0x72, 0xe0, 0x02, - 0x56, 0x2e, 0xad, 0xc7, 0x75, 0xd6, 0x8e, 0xbe, 0x9a, 0x0b, 0xf8, 0x3e, 0x49, 0xc3, 0x49, 0x83, - 0xf2, 0x65, 0xe3, 0xd9, 0xab, 0x7a, 0x20, 0x33, 0x95, 0xee, 0x07, 0x64, 0x3e, 0xed, 0xe4, 0x04, - 0x96, 0x32, 0x61, 0x5f, 0x81, 0x71, 0x3d, 0x19, 0x8a, 0x4c, 0xd9, 0xf1, 0xac, 0xcc, 0x72, 0x9b, - 0x66, 0x41, 0x31, 0xef, 0xec, 0xf5, 0xac, 0x27, 0x1b, 0x70, 0x6a, 0x31, 0xf0, 0xa3, 0x7e, 0x57, - 0x5d, 0x85, 0xa5, 0x4f, 0x3c, 0x00, 0x0e, 0x05, 0x66, 0x56, 0x68, 0x11, 0x8a, 0xba, 0x46, 0x53, - 0x39, 0x33, 0x8c, 0x03, 0xc8, 0x20, 0x46, 0x6c, 0x0d, 0xc6, 0xd1, 0xf8, 0x40, 0x0e, 0x7f, 0xe3, - 0xa6, 0xd8, 0x48, 0x4b, 0x96, 0xc4, 0xc2, 0x90, 0x4f, 0x61, 0xb8, 0xdd, 0x8e, 0x0a, 0x79, 0x31, - 0x9e, 0xc2, 0x48, 0x91, 0xd9, 0x37, 0x61, 0x4a, 0x9e, 0xd3, 0xd6, 0xf9, 0x86, 0x9c, 0x3b, 0x13, - 0xc6, 0x35, 0x98, 0x59, 0x28, 0xef, 0x83, 0xc9, 0xe4, 0x83, 0xb1, 0xc1, 0x02, 0x6a, 0x04, 0x9c, - 0x19, 0xf8, 0xec, 0x1e, 0x1c, 0xbb, 0xe1, 0x46, 0x12, 0xa8, 0x65, 0xb5, 0x98, 0x44, 0xe3, 0x06, - 0x06, 0x02, 0x6c, 0xbb, 0x91, 0xb2, 0x21, 0x15, 0x66, 0xb1, 0x28, 0xa2, 0x67, 0xdf, 0x2d, 0xc1, - 0xac, 0x61, 0x62, 0x22, 0x0f, 0x29, 0xcc, 0x75, 0x3d, 0x65, 0xc4, 0x37, 0x0f, 0x42, 0x93, 0x43, - 0x92, 0xb1, 0x62, 0x85, 0x69, 0xb9, 0xee, 0x61, 0x3f, 0x88, 0x07, 0x2d, 0x54, 0x5c, 0xd3, 0xd3, - 0xe6, 0x42, 0xcd, 0xac, 0x6b, 0x85, 0xc6, 0x16, 0x61, 0x72, 0x69, 0xa1, 0xb9, 0xd8, 0xe8, 0x6f, - 0x74, 0xbc, 0x96, 0x38, 0x64, 0xd7, 0x90, 0x0e, 0x6f, 0x6b, 0xda, 0x1b, 0x51, 0xcb, 0xe9, 0x61, - 0x49, 0xe6, 0xac, 0x6d, 0xd2, 0xb0, 0xba, 0x7a, 0x8f, 0x6e, 0x06, 0x3f, 0xf5, 0x64, 0x4e, 0xb6, - 0x1d, 0xf8, 0x20, 0x9d, 0x75, 0x35, 0x3b, 0xee, 0x6c, 0x0a, 0xca, 0x1e, 0xc9, 0x78, 0xbb, 0xec, - 0xb5, 0xa5, 0x13, 0xa3, 0x98, 0x10, 0x94, 0xa3, 0x0d, 0x7f, 0x58, 0xaf, 0xeb, 0xf2, 0x90, 0xd4, - 0xd3, 0x7d, 0xe5, 0xa1, 0xf5, 0xdf, 0x8d, 0xc0, 0x74, 0x66, 0x7a, 0xd2, 0x79, 0xb9, 0x94, 0x3b, - 0x2f, 0x37, 0x01, 0xa4, 0xb5, 0xe6, 0x90, 0x66, 0x15, 0x15, 0x5d, 0x3b, 0x4e, 0x41, 0xfa, 0xc9, - 0xda, 0xd6, 0xd8, 0x08, 0xa6, 0x52, 0x72, 0x1c, 0xd2, 0xcc, 0x95, 0x30, 0x95, 0xc2, 0x47, 0x63, - 0x9a, 0xb2, 0x61, 0xf3, 0x30, 0x8c, 0xd9, 0xb5, 0xf5, 0xe0, 0x66, 0x4f, 0x00, 0x6c, 0x09, 0x67, - 0x2f, 0xc0, 0x88, 0x50, 0xe6, 0x56, 0x96, 0x48, 0x18, 0xe3, 0x1e, 0x27, 0xb4, 0x3d, 0xa1, 0x39, - 0x51, 0x11, 0xbb, 0x0a, 0x13, 0xf2, 0x2f, 0x4a, 0xe2, 0x34, 0x62, 0xfa, 0x1c, 0x3a, 0x5e, 0x5b, - 0xe5, 0x71, 0x32, 0xf0, 0xc4, 0x29, 0xa7, 0xd9, 0xdf, 0xf8, 0x88, 0xb7, 0x84, 0x84, 0x19, 0x4d, - 0x4f, 0x39, 0x91, 0x04, 0x8a, 0x2a, 0x52, 0x04, 0xa1, 0x53, 0x51, 0x88, 0x51, 0x15, 0xcf, 0xb6, - 0xa8, 0x53, 0xc9, 0xd0, 0x22, 0x9b, 0x4a, 0xd8, 0x05, 0x69, 0x4c, 0x45, 0xf5, 0x54, 0xbe, 0x9e, - 0x8e, 0xa6, 0x47, 0x34, 0x90, 0xa0, 0x8e, 0x9a, 0x14, 0x8b, 0xca, 0xc5, 0xdf, 0xcb, 0x5d, 0xd7, - 0xeb, 0x90, 0x78, 0xc3, 0xca, 0x11, 0x97, 0x0b, 0xa8, 0x9d, 0x22, 0xb0, 0x77, 0x61, 0x4a, 0x3e, - 0x1e, 0xdc, 0xed, 0x06, 0x3e, 0xb2, 0x1f, 0x4f, 0xdd, 0x74, 0xe8, 0xe5, 0x65, 0x51, 0x24, 0x6b, - 0xc9, 0xe0, 0x8a, 0x7d, 0x0d, 0x2f, 0x6a, 0xfa, 0xd2, 0xcc, 0x3b, 0x91, 0xee, 0x6b, 0x48, 0x1a, - 0x49, 0xb8, 0xad, 0x23, 0xb1, 0xb7, 0x60, 0x52, 0xfc, 0xbc, 0xee, 0xed, 0x70, 0x59, 0xe1, 0x64, - 0x7a, 0xed, 0x8a, 0x54, 0x5b, 0xa2, 0x44, 0xd6, 0x67, 0x62, 0xb2, 0x0f, 0xe0, 0x04, 0x72, 0x6a, - 0x05, 0x3d, 0xde, 0xae, 0x6f, 0x6e, 0x7a, 0x1d, 0x4f, 0xfa, 0x73, 0x4d, 0xa5, 0xfe, 0x10, 0xb2, - 0x62, 0xc4, 0x70, 0xdc, 0x14, 0xc5, 0x2e, 0xa6, 0x64, 0xeb, 0x50, 0x5b, 0xec, 0x47, 0x71, 0xd0, - 0xad, 0xc7, 0x71, 0xe8, 0x6d, 0xf4, 0x63, 0x1e, 0xcd, 0x4e, 0x1b, 0x49, 0x7d, 0xc4, 0xe2, 0x48, - 0x0a, 0xa5, 0x5d, 0xaa, 0x85, 0x14, 0x8e, 0x9b, 0x90, 0xd8, 0x39, 0x26, 0xd6, 0xbf, 0x28, 0xc1, - 0xa4, 0x41, 0xca, 0xde, 0x84, 0x89, 0x6b, 0xa1, 0xc7, 0xfd, 0x76, 0x67, 0x57, 0x3b, 0x30, 0xe3, - 0x69, 0x6a, 0x93, 0xe0, 0xf2, 0xab, 0x0d, 0xb4, 0xc4, 0xde, 0x54, 0x2e, 0x74, 0xb6, 0xbc, 0x24, - 0x63, 0xec, 0x69, 0x82, 0x56, 0xd2, 0x2c, 0x63, 0x38, 0x41, 0x69, 0x76, 0x6a, 0x28, 0xec, 0xcb, - 0x30, 0x22, 0xaf, 0x74, 0xc8, 0xf3, 0xef, 0x74, 0xd1, 0x67, 0xca, 0x7c, 0x0e, 0x38, 0x11, 0xd1, - 0x19, 0x21, 0xb2, 0x89, 0xc8, 0xfa, 0xb9, 0x12, 0xb0, 0x3c, 0xea, 0x01, 0xf6, 0xb7, 0x03, 0x9d, - 0x1c, 0xde, 0x4f, 0x56, 0x63, 0xc5, 0xb8, 0x4b, 0x15, 0x35, 0xc9, 0x02, 0xd9, 0xf1, 0xb4, 0xea, - 0x74, 0x83, 0xa0, 0x2c, 0xb6, 0xbe, 0x53, 0x06, 0x48, 0xb1, 0xd9, 0x97, 0xe4, 0xd3, 0xab, 0x1f, - 0xf4, 0xdd, 0x8e, 0xb7, 0xe9, 0x99, 0xb9, 0x8f, 0x91, 0xc9, 0xc7, 0xaa, 0xc4, 0x36, 0x11, 0xd9, - 0x7b, 0x30, 0xdd, 0x6c, 0x98, 0xb4, 0x9a, 0xd7, 0x7a, 0xd4, 0x73, 0x32, 0xe4, 0x59, 0x6c, 0xf4, - 0xf0, 0xd5, 0x47, 0x43, 0x7a, 0xf8, 0xca, 0x81, 0xa0, 0x12, 0x21, 0x58, 0x9a, 0x0d, 0x72, 0xcc, - 0x6f, 0x27, 0x17, 0x13, 0xd8, 0xba, 0xa8, 0xe7, 0xf4, 0xc8, 0x63, 0x5f, 0xc8, 0x09, 0x03, 0x2f, - 0xed, 0xc8, 0xe1, 0x01, 0x39, 0x1b, 0x7e, 0x1e, 0xcd, 0x8f, 0xdd, 0x20, 0xe6, 0x64, 0x75, 0x79, - 0x6a, 0xcf, 0x5f, 0xe9, 0x7d, 0xe0, 0xb0, 0x11, 0x8a, 0x6e, 0x7c, 0x1d, 0xdd, 0x79, 0x5f, 0x49, - 0x0f, 0x4b, 0xf2, 0x66, 0xb0, 0xe0, 0x9a, 0xfc, 0xaf, 0x97, 0xe0, 0x44, 0x21, 0x2d, 0xbb, 0x08, - 0x90, 0xda, 0xb6, 0xa8, 0x97, 0x50, 0x62, 0xa6, 0xb9, 0xb5, 0x6c, 0x0d, 0x83, 0x7d, 0x23, 0x6b, - 0x95, 0x3a, 0x78, 0x23, 0x9c, 0x53, 0x29, 0x2d, 0x4d, 0xab, 0x54, 0x81, 0x2d, 0xca, 0xfa, 0x95, - 0x0a, 0xcc, 0x68, 0xa9, 0xbb, 0x64, 0x5b, 0x0f, 0xf0, 0xb8, 0x7e, 0x00, 0x13, 0xe2, 0x6b, 0xbc, - 0x16, 0xc5, 0xb5, 0xc8, 0xbb, 0xeb, 0x57, 0x72, 0xc1, 0xc4, 0xc4, 0xed, 0xa2, 0x8e, 0x2c, 0x1d, - 0xd4, 0x50, 0x74, 0xe2, 0x83, 0xe2, 0xad, 0x7c, 0x80, 0x8b, 0xc1, 0x9c, 0x45, 0x30, 0xb9, 0xb4, - 0xeb, 0xbb, 0xdd, 0xa4, 0x36, 0x79, 0x87, 0xfd, 0xf9, 0x81, 0xb5, 0x19, 0xd8, 0xb2, 0xba, 0x34, - 0xec, 0x4e, 0x96, 0x15, 0x64, 0x7c, 0x30, 0xa8, 0xe6, 0xde, 0x83, 0x99, 0x5c, 0xa3, 0x8f, 0x94, - 0xf3, 0x76, 0x1d, 0x58, 0xbe, 0x1d, 0x87, 0x77, 0x37, 0x13, 0x7b, 0x9e, 0xeb, 0xb7, 0xe5, 0x8d, - 0xf8, 0x65, 0xdd, 0xdd, 0xec, 0x7f, 0x2d, 0xeb, 0x01, 0xdd, 0x4f, 0xfb, 0xaa, 0x7b, 0xdf, 0x38, - 0x95, 0x3f, 0x37, 0x68, 0x4c, 0x07, 0x5b, 0x3f, 0xd8, 0x55, 0xa0, 0x35, 0x98, 0xc9, 0x77, 0x92, - 0xe7, 0x41, 0x2b, 0x55, 0x62, 0x8b, 0xa1, 0xc3, 0x5d, 0x5b, 0xea, 0x4b, 0xb6, 0xfc, 0xa1, 0xad, - 0xdf, 0xdf, 0xa9, 0xc0, 0xa9, 0x01, 0xed, 0x60, 0xbb, 0xd9, 0x29, 0x29, 0x6d, 0x2b, 0x6f, 0xec, - 0xdf, 0xfc, 0x27, 0x31, 0x31, 0xd9, 0x97, 0x64, 0x82, 0x98, 0x56, 0xe0, 0x6f, 0x7a, 0x5b, 0x64, - 0x55, 0xc0, 0xcb, 0x89, 0x07, 0x09, 0x34, 0x9b, 0x19, 0x46, 0x42, 0xd9, 0x7b, 0x30, 0x8c, 0xb9, - 0x01, 0x32, 0xa9, 0x48, 0x05, 0x06, 0xc2, 0xb5, 0x64, 0xba, 0xe2, 0xa7, 0x91, 0x4c, 0x57, 0x00, - 0xd8, 0x17, 0xa1, 0x52, 0x5f, 0x6f, 0xd2, 0x28, 0x4f, 0xe9, 0xe4, 0xeb, 0xcd, 0xf4, 0x95, 0x2f, - 0xd7, 0x78, 0x8e, 0x4b, 0x50, 0x08, 0xc2, 0xeb, 0x8b, 0x0d, 0x1a, 0x63, 0x9d, 0xf0, 0xfa, 0x62, - 0x23, 0x25, 0xdc, 0x32, 0xa3, 0x0f, 0xaf, 0x2f, 0x36, 0x3e, 0xbb, 0x45, 0xf4, 0xff, 0x2f, 0xcb, - 0xac, 0x36, 0xf2, 0xc3, 0xde, 0x83, 0x09, 0xe5, 0x94, 0xa8, 0x09, 0x3d, 0xf2, 0x76, 0x25, 0xd7, - 0xdf, 0x8c, 0x0b, 0x81, 0x41, 0xa0, 0xde, 0xcb, 0x4b, 0xbc, 0x73, 0xf5, 0xdb, 0xf7, 0x84, 0x43, - 0x36, 0xb4, 0xc7, 0x24, 0x61, 0x57, 0xa0, 0xba, 0xc6, 0x7d, 0xd7, 0x8f, 0x13, 0x33, 0x2f, 0xba, - 0x50, 0xc6, 0x08, 0x33, 0x75, 0x90, 0x04, 0x11, 0xbd, 0xfd, 0xfa, 0x1b, 0x51, 0x2b, 0xf4, 0x30, - 0xfb, 0x55, 0xb2, 0xb3, 0x4b, 0x6f, 0x3f, 0xad, 0xc4, 0x64, 0x90, 0x21, 0xb2, 0x7e, 0xbe, 0x04, - 0xa3, 0x34, 0x90, 0xf2, 0x9d, 0xd5, 0xad, 0x74, 0x67, 0x22, 0x67, 0xfe, 0x2d, 0x2f, 0xeb, 0xcc, - 0xbf, 0x25, 0x53, 0x4c, 0x8d, 0x51, 0x14, 0x5d, 0x72, 0xe1, 0x89, 0xb3, 0x51, 0x45, 0x06, 0x9b, - 0xcf, 0x58, 0x26, 0xa8, 0x87, 0x8d, 0xaa, 0xb2, 0x7e, 0x91, 0x5a, 0x76, 0x7d, 0xb1, 0xc1, 0x2e, - 0x43, 0x75, 0x35, 0x90, 0x69, 0xdb, 0xa8, 0x6d, 0x28, 0xc4, 0x3a, 0x04, 0xd3, 0x3b, 0x48, 0xe1, - 0x89, 0xf6, 0x35, 0xc2, 0x80, 0x4e, 0x46, 0x5a, 0xfb, 0x7a, 0x12, 0x98, 0x69, 0x5f, 0x82, 0x7a, - 0xe8, 0xf6, 0x7d, 0x58, 0x24, 0x24, 0xa4, 0x80, 0x59, 0x86, 0x31, 0xcc, 0x6a, 0xbb, 0xc3, 0xe9, - 0x4d, 0xdf, 0xf1, 0xcb, 0x2f, 0x0f, 0x12, 0x10, 0x4b, 0x0a, 0x91, 0x84, 0x54, 0x4a, 0x69, 0xad, - 0xc3, 0xd9, 0x83, 0xd0, 0xd9, 0x15, 0xa8, 0xb8, 0x0f, 0x55, 0xc6, 0xed, 0xe7, 0x07, 0x55, 0x52, - 0x5f, 0x6f, 0x12, 0x7b, 0x81, 0x6d, 0xfd, 0x5a, 0x49, 0xcf, 0x6d, 0x90, 0xc5, 0x61, 0x97, 0xe0, - 0x78, 0xc4, 0xe3, 0x7e, 0x4f, 0x59, 0xa7, 0x36, 0x83, 0xd0, 0x71, 0x43, 0xea, 0x79, 0x7b, 0x06, - 0xcb, 0xa4, 0x4d, 0xea, 0x5a, 0x10, 0xd6, 0x43, 0x9f, 0x9d, 0x85, 0x71, 0xcf, 0x8f, 0xf9, 0x16, - 0x85, 0xbc, 0xc8, 0x1d, 0x51, 0x07, 0xb1, 0xeb, 0x70, 0x32, 0xf9, 0x2e, 0x47, 0xbe, 0x94, 0xd6, - 0xc6, 0x50, 0x84, 0x8c, 0x16, 0x2e, 0xdf, 0x48, 0xb3, 0x83, 0x0e, 0xb7, 0x8f, 0x27, 0x04, 0x12, - 0xd8, 0x16, 0x50, 0x8b, 0x17, 0xf4, 0xfa, 0xfd, 0x2b, 0xe8, 0x6f, 0x7e, 0x53, 0x8f, 0x11, 0xa4, - 0x22, 0x25, 0x9f, 0xe7, 0x06, 0xf5, 0xcc, 0xfd, 0x2b, 0x76, 0x01, 0x95, 0xf5, 0xfb, 0x65, 0x9d, - 0x59, 0x93, 0x87, 0x3b, 0x4f, 0xf1, 0x4e, 0x5b, 0x7c, 0x47, 0x9b, 0xfd, 0xbc, 0x7d, 0x36, 0xda, - 0x2f, 0x66, 0x36, 0xda, 0xf9, 0x41, 0x2c, 0x48, 0xcf, 0x3d, 0xf4, 0x4e, 0xfb, 0x13, 0x15, 0x38, - 0x59, 0xdc, 0x0e, 0xbd, 0x6b, 0x4a, 0xfb, 0x74, 0xcd, 0x79, 0xa8, 0xde, 0x08, 0xa2, 0x58, 0x73, - 0xfd, 0xc2, 0x9b, 0xa9, 0x6d, 0x82, 0xd9, 0x49, 0x29, 0x7b, 0x01, 0x46, 0xc4, 0xdf, 0x89, 0x8c, - 0x45, 0x7e, 0x98, 0x90, 0xc7, 0x6b, 0xdb, 0x54, 0xc4, 0xae, 0x43, 0xd5, 0xa6, 0xe8, 0xb5, 0x4c, - 0x4f, 0x2b, 0x70, 0xa2, 0x60, 0xb3, 0x90, 0x20, 0xc6, 0x5b, 0x14, 0x04, 0x63, 0x75, 0x18, 0xa5, - 0xc9, 0x94, 0xf1, 0x6a, 0x28, 0x98, 0x81, 0xb2, 0x29, 0x74, 0x93, 0x6d, 0x2b, 0x3a, 0xb1, 0x2d, - 0xe0, 0xfd, 0xf4, 0xca, 0x92, 0x0a, 0x44, 0xc3, 0x6d, 0x41, 0xde, 0x5f, 0x9b, 0x5e, 0x76, 0x09, - 0x22, 0x9b, 0x87, 0xf1, 0x90, 0x77, 0xdc, 0x5d, 0xb9, 0xe7, 0x50, 0xbf, 0x03, 0x82, 0xe4, 0x66, - 0xf3, 0x0c, 0x8c, 0x49, 0x04, 0xaf, 0x4d, 0x56, 0x20, 0xbb, 0x8a, 0x80, 0x95, 0x76, 0x64, 0xad, - 0xc1, 0xec, 0xa0, 0x31, 0x15, 0xe7, 0xdc, 0xd8, 0x0d, 0xb7, 0x38, 0x1e, 0x2a, 0x3a, 0x14, 0xa5, - 0x9c, 0xba, 0x4d, 0xad, 0x61, 0xd9, 0x0d, 0x2c, 0xb2, 0x27, 0x62, 0xed, 0x97, 0xf5, 0xdd, 0x32, - 0x80, 0x32, 0x2e, 0x3e, 0xb5, 0x8b, 0xe8, 0x8b, 0xc6, 0x22, 0x3a, 0xa1, 0xe5, 0xcc, 0x14, 0x9f, - 0x75, 0xa8, 0x3b, 0xba, 0xbb, 0x30, 0x65, 0xe2, 0x1f, 0xfc, 0xb4, 0xf1, 0x5a, 0x6a, 0x77, 0xa5, - 0x88, 0x0e, 0xbc, 0xbd, 0x91, 0x70, 0x6b, 0x03, 0x8e, 0x5f, 0xe7, 0x71, 0x6a, 0x85, 0x55, 0x37, - 0xf5, 0xfb, 0xb3, 0x7d, 0x15, 0xc6, 0x08, 0x3f, 0xd9, 0x18, 0xa5, 0xc9, 0x90, 0xf2, 0x6e, 0xa1, - 0xc9, 0x50, 0x21, 0x08, 0x81, 0xbb, 0xc4, 0x3b, 0x3c, 0xe6, 0x9f, 0x6d, 0x35, 0x4d, 0x60, 0xf2, - 0x53, 0xf0, 0xcb, 0x0e, 0x57, 0xc3, 0x81, 0xfd, 0x73, 0x1f, 0x4e, 0x24, 0x6d, 0x7f, 0x92, 0x7c, - 0x2f, 0xc1, 0xb4, 0xd2, 0xe0, 0x34, 0x8e, 0xfb, 0xb8, 0x6a, 0x7d, 0xaf, 0x04, 0x73, 0x8a, 0x62, - 0xdd, 0x4b, 0x7c, 0xf4, 0x0e, 0x45, 0xcc, 0xde, 0x85, 0x71, 0x8d, 0x86, 0x82, 0x31, 0xf0, 0x5a, - 0xe7, 0xa1, 0x17, 0x6f, 0x3b, 0x91, 0x84, 0xeb, 0xd7, 0x3a, 0x1a, 0x3a, 0xdb, 0x80, 0xb9, 0x66, - 0xfd, 0xf6, 0x6a, 0x1a, 0x4e, 0x75, 0x27, 0xb8, 0x16, 0x74, 0x3a, 0xc1, 0xc3, 0x7b, 0xf6, 0xaa, - 0x7a, 0xfe, 0x0d, 0xd3, 0x79, 0xe0, 0x1d, 0x91, 0x16, 0x93, 0xe5, 0x07, 0xce, 0x26, 0x22, 0x3a, - 0xfd, 0xb0, 0x13, 0xd9, 0xfb, 0x70, 0xb1, 0xfe, 0x8b, 0x12, 0x3c, 0x93, 0xc4, 0xf6, 0x14, 0x7c, - 0x5f, 0xe6, 0x0b, 0x4a, 0x4f, 0xf2, 0x0b, 0xca, 0x4f, 0xe4, 0x0b, 0xee, 0xa4, 0xe3, 0xb3, 0xe2, - 0x27, 0x19, 0x00, 0x54, 0xfb, 0x99, 0x3e, 0x3e, 0x34, 0x2a, 0xcf, 0xe6, 0x72, 0x0a, 0x68, 0xa9, - 0x03, 0xac, 0x77, 0xb4, 0x0e, 0x29, 0x60, 0x68, 0x10, 0x97, 0xb2, 0xc4, 0xdf, 0x2d, 0xc3, 0xf4, - 0xdd, 0x95, 0xa5, 0xc5, 0xc4, 0xed, 0xf0, 0xa9, 0x15, 0x9a, 0xc5, 0x8e, 0x7d, 0xc6, 0xb7, 0x0d, - 0x96, 0x9c, 0xd6, 0x3d, 0x38, 0x96, 0xe9, 0x06, 0xd4, 0xf3, 0xbe, 0x22, 0x83, 0x34, 0x12, 0xb0, - 0xd2, 0xf1, 0x4e, 0x16, 0xb1, 0xbf, 0x7f, 0xc5, 0xce, 0x60, 0x5b, 0xff, 0x72, 0x22, 0xc3, 0x97, - 0x84, 0xf1, 0xab, 0x30, 0xb6, 0x12, 0x45, 0x7d, 0x1e, 0xde, 0xb3, 0x57, 0x75, 0xdb, 0x9c, 0x87, - 0x40, 0x31, 0x87, 0xec, 0x14, 0x81, 0x5d, 0x80, 0x2a, 0xbd, 0x79, 0xa1, 0xa4, 0x1b, 0x5e, 0x93, - 0x24, 0x4f, 0x66, 0xd8, 0x49, 0x31, 0x7b, 0x13, 0x26, 0xe4, 0xdf, 0x72, 0x46, 0x53, 0x87, 0xa3, - 0x35, 0x9e, 0xd0, 0xe5, 0x0a, 0xb0, 0x0d, 0x34, 0xf6, 0x0a, 0x54, 0xea, 0x8b, 0x36, 0xd9, 0x5f, - 0xe9, 0x68, 0x15, 0x3a, 0xd2, 0x48, 0x6e, 0x9c, 0xb3, 0x17, 0x6d, 0x71, 0x40, 0x52, 0xc9, 0x57, - 0xe8, 0xea, 0x08, 0x67, 0x80, 0x32, 0xef, 0x66, 0x54, 0x05, 0x84, 0xb1, 0x4b, 0x30, 0xba, 0xe4, - 0x45, 0xbd, 0x8e, 0xbb, 0x4b, 0x17, 0x47, 0xf2, 0xd5, 0x68, 0x09, 0x32, 0x92, 0x88, 0x48, 0x10, - 0xbb, 0xa0, 0x1e, 0x4c, 0xad, 0xa6, 0xb1, 0x1e, 0x03, 0x5e, 0x45, 0x4d, 0x43, 0xb1, 0xc7, 0x0e, - 0x0e, 0xc5, 0xce, 0x07, 0x51, 0xc3, 0x93, 0x0c, 0xa2, 0xde, 0x80, 0x53, 0xd7, 0xd1, 0x5c, 0x6a, - 0xa6, 0x15, 0xbc, 0x67, 0xaf, 0xd0, 0x05, 0x14, 0xde, 0xff, 0x4a, 0x8b, 0x6a, 0x36, 0x33, 0xa1, - 0xd3, 0x0f, 0x3d, 0xfd, 0x4a, 0x7e, 0x00, 0x23, 0xf6, 0x55, 0x38, 0x5e, 0x54, 0x44, 0xd7, 0x54, - 0x98, 0x8c, 0xa7, 0xb8, 0x02, 0x3d, 0x19, 0x4f, 0x11, 0x07, 0xb6, 0x0a, 0x35, 0x09, 0xaf, 0xb7, - 0xbb, 0x9e, 0x2f, 0xaf, 0xda, 0x26, 0xd3, 0x20, 0x5a, 0xe2, 0xea, 0x8a, 0x42, 0x79, 0xe5, 0x66, - 0x84, 0xeb, 0x64, 0x28, 0xd9, 0x4f, 0x95, 0x60, 0xc2, 0xe6, 0xf2, 0x1d, 0x05, 0x14, 0x9f, 0x53, - 0xe4, 0x3c, 0x90, 0xc4, 0xcf, 0x34, 0xe3, 0xd0, 0xf3, 0xb7, 0x28, 0x14, 0x67, 0x8d, 0x42, 0x71, - 0xde, 0xfd, 0x44, 0xa1, 0x38, 0x92, 0x55, 0xf4, 0x78, 0x6f, 0x7e, 0x22, 0xa4, 0x3a, 0x71, 0x15, - 0x19, 0x2d, 0x10, 0x5d, 0x87, 0x71, 0xd9, 0xf7, 0x7c, 0x99, 0xc5, 0x9d, 0xb7, 0xe5, 0x47, 0x4e, - 0xa3, 0x60, 0xc7, 0xae, 0x73, 0xa5, 0x10, 0x4f, 0x10, 0x72, 0x1f, 0x5a, 0xc8, 0x81, 0x2d, 0xc8, - 0xeb, 0x3f, 0xb1, 0x95, 0xca, 0xb0, 0xdc, 0x5a, 0x6a, 0x9b, 0x51, 0xb1, 0x21, 0x0e, 0x4e, 0x23, - 0x7d, 0xf2, 0x18, 0x24, 0xec, 0x12, 0x8c, 0xdc, 0x76, 0x1f, 0xd5, 0xe9, 0x86, 0x7d, 0xfc, 0xf2, - 0xa4, 0x12, 0x7f, 0x08, 0x5c, 0xa8, 0xfe, 0x73, 0x19, 0x1f, 0xf0, 0x39, 0x9b, 0xd0, 0xd8, 0x9f, - 0x2d, 0xc1, 0x49, 0xb9, 0x8c, 0xd5, 0x57, 0x36, 0x79, 0x1c, 0x8b, 0x7e, 0xa0, 0x2c, 0xac, 0x67, - 0x93, 0x2c, 0xef, 0x77, 0x8b, 0xf1, 0x30, 0x59, 0x84, 0x45, 0x92, 0x21, 0xe9, 0xb8, 0x88, 0x4a, - 0x8d, 0xbc, 0xfa, 0x85, 0xf4, 0x6c, 0x0d, 0xc6, 0x6f, 0x5f, 0xab, 0x27, 0xd5, 0x1e, 0x33, 0xce, - 0x6c, 0x86, 0xe4, 0xd3, 0xd0, 0x8a, 0xbc, 0xf3, 0x75, 0x36, 0x78, 0x1c, 0xb9, 0xb5, 0xb8, 0x8c, - 0xe9, 0x19, 0x8e, 0xa7, 0x56, 0xaa, 0xde, 0x83, 0x16, 0xcf, 0x66, 0x81, 0x4f, 0x10, 0xd9, 0x7b, - 0x32, 0x60, 0x10, 0xb3, 0xf8, 0x88, 0xd3, 0xf8, 0x89, 0x34, 0x55, 0xae, 0x7c, 0x29, 0x80, 0x0a, - 0x74, 0xfb, 0x9a, 0x4e, 0xc0, 0xee, 0x82, 0x4a, 0xf7, 0x70, 0x17, 0x2f, 0xb1, 0xb1, 0xfa, 0x93, - 0x69, 0x90, 0x8e, 0xf2, 0x9c, 0x09, 0xe4, 0x75, 0x77, 0xa6, 0x21, 0x79, 0x5a, 0x76, 0x0f, 0x66, - 0xb9, 0x1f, 0x87, 0xae, 0xe3, 0xb5, 0x29, 0xa9, 0x66, 0x72, 0x09, 0x46, 0x6f, 0xa3, 0xab, 0xeb, - 0x9f, 0x65, 0x81, 0xb6, 0xb2, 0x24, 0xaf, 0xc5, 0x95, 0xd4, 0xb4, 0x4f, 0x20, 0xf5, 0x4a, 0xdb, - 0x04, 0x53, 0xbc, 0xc1, 0x2e, 0x9c, 0x28, 0xa4, 0x62, 0x73, 0x50, 0x6d, 0x7b, 0x51, 0xfa, 0x82, - 0x57, 0xd5, 0x4e, 0x7e, 0xb3, 0x33, 0x00, 0x32, 0x19, 0xa1, 0xa8, 0x56, 0xa9, 0x12, 0x08, 0xc1, - 0x1b, 0xcd, 0x97, 0x60, 0x6a, 0x2b, 0x74, 0x7b, 0xdb, 0x0e, 0xf7, 0xdb, 0xbd, 0xc0, 0xf3, 0x69, - 0xff, 0xb0, 0x27, 0x11, 0xba, 0x4c, 0x40, 0xeb, 0x8b, 0x6a, 0xa2, 0xb2, 0xd7, 0xf4, 0x60, 0xe1, - 0x0a, 0x8e, 0xd2, 0x68, 0xd7, 0x7d, 0xe4, 0xb8, 0x5b, 0xdc, 0xf0, 0x5b, 0xa2, 0x7b, 0xbc, 0x9f, - 0x2d, 0xc1, 0xe9, 0x81, 0x73, 0x91, 0x5d, 0x85, 0x53, 0xae, 0xcc, 0x9a, 0xe0, 0x6c, 0xc7, 0x71, - 0x2f, 0x72, 0xd4, 0xc9, 0x5a, 0xa5, 0xb4, 0x3a, 0x41, 0xc5, 0x37, 0x44, 0xa9, 0x3a, 0x6c, 0x47, - 0xec, 0x3d, 0x78, 0xd6, 0xf3, 0x23, 0xde, 0xea, 0x87, 0xdc, 0x51, 0x0c, 0x5a, 0x5e, 0x3b, 0x74, - 0x42, 0xd7, 0xdf, 0x52, 0xb1, 0xd2, 0xf6, 0x69, 0x85, 0x43, 0x99, 0x19, 0x16, 0xbd, 0x76, 0x68, - 0x23, 0x82, 0xf5, 0xd7, 0xcb, 0x30, 0x3b, 0x68, 0xae, 0xb2, 0x59, 0x18, 0xe5, 0xbe, 0xde, 0x9b, - 0xea, 0xa7, 0x38, 0xde, 0x26, 0x5b, 0x30, 0xf5, 0x65, 0xb5, 0x45, 0x6f, 0x53, 0xb1, 0x17, 0xc0, - 0xdc, 0x70, 0xa9, 0x27, 0x27, 0x5a, 0xfa, 0xb6, 0x7b, 0x06, 0x20, 0xdd, 0x67, 0xa5, 0x51, 0xd5, - 0x1e, 0x73, 0x5b, 0xa1, 0x14, 0x89, 0xec, 0x24, 0x8c, 0xc8, 0x7d, 0x4c, 0x6e, 0xcc, 0x36, 0xfd, - 0x12, 0x0a, 0x15, 0x75, 0x32, 0x6e, 0xc0, 0x95, 0x85, 0x09, 0xa3, 0xb3, 0x47, 0xba, 0x72, 0x70, - 0x0a, 0xe7, 0xf3, 0xe8, 0x27, 0x9f, 0xcf, 0xd6, 0x5f, 0x9a, 0x94, 0xca, 0x62, 0xbd, 0x1f, 0x6f, - 0x2b, 0xf5, 0xf2, 0x72, 0x51, 0x34, 0x9d, 0x74, 0x97, 0xd7, 0xa2, 0xe9, 0xcc, 0x18, 0x3a, 0x75, - 0xad, 0x5e, 0x2e, 0xbc, 0x56, 0x7f, 0x15, 0xc6, 0x16, 0xb7, 0x79, 0xeb, 0x41, 0x12, 0xa2, 0x54, - 0xa5, 0x7b, 0x4b, 0x01, 0x94, 0x6f, 0x99, 0xa4, 0x08, 0xec, 0x12, 0x00, 0x06, 0xf1, 0xca, 0x53, - 0x94, 0xf6, 0x1e, 0x19, 0xc6, 0xfc, 0x92, 0x07, 0xa2, 0x86, 0x82, 0xec, 0x9b, 0xf6, 0x35, 0xdd, - 0x65, 0x51, 0xb2, 0x8f, 0xc2, 0x4d, 0x42, 0x4f, 0x11, 0xc4, 0xe7, 0x69, 0x3b, 0x08, 0xe9, 0x3b, - 0xb5, 0xdc, 0x36, 0xa3, 0x23, 0xb1, 0x2f, 0xc2, 0xe8, 0x22, 0x0f, 0xe3, 0xb5, 0xb5, 0x55, 0xf4, - 0x13, 0x94, 0xcf, 0x70, 0x55, 0xf1, 0xc9, 0xa4, 0x38, 0xee, 0x7c, 0x7f, 0x6f, 0x7e, 0x32, 0xf6, - 0xba, 0x3c, 0x79, 0x5e, 0xc4, 0x56, 0xd8, 0x6c, 0x01, 0x6a, 0xd2, 0x83, 0x28, 0x3d, 0xff, 0xa2, - 0x4e, 0x53, 0x95, 0x1a, 0x16, 0xb9, 0x1b, 0x3d, 0xe4, 0x1b, 0xc9, 0x83, 0x51, 0x39, 0x7c, 0xb6, - 0xac, 0xde, 0x59, 0xd3, 0x9b, 0x0d, 0xa9, 0x0c, 0xcd, 0xca, 0x7a, 0xd1, 0xfa, 0x3c, 0x05, 0xab, - 0xc3, 0xe4, 0x62, 0xd0, 0xed, 0xb9, 0xb1, 0x87, 0xcf, 0x2e, 0xef, 0x92, 0xfa, 0x82, 0xd2, 0xb4, - 0xa5, 0x17, 0x18, 0xba, 0x90, 0x5e, 0xc0, 0xae, 0xc1, 0x94, 0x1d, 0xf4, 0x45, 0xb7, 0x2b, 0xeb, - 0x94, 0xd4, 0x50, 0xd0, 0x9b, 0x2f, 0x14, 0x25, 0x42, 0xa1, 0x22, 0x53, 0x94, 0x91, 0x01, 0xdd, - 0xa0, 0x62, 0x77, 0x0a, 0x6e, 0x8e, 0x75, 0xb5, 0x44, 0x7f, 0x36, 0x2a, 0xc7, 0xac, 0xe0, 0xd2, - 0xf9, 0x0a, 0x8c, 0x37, 0x9b, 0x77, 0xd7, 0x78, 0x14, 0x5f, 0xeb, 0x04, 0x0f, 0x51, 0x2b, 0xa9, - 0xd2, 0x2b, 0xa7, 0x51, 0xe0, 0xc4, 0x62, 0x45, 0x6c, 0x76, 0x82, 0x87, 0xb6, 0x8e, 0xc5, 0xbe, - 0x25, 0xfa, 0x43, 0xd3, 0xe1, 0x29, 0xd7, 0xfb, 0x7e, 0xc7, 0x0c, 0xdc, 0xfb, 0xd3, 0x45, 0x20, - 0x0e, 0x1b, 0x66, 0x67, 0x69, 0xe8, 0x18, 0x41, 0x17, 0x06, 0x8f, 0x76, 0xeb, 0xed, 0x76, 0xc8, - 0xa3, 0x88, 0xd4, 0x07, 0x19, 0x41, 0x87, 0x46, 0x38, 0x57, 0x16, 0x18, 0x11, 0x74, 0x1a, 0x01, - 0x5b, 0x14, 0x7a, 0xad, 0x18, 0x45, 0xf4, 0x2f, 0x5d, 0x69, 0xa0, 0x06, 0x40, 0x57, 0x34, 0x34, - 0xe6, 0xd2, 0x13, 0xd5, 0xeb, 0x99, 0xea, 0xab, 0x46, 0xc3, 0x56, 0x60, 0x5a, 0x02, 0xc4, 0xd2, - 0x92, 0x6f, 0x1c, 0x1e, 0x4b, 0x5f, 0x59, 0x22, 0x36, 0xb8, 0x99, 0xe2, 0x3b, 0x87, 0x7a, 0x3e, - 0x86, 0x0c, 0x1d, 0x7b, 0x0f, 0xa6, 0xf0, 0x01, 0x99, 0xc4, 0xb1, 0x10, 0x37, 0xf2, 0x09, 0x99, - 0xd7, 0x9c, 0x4a, 0x32, 0xfe, 0x86, 0x13, 0x51, 0xb4, 0x9d, 0xba, 0x1b, 0xbe, 0x07, 0x53, 0xe8, - 0xd2, 0x98, 0x32, 0x38, 0x91, 0x32, 0xa0, 0x92, 0x2c, 0x83, 0xb8, 0x13, 0xa5, 0x0c, 0x7e, 0xa6, - 0x04, 0xa7, 0x45, 0x45, 0x6e, 0x2c, 0xc6, 0x54, 0x1e, 0xd3, 0x51, 0x28, 0xa0, 0xbf, 0xa6, 0x7c, - 0xfc, 0xea, 0xb5, 0x8b, 0x4a, 0xa9, 0xbc, 0xa8, 0xa1, 0x5d, 0xdc, 0x79, 0xe3, 0x62, 0x3d, 0xfd, - 0xd9, 0x54, 0x44, 0x32, 0xd3, 0xf3, 0x40, 0x9e, 0xba, 0xf2, 0x1e, 0x45, 0xdb, 0x45, 0x1c, 0xb0, - 0x51, 0xa2, 0xf1, 0xc5, 0x8d, 0x3a, 0xf5, 0x89, 0x1b, 0x35, 0x90, 0xa7, 0xde, 0xa8, 0xb8, 0x13, - 0x15, 0x36, 0xea, 0x2a, 0x4c, 0xa2, 0x6a, 0x45, 0x2a, 0x6d, 0x48, 0x4f, 0x6b, 0xe1, 0x9a, 0x30, - 0x0a, 0xec, 0x09, 0xf1, 0xf3, 0x3e, 0xfd, 0x62, 0x5f, 0x04, 0xf2, 0x6e, 0xde, 0x16, 0xaa, 0xc2, - 0xe9, 0xf4, 0xf0, 0x98, 0x42, 0xf5, 0x7b, 0x2f, 0x84, 0xde, 0xf0, 0xfc, 0x38, 0xb5, 0xd5, 0xcf, - 0x69, 0xb6, 0xfa, 0x9b, 0x43, 0xd5, 0xd1, 0x5a, 0xf5, 0xe6, 0x50, 0x75, 0xa6, 0xc6, 0xec, 0xb1, - 0x64, 0x1c, 0xed, 0x13, 0x85, 0xdf, 0x85, 0x96, 0x8b, 0x66, 0xfd, 0xf6, 0x6a, 0x7a, 0xfc, 0xfe, - 0xc1, 0x0a, 0x49, 0x34, 0xbe, 0x6d, 0x9f, 0x90, 0xc4, 0x7b, 0x32, 0xb8, 0x5c, 0xeb, 0x06, 0x65, - 0xb9, 0x30, 0xc0, 0x59, 0xcb, 0x45, 0x86, 0xc6, 0xce, 0x60, 0x5b, 0x7f, 0x63, 0x32, 0xc3, 0x97, - 0xc2, 0x10, 0x2c, 0x18, 0x91, 0x86, 0x09, 0xea, 0x64, 0xf4, 0x03, 0x93, 0x66, 0x0b, 0x9b, 0x4a, - 0xd8, 0x69, 0xa8, 0x34, 0x9b, 0x77, 0xa9, 0x93, 0x31, 0x18, 0x21, 0x8a, 0x02, 0x5b, 0xc0, 0xc4, - 0x08, 0x61, 0x84, 0x81, 0xf6, 0x78, 0x8f, 0xd8, 0x18, 0x6d, 0x84, 0x8a, 0xfe, 0x56, 0x66, 0x82, - 0xa1, 0xb4, 0xbf, 0xc9, 0x4c, 0x90, 0x1a, 0x07, 0x16, 0x61, 0xb6, 0x1e, 0x45, 0x3c, 0x14, 0x33, - 0x82, 0x1c, 0xd7, 0x43, 0x3a, 0xca, 0xd2, 0x8e, 0x8e, 0x95, 0xba, 0xad, 0xc8, 0x1e, 0x88, 0xc8, - 0xce, 0x43, 0xb5, 0xde, 0x6f, 0x7b, 0xdc, 0x6f, 0x19, 0xc9, 0x22, 0x5d, 0x82, 0xd9, 0x49, 0x29, - 0xfb, 0x00, 0x4e, 0x64, 0x92, 0xce, 0x52, 0x0f, 0x8c, 0xa6, 0x42, 0x5a, 0x1d, 0xb5, 0x53, 0x27, - 0x37, 0xd9, 0x25, 0xc5, 0x94, 0xac, 0x0e, 0xb5, 0x65, 0x0c, 0xc1, 0x5d, 0xe2, 0xf2, 0x86, 0x3c, - 0x08, 0x51, 0x57, 0x20, 0xc3, 0x08, 0x25, 0x64, 0x6e, 0x27, 0x85, 0x76, 0x0e, 0x9d, 0xdd, 0x82, - 0x63, 0x59, 0x98, 0xd8, 0xea, 0xa5, 0x0d, 0x04, 0x85, 0x64, 0x8e, 0x0b, 0x6e, 0xf6, 0x45, 0x54, - 0x6c, 0x03, 0x66, 0x52, 0x27, 0x4f, 0xd3, 0x32, 0xa2, 0x62, 0x58, 0x92, 0x72, 0x65, 0x1d, 0x79, - 0x86, 0x26, 0xe3, 0xb1, 0xd4, 0x61, 0x34, 0xb1, 0x90, 0xd8, 0x79, 0x76, 0xac, 0x0d, 0x53, 0x4d, - 0x6f, 0xcb, 0xf7, 0xfc, 0xad, 0x5b, 0x7c, 0xb7, 0xe1, 0x7a, 0x21, 0x45, 0x13, 0xcc, 0x26, 0xf7, - 0xb1, 0xbb, 0xdd, 0x2e, 0x8f, 0x43, 0x5c, 0xf5, 0xa2, 0x1c, 0x43, 0xf3, 0x4b, 0x98, 0x0b, 0x4e, - 0xd2, 0x09, 0x71, 0xef, 0xf4, 0x5c, 0xcf, 0xd0, 0x16, 0x4c, 0x9e, 0x86, 0x75, 0x6a, 0xe2, 0x90, - 0xd6, 0xa9, 0x0e, 0xcc, 0x2c, 0xfb, 0xad, 0x70, 0x17, 0x1d, 0x15, 0x54, 0xe3, 0x26, 0x0f, 0x68, - 0xdc, 0x8b, 0xd4, 0xb8, 0x67, 0x5d, 0x35, 0xc3, 0x8a, 0x9a, 0x97, 0x67, 0xcc, 0x9a, 0x30, 0x83, - 0x47, 0x90, 0x95, 0xa5, 0xc6, 0x8a, 0xef, 0xc5, 0x9e, 0x1b, 0xf3, 0x36, 0x69, 0x21, 0xc9, 0xdb, - 0x6b, 0xd2, 0x0a, 0xe1, 0xb5, 0x7b, 0x8e, 0xa7, 0x50, 0x74, 0xa6, 0x39, 0xfa, 0xfd, 0x4c, 0x01, - 0xd3, 0x7f, 0x48, 0xa6, 0x80, 0x15, 0x98, 0xce, 0x66, 0xb8, 0xa8, 0xa5, 0xca, 0x43, 0x84, 0x45, - 0x42, 0x07, 0x09, 0xfa, 0xa8, 0x75, 0x1a, 0xc9, 0x9c, 0x32, 0xb9, 0x2d, 0x32, 0x56, 0x85, 0x19, - 0xc3, 0xaa, 0x60, 0x48, 0xa5, 0xa3, 0x58, 0x15, 0x1a, 0x00, 0xd7, 0x82, 0xb0, 0xc5, 0xeb, 0x18, - 0xfd, 0xcf, 0x8c, 0x17, 0x2a, 0x05, 0xd3, 0xb4, 0x50, 0xae, 0x9f, 0x4d, 0xf1, 0xdb, 0xc9, 0x46, - 0xff, 0x6b, 0x3c, 0x98, 0x0b, 0xa7, 0x1a, 0x21, 0xdf, 0xe4, 0x61, 0xc8, 0xdb, 0x74, 0x20, 0x5a, - 0xf0, 0xfc, 0xb6, 0x7a, 0x76, 0x94, 0x9e, 0x86, 0xe8, 0x29, 0x94, 0x24, 0xf6, 0x66, 0x43, 0x22, - 0xe9, 0x7b, 0xf3, 0x00, 0x3e, 0x39, 0xab, 0xc6, 0xf1, 0xa3, 0x5a, 0x35, 0x96, 0x61, 0x6a, 0xc5, - 0x6f, 0x75, 0xfa, 0x6d, 0x4e, 0xbe, 0xf9, 0xf4, 0x68, 0x28, 0x6a, 0x86, 0x9e, 0x2c, 0x71, 0xc8, - 0x85, 0x5f, 0x5f, 0x57, 0x26, 0xd1, 0xbe, 0xb6, 0x8c, 0x93, 0x9f, 0xd8, 0x96, 0xc1, 0xea, 0x30, - 0xde, 0x0a, 0x39, 0xe6, 0x23, 0x70, 0x3b, 0x11, 0x29, 0x40, 0x85, 0x23, 0xbd, 0x98, 0xa2, 0xd9, - 0x3a, 0x8d, 0x75, 0x17, 0x66, 0x07, 0x21, 0xb2, 0x2b, 0x30, 0x1c, 0x88, 0x01, 0xcc, 0xbc, 0x30, - 0x7e, 0x57, 0x0c, 0x9f, 0x9c, 0xc6, 0x3a, 0x5b, 0x89, 0x6b, 0xfd, 0x10, 0x9c, 0x2c, 0x46, 0x30, - 0x4f, 0xfc, 0xa5, 0x83, 0x4e, 0xfc, 0xe5, 0xfc, 0x89, 0xdf, 0xfa, 0xf1, 0x72, 0xa6, 0xb5, 0x87, - 0x33, 0x35, 0x7c, 0x1e, 0xf2, 0x7b, 0x02, 0xf1, 0xaf, 0xf1, 0xec, 0xce, 0x70, 0x19, 0x8a, 0x45, - 0x3f, 0x99, 0x20, 0x8e, 0x65, 0x09, 0xee, 0x85, 0x1d, 0x76, 0x15, 0xc6, 0xb5, 0xe9, 0x8e, 0xbb, - 0xef, 0xa0, 0xc5, 0x61, 0xc3, 0x66, 0xba, 0x02, 0x4e, 0x02, 0x6d, 0xfe, 0xca, 0x44, 0x21, 0x7f, - 0xb1, 0x1a, 0x88, 0x3d, 0x5f, 0x6e, 0xac, 0xb6, 0xf8, 0x93, 0x31, 0xc0, 0x9d, 0x9e, 0xdc, 0x04, - 0xf0, 0x6f, 0xeb, 0x2f, 0x4e, 0x4a, 0x1d, 0x4e, 0x37, 0x28, 0x0c, 0x8a, 0xd2, 0xc9, 0x18, 0x1a, - 0xca, 0x47, 0x31, 0x34, 0x54, 0x0e, 0x36, 0x34, 0x0c, 0x1d, 0x64, 0x68, 0xc8, 0x58, 0x02, 0x86, - 0x8f, 0x68, 0x09, 0x18, 0x3d, 0x92, 0x25, 0xc0, 0x30, 0x52, 0x54, 0x0f, 0x32, 0x52, 0xfc, 0x89, - 0xdd, 0xe0, 0x69, 0xb5, 0x1b, 0x14, 0x29, 0xf9, 0x47, 0xb2, 0x1b, 0xe4, 0x8e, 0xfd, 0x33, 0x4f, - 0xe6, 0xd8, 0xcf, 0x9e, 0xd8, 0xb1, 0xff, 0xd8, 0xa7, 0x3d, 0xf6, 0x1f, 0x7f, 0x92, 0xc7, 0xfe, - 0x13, 0x7f, 0x1c, 0x8f, 0xfd, 0x27, 0xff, 0x68, 0x8e, 0xfd, 0x57, 0xa0, 0xda, 0x08, 0xa2, 0xf8, - 0x5a, 0x10, 0x76, 0x71, 0xe3, 0x9d, 0xa0, 0x5b, 0x96, 0x20, 0xc2, 0xb4, 0xcb, 0x5d, 0x43, 0x57, - 0x26, 0x44, 0xb6, 0xa0, 0x26, 0x9c, 0x3a, 0x18, 0xcf, 0xa6, 0x17, 0x5d, 0x34, 0x53, 0xe8, 0x7c, - 0x9c, 0x9f, 0x6f, 0xea, 0xbc, 0x7c, 0x07, 0x66, 0x54, 0x9c, 0x20, 0x6e, 0xad, 0x68, 0x73, 0x38, - 0x9d, 0xae, 0xcc, 0x34, 0xa0, 0x50, 0x95, 0x1a, 0x0f, 0x0f, 0x67, 0x49, 0x07, 0x9a, 0x13, 0x46, - 0x6a, 0xa3, 0x37, 0x87, 0xaa, 0xb5, 0xda, 0xcc, 0x21, 0xcc, 0x09, 0x3f, 0x0c, 0xb5, 0xec, 0x09, - 0xe7, 0xe0, 0x07, 0x01, 0x9e, 0x58, 0x8e, 0x5b, 0x71, 0xfe, 0xca, 0x9e, 0x30, 0xd8, 0x25, 0x80, - 0x46, 0xe8, 0xed, 0xb8, 0x31, 0xbf, 0xa5, 0x1c, 0xd1, 0xe9, 0x05, 0x0c, 0x09, 0x15, 0xcb, 0xc0, - 0xd6, 0x50, 0x92, 0xc3, 0x75, 0xb9, 0xe8, 0x70, 0x6d, 0xfd, 0x58, 0x19, 0x66, 0x64, 0x4e, 0xc5, - 0xa7, 0xdf, 0xd9, 0xe3, 0x2b, 0x86, 0xc9, 0xe4, 0xd9, 0xf4, 0x45, 0x27, 0xfd, 0xeb, 0xf6, 0x71, - 0xf7, 0xf8, 0x26, 0x9c, 0xc8, 0x75, 0x05, 0x9a, 0x4d, 0x96, 0x54, 0x36, 0xcb, 0x9c, 0xe1, 0x64, - 0xb6, 0xb8, 0x92, 0xfb, 0x57, 0xec, 0x1c, 0x85, 0xf5, 0xab, 0xc3, 0x39, 0xfe, 0xe4, 0xf8, 0xa1, - 0xbb, 0x72, 0x94, 0x8e, 0xe6, 0xca, 0x51, 0x3e, 0x9c, 0x2b, 0x47, 0x46, 0x4f, 0xa9, 0x1c, 0x46, - 0x4f, 0xf9, 0x00, 0x26, 0xd7, 0xb8, 0xdb, 0x8d, 0xd6, 0x02, 0x7a, 0x7a, 0x49, 0x06, 0x51, 0xaa, - 0x64, 0x95, 0xa2, 0x4c, 0x9d, 0xfa, 0x93, 0xf0, 0x8d, 0x58, 0x10, 0x88, 0x9d, 0x58, 0xbe, 0xc6, - 0x64, 0x9b, 0x1c, 0x74, 0x53, 0xce, 0xf0, 0x3e, 0xa6, 0x9c, 0x26, 0x4c, 0x10, 0x5d, 0xfa, 0x0a, - 0x42, 0x6a, 0x73, 0x10, 0x45, 0x08, 0x57, 0xb5, 0xab, 0xac, 0x0a, 0x53, 0x49, 0xed, 0xd2, 0xdc, - 0x60, 0x30, 0x11, 0x5d, 0xa0, 0xee, 0x2a, 0x45, 0x17, 0x8c, 0xa6, 0x5d, 0xa0, 0xee, 0x35, 0x65, - 0x17, 0x68, 0x48, 0xec, 0x5d, 0x98, 0xaa, 0x37, 0x56, 0x74, 0xb2, 0x6a, 0xea, 0x4d, 0xe2, 0xf6, - 0x3c, 0xc7, 0x20, 0xcd, 0xe0, 0xee, 0x77, 0xfc, 0x1e, 0xfb, 0x43, 0x3a, 0x7e, 0x67, 0x0f, 0x8a, - 0x70, 0xc4, 0x83, 0xa2, 0xf5, 0x2f, 0x26, 0x94, 0x7c, 0xf8, 0x6c, 0xef, 0xf7, 0xcc, 0x1b, 0xbb, - 0xca, 0x11, 0x6f, 0xec, 0x86, 0x0e, 0x52, 0x86, 0x35, 0x9d, 0x7b, 0xe4, 0x53, 0xdf, 0xbe, 0x8d, - 0x1e, 0x51, 0x8b, 0xce, 0x2c, 0xbe, 0xea, 0x61, 0x16, 0x5f, 0xa1, 0xe6, 0x3d, 0xf6, 0xe9, 0x35, - 0x6f, 0x38, 0xb2, 0xe6, 0xdd, 0x4c, 0xb3, 0xa5, 0x8c, 0x1f, 0x18, 0xfc, 0x79, 0x86, 0x6c, 0x55, - 0x33, 0xc5, 0x29, 0x33, 0x93, 0xbc, 0x29, 0x3f, 0x50, 0xea, 0xfc, 0x87, 0xc5, 0xea, 0xfc, 0xfe, - 0x1b, 0xd0, 0x9f, 0x28, 0xf4, 0x7f, 0xa2, 0xd0, 0xff, 0xa1, 0x28, 0xf4, 0x77, 0x81, 0xb9, 0xfd, - 0x78, 0x5b, 0xe8, 0xc5, 0x2d, 0x4c, 0xdb, 0x2c, 0x86, 0x18, 0x55, 0x7b, 0x5a, 0x23, 0xf9, 0x52, - 0x7d, 0x8d, 0x18, 0xa5, 0x68, 0xf3, 0x48, 0x14, 0xeb, 0x59, 0x53, 0xb1, 0x1e, 0xae, 0x8d, 0x1c, - 0x5a, 0xb1, 0x0e, 0x71, 0x9d, 0xad, 0xbb, 0xa1, 0x8f, 0xdb, 0xd4, 0x25, 0x18, 0x55, 0xc9, 0x83, - 0x4b, 0xe9, 0x85, 0x47, 0x3e, 0x6b, 0xb0, 0xc2, 0x62, 0x97, 0xa1, 0xaa, 0x88, 0xf5, 0x17, 0xe8, - 0x1e, 0x12, 0xcc, 0xc8, 0xcb, 0x4a, 0x30, 0xeb, 0x3f, 0x18, 0x52, 0xb2, 0x5c, 0x7c, 0x46, 0xc3, - 0x0d, 0xdd, 0x2e, 0x3e, 0x7d, 0x9c, 0x2c, 0x35, 0x4d, 0xad, 0xcf, 0xac, 0xce, 0x4c, 0xfc, 0x9d, - 0x49, 0xf2, 0x89, 0xb2, 0x3f, 0xbf, 0x0a, 0x23, 0xa4, 0x55, 0xc9, 0x73, 0x00, 0x6a, 0x14, 0xb9, - 0x27, 0x2c, 0x09, 0x87, 0xbd, 0x25, 0x23, 0x6f, 0x29, 0x75, 0xca, 0x50, 0xfa, 0x16, 0xb8, 0x90, - 0x6f, 0x05, 0x6f, 0x81, 0xa7, 0xc8, 0xec, 0xaa, 0x8c, 0x36, 0xd5, 0x5f, 0xd2, 0x49, 0x62, 0x76, - 0x73, 0x0f, 0x28, 0xa4, 0xa8, 0xe9, 0x39, 0x65, 0xe4, 0x28, 0x79, 0xd5, 0x47, 0xff, 0x48, 0xf3, - 0xaa, 0x2f, 0x03, 0xd0, 0x9e, 0x9b, 0xba, 0xd9, 0xbc, 0x84, 0x22, 0x89, 0x42, 0x3e, 0xe2, 0xb8, - 0x33, 0xe0, 0xdd, 0x2b, 0x8d, 0xd0, 0xfa, 0xa7, 0x0c, 0x66, 0x9a, 0xcd, 0xbb, 0x4b, 0x9e, 0xbb, - 0xe5, 0x07, 0x51, 0xec, 0xb5, 0x56, 0xfc, 0xcd, 0x40, 0x28, 0xe9, 0xc9, 0xbe, 0xa0, 0x65, 0xc4, - 0x4e, 0xf7, 0x84, 0xa4, 0x58, 0x1c, 0x02, 0x97, 0xc3, 0x50, 0x19, 0x63, 0xe5, 0x21, 0x90, 0x0b, - 0x80, 0x2d, 0xe1, 0x42, 0x0f, 0x6e, 0xf6, 0x31, 0x80, 0x91, 0x7c, 0x99, 0x50, 0x0f, 0x8e, 0x24, - 0xc8, 0x56, 0x65, 0x8c, 0xe7, 0x27, 0x2c, 0x9d, 0x8b, 0x4e, 0x19, 0xd9, 0xd9, 0xd3, 0x62, 0xb9, - 0xa2, 0x49, 0x2b, 0x41, 0x59, 0xde, 0x43, 0xb8, 0xee, 0x93, 0x9b, 0x5b, 0x03, 0xbb, 0x70, 0xc2, - 0x48, 0x73, 0x72, 0xd8, 0xbb, 0xbe, 0x57, 0x48, 0xef, 0xb6, 0x30, 0xee, 0xa1, 0xe0, 0xc2, 0x4f, - 0x7f, 0xcb, 0xb8, 0xb0, 0x06, 0xf6, 0x63, 0x25, 0x38, 0x53, 0x58, 0x92, 0xac, 0xee, 0x71, 0x23, - 0x43, 0xbe, 0x26, 0x34, 0xe4, 0xab, 0xcd, 0x83, 0xaa, 0xfe, 0xff, 0xd8, 0x7b, 0xd7, 0x18, 0x39, - 0x92, 0xe4, 0x30, 0x98, 0xd5, 0x3d, 0x8f, 0x9e, 0x98, 0x57, 0x4d, 0x72, 0x48, 0x0e, 0x1f, 0xcb, - 0x21, 0x6b, 0x77, 0xb9, 0xdc, 0xd9, 0x5d, 0xee, 0x92, 0x7b, 0xfb, 0xe0, 0xde, 0x3e, 0xae, 0xa7, - 0xbb, 0x87, 0xd3, 0x64, 0x4f, 0x77, 0x6f, 0x55, 0x0f, 0xf9, 0xf1, 0xf6, 0x4e, 0xa5, 0x62, 0x77, - 0xcd, 0x4c, 0x2d, 0x7b, 0xba, 0x7a, 0xab, 0xaa, 0x49, 0xce, 0x41, 0x1f, 0xa4, 0x93, 0xbe, 0xef, - 0x04, 0xc8, 0x38, 0xe9, 0xf4, 0x30, 0xbc, 0x38, 0xc8, 0xb6, 0x2c, 0xcb, 0x82, 0x7e, 0x18, 0xb6, - 0x60, 0xfd, 0xb0, 0xa0, 0x03, 0x6c, 0xeb, 0x9f, 0x00, 0x41, 0x96, 0xfc, 0x80, 0x0c, 0x9f, 0x85, - 0x85, 0x74, 0xfa, 0x63, 0x1f, 0xe4, 0x5f, 0x82, 0x0d, 0x58, 0x80, 0x0c, 0x23, 0x23, 0x33, 0xab, - 0x32, 0xab, 0xab, 0x7b, 0x86, 0x47, 0x9e, 0xec, 0x93, 0xf5, 0x87, 0x9c, 0x8e, 0x8c, 0x88, 0xca, - 0x67, 0x64, 0x64, 0x64, 0x64, 0x84, 0x9d, 0x21, 0x0a, 0xc6, 0x7f, 0x89, 0x7c, 0x4b, 0x83, 0x53, - 0x0a, 0x46, 0x2c, 0x43, 0xc3, 0x38, 0x12, 0x59, 0xe6, 0xbc, 0xfe, 0xf8, 0xe9, 0xcc, 0xeb, 0x67, - 0xd5, 0xb6, 0x24, 0xd2, 0x52, 0x6e, 0xc3, 0xa8, 0x1a, 0x92, 0x07, 0xb0, 0x84, 0x45, 0xe2, 0xde, - 0x91, 0xce, 0x59, 0x7e, 0x5d, 0xb9, 0x9c, 0x54, 0x9b, 0x85, 0xee, 0xa1, 0x1a, 0xf7, 0xfa, 0xb5, - 0x6f, 0x7f, 0xb6, 0x3a, 0xaf, 0xa0, 0x8b, 0x98, 0xf3, 0x76, 0x72, 0x79, 0xe9, 0xf5, 0x76, 0x7c, - 0xc5, 0xcc, 0x92, 0xfe, 0x04, 0xf9, 0x97, 0x1a, 0xbb, 0xbb, 0x60, 0xcd, 0xd8, 0x08, 0xfc, 0xfd, - 0xb8, 0x5c, 0x38, 0x77, 0x8f, 0xe8, 0xb6, 0xee, 0xd3, 0xe9, 0xb6, 0xe7, 0xb1, 0xca, 0x4c, 0x26, - 0xd8, 0x3b, 0x81, 0xbf, 0x9f, 0x54, 0x5f, 0xee, 0xb8, 0x91, 0x95, 0x24, 0x5f, 0xd5, 0xe0, 0xb4, - 0x62, 0x72, 0x95, 0xb3, 0x1a, 0xf1, 0x00, 0x49, 0xf1, 0x23, 0x43, 0xa9, 0x68, 0xfd, 0x0a, 0x9f, - 0xff, 0x97, 0xb0, 0x06, 0xc9, 0x6e, 0x81, 0x75, 0xb1, 0xf7, 0x19, 0x96, 0x54, 0x85, 0xd1, 0x5f, - 0x21, 0x1e, 0x2c, 0xa1, 0xb7, 0x98, 0xf2, 0x08, 0x61, 0x79, 0xf4, 0x23, 0x84, 0x38, 0xb3, 0x1e, - 0xa6, 0xfd, 0x18, 0xfd, 0x12, 0x61, 0x98, 0x2b, 0xf9, 0x7f, 0xe1, 0xf4, 0x10, 0x30, 0x5e, 0x6d, - 0x27, 0x46, 0xae, 0xb6, 0x97, 0xbe, 0xfb, 0xd9, 0xea, 0x0b, 0x59, 0x5f, 0xcb, 0x5a, 0x69, 0xa3, - 0xbf, 0x40, 0x1c, 0x80, 0xa4, 0x90, 0xeb, 0x44, 0xd9, 0x13, 0xf4, 0x25, 0x3e, 0x3f, 0x24, 0x7c, - 0x2a, 0xcb, 0xa5, 0x3a, 0xc8, 0x5b, 0x5e, 0x82, 0x44, 0x5c, 0x98, 0x93, 0x52, 0x9e, 0x1c, 0xf0, - 0xfb, 0xc3, 0x11, 0x1f, 0xf9, 0xf6, 0x67, 0xab, 0x0a, 0x36, 0x3d, 0x19, 0xc9, 0xb9, 0x54, 0x14, - 0x15, 0x54, 0x46, 0x24, 0xbf, 0xa1, 0xc1, 0x32, 0x05, 0x24, 0x93, 0x8a, 0x37, 0x6a, 0x65, 0xdc, - 0xac, 0xdf, 0x7b, 0x3a, 0xb3, 0xfe, 0x22, 0xd6, 0x51, 0x9e, 0xf5, 0x43, 0x5d, 0x92, 0x59, 0x39, - 0x9c, 0xed, 0x8a, 0x63, 0xa2, 0x32, 0xdb, 0x4f, 0x1f, 0x61, 0xb6, 0xb3, 0x01, 0x38, 0x7c, 0xb6, - 0x8f, 0xfc, 0x0a, 0x69, 0xc1, 0x1c, 0x3f, 0x14, 0xb1, 0x0e, 0x3b, 0xaf, 0x3c, 0xe4, 0x95, 0x8b, - 0xd8, 0x49, 0x95, 0x67, 0x84, 0x19, 0x6a, 0xa1, 0xc2, 0x85, 0xf4, 0xe0, 0x38, 0xfb, 0xad, 0x5a, - 0xad, 0x56, 0x47, 0x5a, 0xad, 0x2e, 0xf3, 0x16, 0x5d, 0xe0, 0xfc, 0x53, 0xc6, 0x2b, 0x39, 0xf2, - 0x63, 0x06, 0x63, 0xd2, 0x07, 0xa2, 0x80, 0xd9, 0xa2, 0xbd, 0x30, 0xde, 0x56, 0xf5, 0x02, 0xff, - 0xe6, 0x6a, 0xfa, 0x9b, 0xe9, 0x95, 0x9b, 0xc1, 0x9b, 0x38, 0xb0, 0xc8, 0xa1, 0xfe, 0x7d, 0x97, - 0x49, 0xf8, 0x8b, 0x4a, 0xec, 0xcd, 0x54, 0x29, 0x3b, 0xd9, 0x89, 0x2f, 0x61, 0x6c, 0xd4, 0x94, - 0x40, 0x4f, 0xf3, 0x23, 0x0d, 0x58, 0x2a, 0xf6, 0xfb, 0x5d, 0xcf, 0xed, 0x60, 0x2b, 0xcd, 0x01, - 0x6d, 0x93, 0x91, 0xe4, 0x34, 0x75, 0x58, 0x21, 0x3f, 0x6e, 0x06, 0x83, 0x94, 0xb8, 0x19, 0xa2, - 0x35, 0x7e, 0x52, 0x1b, 0xaa, 0x34, 0x79, 0x19, 0x66, 0xf0, 0x87, 0x14, 0x46, 0x0d, 0x6d, 0x37, - 0xac, 0x8a, 0x68, 0x15, 0x4a, 0x10, 0xa8, 0xb2, 0x24, 0x87, 0x74, 0xce, 0x33, 0x65, 0x89, 0x1b, - 0x18, 0x12, 0x93, 0xc2, 0xaa, 0x78, 0x1c, 0x96, 0x4f, 0x94, 0x2e, 0x3c, 0x9a, 0xf0, 0x27, 0x61, - 0xc6, 0xaf, 0xe7, 0xd4, 0x69, 0x47, 0x2e, 0x4b, 0x7a, 0xbb, 0x14, 0x54, 0x5a, 0xe8, 0xed, 0x92, - 0xb6, 0xfe, 0x8f, 0x34, 0x38, 0xde, 0x90, 0x32, 0x6d, 0xb7, 0x7c, 0x1c, 0x97, 0xf1, 0x39, 0xa5, - 0x9f, 0x56, 0x9a, 0x5b, 0x39, 0xc5, 0x37, 0x9d, 0x29, 0x38, 0x65, 0xcc, 0xac, 0xfa, 0xe0, 0xc3, - 0x61, 0xac, 0x98, 0x94, 0x6d, 0x98, 0xa1, 0x33, 0xf8, 0x63, 0xe6, 0x87, 0x31, 0x7e, 0x3a, 0x07, - 0xb3, 0xd2, 0x8a, 0x21, 0x9f, 0x83, 0x39, 0xf9, 0xb3, 0xb2, 0xe1, 0x4f, 0xae, 0xa5, 0xa9, 0x60, - 0xa1, 0xe5, 0xcf, 0x75, 0xf6, 0x15, 0xcb, 0x1f, 0x5d, 0x17, 0x08, 0x7d, 0xcc, 0x93, 0xd0, 0x07, - 0x19, 0x27, 0x21, 0x9c, 0xe5, 0x92, 0xa5, 0x67, 0xec, 0x79, 0xe8, 0xdd, 0xe1, 0xf3, 0x10, 0x1a, - 0x9d, 0x24, 0xfa, 0xd1, 0xa7, 0x22, 0xe3, 0x67, 0x35, 0xd0, 0xd3, 0x6b, 0xfa, 0xfb, 0xd2, 0x2b, - 0x8f, 0x71, 0x4d, 0xf4, 0xf5, 0x5c, 0x9c, 0x1e, 0x49, 0x44, 0x74, 0xf8, 0x41, 0x75, 0x7a, 0x7d, - 0x4f, 0xb9, 0xc1, 0x39, 0xab, 0xc6, 0xcd, 0x95, 0x03, 0x5a, 0x65, 0x07, 0xcb, 0x9e, 0xf8, 0xf4, - 0x97, 0x56, 0x8f, 0x19, 0x77, 0x61, 0x39, 0xdd, 0x1d, 0x78, 0x8b, 0x53, 0x84, 0x45, 0x15, 0x9e, - 0x4e, 0xae, 0x96, 0xa6, 0x32, 0xd3, 0xf8, 0xc6, 0x1f, 0xe4, 0xd2, 0xbc, 0xb9, 0x03, 0x2c, 0x95, - 0x51, 0xb2, 0x93, 0x0e, 0x97, 0x51, 0x0c, 0x64, 0x8a, 0xb2, 0xc7, 0xc9, 0xd4, 0x18, 0xc7, 0x00, - 0xc8, 0x67, 0xc7, 0x00, 0x20, 0x6f, 0xa6, 0x1e, 0x07, 0x48, 0x71, 0x15, 0x1f, 0xba, 0xf7, 0xec, - 0xe4, 0x81, 0xc0, 0xd0, 0x9b, 0x80, 0x65, 0x25, 0x59, 0x89, 0xa0, 0x9f, 0x4c, 0x6c, 0xee, 0x11, - 0x16, 0x30, 0xe2, 0x4c, 0x64, 0xb2, 0x09, 0xd3, 0xb4, 0x9a, 0x5b, 0x4e, 0x9f, 0x5f, 0xce, 0x90, - 0x38, 0x4a, 0x49, 0x37, 0x3e, 0x1f, 0x4a, 0x81, 0x4a, 0xba, 0x2e, 0xd5, 0x10, 0xe4, 0x89, 0xc5, - 0x11, 0x8d, 0x3f, 0xd7, 0xe8, 0xfa, 0x6f, 0xdf, 0xff, 0x6b, 0x96, 0x19, 0x91, 0x36, 0x69, 0x8c, - 0x7f, 0xf6, 0x9f, 0xe4, 0x58, 0xc2, 0x2b, 0x3e, 0x7d, 0xae, 0xc3, 0x14, 0x0b, 0x5f, 0xc2, 0x5d, - 0xd2, 0x64, 0x2e, 0xac, 0x20, 0x89, 0xfa, 0xc8, 0x22, 0x9b, 0x98, 0x9c, 0x40, 0x36, 0x9d, 0xe5, - 0x8e, 0x64, 0x3a, 0x93, 0xec, 0xf9, 0xf9, 0xa7, 0x66, 0xcf, 0xff, 0x62, 0x9c, 0xdb, 0xaa, 0x18, - 0x1d, 0x21, 0x17, 0xc6, 0x85, 0x74, 0x2a, 0xb9, 0xa1, 0xac, 0x25, 0x09, 0x3b, 0xf2, 0xa6, 0x9c, - 0x9c, 0x4e, 0x7a, 0x8c, 0x7e, 0x48, 0x1a, 0x3a, 0xe3, 0xeb, 0x93, 0xac, 0x8f, 0x79, 0x47, 0x5d, - 0x52, 0x42, 0x6e, 0xe0, 0x3a, 0x49, 0xd9, 0x3a, 0x59, 0xf0, 0x8d, 0x4b, 0x30, 0x41, 0xe7, 0x26, - 0xef, 0x4d, 0xc4, 0xa3, 0xf3, 0x57, 0xc6, 0xa3, 0xe5, 0x74, 0x2d, 0xe3, 0x9e, 0x24, 0xa7, 0x52, - 0xc5, 0x6d, 0x4b, 0x5e, 0xcb, 0x88, 0x41, 0x5b, 0x10, 0x27, 0xa1, 0x92, 0x5b, 0xb0, 0xbf, 0xe3, - 0x0c, 0xe7, 0x88, 0x4c, 0x50, 0x49, 0x05, 0x16, 0xee, 0x78, 0xbd, 0x8e, 0xff, 0x30, 0x2c, 0xbb, - 0xe1, 0xfd, 0xc8, 0xef, 0x73, 0x9f, 0x74, 0xb4, 0xfb, 0x3f, 0x64, 0x25, 0x76, 0x87, 0x15, 0xc9, - 0x97, 0x24, 0x2a, 0x11, 0x59, 0x87, 0x79, 0x25, 0x82, 0x3b, 0xbf, 0xfb, 0x44, 0x1b, 0xa7, 0x1a, - 0xff, 0x5d, 0xb6, 0x71, 0x2a, 0x24, 0x74, 0x97, 0xe6, 0xf5, 0x97, 0x6e, 0x40, 0x87, 0xea, 0xce, - 0x71, 0xc8, 0xeb, 0x50, 0x60, 0xc1, 0x7b, 0xaa, 0x65, 0xf9, 0xd2, 0x2a, 0x44, 0x58, 0x2a, 0xa0, - 0x9c, 0x40, 0x24, 0x25, 0x98, 0x5f, 0xf7, 0xa3, 0x6a, 0x2f, 0x8c, 0x9c, 0x5e, 0xdb, 0x8d, 0xe3, - 0xd5, 0x63, 0x63, 0xef, 0xf9, 0x91, 0xed, 0xf1, 0x12, 0x95, 0x5e, 0xa5, 0xa1, 0x5d, 0x7d, 0xd3, - 0xf7, 0x7a, 0x4c, 0x74, 0xce, 0x26, 0x5d, 0xfd, 0xb1, 0xef, 0xf5, 0x86, 0x42, 0xc4, 0x27, 0xa8, - 0xe4, 0x03, 0x98, 0xab, 0x79, 0xbd, 0xc1, 0x23, 0xd1, 0xd1, 0x73, 0xc9, 0x3d, 0x59, 0x97, 0xc2, - 0x33, 0xba, 0x59, 0x21, 0x78, 0x67, 0x99, 0xee, 0x3d, 0x7f, 0xf0, 0x0f, 0x56, 0x8f, 0x7d, 0xe3, - 0x97, 0x57, 0x8f, 0x7d, 0xfa, 0xcb, 0x2c, 0xe2, 0x0e, 0x7b, 0x95, 0x6b, 0x4e, 0xd4, 0xfd, 0x8e, - 0x6b, 0xf0, 0x1c, 0x77, 0x3c, 0x52, 0xf9, 0x4b, 0x30, 0xcd, 0x56, 0x72, 0x3a, 0xad, 0x77, 0x32, - 0x65, 0x4d, 0x81, 0x41, 0x0c, 0x98, 0xf7, 0x7a, 0x36, 0xf3, 0xc5, 0xf4, 0x7b, 0x5d, 0x96, 0x0f, - 0xad, 0x60, 0xce, 0x7a, 0x3d, 0xf4, 0xc0, 0x6c, 0xf4, 0xba, 0x07, 0xc6, 0x6b, 0xa0, 0x73, 0xe9, - 0x1c, 0x67, 0x11, 0x46, 0xdf, 0x8f, 0x6a, 0xd9, 0x94, 0x25, 0x6a, 0xdb, 0xeb, 0x04, 0x26, 0x42, - 0xf1, 0xe1, 0x6d, 0xdd, 0x8d, 0x1e, 0xfa, 0xc1, 0x7d, 0xd3, 0x0d, 0xa3, 0xc0, 0x63, 0x49, 0x89, - 0x51, 0x26, 0x7d, 0x8e, 0xbc, 0x0b, 0x93, 0xe8, 0x51, 0x9e, 0xda, 0x24, 0xd3, 0xdf, 0x58, 0x9f, - 0xe7, 0x6b, 0x79, 0x12, 0xdd, 0xd3, 0x4d, 0x46, 0x44, 0xae, 0xc3, 0x44, 0xd9, 0xed, 0x1d, 0xa4, - 0x52, 0x8b, 0x0e, 0x11, 0xc7, 0xb2, 0xb1, 0xe3, 0xf6, 0x0e, 0x4c, 0x24, 0x31, 0x7e, 0x36, 0x07, - 0x27, 0x32, 0xaa, 0x75, 0xfb, 0x73, 0x3f, 0xa0, 0x1b, 0xc4, 0xba, 0xb2, 0x41, 0x88, 0x3b, 0xff, - 0x91, 0x1d, 0x9f, 0xb9, 0x5f, 0xfc, 0xa2, 0x06, 0xa7, 0xd4, 0x55, 0xcd, 0x9f, 0x90, 0xdc, 0x7e, - 0x9d, 0x7c, 0x1e, 0xa6, 0x36, 0x5d, 0xa7, 0xe3, 0x8a, 0x3c, 0x82, 0xe9, 0xb4, 0xe1, 0xac, 0x90, - 0xb1, 0x4d, 0xa2, 0x05, 0x30, 0x28, 0x29, 0xf3, 0xca, 0xb1, 0x93, 0x8c, 0x21, 0xa2, 0x4a, 0x65, - 0x7d, 0x6a, 0x8c, 0xe7, 0xcc, 0xa7, 0x39, 0x38, 0x3b, 0x86, 0x86, 0x0e, 0x1c, 0x1d, 0x7a, 0x79, - 0xe0, 0x50, 0xb9, 0x40, 0x28, 0x79, 0x1f, 0x16, 0x5b, 0xfc, 0x24, 0x24, 0x86, 0x23, 0x97, 0x08, - 0x19, 0x71, 0x48, 0x12, 0x1e, 0x64, 0x66, 0x1a, 0x59, 0x09, 0xc1, 0x96, 0x1f, 0x1b, 0x82, 0x4d, - 0x8e, 0x68, 0x36, 0xf1, 0x3d, 0x46, 0x34, 0x9b, 0x1c, 0x1f, 0xd1, 0x6c, 0x2a, 0x15, 0xd1, 0xec, - 0x9b, 0x1a, 0x2c, 0xab, 0x5d, 0xc3, 0x05, 0x40, 0x12, 0x0e, 0x4e, 0x1b, 0x1d, 0x0e, 0x6e, 0x7c, - 0x40, 0xf4, 0x0f, 0x60, 0xca, 0xc2, 0x21, 0xc6, 0x66, 0x2f, 0xc4, 0x0a, 0x73, 0x6a, 0x28, 0x10, - 0x85, 0xbd, 0xc9, 0xe2, 0x31, 0xa3, 0x38, 0x99, 0xf1, 0xfb, 0x1a, 0xe8, 0x2a, 0xf2, 0x93, 0xce, - 0xa7, 0xf7, 0x94, 0xf9, 0x34, 0xa2, 0x42, 0xa3, 0x03, 0xfd, 0x7d, 0x10, 0x07, 0xfa, 0xcb, 0x8f, - 0x63, 0xc0, 0x52, 0xe4, 0x82, 0x1c, 0x04, 0x9b, 0xfd, 0x6f, 0xdc, 0x4a, 0xf7, 0x76, 0x1c, 0xa6, - 0x92, 0xb7, 0x9d, 0x27, 0x88, 0x1e, 0xd7, 0x55, 0x26, 0x47, 0x35, 0xfe, 0xeb, 0xd0, 0xd8, 0x1d, - 0x69, 0x3e, 0x1b, 0x30, 0x55, 0xf6, 0xf7, 0x1d, 0x4f, 0x4c, 0x63, 0xac, 0x67, 0x07, 0x21, 0x26, - 0x2f, 0x39, 0x5a, 0x30, 0xc0, 0x0b, 0x30, 0x59, 0xf7, 0x7b, 0xc5, 0x32, 0xf7, 0x43, 0x47, 0x3e, - 0x3d, 0xbf, 0x67, 0x3b, 0x1d, 0x93, 0x15, 0x90, 0x1a, 0x80, 0xd5, 0x0e, 0x5c, 0xb7, 0x67, 0x79, - 0x5f, 0x71, 0x53, 0x2a, 0x28, 0x1d, 0xaf, 0xee, 0x00, 0xc5, 0x2c, 0xbb, 0x97, 0x47, 0x44, 0x3b, - 0xf4, 0xbe, 0x22, 0x6f, 0xd9, 0x12, 0x3d, 0x4a, 0x19, 0x1e, 0xf4, 0x36, 0x35, 0x2b, 0xae, 0x7e, - 0x3f, 0xa4, 0x4c, 0xe6, 0xa7, 0xb0, 0x87, 0xaf, 0x66, 0x4a, 0x99, 0xdf, 0xd7, 0xe0, 0xec, 0x18, - 0x9a, 0xa7, 0x30, 0x2a, 0x7f, 0xd5, 0x1d, 0xee, 0x02, 0x24, 0x44, 0x98, 0x9c, 0xdf, 0xeb, 0xf0, - 0x77, 0x29, 0xf3, 0x3c, 0x39, 0x3f, 0x05, 0x28, 0xc9, 0xf9, 0x29, 0x80, 0xaa, 0x63, 0x9b, 0xae, - 0xb7, 0xbb, 0xc7, 0x1c, 0x00, 0xe7, 0x99, 0xa4, 0xdc, 0x43, 0x88, 0xac, 0x8e, 0x31, 0x1c, 0xe3, - 0xdf, 0x4e, 0xc1, 0x69, 0xd3, 0xdd, 0xf5, 0xe8, 0x81, 0x75, 0x3b, 0xf4, 0x7a, 0xbb, 0x4a, 0x2c, - 0x3a, 0x23, 0x25, 0x88, 0x78, 0x9e, 0x33, 0x0a, 0x89, 0x67, 0xe2, 0x8b, 0x50, 0xa0, 0x3a, 0x8c, - 0x24, 0x8b, 0xf0, 0xf2, 0xb3, 0xe7, 0x77, 0x5c, 0x9e, 0x93, 0x43, 0x14, 0x93, 0x35, 0xae, 0x3f, - 0x4b, 0x99, 0x28, 0xa9, 0xfe, 0xfc, 0x17, 0x9f, 0xad, 0x02, 0x4b, 0xcd, 0x8f, 0xc1, 0x55, 0x99, - 0x0e, 0x1d, 0x1f, 0x72, 0x27, 0x46, 0x1c, 0x72, 0xb7, 0x60, 0xb9, 0xd8, 0x61, 0xba, 0x82, 0xd3, - 0x6d, 0x06, 0x5e, 0xaf, 0xed, 0xf5, 0x9d, 0xae, 0x30, 0xdc, 0x60, 0x2f, 0x3b, 0x71, 0xb9, 0xdd, - 0x8f, 0x11, 0xcc, 0x4c, 0x32, 0xda, 0x8c, 0x72, 0xdd, 0xc2, 0x40, 0x67, 0xfc, 0x5e, 0x1b, 0x9b, - 0xd1, 0xe9, 0x85, 0xd8, 0x8a, 0xd0, 0x8c, 0x8b, 0xf1, 0x78, 0x8d, 0x5e, 0x0c, 0xad, 0x9a, 0x75, - 0xcb, 0x65, 0x11, 0xaa, 0x44, 0xa2, 0x2c, 0xe6, 0xb5, 0x12, 0x75, 0x43, 0x74, 0xb6, 0x55, 0xf0, - 0x12, 0x3a, 0xcb, 0xda, 0xa4, 0x74, 0x85, 0x21, 0xba, 0x30, 0xdc, 0x93, 0xe9, 0x18, 0x1e, 0x79, - 0x95, 0x4e, 0x85, 0x7d, 0x3f, 0x72, 0x71, 0x0a, 0xcf, 0x24, 0x87, 0xf1, 0x00, 0xa1, 0xec, 0x30, - 0x2e, 0xa1, 0x90, 0x77, 0xe1, 0x78, 0xa5, 0x74, 0x4d, 0xdc, 0x46, 0x94, 0xfd, 0xf6, 0x00, 0xbd, - 0x4c, 0x00, 0xbf, 0x87, 0x63, 0xe8, 0xb6, 0xaf, 0x51, 0x69, 0x92, 0x85, 0x46, 0x2e, 0xc1, 0x74, - 0xb5, 0x2c, 0x6b, 0xc9, 0x3c, 0x1b, 0x2c, 0x77, 0x9d, 0x13, 0x85, 0xa4, 0x91, 0x9c, 0x16, 0xe7, - 0x0e, 0x3d, 0xd6, 0x9d, 0x3e, 0xc2, 0x49, 0xf1, 0x7a, 0x5a, 0xcb, 0x97, 0x52, 0xa2, 0xa4, 0xb4, - 0xfc, 0xb4, 0x6e, 0xff, 0x36, 0x92, 0xde, 0x70, 0x7b, 0x6e, 0x90, 0xa4, 0x42, 0x99, 0x64, 0x7d, - 0x4b, 0x49, 0x77, 0xe3, 0x12, 0x53, 0x45, 0x24, 0x26, 0x9c, 0x68, 0x06, 0xee, 0x03, 0xcf, 0x1f, - 0x84, 0xea, 0xc7, 0x17, 0x93, 0x93, 0x50, 0x9f, 0x23, 0xd8, 0xe9, 0x5a, 0x64, 0x93, 0xf2, 0xec, - 0xb7, 0x2c, 0x40, 0x70, 0xc9, 0xef, 0xb8, 0x21, 0x93, 0x40, 0x7f, 0x8d, 0xb2, 0xdf, 0x4a, 0x6d, - 0x1b, 0x23, 0x95, 0xbf, 0x89, 0xd9, 0x6f, 0x87, 0x70, 0xc9, 0xdb, 0x30, 0x89, 0x3f, 0xf9, 0xf9, - 0xe1, 0x78, 0x06, 0xdb, 0xe4, 0xec, 0xd0, 0xa6, 0x98, 0x26, 0x23, 0x20, 0x55, 0x98, 0xe6, 0xa7, - 0xf8, 0xc7, 0xc9, 0xe1, 0xc8, 0xcd, 0x01, 0x6c, 0xb6, 0x71, 0x7a, 0xa3, 0x03, 0x73, 0xf2, 0x07, - 0xe9, 0x2a, 0xdb, 0x74, 0xc2, 0x3d, 0xb7, 0x43, 0x7f, 0xf1, 0xf4, 0xcb, 0xb8, 0xca, 0xf6, 0x10, - 0x6a, 0xd3, 0x7a, 0x98, 0x12, 0x0a, 0xdd, 0xa7, 0xab, 0xe1, 0x76, 0xc8, 0xab, 0xc2, 0xed, 0x7a, - 0x1e, 0xda, 0x88, 0x3b, 0x26, 0x2f, 0x32, 0xbe, 0x08, 0xcb, 0xf5, 0x41, 0xb7, 0xeb, 0xdc, 0xeb, - 0xba, 0x22, 0x3d, 0x5f, 0xe4, 0x44, 0x2e, 0x59, 0x87, 0x49, 0xfc, 0x83, 0xeb, 0x1c, 0xc7, 0xe3, - 0xfc, 0x87, 0x09, 0x0e, 0xba, 0x4e, 0x6b, 0x18, 0xc8, 0x8e, 0xfe, 0x54, 0x02, 0xd9, 0x51, 0x80, - 0xf1, 0x3b, 0x1a, 0x2c, 0x0b, 0xdf, 0x94, 0xc0, 0x69, 0xdf, 0x77, 0x03, 0xae, 0x3f, 0x5e, 0x52, - 0xe6, 0x1a, 0x2e, 0x82, 0xd4, 0x34, 0x62, 0xb3, 0xee, 0xa6, 0xa8, 0x84, 0xaa, 0x92, 0x65, 0x55, - 0xf8, 0xb0, 0xca, 0x90, 0x77, 0x61, 0x96, 0x6f, 0xb9, 0x52, 0x84, 0x72, 0x8c, 0xa3, 0xc9, 0x8f, - 0xc7, 0x69, 0x4f, 0x29, 0x19, 0xdd, 0xf8, 0x69, 0x0d, 0x74, 0xb5, 0x29, 0x4f, 0xaa, 0x57, 0x64, - 0x6b, 0x9b, 0xea, 0x37, 0xc6, 0x4c, 0xdd, 0x7f, 0x35, 0x9b, 0xee, 0x5b, 0x3e, 0x77, 0xdf, 0x94, - 0x43, 0xc7, 0x6a, 0x89, 0x15, 0x21, 0x09, 0x1d, 0x2b, 0x5b, 0x11, 0x62, 0xd4, 0x78, 0x4c, 0x72, - 0x87, 0x8c, 0xc9, 0xfb, 0x62, 0x4c, 0xf2, 0xa3, 0x27, 0xc6, 0xf1, 0x31, 0xe3, 0x60, 0x25, 0x2b, - 0x64, 0xe2, 0x48, 0x36, 0xbc, 0x63, 0x98, 0xca, 0x89, 0x91, 0xa4, 0x25, 0x33, 0xe7, 0x24, 0x1b, - 0x06, 0x27, 0x8f, 0xce, 0xf4, 0x10, 0x71, 0xff, 0x0e, 0xcc, 0x15, 0xa3, 0xc8, 0x69, 0xef, 0xb9, - 0x9d, 0x32, 0x15, 0x4f, 0x52, 0x6c, 0x48, 0x87, 0xc3, 0x65, 0x93, 0x8a, 0x8c, 0xcb, 0xd2, 0x01, - 0x38, 0x21, 0xf7, 0xb8, 0x8e, 0xd3, 0x01, 0x50, 0x88, 0x9a, 0x0e, 0x80, 0x42, 0xc8, 0xab, 0x30, - 0x5d, 0xed, 0x3d, 0xf0, 0x68, 0x9f, 0xb0, 0xf0, 0x90, 0x68, 0x08, 0xf5, 0x18, 0x48, 0x16, 0xae, - 0x1c, 0x8b, 0x5c, 0x97, 0x8e, 0x8d, 0x33, 0x89, 0xa9, 0x49, 0xc4, 0x98, 0xe6, 0x45, 0xf2, 0x91, - 0x30, 0x3e, 0x47, 0xbe, 0x09, 0xd3, 0xc2, 0x6c, 0x0e, 0xc9, 0x0e, 0xc2, 0x29, 0x87, 0x83, 0xf2, - 0x08, 0x64, 0xba, 0x7e, 0xe4, 0x34, 0xd2, 0xb3, 0xc9, 0xfa, 0x91, 0xd3, 0x48, 0xcb, 0xeb, 0x47, - 0x4e, 0x28, 0x1d, 0x5b, 0x1c, 0xe7, 0x0e, 0xb5, 0x38, 0xde, 0x86, 0xb9, 0xa6, 0x13, 0x44, 0x1e, - 0xd5, 0x7b, 0x7a, 0x51, 0xb8, 0x32, 0xaf, 0x18, 0xe9, 0xa5, 0xa2, 0xf5, 0xf3, 0x7c, 0x20, 0x4f, - 0xf6, 0x25, 0x7c, 0x35, 0x25, 0x7f, 0x02, 0xcf, 0xf6, 0xb7, 0x5e, 0x78, 0x12, 0x7f, 0x6b, 0xec, - 0x54, 0x34, 0xcc, 0x2e, 0x26, 0x86, 0x42, 0x3c, 0x08, 0xa5, 0xac, 0xb3, 0x31, 0x22, 0xf9, 0x12, - 0xcc, 0xd1, 0xbf, 0x9b, 0x7e, 0xd7, 0x6b, 0x7b, 0x6e, 0xb8, 0xa2, 0x63, 0xe3, 0xce, 0x67, 0xae, - 0x7e, 0x44, 0x3a, 0xb0, 0xdc, 0x88, 0x2d, 0x60, 0x64, 0x9c, 0xbe, 0x71, 0x51, 0xb8, 0x91, 0x0f, - 0x60, 0x8e, 0xce, 0xbe, 0x7b, 0x4e, 0xc8, 0xd4, 0xdd, 0xa5, 0xc4, 0x12, 0xd8, 0xe1, 0xf0, 0xa1, - 0x8c, 0x1c, 0x32, 0x01, 0xdd, 0xe6, 0x8b, 0x7d, 0x26, 0x20, 0x89, 0x34, 0xdb, 0xfb, 0x43, 0xc2, - 0x51, 0xa0, 0x91, 0x2f, 0xc0, 0x5c, 0xb1, 0xdf, 0x4f, 0x24, 0xce, 0x71, 0xc9, 0x3e, 0xdb, 0xef, - 0xdb, 0x99, 0x52, 0x47, 0xa1, 0x48, 0x0b, 0xe6, 0xe5, 0xc7, 0x12, 0xcc, 0xe4, 0x95, 0xf8, 0x04, - 0x70, 0x22, 0xb9, 0x42, 0xe0, 0x87, 0x51, 0xe5, 0x38, 0xc1, 0x0e, 0x03, 0x25, 0x98, 0x67, 0x06, - 0x4a, 0xa1, 0xcd, 0x9c, 0x1c, 0x5a, 0x3d, 0x19, 0x4a, 0x8d, 0x4a, 0xc3, 0x22, 0x10, 0x78, 0x91, - 0xe7, 0x74, 0x79, 0xaa, 0x94, 0x95, 0x53, 0xb8, 0x6a, 0x79, 0x04, 0x02, 0x2c, 0xc1, 0x7c, 0x7c, - 0x8e, 0xc2, 0x25, 0x45, 0x64, 0xfc, 0xa9, 0x06, 0xa7, 0x46, 0x8c, 0x78, 0x9c, 0x48, 0x43, 0x1b, - 0x9f, 0x48, 0x83, 0x4a, 0x0e, 0xd5, 0xee, 0x84, 0xed, 0x1f, 0x7e, 0xb0, 0x18, 0xeb, 0x5b, 0x3e, - 0x10, 0x9e, 0x7a, 0x93, 0x7f, 0xfa, 0xa6, 0x8f, 0xf7, 0x00, 0xf9, 0xe1, 0x4d, 0x88, 0xe3, 0xb1, - 0x4a, 0xb1, 0x28, 0xd1, 0x3c, 0xb3, 0x67, 0x3c, 0xac, 0x1f, 0xfb, 0xca, 0x0a, 0xce, 0x60, 0x6d, - 0xfc, 0x4c, 0x0e, 0x66, 0xa5, 0x75, 0x48, 0x2e, 0x48, 0xcf, 0xe1, 0x75, 0x96, 0x1b, 0x56, 0xe2, - 0x90, 0x63, 0x3b, 0x11, 0x2e, 0xaa, 0xdc, 0xe1, 0xb7, 0x1d, 0x18, 0x01, 0x50, 0x4a, 0x36, 0x92, - 0x0a, 0xf9, 0x87, 0xe5, 0xe4, 0xcb, 0x00, 0x35, 0x27, 0x8c, 0x8a, 0xed, 0xc8, 0x7b, 0xe0, 0x1e, - 0x61, 0xd3, 0x11, 0x51, 0x72, 0x4f, 0x60, 0x16, 0x30, 0x07, 0xc9, 0x52, 0x7b, 0x84, 0xc4, 0x90, - 0x0e, 0x81, 0x9c, 0xad, 0x80, 0x0f, 0xc1, 0xb0, 0x08, 0x11, 0x58, 0xc6, 0x8f, 0x6b, 0x00, 0xdb, - 0xd5, 0x12, 0xa6, 0x17, 0x7a, 0x52, 0x2d, 0x22, 0x3b, 0xb2, 0xbe, 0xe0, 0x3e, 0x46, 0x7f, 0xf8, - 0x43, 0x0d, 0x16, 0x54, 0x34, 0xf2, 0x3e, 0x2c, 0x5a, 0xed, 0xc0, 0xef, 0x76, 0xef, 0x39, 0xed, - 0xfb, 0x35, 0xaf, 0xe7, 0xb2, 0x68, 0xe3, 0x93, 0x6c, 0xf3, 0x0a, 0xe3, 0x22, 0xbb, 0x4b, 0xcb, - 0xcc, 0x34, 0x32, 0xf9, 0x09, 0x0d, 0xe6, 0xad, 0x3d, 0xff, 0x61, 0x1c, 0xbc, 0x9b, 0x8f, 0xe0, - 0x97, 0xa9, 0x30, 0x08, 0xf7, 0xfc, 0x87, 0x49, 0x26, 0x7a, 0xc5, 0x93, 0xf9, 0xbd, 0xa3, 0x39, - 0x99, 0xb4, 0x7d, 0x3c, 0xc0, 0x44, 0xe1, 0x15, 0xe5, 0x23, 0xa6, 0xfa, 0x4d, 0xe3, 0x2f, 0x35, - 0x98, 0xc5, 0xa3, 0x4e, 0xb7, 0x8b, 0x4a, 0xda, 0x5f, 0xa7, 0xb4, 0xe6, 0x71, 0xbb, 0xc6, 0x0c, - 0xec, 0x1b, 0xb0, 0x98, 0x42, 0x23, 0x06, 0x4c, 0x59, 0x18, 0x1a, 0x43, 0xb6, 0x92, 0xb0, 0x60, - 0x19, 0x26, 0x2f, 0x31, 0x2a, 0x12, 0xd9, 0xed, 0xab, 0xe8, 0x74, 0x70, 0x0d, 0xc0, 0x13, 0x20, - 0x71, 0x14, 0x22, 0xe9, 0x9a, 0xdc, 0xbe, 0x6a, 0x4a, 0x58, 0x46, 0x1d, 0xa6, 0x2c, 0x3f, 0x88, - 0xd6, 0x0f, 0xd8, 0xe9, 0xa3, 0xec, 0x86, 0x6d, 0xd9, 0xab, 0xc0, 0xc3, 0x3b, 0xbf, 0xb6, 0xc9, - 0x8b, 0xc8, 0x2a, 0x4c, 0x6e, 0x78, 0x6e, 0xb7, 0x23, 0x7b, 0x9b, 0xef, 0x50, 0x80, 0xc9, 0xe0, - 0xf4, 0x84, 0x76, 0x32, 0xc9, 0xe9, 0x97, 0xb8, 0xb5, 0x3f, 0xe9, 0xba, 0x29, 0x29, 0xfd, 0x7b, - 0x31, 0xce, 0x7c, 0x35, 0xfc, 0xa5, 0x31, 0x5d, 0xfd, 0xeb, 0x1a, 0x9c, 0x19, 0x4d, 0x22, 0x7b, - 0xca, 0x6b, 0x63, 0x3c, 0xe5, 0x9f, 0x4f, 0xdf, 0x82, 0x23, 0x1a, 0xbf, 0x05, 0x4f, 0xee, 0xbe, - 0xcb, 0xf8, 0x50, 0xa1, 0xed, 0x8a, 0x44, 0x7e, 0x17, 0xc6, 0xd4, 0x19, 0x11, 0xd9, 0x30, 0x47, - 0x48, 0x63, 0x72, 0x5a, 0xe3, 0x5f, 0x4c, 0xc0, 0xe9, 0x91, 0x14, 0x64, 0x53, 0x4a, 0x0f, 0xba, - 0x10, 0x27, 0x26, 0x1c, 0x89, 0x7f, 0x05, 0xff, 0x45, 0x5f, 0xd4, 0xf4, 0x9b, 0xc8, 0x46, 0x9c, - 0x16, 0x32, 0x87, 0xbc, 0x5e, 0x3a, 0x94, 0x17, 0x43, 0x47, 0x66, 0x30, 0x9c, 0x21, 0x12, 0x9f, - 0xdf, 0xba, 0x91, 0xe3, 0x75, 0x43, 0x79, 0xd9, 0x75, 0x18, 0xc8, 0x14, 0x65, 0xc9, 0xf3, 0x85, - 0x89, 0xec, 0xe7, 0x0b, 0xc6, 0xff, 0xd4, 0x60, 0x26, 0xae, 0x36, 0x39, 0x03, 0x27, 0x5b, 0x66, - 0xb1, 0x54, 0xb1, 0x5b, 0x77, 0x9b, 0x15, 0x7b, 0xbb, 0x6e, 0x35, 0x2b, 0xa5, 0xea, 0x46, 0xb5, - 0x52, 0xd6, 0x8f, 0x91, 0x25, 0x98, 0xdf, 0xae, 0xdf, 0xaa, 0x37, 0xee, 0xd4, 0xed, 0x8a, 0x69, - 0x36, 0x4c, 0x5d, 0x23, 0xf3, 0x30, 0x63, 0xae, 0x17, 0x4b, 0x76, 0xbd, 0x51, 0xae, 0xe8, 0x39, - 0xa2, 0xc3, 0x5c, 0xa9, 0x51, 0xaf, 0x57, 0x4a, 0xad, 0xea, 0xed, 0x6a, 0xeb, 0xae, 0x9e, 0x27, - 0x04, 0x16, 0x10, 0xa1, 0x69, 0x56, 0xeb, 0xa5, 0x6a, 0xb3, 0x58, 0xd3, 0x27, 0x28, 0x8c, 0xe2, - 0x4b, 0xb0, 0xc9, 0x98, 0xd1, 0xad, 0xed, 0xf5, 0x8a, 0x3e, 0x45, 0x51, 0xe8, 0x5f, 0x12, 0xca, - 0x34, 0xfd, 0x3c, 0xa2, 0x94, 0x8b, 0xad, 0xe2, 0x7a, 0xd1, 0xaa, 0xe8, 0x05, 0x72, 0x0a, 0x8e, - 0x2b, 0x20, 0xbb, 0xd6, 0xb8, 0x51, 0xad, 0xeb, 0x33, 0x64, 0x19, 0xf4, 0x18, 0x56, 0x5e, 0xb7, - 0xb7, 0xad, 0x8a, 0xa9, 0x43, 0x1a, 0x5a, 0x2f, 0x6e, 0x55, 0xf4, 0x59, 0xe3, 0x3d, 0xf6, 0x5a, - 0x95, 0x75, 0x35, 0x39, 0x09, 0xc4, 0x6a, 0x15, 0x5b, 0xdb, 0x56, 0xaa, 0xf1, 0xb3, 0x30, 0x6d, - 0x6d, 0x97, 0x4a, 0x15, 0xcb, 0xd2, 0x35, 0x02, 0x30, 0xb5, 0x51, 0xac, 0xd6, 0x2a, 0x65, 0x3d, - 0x67, 0x7c, 0x43, 0x83, 0x25, 0xa1, 0x32, 0x8a, 0x7b, 0xbc, 0x27, 0x5c, 0x8b, 0xef, 0x2b, 0x27, - 0x61, 0xf1, 0xf4, 0x30, 0xf5, 0x91, 0x31, 0xcb, 0xf0, 0x17, 0x35, 0x38, 0x91, 0x89, 0x4d, 0xee, - 0x82, 0x2e, 0x6a, 0x10, 0x3f, 0x23, 0xd6, 0x14, 0x8d, 0x5b, 0xd0, 0xa5, 0xd0, 0x98, 0x6d, 0x35, - 0xde, 0xae, 0xcc, 0x21, 0x36, 0x47, 0xcf, 0x9d, 0x64, 0x7c, 0xaa, 0xc1, 0xa9, 0x11, 0x9f, 0x21, - 0x25, 0x98, 0x8a, 0x53, 0x21, 0x8e, 0x71, 0xc7, 0x5c, 0xfe, 0xf6, 0x67, 0xab, 0x1c, 0x91, 0x2e, - 0x0d, 0x96, 0xec, 0xd0, 0x9c, 0x8a, 0x73, 0x1b, 0x62, 0x82, 0x41, 0xd6, 0x7d, 0xa7, 0x53, 0x3d, - 0xcf, 0xbf, 0x54, 0xbc, 0x63, 0xad, 0xcf, 0xf2, 0xbe, 0xcb, 0x3b, 0x0f, 0x43, 0xcc, 0x30, 0x68, - 0xfc, 0x82, 0x46, 0xb5, 0xc1, 0x34, 0x22, 0x55, 0x92, 0x93, 0x5c, 0x62, 0x45, 0xb3, 0xce, 0xb7, - 0x0d, 0x54, 0x6f, 0x59, 0xfe, 0x31, 0x3c, 0x87, 0xd8, 0x4e, 0xa0, 0xc4, 0xc4, 0x50, 0x68, 0xc8, - 0x75, 0x80, 0xca, 0xa3, 0xc8, 0x0d, 0x7a, 0x4e, 0x37, 0x8e, 0x6e, 0xc4, 0xa2, 0xf8, 0x71, 0xa8, - 0xaa, 0xa0, 0x4b, 0xc8, 0xc6, 0xd7, 0x34, 0x98, 0x13, 0x89, 0xd5, 0xba, 0x6e, 0x10, 0x3d, 0xd9, - 0xf4, 0xba, 0xae, 0x4c, 0xaf, 0xf8, 0xf5, 0x91, 0xc4, 0x9f, 0x16, 0x67, 0xce, 0xac, 0x7f, 0xad, - 0x81, 0x9e, 0x46, 0x24, 0xef, 0x43, 0xc1, 0x72, 0x1f, 0xb8, 0x81, 0x17, 0x1d, 0x70, 0x41, 0x29, - 0x52, 0x50, 0x33, 0x1c, 0x5e, 0xc6, 0xe6, 0x43, 0xc8, 0x7f, 0x99, 0x31, 0xcd, 0x51, 0xe5, 0xbd, - 0x64, 0x27, 0xc9, 0x3f, 0x2d, 0x3b, 0x89, 0xf1, 0xc7, 0x39, 0x38, 0x75, 0xc3, 0x8d, 0xe4, 0x36, - 0xc5, 0x6e, 0x32, 0xaf, 0x1d, 0xad, 0x5d, 0x52, 0x4b, 0x56, 0x60, 0x1a, 0x8b, 0xc4, 0xf8, 0x9a, - 0xe2, 0x27, 0x59, 0x8f, 0xe7, 0x75, 0x5e, 0xc9, 0x71, 0x3b, 0xe2, 0xdb, 0x57, 0xa4, 0x3c, 0x95, - 0xf1, 0xb4, 0xbe, 0x04, 0x0b, 0x98, 0xc9, 0x66, 0x40, 0x97, 0x83, 0xdb, 0xe1, 0xf6, 0xa2, 0x82, - 0x99, 0x82, 0x92, 0x35, 0xd0, 0x29, 0xa4, 0xd8, 0xbe, 0xdf, 0xf3, 0x1f, 0x76, 0xdd, 0xce, 0xae, - 0xdb, 0xc1, 0x6d, 0xbd, 0x60, 0x0e, 0xc1, 0x05, 0xcf, 0xed, 0x1e, 0x3b, 0xeb, 0xb9, 0x1d, 0x34, - 0xea, 0x70, 0x9e, 0x09, 0xf4, 0xcc, 0x75, 0x98, 0xfd, 0x1e, 0x33, 0xd8, 0x1a, 0xff, 0x49, 0x83, - 0x65, 0x6c, 0x9c, 0xf4, 0x61, 0xbc, 0xb6, 0x78, 0x2d, 0xe9, 0x2d, 0x29, 0x0d, 0xa3, 0x43, 0x41, - 0xea, 0x52, 0x88, 0x7b, 0x31, 0x31, 0x22, 0xe5, 0x8e, 0x60, 0x44, 0x92, 0x6c, 0x60, 0x13, 0x4f, - 0xcb, 0x06, 0x76, 0x73, 0xa2, 0x90, 0xd7, 0x27, 0x92, 0x21, 0x37, 0x7e, 0x22, 0x07, 0xd3, 0xa6, - 0xdb, 0x75, 0x9d, 0xd0, 0x25, 0x97, 0x60, 0xba, 0xee, 0x47, 0x6e, 0xb8, 0x55, 0x96, 0x5d, 0xd0, - 0x7b, 0x14, 0x64, 0xef, 0x77, 0x4c, 0x51, 0x48, 0x27, 0x7c, 0x33, 0xf0, 0x3b, 0x83, 0x76, 0x24, - 0x4f, 0xf8, 0x3e, 0x03, 0x99, 0xa2, 0x8c, 0xbc, 0x0c, 0x33, 0x9c, 0x73, 0x7c, 0xb3, 0x8c, 0x9e, - 0xf5, 0x01, 0x03, 0x62, 0xce, 0xab, 0x18, 0x01, 0x75, 0x5a, 0xa6, 0x60, 0x4c, 0x48, 0x3a, 0xed, - 0x90, 0xce, 0x20, 0x54, 0xf5, 0xc9, 0x31, 0xaa, 0xfa, 0x6b, 0x30, 0x55, 0x0c, 0x43, 0x37, 0x12, - 0xc1, 0x3a, 0xe6, 0x92, 0x7c, 0x8a, 0x6e, 0xc4, 0x18, 0x3b, 0x58, 0x6e, 0x72, 0x3c, 0xe3, 0xcf, - 0x73, 0x30, 0x89, 0x7f, 0xe2, 0xbd, 0x6d, 0xd0, 0xde, 0x53, 0xee, 0x6d, 0x83, 0xf6, 0x9e, 0x89, - 0x50, 0x72, 0x15, 0x4d, 0x1b, 0x22, 0x63, 0x28, 0x6f, 0x3d, 0xda, 0xec, 0x3b, 0x09, 0xd8, 0x94, - 0x71, 0x62, 0xaf, 0x89, 0x7c, 0xa6, 0xd7, 0xc4, 0x49, 0xc8, 0x35, 0x2c, 0xde, 0x62, 0x8c, 0x25, - 0xe7, 0x87, 0x66, 0xae, 0x61, 0x61, 0x6f, 0x6c, 0x16, 0xaf, 0xbd, 0xf1, 0x26, 0x6f, 0x28, 0xeb, - 0x8d, 0x3d, 0xe7, 0xda, 0x1b, 0x6f, 0x9a, 0xbc, 0x84, 0xf6, 0x2f, 0xd6, 0x19, 0x6f, 0x7f, 0x59, - 0x24, 0x09, 0xec, 0x5f, 0x6c, 0x1b, 0xde, 0xf4, 0x9a, 0x09, 0x02, 0xb9, 0x06, 0xb3, 0x3c, 0xa4, - 0x09, 0xe2, 0x4b, 0x21, 0x47, 0x78, 0xc8, 0x13, 0x46, 0x21, 0x23, 0xb1, 0x7b, 0x40, 0x3e, 0x40, - 0x3c, 0x3f, 0x9e, 0xb8, 0x07, 0x14, 0x43, 0x18, 0x9a, 0x12, 0x0a, 0xad, 0x12, 0xbb, 0x48, 0x4c, - 0x22, 0x44, 0x60, 0x95, 0xf8, 0x6d, 0x23, 0x66, 0x0d, 0x8a, 0x11, 0x8c, 0x5f, 0xcb, 0x41, 0xa1, - 0xd9, 0x1d, 0xec, 0x7a, 0xbd, 0xdb, 0x57, 0x09, 0x01, 0x3c, 0xc6, 0x89, 0xb4, 0x52, 0xf4, 0x6f, - 0x72, 0x1a, 0x0a, 0xe2, 0xe4, 0x26, 0x04, 0x52, 0xc8, 0x4f, 0x6d, 0x2b, 0x20, 0xc6, 0x9d, 0x07, - 0x0d, 0x14, 0x3f, 0xc9, 0x55, 0x88, 0xcf, 0x5f, 0xa3, 0x0e, 0x6a, 0x13, 0x74, 0xb1, 0x98, 0x31, - 0x1a, 0x79, 0x05, 0x70, 0x93, 0xe0, 0x87, 0x07, 0x61, 0x01, 0x67, 0x55, 0xe3, 0x7a, 0x0a, 0x23, - 0x41, 0x34, 0xf4, 0xdf, 0x90, 0x33, 0x40, 0x9e, 0x50, 0x09, 0x58, 0x8e, 0x40, 0x41, 0x22, 0xb2, - 0x3f, 0xbe, 0xab, 0xc6, 0x91, 0x9c, 0x56, 0xee, 0xb7, 0x18, 0xa5, 0x14, 0xc8, 0xf1, 0xf6, 0x55, - 0x35, 0x84, 0xe4, 0xef, 0xce, 0xc0, 0x9c, 0x5c, 0x1f, 0x62, 0xc2, 0xf1, 0xb0, 0x4b, 0xcf, 0xee, - 0xdc, 0x69, 0xb2, 0x8f, 0x85, 0x7c, 0x3b, 0xbd, 0xa0, 0x56, 0x88, 0xe2, 0x31, 0x0f, 0x4a, 0x11, - 0x8b, 0x65, 0xf3, 0x98, 0xb9, 0x14, 0x26, 0x60, 0x86, 0x47, 0x8a, 0x50, 0xf0, 0xfb, 0xe1, 0xae, - 0xdb, 0xf3, 0xc4, 0x05, 0xcd, 0xb3, 0x0a, 0xa3, 0x06, 0x2f, 0x1c, 0xe2, 0x15, 0x93, 0x91, 0x37, - 0x60, 0xca, 0xef, 0xbb, 0x3d, 0xc7, 0x4b, 0xf9, 0xcc, 0x08, 0x06, 0x6e, 0xaf, 0x58, 0x95, 0x08, - 0x39, 0x32, 0x79, 0x15, 0x26, 0xfc, 0xfb, 0xf1, 0x78, 0x9d, 0x56, 0x89, 0xee, 0x47, 0x8e, 0x44, - 0x82, 0x88, 0x94, 0xe0, 0x63, 0x67, 0x7f, 0x87, 0x8f, 0x98, 0x4a, 0x70, 0xd3, 0xd9, 0xdf, 0x91, - 0x09, 0x28, 0x22, 0xf9, 0x00, 0xa0, 0xef, 0xec, 0xba, 0x81, 0xdd, 0x19, 0x44, 0x07, 0xa9, 0x14, - 0xd9, 0x8c, 0xac, 0x49, 0x8b, 0xcb, 0x83, 0xe8, 0x40, 0xa2, 0x9d, 0xe9, 0x0b, 0x20, 0x29, 0x02, - 0xec, 0x3b, 0x51, 0xe4, 0x06, 0xfb, 0x3e, 0xf7, 0x5a, 0x4d, 0xc2, 0x80, 0x32, 0x06, 0x5b, 0x71, - 0xb1, 0xc4, 0x41, 0x22, 0xc2, 0x4a, 0x7b, 0x81, 0x83, 0xd7, 0xf0, 0x43, 0x95, 0xf6, 0x02, 0xa5, - 0x95, 0x14, 0x91, 0xbc, 0x0d, 0xd3, 0x98, 0xf7, 0x35, 0xe8, 0xf0, 0x20, 0x3d, 0xe7, 0x14, 0x9a, - 0x32, 0x2b, 0x93, 0xc8, 0x04, 0x3a, 0xad, 0x2d, 0x0f, 0xf8, 0x5c, 0xf7, 0x1f, 0xe2, 0xbd, 0x40, - 0xba, 0xb6, 0x56, 0x5c, 0x2c, 0xd7, 0x36, 0x21, 0xa2, 0x43, 0xb9, 0xeb, 0x45, 0x5d, 0xe7, 0x1e, - 0xbf, 0x6c, 0x57, 0x87, 0xf2, 0x06, 0x16, 0xc9, 0x43, 0xc9, 0x90, 0xc9, 0x75, 0x28, 0x88, 0x30, - 0xac, 0xfc, 0xc9, 0xaf, 0x5a, 0x69, 0x1e, 0x7c, 0x55, 0xae, 0x34, 0x0f, 0xbc, 0x4a, 0xfb, 0x27, - 0x6c, 0x7b, 0xfb, 0xfc, 0xa5, 0xae, 0xda, 0x3f, 0x56, 0xa9, 0xba, 0x25, 0xf7, 0x0f, 0x45, 0x24, - 0xef, 0xc3, 0x34, 0x5d, 0xbf, 0x1d, 0x7f, 0x97, 0x87, 0x39, 0x31, 0xd4, 0xfe, 0x61, 0x65, 0x43, - 0xd3, 0x55, 0x10, 0xd1, 0x85, 0xec, 0x3c, 0x0c, 0x6d, 0xaf, 0x8d, 0x71, 0x80, 0xd3, 0xcb, 0xb1, - 0x78, 0xc7, 0xaa, 0x96, 0x24, 0xb2, 0x49, 0xe7, 0x61, 0x58, 0x6d, 0x93, 0x6b, 0x30, 0x89, 0x19, - 0x97, 0x78, 0xd0, 0x5f, 0x95, 0x06, 0x73, 0x2d, 0xc9, 0x34, 0x88, 0x4a, 0x07, 0x72, 0x3f, 0xc4, - 0xc7, 0x4f, 0x3c, 0xef, 0x91, 0xda, 0x27, 0x5b, 0x16, 0xbe, 0x88, 0x92, 0xab, 0xc8, 0xd1, 0x69, - 0x15, 0x7b, 0x6e, 0x64, 0x7b, 0x9f, 0xf0, 0xcc, 0x45, 0xea, 0xe7, 0xea, 0x6e, 0x54, 0xfd, 0x50, - 0xfe, 0x5c, 0xcf, 0x8d, 0xaa, 0x9f, 0xf0, 0xa1, 0xdb, 0x1b, 0xdc, 0x43, 0xe3, 0x7b, 0xc6, 0xd0, - 0xed, 0x0d, 0xd2, 0x43, 0xb7, 0x37, 0xb8, 0x47, 0xc9, 0xbc, 0x5e, 0x34, 0xe8, 0xb9, 0xfc, 0xe9, - 0xad, 0x4a, 0x56, 0xc5, 0x22, 0x99, 0x8c, 0x21, 0x93, 0xf3, 0x00, 0x89, 0xb7, 0x03, 0xbb, 0x47, - 0x32, 0x25, 0xc8, 0x3b, 0x13, 0xff, 0xf9, 0x97, 0x56, 0xb5, 0x75, 0x80, 0x82, 0x88, 0x15, 0x45, - 0xd5, 0xf0, 0xe5, 0xac, 0xba, 0x90, 0x8b, 0x30, 0x27, 0x47, 0xb2, 0xe2, 0x9b, 0xc1, 0xac, 0xd3, - 0xf7, 0x44, 0x2c, 0xab, 0xf1, 0x39, 0x6d, 0x5e, 0x82, 0x25, 0xe5, 0x85, 0x5a, 0xe2, 0x86, 0x69, - 0xea, 0x72, 0x01, 0xee, 0xbd, 0x25, 0x80, 0x30, 0x72, 0x82, 0xc8, 0xee, 0x38, 0xd1, 0x51, 0xcc, - 0xc8, 0x05, 0x2a, 0xcf, 0xd9, 0x83, 0x00, 0xa4, 0x2b, 0x3b, 0x91, 0xcb, 0x1a, 0x67, 0xd4, 0xe0, - 0xf4, 0x48, 0x59, 0x4b, 0x5e, 0x04, 0x7d, 0xc7, 0xe1, 0x96, 0xd6, 0xf6, 0x9e, 0xd3, 0xeb, 0xb9, - 0x5d, 0xde, 0xb0, 0x45, 0x01, 0x2f, 0x31, 0x30, 0xe7, 0xf6, 0x81, 0xd4, 0x3b, 0xd2, 0x22, 0x3b, - 0x42, 0xef, 0x70, 0x06, 0xdf, 0xd2, 0xe0, 0xdc, 0x38, 0x91, 0x4d, 0xce, 0x40, 0xa1, 0x1f, 0x78, - 0x3e, 0x1e, 0x0d, 0x78, 0x1f, 0x8a, 0xdf, 0x98, 0xf2, 0x07, 0x75, 0xd8, 0xc8, 0xd9, 0xe5, 0x4f, - 0xbe, 0xcc, 0x19, 0x84, 0xb4, 0x9c, 0xdd, 0x90, 0x76, 0x71, 0xc7, 0xdd, 0x71, 0x06, 0xdd, 0xc8, - 0x0e, 0xdb, 0x7b, 0x6e, 0x07, 0x1f, 0x65, 0xa2, 0xff, 0xaa, 0xa9, 0xf3, 0x02, 0x4b, 0xc0, 0x87, - 0x6a, 0x3c, 0x39, 0xa2, 0xc6, 0x37, 0x27, 0x0a, 0x9a, 0x9e, 0x33, 0xd1, 0x45, 0xce, 0xf8, 0xb1, - 0x1c, 0xac, 0x8c, 0x92, 0x51, 0xe4, 0xbd, 0xac, 0x3e, 0x60, 0xb7, 0x4b, 0x32, 0x5c, 0xbe, 0x5d, - 0x92, 0x67, 0xcf, 0x35, 0x88, 0x9f, 0x54, 0x1e, 0x16, 0x1e, 0x45, 0xc0, 0x28, 0x4d, 0xdf, 0x09, - 0xc3, 0x87, 0x54, 0x0c, 0xe7, 0xa5, 0x80, 0xed, 0x1c, 0x26, 0xd3, 0x08, 0x18, 0x79, 0x0b, 0xa0, - 0xdd, 0xf5, 0x43, 0x17, 0x9d, 0x38, 0xb8, 0x7e, 0xc7, 0x1e, 0x8a, 0xc4, 0x50, 0xf9, 0xd6, 0x1e, - 0xa1, 0x25, 0xbf, 0x23, 0xe6, 0x93, 0x03, 0xa7, 0x46, 0x6c, 0x4a, 0x74, 0x78, 0xf0, 0x8d, 0x24, - 0x93, 0x41, 0x3c, 0x5d, 0x26, 0x85, 0xb0, 0x34, 0x6f, 0xe9, 0x1e, 0xcf, 0x8d, 0x9a, 0x23, 0x07, - 0x40, 0x86, 0x77, 0x1e, 0xca, 0x9d, 0x3f, 0x8c, 0x18, 0x04, 0x31, 0x77, 0x06, 0xd9, 0x0e, 0xba, - 0x64, 0x15, 0x66, 0x45, 0xc6, 0x78, 0x7a, 0x7e, 0x62, 0xcc, 0x81, 0x83, 0x6e, 0xb9, 0x38, 0x79, - 0x30, 0xbe, 0x32, 0x4b, 0xdf, 0xc5, 0x56, 0xde, 0x0c, 0x42, 0x5a, 0x07, 0x7d, 0xd1, 0xba, 0x73, - 0x62, 0x7e, 0xab, 0xfa, 0x00, 0x2f, 0xfd, 0xdb, 0x9a, 0x18, 0xfe, 0xe1, 0x0d, 0xf5, 0xb0, 0xfa, - 0x11, 0xc0, 0x77, 0x8b, 0xbc, 0x62, 0xf8, 0x37, 0xd5, 0x14, 0xc5, 0xaa, 0xe3, 0x9a, 0x22, 0xff, - 0x49, 0x2e, 0xc1, 0x62, 0xc0, 0xdc, 0xb9, 0x23, 0x9f, 0xf7, 0x27, 0xcb, 0x70, 0x35, 0xcf, 0xc0, - 0x2d, 0x1f, 0xfb, 0x94, 0xd7, 0xeb, 0x66, 0xdc, 0x61, 0x92, 0x7e, 0x41, 0xae, 0xc0, 0x0c, 0xd5, - 0x2f, 0x30, 0x66, 0x56, 0xea, 0xc1, 0x14, 0xe2, 0xa1, 0xb6, 0x66, 0x16, 0x3e, 0xe6, 0x7f, 0x73, - 0x5e, 0x3f, 0x1e, 0x0b, 0x40, 0x55, 0xaa, 0x92, 0x93, 0x30, 0x15, 0xb9, 0x3d, 0x27, 0x5e, 0xdc, - 0xfc, 0x17, 0x79, 0x1e, 0x16, 0xd8, 0x2b, 0xe7, 0xd4, 0xc0, 0xce, 0x23, 0x34, 0x9e, 0xde, 0x47, - 0x4b, 0x8f, 0xc6, 0x2b, 0xf1, 0x9b, 0x39, 0xd1, 0x22, 0x59, 0xc5, 0x22, 0xa7, 0x60, 0xda, 0x0f, - 0x76, 0xa5, 0xfe, 0x9d, 0xf2, 0x83, 0x5d, 0xda, 0xb9, 0x97, 0x41, 0x67, 0x8f, 0x08, 0x59, 0x30, - 0x97, 0xf0, 0xa0, 0xd7, 0xe6, 0x6f, 0x3f, 0x16, 0x18, 0x7c, 0x3b, 0x74, 0x03, 0xeb, 0xa0, 0xd7, - 0xa6, 0x98, 0x61, 0xe8, 0xdb, 0x72, 0xfc, 0x3d, 0x5e, 0x91, 0x85, 0x30, 0xf4, 0x93, 0x40, 0x7c, - 0x1d, 0xb2, 0x0e, 0xf3, 0x94, 0x4f, 0x1c, 0x46, 0x90, 0x8b, 0xe1, 0x67, 0x86, 0x35, 0xc0, 0x83, - 0x5e, 0x5b, 0x54, 0xd1, 0x9c, 0x0b, 0xa5, 0x5f, 0xe4, 0x16, 0xe8, 0x92, 0xaa, 0x8c, 0xaf, 0xca, - 0x53, 0xef, 0x1b, 0x12, 0x36, 0x92, 0x8a, 0x5d, 0xed, 0xed, 0xf8, 0xe6, 0x62, 0x5b, 0x05, 0xc4, - 0xe2, 0x68, 0x4a, 0x9f, 0x36, 0x57, 0x78, 0x73, 0x43, 0x74, 0x15, 0xb5, 0xbb, 0xfe, 0xae, 0xed, - 0x3e, 0xa2, 0x13, 0xc3, 0xf8, 0xfb, 0x9a, 0x10, 0xf8, 0x19, 0x4c, 0x89, 0x01, 0xf3, 0x7b, 0x4e, - 0x68, 0x87, 0xe1, 0x3e, 0xf3, 0x60, 0xe4, 0xb1, 0xd2, 0x67, 0xf7, 0x9c, 0xd0, 0x0a, 0xf7, 0x45, - 0xda, 0xae, 0x13, 0x14, 0x07, 0xe3, 0xb9, 0xdb, 0xf2, 0xc1, 0x80, 0xf5, 0xe8, 0xf1, 0x3d, 0x27, - 0x6c, 0xd0, 0x32, 0x39, 0xb8, 0xfb, 0x73, 0xb0, 0x80, 0x7c, 0xdb, 0x9e, 0x60, 0x8c, 0x01, 0x7b, - 0xcc, 0x39, 0xca, 0xb8, 0xed, 0x31, 0xce, 0x7c, 0x70, 0xff, 0xc3, 0x14, 0x9c, 0xcc, 0xee, 0x3d, - 0x5c, 0x43, 0xb4, 0xcf, 0xf1, 0x69, 0x31, 0xaf, 0xdb, 0x0c, 0x85, 0xb0, 0x60, 0x4b, 0x59, 0x83, - 0x97, 0xcb, 0x1c, 0xbc, 0x35, 0x58, 0x42, 0x46, 0xfc, 0x08, 0xd2, 0xf5, 0xc2, 0x88, 0xc7, 0x10, - 0x32, 0x17, 0x69, 0x01, 0xdb, 0x74, 0x6a, 0x14, 0x4c, 0x67, 0xa6, 0xd8, 0x36, 0xfc, 0x87, 0x3d, - 0xfa, 0x61, 0xb6, 0x67, 0xcc, 0x73, 0x68, 0x03, 0x81, 0xe4, 0x04, 0x4c, 0x39, 0xfd, 0x3e, 0xfd, - 0x24, 0xdb, 0x2a, 0x26, 0x9d, 0x7e, 0x9f, 0x45, 0xae, 0x67, 0x59, 0x01, 0x77, 0xd0, 0xdf, 0x4c, - 0xbc, 0x6c, 0x98, 0x43, 0x20, 0xf3, 0x41, 0xc3, 0xb7, 0x11, 0x94, 0x56, 0xa0, 0x4c, 0x23, 0x0a, - 0x38, 0xfd, 0x18, 0xe1, 0x34, 0x14, 0x84, 0xe7, 0x03, 0x7b, 0x39, 0x66, 0x4e, 0x3b, 0xdc, 0xeb, - 0xe1, 0x0d, 0x38, 0xc5, 0x53, 0x10, 0xda, 0xac, 0x49, 0xfd, 0x3e, 0x7f, 0xba, 0xcd, 0xe2, 0x94, - 0x9b, 0xcb, 0xbc, 0x98, 0xf6, 0x64, 0xb1, 0xdf, 0x8f, 0x1f, 0x70, 0x9f, 0x11, 0x64, 0xf7, 0x3c, - 0x16, 0xe9, 0x90, 0x79, 0xff, 0xe2, 0xe2, 0x00, 0xa4, 0x5c, 0xe1, 0x18, 0xeb, 0x32, 0x82, 0x58, - 0x26, 0xf1, 0x4a, 0xb2, 0xf9, 0x8b, 0x00, 0xa6, 0x3e, 0xe1, 0xfd, 0x38, 0x0e, 0x1a, 0x42, 0xc9, - 0x5b, 0x30, 0x72, 0x2e, 0xa2, 0x76, 0x5e, 0x30, 0x4f, 0xb0, 0x72, 0xe6, 0xd5, 0x5c, 0xf3, 0x77, - 0x2b, 0x58, 0x48, 0x3e, 0x80, 0x73, 0xa2, 0x82, 0x4e, 0x18, 0x7a, 0xbb, 0x3d, 0x5b, 0x8c, 0x02, - 0x3a, 0x9e, 0xa0, 0x86, 0x5e, 0x30, 0x4f, 0x73, 0x9c, 0x22, 0xa2, 0x94, 0x19, 0x06, 0x7b, 0xfa, - 0xfb, 0x1a, 0x2c, 0x47, 0xde, 0xbe, 0x6b, 0xdf, 0x73, 0xa3, 0x87, 0xae, 0xdb, 0xb3, 0xbd, 0x7d, - 0xca, 0x97, 0x45, 0xd3, 0x99, 0x31, 0x09, 0x2d, 0x5b, 0x67, 0x45, 0x55, 0x56, 0x42, 0x9a, 0xf0, - 0xbc, 0x42, 0xc1, 0xbe, 0xbb, 0x4f, 0x35, 0xb6, 0x7e, 0xe0, 0xb3, 0x99, 0xe2, 0xfb, 0x7d, 0x96, - 0xf6, 0x63, 0xc6, 0xbc, 0x28, 0xb1, 0x28, 0xc6, 0xa8, 0x4d, 0x86, 0x59, 0xa3, 0x88, 0xa4, 0x04, - 0xe7, 0xb9, 0x2b, 0x07, 0x67, 0xe0, 0xf5, 0x76, 0x6d, 0xaa, 0x36, 0xf9, 0x3b, 0x3b, 0x76, 0x18, - 0xb9, 0x7d, 0x96, 0xc6, 0xc3, 0x3c, 0xcb, 0xb0, 0x9a, 0x31, 0xd2, 0x3a, 0xc3, 0xb1, 0x22, 0xb7, - 0x4f, 0x8a, 0xf0, 0xcc, 0x68, 0x26, 0xfb, 0xce, 0x23, 0xe6, 0x5b, 0x63, 0x9e, 0x19, 0xc1, 0x63, - 0xcb, 0x79, 0xc4, 0x57, 0xd6, 0xdb, 0xb0, 0xc8, 0x0f, 0x5e, 0x5c, 0x6b, 0xc3, 0x99, 0xc7, 0xf7, - 0x14, 0x7c, 0x76, 0xc3, 0x72, 0x41, 0x02, 0x07, 0x55, 0x3b, 0x62, 0x67, 0xfb, 0x23, 0x0d, 0x4e, - 0x64, 0x9e, 0xdc, 0xc8, 0x0f, 0x03, 0x7b, 0x97, 0x1c, 0xf9, 0x76, 0xe0, 0xb6, 0xbd, 0xbe, 0x87, - 0x81, 0x9e, 0xd8, 0xcd, 0xc6, 0xb5, 0x71, 0x67, 0x3e, 0x7c, 0xe3, 0xdc, 0xf2, 0xcd, 0x98, 0x88, - 0x99, 0x5c, 0xf5, 0x20, 0x05, 0x3e, 0xf3, 0x11, 0x9c, 0xc8, 0x44, 0xcd, 0x30, 0x85, 0xbe, 0x2c, - 0x9b, 0x42, 0x93, 0xbb, 0xea, 0x54, 0xa3, 0x25, 0x13, 0x29, 0x6f, 0xde, 0x6f, 0xc7, 0xcd, 0x4b, - 0x9d, 0xf1, 0x48, 0x25, 0x2d, 0xe5, 0xb3, 0xcc, 0x14, 0x82, 0x68, 0xb4, 0xa0, 0xff, 0x08, 0x4e, - 0x70, 0x51, 0xc3, 0x36, 0xb9, 0x98, 0x1d, 0xab, 0xe8, 0x0b, 0x59, 0xec, 0x98, 0x0c, 0xba, 0x41, - 0xf1, 0x63, 0xae, 0xc7, 0x9d, 0x61, 0x20, 0x6f, 0xc3, 0x77, 0xf3, 0x42, 0xb0, 0x67, 0x54, 0x27, - 0x43, 0x88, 0x69, 0x59, 0x42, 0xec, 0xe8, 0x12, 0xb4, 0x0e, 0x44, 0xde, 0xba, 0x42, 0xf9, 0x01, - 0xd5, 0xaa, 0x9a, 0xad, 0x44, 0xda, 0x08, 0xf8, 0xcb, 0xa0, 0xa5, 0x76, 0x1a, 0x44, 0x0f, 0x47, - 0x4c, 0x61, 0xa0, 0x9f, 0x64, 0xba, 0x4c, 0x81, 0x01, 0xaa, 0x1d, 0x72, 0x01, 0xe6, 0xd8, 0xc9, - 0x5c, 0x91, 0xb0, 0x80, 0xb0, 0x22, 0x8a, 0xd9, 0x77, 0xb3, 0xc4, 0x6c, 0x72, 0xc5, 0xc2, 0x95, - 0xf2, 0x83, 0x5e, 0x9b, 0x49, 0xd4, 0x94, 0xfc, 0xb5, 0xe1, 0xb4, 0xb4, 0x13, 0xf0, 0x1e, 0x12, - 0x6d, 0x9a, 0xc6, 0x36, 0x3d, 0xa7, 0xb6, 0x29, 0xd9, 0x20, 0x58, 0xcf, 0xf1, 0x86, 0x9d, 0x74, - 0x32, 0xe1, 0x64, 0x13, 0x16, 0x70, 0x1a, 0x79, 0xbd, 0xc8, 0x0d, 0x1e, 0xd0, 0xcd, 0xb2, 0xa0, - 0xdc, 0xf6, 0x0f, 0x0d, 0x5c, 0x55, 0x20, 0x9a, 0x38, 0xff, 0xe2, 0x9f, 0xf1, 0xa9, 0xed, 0xcc, - 0x68, 0x12, 0xb2, 0x0c, 0x93, 0x1d, 0xb7, 0x1b, 0x39, 0x7c, 0x69, 0xb0, 0x1f, 0x54, 0xc3, 0xdc, - 0x19, 0x74, 0xbb, 0x42, 0xc3, 0xa4, 0x7f, 0x73, 0x6e, 0x3f, 0x95, 0x03, 0x3d, 0xdd, 0x43, 0xc4, - 0x80, 0x9c, 0x48, 0xc4, 0x32, 0xec, 0x1c, 0xb5, 0x79, 0xcc, 0xcc, 0x79, 0x1d, 0x72, 0x1d, 0x00, - 0x93, 0x2f, 0x07, 0xee, 0xae, 0xfb, 0x88, 0x9f, 0x4a, 0xf0, 0xac, 0x90, 0x40, 0x15, 0x9a, 0x19, - 0xb4, 0x1f, 0x53, 0x30, 0xb9, 0x0a, 0xe0, 0x3e, 0x62, 0x09, 0x72, 0x84, 0x8a, 0x95, 0xf1, 0x19, - 0xcd, 0x9c, 0xe1, 0x58, 0xd5, 0x0e, 0xd9, 0x04, 0x22, 0x48, 0xa4, 0xaf, 0x4e, 0x1c, 0xf2, 0x55, - 0xcd, 0xd4, 0x39, 0x55, 0x5d, 0x7c, 0x9c, 0x9f, 0xeb, 0x67, 0x60, 0x9a, 0xe7, 0xe8, 0xa1, 0x7f, - 0x72, 0x24, 0xe3, 0xab, 0x1a, 0x5c, 0x38, 0x6c, 0x1d, 0x92, 0x3b, 0x70, 0x12, 0x5d, 0x0a, 0x43, - 0x3f, 0x5e, 0xca, 0x76, 0xdb, 0x69, 0xef, 0xb9, 0x5c, 0xf2, 0x19, 0x99, 0x0b, 0xba, 0xdf, 0xb7, - 0xac, 0x86, 0xb4, 0x96, 0xfb, 0x7d, 0x2b, 0xf4, 0xc5, 0xef, 0x12, 0x25, 0xe7, 0x03, 0xd2, 0x81, - 0xb3, 0x63, 0x28, 0x25, 0x55, 0x43, 0x93, 0x55, 0x8d, 0xcb, 0xa0, 0xef, 0xb8, 0x1d, 0x37, 0xc0, - 0xe7, 0xfc, 0xb4, 0x6a, 0x0f, 0xae, 0xe1, 0x98, 0xcc, 0x99, 0x0b, 0x31, 0xdc, 0x0a, 0xfd, 0xdb, - 0xd7, 0xf8, 0x57, 0xbe, 0x9a, 0x17, 0x5a, 0xb4, 0x6c, 0xa2, 0x22, 0xd7, 0xe0, 0x78, 0x2a, 0x12, - 0x9b, 0x14, 0xda, 0x27, 0xb7, 0xa2, 0x99, 0x4b, 0xb4, 0x58, 0x8d, 0xdd, 0xf9, 0x3c, 0xcc, 0xc9, - 0xbb, 0x33, 0x9f, 0x0a, 0x14, 0x79, 0xb6, 0x93, 0xec, 0xc9, 0xe4, 0x1e, 0x2c, 0x48, 0xa2, 0x85, - 0x6a, 0xbb, 0xf9, 0x0c, 0xf9, 0x27, 0xd7, 0xe6, 0x4a, 0x22, 0x73, 0x7a, 0x3b, 0x3e, 0xf3, 0xac, - 0x53, 0x59, 0x98, 0xf3, 0x6d, 0x19, 0x85, 0x58, 0x00, 0xb4, 0xa5, 0x76, 0xd7, 0xdb, 0xf7, 0x92, - 0x20, 0x0a, 0x69, 0xfe, 0xa6, 0x13, 0xb9, 0x35, 0x8a, 0xc1, 0x66, 0x4e, 0x42, 0x21, 0x9f, 0x6d, - 0x03, 0x81, 0x74, 0xe6, 0x43, 0x29, 0xbe, 0x29, 0x7e, 0xe5, 0x1c, 0x4c, 0xf4, 0x32, 0xd3, 0x17, - 0xe0, 0xb9, 0xfb, 0x1c, 0x4c, 0x44, 0x99, 0x41, 0xba, 0xa3, 0xf8, 0x28, 0xc9, 0xc7, 0xe0, 0x7f, - 0x68, 0x70, 0x3c, 0xa3, 0x56, 0xe4, 0x79, 0x98, 0x76, 0x1e, 0xb8, 0x81, 0x08, 0xe5, 0xca, 0xa3, - 0x35, 0x71, 0x90, 0x29, 0xfe, 0x20, 0xab, 0x30, 0x79, 0x6f, 0x10, 0x84, 0x11, 0x0f, 0xe9, 0x84, - 0x3e, 0x26, 0x08, 0x30, 0xd9, 0x7f, 0xe4, 0x3a, 0x2c, 0xf4, 0xdd, 0xc0, 0xf3, 0x3b, 0x76, 0xe8, - 0xb6, 0xfd, 0x5e, 0x87, 0x69, 0xb9, 0x79, 0xfe, 0x92, 0x4a, 0x29, 0x31, 0xe7, 0xd9, 0x6f, 0x8b, - 0xfd, 0x24, 0x77, 0xe1, 0xf4, 0xbe, 0xf3, 0x88, 0x4e, 0x83, 0xf6, 0x20, 0x08, 0xa8, 0x12, 0xe4, - 0xf7, 0xb9, 0x61, 0x8c, 0xb9, 0xb6, 0xb3, 0xf0, 0xdf, 0xa3, 0x91, 0xcc, 0x53, 0xfb, 0xce, 0xa3, - 0x52, 0x5c, 0xd2, 0x88, 0x0b, 0x78, 0xdb, 0x07, 0x62, 0x96, 0x67, 0x5a, 0x3b, 0x8f, 0x62, 0x51, - 0x7b, 0x05, 0x48, 0x6c, 0x9f, 0x8a, 0xb5, 0x0f, 0x2e, 0xe0, 0x96, 0x44, 0x49, 0xac, 0x36, 0xf0, - 0xcf, 0xfe, 0xc6, 0x94, 0xe8, 0x72, 0xc5, 0x4c, 0x4a, 0x5e, 0x01, 0x9d, 0x8a, 0xe7, 0xdd, 0x40, - 0x32, 0xc0, 0x25, 0x93, 0x7e, 0x51, 0x2a, 0xe3, 0xf7, 0x5f, 0x53, 0x81, 0xbb, 0x1b, 0xdf, 0xa5, - 0x99, 0xfc, 0x17, 0xd5, 0x4a, 0x9c, 0x40, 0x5c, 0xed, 0xd0, 0x3f, 0x49, 0x15, 0x96, 0x30, 0x9d, - 0x57, 0xe8, 0xf9, 0x98, 0x69, 0x0f, 0x0f, 0xdc, 0x13, 0x8a, 0x31, 0x15, 0x6b, 0xd2, 0x94, 0x90, - 0xe8, 0x89, 0xdb, 0xd4, 0xfb, 0x29, 0x08, 0xf9, 0x3c, 0x9c, 0x91, 0x37, 0xaa, 0xd4, 0x96, 0x8e, - 0xcf, 0xf9, 0xcc, 0x53, 0xc9, 0x1e, 0x54, 0x56, 0x36, 0xf7, 0x75, 0x38, 0x8f, 0x0b, 0xdb, 0xeb, - 0xf4, 0xed, 0xa1, 0x9c, 0x8a, 0xd8, 0x5c, 0x96, 0x52, 0xea, 0x0c, 0xc5, 0xaa, 0x76, 0xfa, 0xa9, - 0xf4, 0x8a, 0xd8, 0xea, 0x66, 0xe6, 0xb6, 0xcf, 0xb6, 0xc8, 0x67, 0xe4, 0xc6, 0x0c, 0x6d, 0xfa, - 0x4c, 0x74, 0x0c, 0x6f, 0xfc, 0xbb, 0xb0, 0x94, 0x1c, 0x25, 0xc4, 0xee, 0x5d, 0x40, 0x29, 0x7a, - 0x46, 0x66, 0x28, 0x8e, 0xe8, 0x6c, 0x7b, 0x62, 0xd1, 0xaa, 0x86, 0x08, 0xe5, 0x98, 0x6c, 0x03, - 0x85, 0x20, 0x24, 0x35, 0x58, 0x76, 0x1e, 0x86, 0xf4, 0xc8, 0xe7, 0x0f, 0x7a, 0x51, 0x18, 0x7f, - 0x6b, 0x66, 0xf8, 0x5b, 0xc2, 0x99, 0x83, 0x2b, 0x0b, 0xc4, 0x79, 0x18, 0x16, 0x39, 0x99, 0xe0, - 0xf6, 0x31, 0x10, 0xa6, 0x70, 0x28, 0xf5, 0x86, 0xc3, 0x78, 0x31, 0xff, 0xfe, 0x61, 0x4a, 0x39, - 0xb2, 0x2c, 0x96, 0xca, 0x35, 0x6f, 0xa9, 0x17, 0x70, 0xb3, 0x8a, 0xf7, 0x48, 0xba, 0xb7, 0x99, - 0x67, 0x8b, 0x84, 0x2f, 0x1b, 0x14, 0x25, 0x30, 0xda, 0x90, 0x30, 0xf2, 0x24, 0xd6, 0x63, 0xdf, - 0xef, 0xb8, 0xec, 0x45, 0x84, 0x39, 0x8f, 0x60, 0x5a, 0x81, 0xad, 0xc4, 0xae, 0xf7, 0xa9, 0x06, - 0x7a, 0xfa, 0x53, 0xe4, 0x5d, 0x98, 0x62, 0xa7, 0x3a, 0xae, 0x16, 0x1b, 0xd9, 0x75, 0x62, 0x23, - 0xcd, 0x0e, 0x78, 0x9b, 0xc7, 0x4c, 0x4e, 0x43, 0xde, 0x84, 0x09, 0xdf, 0xeb, 0x08, 0x6f, 0x98, - 0x0b, 0xe3, 0x68, 0x1b, 0xd5, 0x72, 0x09, 0x6f, 0xd0, 0xbc, 0x0e, 0x37, 0x44, 0xad, 0x17, 0xc4, - 0x53, 0x74, 0xe3, 0x63, 0x38, 0x3b, 0xe6, 0x83, 0xa4, 0x02, 0x8b, 0x29, 0x4f, 0xa1, 0x23, 0x3a, - 0x11, 0x39, 0x89, 0x13, 0x51, 0x20, 0x8c, 0x13, 0x5d, 0x38, 0x3d, 0xb2, 0x82, 0xa4, 0x3a, 0x52, - 0x82, 0x60, 0x6c, 0xb4, 0x74, 0x99, 0x3c, 0x59, 0x53, 0xd2, 0x85, 0x7f, 0xed, 0xeb, 0x39, 0x38, - 0x9e, 0x31, 0x89, 0xfe, 0xaf, 0xd5, 0xcd, 0xfe, 0xb1, 0xc6, 0xfb, 0x43, 0x15, 0x06, 0xa4, 0x05, - 0xdc, 0x11, 0x8d, 0x0b, 0x8e, 0x4b, 0xa3, 0x05, 0x87, 0xec, 0xdf, 0xc3, 0x83, 0xe4, 0x21, 0x40, - 0xf6, 0x62, 0x61, 0x90, 0x27, 0xf0, 0xbc, 0xe1, 0xc3, 0xf7, 0x11, 0x9c, 0xc8, 0x14, 0xec, 0xe4, - 0x34, 0x14, 0xf0, 0x01, 0x4c, 0x62, 0xa9, 0x9c, 0xa6, 0xbf, 0xb7, 0x03, 0xb4, 0x82, 0xdf, 0x73, - 0x9d, 0xc0, 0x0d, 0xb8, 0x9d, 0x8c, 0x5b, 0xc1, 0x19, 0x4c, 0x36, 0x93, 0x75, 0xd4, 0x5d, 0x8c, - 0x5f, 0xdd, 0x93, 0x2d, 0x38, 0xce, 0xa4, 0x0b, 0x33, 0x77, 0xd8, 0xfc, 0xba, 0x5f, 0xcd, 0xd1, - 0x88, 0x24, 0x68, 0x08, 0x62, 0xa6, 0x0f, 0x46, 0x6d, 0x2e, 0xed, 0xa6, 0x41, 0x54, 0x1b, 0x3e, - 0x99, 0x8d, 0x4d, 0xd6, 0x61, 0x96, 0x31, 0x67, 0x37, 0x05, 0xcc, 0x4f, 0xeb, 0xe2, 0xd8, 0x2f, - 0x94, 0xf0, 0x5d, 0x68, 0x18, 0xff, 0x4d, 0x9e, 0x85, 0x79, 0x74, 0x89, 0xb5, 0xf7, 0x65, 0x37, - 0x34, 0x73, 0x0e, 0x81, 0xdc, 0xfd, 0xcc, 0xf8, 0x77, 0xb1, 0x8e, 0xa4, 0xdc, 0x51, 0xd2, 0x1d, - 0x38, 0x74, 0x7b, 0xc2, 0x15, 0x6f, 0xc6, 0xe4, 0xbf, 0x1e, 0x53, 0x2b, 0x20, 0x6f, 0xc1, 0x1c, - 0x65, 0xbb, 0x3b, 0xe8, 0xb1, 0x9d, 0x39, 0xaf, 0x04, 0xef, 0xdd, 0x62, 0x45, 0x74, 0xd8, 0x36, - 0x8f, 0x99, 0xb3, 0xfb, 0xc9, 0x4f, 0x72, 0x05, 0x66, 0xc2, 0xfd, 0xa8, 0x2f, 0xef, 0xe7, 0xc2, - 0x5f, 0xc3, 0xda, 0x6a, 0x35, 0x39, 0x49, 0x81, 0xe2, 0x24, 0x06, 0xf4, 0xf5, 0x29, 0xe6, 0xb1, - 0x61, 0xbc, 0x04, 0xb3, 0x12, 0x6f, 0xda, 0x18, 0x16, 0x45, 0x41, 0x34, 0x86, 0xfd, 0xe2, 0x83, - 0x7d, 0x0f, 0x0a, 0x82, 0x25, 0x3d, 0xc7, 0xed, 0xf9, 0xa1, 0xd0, 0x87, 0xf0, 0x6f, 0x0a, 0x43, - 0x8b, 0x1a, 0x6d, 0xe4, 0xa4, 0x89, 0x7f, 0xe3, 0x59, 0x1e, 0x2f, 0x09, 0x31, 0x11, 0x04, 0xbe, - 0x9c, 0x89, 0x4d, 0xd9, 0x14, 0xde, 0xea, 0x86, 0xec, 0x3d, 0x8d, 0x30, 0xaa, 0xc7, 0x46, 0x90, - 0xd4, 0xa5, 0xee, 0xa8, 0xf3, 0x86, 0x72, 0x64, 0xcf, 0x0d, 0x1f, 0xd9, 0x59, 0x50, 0x56, 0x4e, - 0xc9, 0xbe, 0x0c, 0x08, 0x63, 0x47, 0xf6, 0x44, 0x81, 0x9a, 0x50, 0x14, 0x28, 0xe9, 0x9a, 0x2e, - 0x19, 0x3d, 0x76, 0xe2, 0x17, 0xd7, 0x74, 0x69, 0x95, 0xee, 0x57, 0xe2, 0x19, 0xa2, 0x5c, 0x2b, - 0x93, 0x6b, 0x70, 0x82, 0x99, 0xa9, 0x59, 0xe6, 0xcc, 0xb4, 0x2e, 0x79, 0x1c, 0x0b, 0x59, 0xda, - 0xe8, 0x58, 0xa7, 0x3c, 0xfc, 0x1a, 0x8a, 0xbc, 0x06, 0xcb, 0x5e, 0x2f, 0x74, 0xdb, 0x83, 0xc0, - 0xb5, 0xc3, 0xfb, 0x5e, 0x9f, 0xe5, 0x70, 0x3f, 0xe0, 0x06, 0x64, 0x22, 0xca, 0xac, 0xfb, 0x5e, - 0x1f, 0xf3, 0xb9, 0x8b, 0x1e, 0xfe, 0xd5, 0x9c, 0xb8, 0xdc, 0x5c, 0xf7, 0xfd, 0x28, 0x8c, 0x02, - 0xa7, 0xaf, 0x38, 0xce, 0x90, 0x7d, 0x38, 0x8d, 0x55, 0xba, 0x86, 0xa9, 0x42, 0xfd, 0x40, 0x5c, - 0x06, 0xc7, 0x0b, 0x6c, 0xf6, 0xda, 0xab, 0xea, 0xc5, 0x40, 0x91, 0x62, 0x17, 0x65, 0x64, 0xba, - 0xae, 0x24, 0xae, 0x9b, 0xc7, 0xcc, 0x53, 0x8c, 0xe7, 0x10, 0x16, 0xd9, 0xcc, 0x90, 0x35, 0x69, - 0xcf, 0x99, 0xf5, 0x44, 0xf0, 0xa8, 0x5c, 0x65, 0x91, 0x44, 0xde, 0x87, 0x19, 0xaf, 0x23, 0x72, - 0xb3, 0xe6, 0x33, 0x7c, 0x36, 0xaa, 0x1d, 0x96, 0x42, 0x2b, 0xe1, 0x41, 0x97, 0x86, 0xc7, 0xa1, - 0xeb, 0xf3, 0x8a, 0x86, 0x63, 0x6c, 0x8a, 0x7b, 0xb4, 0x61, 0x32, 0xb2, 0x90, 0xec, 0x7d, 0xb8, - 0xcf, 0xa1, 0x14, 0x90, 0x72, 0xc2, 0xf2, 0x5f, 0xbc, 0xcb, 0x7f, 0x04, 0x2e, 0x1f, 0xb5, 0xa7, - 0xa8, 0xdc, 0x18, 0xd1, 0xed, 0x33, 0x2c, 0x81, 0x87, 0xda, 0x7b, 0x17, 0x41, 0x4e, 0x3c, 0xe4, - 0x89, 0x89, 0x22, 0x60, 0xdb, 0x81, 0x67, 0xfc, 0x59, 0x1e, 0x16, 0x54, 0xd7, 0x2a, 0xf2, 0x12, - 0x4c, 0x48, 0xe2, 0xf2, 0x54, 0x86, 0xff, 0x15, 0x0a, 0x49, 0x44, 0x3a, 0x92, 0x78, 0x24, 0x37, - 0x61, 0x01, 0x5f, 0x87, 0xa1, 0x1a, 0x17, 0x79, 0xdc, 0x6d, 0xe0, 0xa8, 0x0e, 0x01, 0x73, 0x94, - 0x96, 0x6e, 0x8f, 0xb4, 0x50, 0xf2, 0x9c, 0x99, 0x18, 0xed, 0x39, 0xc3, 0x9b, 0x32, 0xc2, 0x73, - 0x66, 0x72, 0x8c, 0xe7, 0x4c, 0x42, 0x29, 0x7b, 0xce, 0xa0, 0xff, 0xd4, 0xf4, 0x28, 0xff, 0xa9, - 0x84, 0x86, 0xf9, 0x4f, 0x25, 0x9e, 0x2f, 0x85, 0x91, 0x9e, 0x2f, 0x09, 0x0d, 0xf7, 0x7c, 0x49, - 0x7c, 0x51, 0x66, 0x46, 0xfa, 0xa2, 0x48, 0x44, 0xcc, 0x17, 0xe5, 0x39, 0xde, 0xb1, 0x81, 0xf3, - 0xd0, 0xc6, 0x1e, 0xe7, 0xe7, 0x23, 0xec, 0x32, 0xd3, 0x79, 0x88, 0xaf, 0x38, 0xa8, 0x7a, 0xc2, - 0x9f, 0x7e, 0x18, 0xdf, 0x4a, 0x89, 0x21, 0x31, 0xe6, 0xcf, 0xc3, 0x02, 0xdb, 0x8d, 0x79, 0x62, - 0x18, 0xb6, 0x1d, 0xcf, 0x9b, 0xf3, 0x02, 0xca, 0xae, 0xaf, 0x5e, 0x80, 0xc5, 0x18, 0x8d, 0xdf, - 0xe0, 0x60, 0x58, 0x1a, 0x33, 0xa6, 0xe6, 0x77, 0x37, 0x32, 0xbf, 0x80, 0x87, 0xb7, 0x55, 0xf8, - 0xb1, 0x0b, 0x90, 0x57, 0x80, 0x24, 0x68, 0xf1, 0x43, 0xb8, 0x09, 0x44, 0x5d, 0x8a, 0x51, 0xe3, - 0xd7, 0x6a, 0x3f, 0xaf, 0xa5, 0x9c, 0x37, 0xbe, 0x5f, 0xd5, 0x7f, 0x09, 0xe2, 0xaf, 0xdb, 0xfc, - 0x02, 0x5e, 0xb4, 0x40, 0x17, 0x05, 0x4d, 0x0e, 0x37, 0xfe, 0xbf, 0x21, 0xd3, 0xfc, 0xf7, 0xab, - 0x5a, 0x67, 0x61, 0x26, 0x39, 0x09, 0xb1, 0xed, 0xaa, 0x10, 0xf2, 0x43, 0x90, 0xf1, 0xab, 0x13, - 0xca, 0x8d, 0xb3, 0xa8, 0x03, 0xd5, 0x81, 0x42, 0xdf, 0xe6, 0x13, 0x80, 0x8b, 0xe8, 0x8b, 0x23, - 0x26, 0x31, 0x7f, 0x18, 0x64, 0x59, 0x0d, 0x13, 0xc2, 0xd0, 0x17, 0xef, 0x84, 0x6c, 0x66, 0x4b, - 0x94, 0xce, 0x84, 0x82, 0x1d, 0x93, 0xc7, 0x6b, 0xe3, 0xd9, 0x89, 0x2b, 0x3d, 0xba, 0x86, 0xd1, - 0xa6, 0x18, 0xff, 0x12, 0x1f, 0xd8, 0x06, 0xf4, 0x12, 0x09, 0x55, 0xe6, 0x59, 0x96, 0xb7, 0x21, - 0xe6, 0xd8, 0x85, 0xc8, 0x19, 0xef, 0xfb, 0x42, 0x99, 0x6d, 0x05, 0xe6, 0xf0, 0x3e, 0x57, 0x30, - 0x9c, 0xc8, 0xf0, 0x63, 0x1b, 0x6e, 0x7c, 0xa9, 0xba, 0x65, 0xce, 0x52, 0x3a, 0xc1, 0x66, 0x4f, - 0xb1, 0xbd, 0xa7, 0x2a, 0x39, 0x29, 0x92, 0x3d, 0x8d, 0xed, 0x81, 0xe4, 0xb2, 0x16, 0xab, 0x2a, - 0x19, 0xe1, 0x95, 0x0a, 0xb7, 0xe1, 0xf4, 0xd0, 0x1d, 0x64, 0xfc, 0x25, 0xe6, 0x57, 0x79, 0x79, - 0xc4, 0x97, 0x52, 0x97, 0x93, 0x6e, 0x60, 0x9e, 0x0c, 0x55, 0x10, 0xff, 0x08, 0x3e, 0xc4, 0x1b, - 0x3d, 0xf0, 0x63, 0xd2, 0x90, 0x27, 0x4a, 0x56, 0x4e, 0x56, 0xb2, 0xe4, 0x9b, 0xdf, 0xbc, 0x7a, - 0xf3, 0xbb, 0x01, 0x17, 0xa8, 0x44, 0xe4, 0x33, 0xc7, 0x7d, 0xe0, 0x06, 0x07, 0x7e, 0x0f, 0x23, - 0x04, 0xf7, 0x63, 0xc1, 0xc0, 0xae, 0xaa, 0xcf, 0x51, 0x3c, 0x9c, 0x17, 0x15, 0x8e, 0xb5, 0x85, - 0x48, 0x2c, 0xf2, 0xf5, 0x3f, 0xcc, 0xc3, 0xb3, 0x47, 0x98, 0x5c, 0x63, 0xea, 0xfe, 0x05, 0xf5, - 0x28, 0x90, 0x53, 0x6e, 0x81, 0x28, 0xd3, 0xe4, 0x76, 0x61, 0xc4, 0x41, 0xe0, 0x87, 0x61, 0x91, - 0x6d, 0x62, 0xec, 0x25, 0xe2, 0xce, 0xa0, 0x7b, 0x84, 0x5d, 0xec, 0xac, 0x88, 0xb3, 0x92, 0x22, - 0xc5, 0x8d, 0x0d, 0x65, 0xb7, 0x15, 0xc3, 0x48, 0x0b, 0x66, 0x11, 0x6d, 0xc7, 0xf1, 0xba, 0x47, - 0x0a, 0xf8, 0x21, 0xa2, 0xb8, 0xc8, 0x64, 0xec, 0xc5, 0x35, 0x05, 0x6c, 0xe0, 0x6f, 0x72, 0x09, - 0x16, 0x7b, 0x83, 0x7d, 0xaa, 0xe4, 0xb2, 0x99, 0xcb, 0x1f, 0x7c, 0x4c, 0x9a, 0xf3, 0xbd, 0xc1, - 0x7e, 0xb1, 0xdf, 0xc7, 0x09, 0x88, 0x2f, 0x43, 0x96, 0x28, 0x1e, 0xcf, 0xee, 0xcf, 0x31, 0xa7, - 0x10, 0x93, 0x32, 0x60, 0x22, 0x88, 0xe3, 0x2e, 0x03, 0x7b, 0x27, 0xc8, 0xd3, 0xb9, 0xb3, 0x1f, - 0xc6, 0x7f, 0xcf, 0x89, 0x7b, 0x89, 0xd1, 0xab, 0xf4, 0x6f, 0x86, 0x28, 0x63, 0x88, 0x2e, 0x83, - 0x4e, 0xbb, 0x3e, 0x11, 0x81, 0xf1, 0x18, 0x2d, 0xf4, 0x06, 0xfb, 0x71, 0xdf, 0xc9, 0x1d, 0x3f, - 0x25, 0x77, 0xfc, 0x5b, 0xc2, 0x4c, 0x9d, 0x29, 0xcc, 0x46, 0x77, 0x39, 0xd5, 0xfe, 0x2e, 0x1d, - 0x4d, 0x64, 0xfd, 0xcd, 0xb8, 0x65, 0x8c, 0x5b, 0xca, 0x29, 0x66, 0x72, 0xc8, 0x29, 0x26, 0x63, - 0xed, 0x4d, 0x65, 0xad, 0xbd, 0x21, 0x17, 0x9c, 0xe9, 0x0c, 0x17, 0x9c, 0xcc, 0x05, 0x5a, 0x38, - 0x64, 0x81, 0xce, 0xc8, 0xf3, 0xe4, 0xf7, 0x72, 0x70, 0xf1, 0xd0, 0x7d, 0xe3, 0x6f, 0x46, 0x3a, - 0x63, 0xa4, 0xb3, 0xfb, 0xf3, 0xcf, 0x72, 0x42, 0x9b, 0x56, 0x0f, 0xc9, 0x1f, 0xc1, 0x71, 0x71, - 0x48, 0x66, 0x7a, 0x43, 0xe2, 0xa9, 0x36, 0x7b, 0xed, 0xc5, 0xac, 0xe3, 0x31, 0xa2, 0x65, 0x1c, - 0x61, 0x97, 0xf8, 0xc1, 0x38, 0x29, 0xff, 0x3f, 0xe7, 0x48, 0x4c, 0xee, 0xc2, 0x49, 0x4c, 0xf8, - 0xd8, 0x96, 0x7d, 0xec, 0xec, 0xc0, 0xdd, 0xe1, 0xbd, 0x7e, 0x71, 0xe8, 0xe8, 0xe8, 0xb5, 0xa5, - 0xea, 0x98, 0xee, 0xce, 0xe6, 0x31, 0x73, 0x39, 0xcc, 0x80, 0x73, 0x43, 0x54, 0xea, 0xcc, 0xfd, - 0xcf, 0x34, 0x30, 0x0e, 0xef, 0x35, 0x34, 0x8f, 0xa4, 0xbb, 0x7d, 0xc6, 0x9c, 0x75, 0xa4, 0x3e, - 0x7c, 0x16, 0xe6, 0x03, 0x77, 0x27, 0x70, 0xc3, 0x3d, 0xc5, 0x86, 0x39, 0xc7, 0x81, 0xa2, 0x7b, - 0x44, 0xee, 0x99, 0xc7, 0x3a, 0xae, 0x0a, 0xa2, 0xd8, 0x0f, 0xe2, 0xdc, 0xb8, 0x31, 0xa1, 0x33, - 0x4b, 0xae, 0x26, 0xfb, 0x11, 0x7b, 0x4a, 0xe6, 0xf4, 0xbc, 0xc9, 0xb3, 0xe5, 0xec, 0x78, 0x5d, - 0xd7, 0xf8, 0xad, 0x58, 0x77, 0xcb, 0xea, 0x4e, 0xf2, 0x91, 0xf4, 0x42, 0x3a, 0x3f, 0xa4, 0x96, - 0x66, 0x91, 0x1c, 0xc5, 0xd8, 0x5c, 0x7b, 0x4a, 0xc6, 0xe6, 0xeb, 0xe2, 0x99, 0x15, 0xdd, 0x55, - 0x6e, 0x5f, 0x25, 0x2f, 0xc2, 0x34, 0x7b, 0x59, 0x25, 0xaa, 0xbb, 0xa8, 0x54, 0xf7, 0xf6, 0x55, - 0x53, 0x94, 0x1b, 0x9f, 0xc6, 0x3e, 0xa1, 0x43, 0x8d, 0xb8, 0x7d, 0x95, 0xbc, 0x75, 0xb4, 0x17, - 0xcf, 0x05, 0xf1, 0xe2, 0x39, 0x7e, 0xed, 0xfc, 0xb6, 0xf2, 0xda, 0xf9, 0xb9, 0xf1, 0xbd, 0xc5, - 0xdd, 0x8d, 0x59, 0x06, 0x92, 0xc2, 0xa7, 0xbf, 0xc4, 0x22, 0xbf, 0x1b, 0x3f, 0x91, 0x87, 0x67, - 0xc6, 0x52, 0x90, 0x73, 0x50, 0x28, 0x36, 0xab, 0xad, 0x64, 0x94, 0xe9, 0x2a, 0x12, 0x10, 0x72, - 0x03, 0x66, 0xd6, 0x9d, 0xd0, 0x6b, 0xd3, 0x29, 0x9d, 0xe9, 0x6c, 0x35, 0xc4, 0x36, 0x46, 0xdf, - 0x3c, 0x66, 0x26, 0xb4, 0xc4, 0x86, 0x25, 0x5c, 0x17, 0x4a, 0xda, 0xf8, 0x7c, 0x86, 0x49, 0x6e, - 0x88, 0xe1, 0x10, 0x19, 0x95, 0x3c, 0x43, 0x40, 0x72, 0x0f, 0x88, 0x65, 0x6d, 0x96, 0xdc, 0x20, - 0xe2, 0x46, 0xaa, 0xc8, 0x8b, 0x9f, 0xcf, 0xbe, 0x76, 0x48, 0xdf, 0x0d, 0xd1, 0x6d, 0x1e, 0x33, - 0x33, 0xb8, 0x91, 0x8b, 0x30, 0xdb, 0x0f, 0xbc, 0x07, 0x4e, 0xe4, 0xa2, 0x13, 0x3c, 0xd5, 0x82, - 0xe6, 0x36, 0x8f, 0x99, 0xc0, 0x81, 0xb7, 0xdc, 0x83, 0x6c, 0xd9, 0xf0, 0x23, 0x42, 0xf5, 0x1c, - 0xdd, 0x5b, 0x8f, 0x91, 0x06, 0xea, 0x32, 0x14, 0x9a, 0xe2, 0x55, 0x82, 0x14, 0xaf, 0x40, 0xbc, - 0x40, 0x30, 0xe3, 0x52, 0x3e, 0xb3, 0x7f, 0x4e, 0x13, 0x46, 0xbc, 0xc3, 0xfb, 0x96, 0xbc, 0x08, - 0x22, 0xcb, 0xbf, 0xb8, 0x20, 0xc3, 0xf8, 0x09, 0xf1, 0xc3, 0x1b, 0x33, 0x2e, 0x26, 0x6f, 0x80, - 0x92, 0xdd, 0x9f, 0xd7, 0x05, 0xd3, 0x54, 0x73, 0x74, 0x26, 0xbd, 0x4d, 0x05, 0x8d, 0x57, 0xca, - 0x87, 0x17, 0x8f, 0x3c, 0x1a, 0xe4, 0x5d, 0xd0, 0x31, 0x49, 0xba, 0x23, 0x8d, 0xac, 0x9a, 0xae, - 0x80, 0x85, 0xaf, 0x6d, 0x3a, 0x5e, 0x60, 0x2e, 0xb6, 0x55, 0x6a, 0xfe, 0xc1, 0x5f, 0xe3, 0x19, - 0x22, 0xab, 0x9d, 0x66, 0xea, 0x62, 0xfe, 0x49, 0x83, 0x5e, 0x54, 0x94, 0x75, 0x2a, 0xf6, 0xba, - 0xec, 0x6f, 0x8d, 0x8e, 0x7d, 0x21, 0x2d, 0xda, 0xbf, 0x93, 0x87, 0x73, 0xe3, 0xc8, 0x49, 0x11, - 0xf4, 0x0a, 0x0b, 0xa4, 0xcb, 0x9f, 0x1b, 0xfb, 0x81, 0x9c, 0xb1, 0x98, 0x05, 0xd9, 0xb5, 0x3b, - 0x71, 0xa1, 0x39, 0x84, 0x4e, 0xc7, 0x99, 0xc1, 0xe2, 0x88, 0x0e, 0x38, 0xce, 0x9c, 0x94, 0x8e, - 0xb3, 0x28, 0x26, 0xcf, 0xc2, 0x54, 0xb1, 0x64, 0x6d, 0x9b, 0x35, 0x39, 0x5c, 0x8b, 0xd3, 0x0e, - 0xf1, 0x51, 0x2f, 0x2f, 0x22, 0x3f, 0x04, 0x7a, 0x3a, 0x33, 0x2b, 0x1e, 0x9e, 0xa5, 0x50, 0x64, - 0x72, 0xca, 0x50, 0x91, 0x9c, 0x07, 0xeb, 0x9b, 0x24, 0x1b, 0xe5, 0xf9, 0xfb, 0xcc, 0x21, 0x5e, - 0xc4, 0x80, 0xa9, 0x66, 0xe0, 0x86, 0x6e, 0x24, 0x3f, 0x8b, 0xee, 0x23, 0xc4, 0xe4, 0x25, 0xfc, - 0xd1, 0xb2, 0x73, 0xc0, 0x82, 0x5a, 0x4e, 0xc9, 0xc1, 0x8b, 0xf1, 0x95, 0x33, 0x05, 0x9b, 0x12, - 0x0a, 0x25, 0xa8, 0x39, 0x83, 0x5e, 0x7b, 0x6f, 0xdb, 0xac, 0x71, 0xb5, 0x96, 0x11, 0x74, 0x11, - 0x4a, 0x1b, 0x18, 0x9a, 0x12, 0x8a, 0xf1, 0x93, 0x1a, 0x2c, 0x67, 0xb5, 0xe3, 0x10, 0x7f, 0xa6, - 0xd7, 0x60, 0x16, 0xef, 0x6f, 0x77, 0xfc, 0x60, 0xdf, 0x89, 0xe4, 0xc7, 0xe3, 0x12, 0xd8, 0xc4, - 0xfb, 0xe6, 0x0d, 0xfc, 0x9b, 0xac, 0x8a, 0xdd, 0x4a, 0xca, 0xbb, 0x84, 0x00, 0xbe, 0x71, 0x19, - 0x45, 0x80, 0x6a, 0xa7, 0xd9, 0xe8, 0xb3, 0xe4, 0xa1, 0xaf, 0xc3, 0x04, 0xad, 0x56, 0x6a, 0xf6, - 0xd2, 0xf9, 0x53, 0xdc, 0xaa, 0x71, 0x24, 0x56, 0xab, 0xd0, 0xd9, 0xef, 0x9a, 0x88, 0x6c, 0xdc, - 0x81, 0x05, 0x15, 0x83, 0x54, 0xd4, 0xfc, 0x51, 0xb3, 0xd7, 0x74, 0xce, 0x69, 0xdd, 0xf7, 0x59, - 0x00, 0x93, 0xf5, 0xd3, 0xdf, 0xfe, 0x6c, 0x15, 0xe8, 0x4f, 0x46, 0x93, 0x95, 0x5f, 0xca, 0xf8, - 0x99, 0x1c, 0x2c, 0x27, 0x41, 0x16, 0xc5, 0x1a, 0xfa, 0x81, 0x0d, 0xe0, 0x55, 0x54, 0x02, 0x4c, - 0x09, 0x25, 0x74, 0xb8, 0x81, 0x63, 0xe2, 0xda, 0xdc, 0x80, 0x95, 0x51, 0xf8, 0xe4, 0x25, 0x98, - 0xc1, 0x58, 0xdf, 0x7d, 0xa7, 0xed, 0xca, 0x22, 0xb7, 0x27, 0x80, 0x66, 0x52, 0x6e, 0xfc, 0x9e, - 0x06, 0x67, 0x78, 0xd8, 0x8d, 0x2d, 0xc7, 0xeb, 0xe1, 0x75, 0x61, 0xdb, 0x7d, 0x3a, 0x01, 0xe8, - 0x6e, 0x28, 0x72, 0xec, 0x79, 0x35, 0xba, 0xca, 0xd0, 0xd7, 0x46, 0xb7, 0x96, 0xbc, 0x88, 0xf1, - 0xeb, 0xb9, 0x3b, 0xf3, 0x04, 0x8b, 0x10, 0xda, 0xa3, 0x00, 0x39, 0x42, 0x28, 0x62, 0x18, 0x3f, - 0x0a, 0xe7, 0xc7, 0x7f, 0x80, 0x7c, 0x19, 0xe6, 0x31, 0xdb, 0xff, 0x76, 0x7f, 0x37, 0x70, 0x3a, - 0xae, 0x30, 0x12, 0x8b, 0x9b, 0x0e, 0xb9, 0x8c, 0x85, 0xe3, 0xe7, 0x11, 0x2b, 0x29, 0xdc, 0x1e, - 0x70, 0x22, 0x25, 0xb6, 0x8d, 0xcc, 0xcd, 0xf8, 0x31, 0x0d, 0xc8, 0x30, 0x0f, 0xf2, 0x26, 0xcc, - 0x6d, 0xb7, 0x4a, 0x56, 0xe4, 0x04, 0xd1, 0xa6, 0x3f, 0x08, 0x78, 0x2c, 0x7c, 0x16, 0xc0, 0x30, - 0x6a, 0xdb, 0xec, 0x62, 0x78, 0xcf, 0x1f, 0x04, 0xa6, 0x82, 0x87, 0x19, 0xe1, 0x5d, 0xf7, 0x7e, - 0xc7, 0x39, 0x50, 0x33, 0xc2, 0x73, 0x98, 0x92, 0x11, 0x9e, 0xc3, 0x8c, 0x5f, 0xd1, 0xe0, 0xac, - 0x78, 0x38, 0xd9, 0xc9, 0xa8, 0x4b, 0x09, 0xc3, 0xf4, 0x06, 0x22, 0x2b, 0xd7, 0x38, 0x45, 0x7f, - 0x49, 0x44, 0xb2, 0xc6, 0x0a, 0xa2, 0xc6, 0xcf, 0x68, 0xc9, 0x17, 0x60, 0xc2, 0x8a, 0xfc, 0xfe, - 0x11, 0x42, 0x59, 0xeb, 0xf1, 0x88, 0x46, 0x7e, 0x1f, 0x59, 0x20, 0xa5, 0xe1, 0xc2, 0xb2, 0x5c, - 0x39, 0x51, 0x63, 0xb2, 0x05, 0xd3, 0x3c, 0x0f, 0x42, 0xca, 0x79, 0x77, 0x4c, 0x9b, 0xd6, 0x17, - 0x45, 0xbc, 0x6c, 0x9e, 0x3f, 0xca, 0x14, 0x3c, 0x8c, 0x9f, 0xd6, 0x60, 0x96, 0xaa, 0x3a, 0x68, - 0x31, 0x78, 0xd2, 0x29, 0xad, 0xaa, 0xd0, 0xe2, 0x3d, 0x43, 0xcc, 0xfe, 0x48, 0xbb, 0xf1, 0x1b, - 0xb0, 0x98, 0x22, 0x20, 0x06, 0x46, 0x4a, 0xed, 0x7a, 0x6d, 0xee, 0xcd, 0xc9, 0xde, 0x02, 0x28, - 0x30, 0xe3, 0xa7, 0x34, 0x58, 0x6e, 0xdc, 0x8f, 0x1c, 0xe6, 0xbf, 0x61, 0x0e, 0xba, 0x62, 0xbd, - 0x53, 0xf5, 0x4d, 0xbc, 0xc0, 0x65, 0x41, 0x19, 0x99, 0xfa, 0xc6, 0x61, 0x66, 0x5c, 0x4a, 0x36, - 0xa1, 0xc0, 0xf7, 0x97, 0x90, 0x67, 0x30, 0x3a, 0x2f, 0x99, 0x33, 0x12, 0xc6, 0x1c, 0x89, 0xb6, - 0x04, 0x45, 0x18, 0xa7, 0x31, 0x63, 0x6a, 0xe3, 0xcf, 0x35, 0x38, 0x35, 0x82, 0x86, 0xbc, 0x07, - 0x93, 0x18, 0x30, 0x8a, 0x8f, 0xde, 0xb9, 0x11, 0x9f, 0x88, 0xda, 0x7b, 0xb7, 0xaf, 0xb2, 0x8d, - 0x68, 0x9f, 0xfe, 0x30, 0x19, 0x15, 0xf9, 0x08, 0x66, 0x8a, 0x9d, 0x0e, 0x3f, 0xd8, 0xe5, 0x94, - 0x83, 0xdd, 0x88, 0x2f, 0x5e, 0x89, 0xf1, 0xd9, 0xc1, 0x8e, 0x85, 0x2e, 0xe9, 0x74, 0x6c, 0x1e, - 0x0c, 0x2b, 0xe1, 0x77, 0xe6, 0x5d, 0x58, 0x50, 0x91, 0x1f, 0x2b, 0x7e, 0xcf, 0xa7, 0x1a, 0xe8, - 0x6a, 0x1d, 0xbe, 0x3f, 0x91, 0xbe, 0xb3, 0x86, 0xf9, 0x90, 0x49, 0xf5, 0xb3, 0x39, 0x38, 0x91, - 0xd9, 0xc3, 0xe4, 0x15, 0x98, 0x2a, 0xf6, 0xfb, 0xd5, 0x32, 0x9f, 0x55, 0x5c, 0x43, 0xc2, 0x9b, - 0x0d, 0xe5, 0xdc, 0xcb, 0x90, 0xc8, 0xeb, 0x50, 0x60, 0x6e, 0x42, 0x65, 0x21, 0x70, 0x30, 0x74, - 0x31, 0xf7, 0x61, 0x52, 0x73, 0x09, 0x09, 0x44, 0xb2, 0x01, 0x0b, 0x3c, 0xe8, 0x2f, 0xfa, 0x8c, - 0xc5, 0xf9, 0x3d, 0xd1, 0xcd, 0x4e, 0x5c, 0x97, 0x30, 0x6f, 0x33, 0x45, 0x76, 0xa6, 0xa8, 0x48, - 0x0d, 0x74, 0xe4, 0x29, 0x73, 0x62, 0x09, 0x8d, 0x24, 0x37, 0xcd, 0x11, 0xbc, 0x86, 0x28, 0xe3, - 0xe1, 0x4a, 0xde, 0x82, 0x7d, 0xff, 0x86, 0x2b, 0xf9, 0xc6, 0x91, 0x86, 0xeb, 0x17, 0x26, 0xd8, - 0x62, 0x4e, 0x93, 0x51, 0x8d, 0x46, 0x4a, 0xe7, 0x87, 0x1a, 0x0d, 0x3d, 0xb1, 0xf1, 0xb0, 0xb6, - 0x65, 0x98, 0x6e, 0xf1, 0xec, 0x6a, 0x6c, 0x65, 0x3c, 0x93, 0x59, 0x05, 0x86, 0x73, 0xfb, 0x2a, - 0x53, 0x5f, 0x78, 0xb6, 0x35, 0x53, 0x90, 0x92, 0xdb, 0x30, 0x5b, 0xea, 0xba, 0x4e, 0x6f, 0xd0, - 0x6f, 0x1d, 0xcd, 0x3b, 0x61, 0x85, 0xb7, 0x65, 0xae, 0xcd, 0xc8, 0xd0, 0xab, 0x01, 0x25, 0xb9, - 0xcc, 0x88, 0xb4, 0xe2, 0x60, 0x36, 0x13, 0x68, 0x2b, 0x7d, 0x6d, 0x4c, 0xff, 0xa4, 0x81, 0x23, - 0x53, 0x1f, 0x11, 0x1b, 0x16, 0x6a, 0x4e, 0x18, 0xb5, 0x02, 0xa7, 0x17, 0x62, 0xea, 0x93, 0x23, - 0x84, 0x71, 0x3f, 0xcb, 0x2b, 0xcc, 0x6c, 0xa8, 0x51, 0x4c, 0xca, 0x6c, 0xa8, 0x2a, 0x3b, 0xaa, - 0x2f, 0x6d, 0x78, 0x3d, 0xa7, 0xeb, 0x7d, 0x45, 0xc4, 0xfc, 0x62, 0xfa, 0xd2, 0x8e, 0x00, 0x9a, - 0x49, 0xb9, 0xf1, 0xa5, 0xa1, 0x71, 0x63, 0xb5, 0x9c, 0x85, 0x69, 0x1e, 0x11, 0x92, 0x45, 0x48, - 0x6c, 0x56, 0xea, 0xe5, 0x6a, 0xfd, 0x86, 0xae, 0x91, 0x05, 0x80, 0xa6, 0xd9, 0x28, 0x55, 0x2c, - 0x8b, 0xfe, 0xce, 0xd1, 0xdf, 0x3c, 0x7c, 0xe2, 0xc6, 0x76, 0x4d, 0xcf, 0x4b, 0x11, 0x14, 0x27, - 0x8c, 0xaf, 0xe5, 0xe0, 0x64, 0xf6, 0x50, 0x92, 0x16, 0x7f, 0xc2, 0xc0, 0xfc, 0x54, 0xde, 0x1c, - 0x3b, 0xee, 0x99, 0xe0, 0x74, 0x2c, 0xce, 0x88, 0xc5, 0x78, 0xcc, 0x89, 0x0b, 0x4e, 0x16, 0x34, - 0xca, 0xeb, 0x70, 0x67, 0x55, 0x35, 0x61, 0xd5, 0xc5, 0x31, 0xdf, 0xb3, 0xd4, 0x54, 0x55, 0x25, - 0x58, 0x19, 0xf5, 0x79, 0xb5, 0x97, 0x16, 0x61, 0xb6, 0xd8, 0x6c, 0xd6, 0xaa, 0xa5, 0x62, 0xab, - 0xda, 0xa8, 0xeb, 0x1a, 0x99, 0x81, 0xc9, 0x1b, 0x66, 0x63, 0xbb, 0xa9, 0xe7, 0x8c, 0x7f, 0xaf, - 0xc1, 0x99, 0xd1, 0xdf, 0x22, 0x2b, 0x30, 0xed, 0x0f, 0xa2, 0xb6, 0x2f, 0x0e, 0x48, 0xa6, 0xf8, - 0x49, 0x16, 0x20, 0xc7, 0x95, 0x92, 0x19, 0x33, 0xe7, 0xf7, 0xc9, 0x2e, 0xf7, 0x19, 0xe1, 0x2f, - 0x35, 0x8f, 0x14, 0x31, 0xef, 0x39, 0x3e, 0x7b, 0x56, 0x54, 0xca, 0x54, 0x1c, 0xb4, 0x79, 0x5a, - 0xda, 0x14, 0x85, 0xe4, 0x59, 0x98, 0xdf, 0x71, 0xbc, 0xee, 0x20, 0x70, 0x6d, 0x74, 0x4c, 0xc7, - 0x35, 0x30, 0x69, 0xce, 0x71, 0x60, 0x89, 0xc2, 0x8c, 0x3f, 0xd4, 0x60, 0xbe, 0x9a, 0x78, 0x16, - 0x3f, 0xa9, 0x38, 0x7a, 0x47, 0x11, 0x47, 0x2b, 0x71, 0xfc, 0xdd, 0xf8, 0x03, 0x63, 0x74, 0xea, - 0xf5, 0x38, 0x48, 0x5a, 0x5e, 0xf1, 0xc0, 0x91, 0xa9, 0x45, 0xf8, 0xa9, 0x38, 0x49, 0xab, 0x1a, - 0x44, 0x4d, 0x92, 0x67, 0xff, 0x34, 0x0f, 0x4b, 0x43, 0xdf, 0x25, 0x16, 0x4c, 0x17, 0xef, 0x58, - 0x8d, 0x6a, 0xb9, 0xc4, 0x5b, 0xb7, 0x9a, 0x78, 0xa3, 0x52, 0xe8, 0x70, 0x4d, 0x59, 0xdc, 0xbb, - 0x87, 0xa1, 0xed, 0x7b, 0x9d, 0xb6, 0xe2, 0x0e, 0x2d, 0x38, 0xa1, 0x7e, 0xf0, 0x95, 0x41, 0x80, - 0x1e, 0xde, 0xbc, 0xe5, 0xb1, 0x93, 0xab, 0x80, 0x0f, 0x33, 0x46, 0x9f, 0x67, 0x87, 0x96, 0x0f, - 0xb3, 0x4e, 0xf8, 0x91, 0x3a, 0x4c, 0xdd, 0xf0, 0xa2, 0xcd, 0xc1, 0x3d, 0xde, 0x2b, 0xe7, 0x93, - 0x0c, 0xf4, 0x9b, 0x83, 0x7b, 0xc3, 0x6c, 0xd1, 0x86, 0xcc, 0x62, 0xde, 0x28, 0x2c, 0x39, 0x17, - 0x72, 0x0b, 0x26, 0x8b, 0x77, 0x2c, 0xb3, 0xc8, 0x65, 0x96, 0xe4, 0xef, 0x6b, 0x16, 0x47, 0x70, - 0xa3, 0xad, 0x0f, 0x1c, 0x85, 0x1b, 0xe3, 0x91, 0x8e, 0xfb, 0x35, 0xf1, 0x58, 0x71, 0xbf, 0xd6, - 0xe7, 0x61, 0x96, 0x1f, 0x73, 0xf1, 0x04, 0xf9, 0x08, 0x8e, 0x67, 0x0c, 0x35, 0x71, 0xf0, 0x55, - 0x01, 0xfa, 0x21, 0x14, 0x7b, 0x07, 0x0f, 0xf7, 0xdc, 0xc0, 0x1d, 0x1e, 0x3b, 0xb5, 0xee, 0x62, - 0x96, 0x64, 0xd6, 0xde, 0x1c, 0x62, 0x67, 0xfc, 0xb6, 0x06, 0x2b, 0xa3, 0x26, 0x00, 0x3d, 0xb3, - 0xab, 0x91, 0x45, 0x4f, 0xc6, 0x99, 0x96, 0xd5, 0xd7, 0x00, 0x02, 0x8d, 0x7c, 0x00, 0xb3, 0xcc, - 0x63, 0xd4, 0x7a, 0x7d, 0xdb, 0xac, 0x72, 0x59, 0xf6, 0xcc, 0x77, 0x3f, 0x5b, 0x3d, 0xc5, 0x9d, - 0x4c, 0xc3, 0xd7, 0xed, 0x41, 0xe0, 0x25, 0xa4, 0x2b, 0x9a, 0x29, 0x53, 0xd0, 0x23, 0x96, 0x33, - 0xe8, 0x78, 0xae, 0x38, 0x60, 0x8a, 0xe8, 0x8b, 0x1c, 0x26, 0x2b, 0x3c, 0x02, 0x66, 0x7c, 0x4d, - 0x83, 0x33, 0xa3, 0x67, 0x1b, 0x55, 0xa2, 0x5a, 0xcc, 0xf1, 0x56, 0xc4, 0x3f, 0x44, 0x25, 0x2a, - 0xf6, 0xce, 0x95, 0x79, 0x0a, 0x44, 0x4a, 0xc4, 0x4d, 0xa1, 0xc2, 0x82, 0x86, 0x44, 0xb1, 0xa5, - 0x54, 0x26, 0x12, 0x88, 0xc6, 0x5d, 0x38, 0x35, 0x62, 0x6e, 0x92, 0xf7, 0x33, 0xf3, 0xb7, 0x63, - 0x7c, 0x1b, 0x39, 0x80, 0x91, 0x9c, 0x7c, 0x41, 0xc6, 0x37, 0xfe, 0x0d, 0x73, 0x35, 0xcf, 0x98, - 0xa8, 0x54, 0xdf, 0xc3, 0x7c, 0xe1, 0xc5, 0x5e, 0x7b, 0xcf, 0x0f, 0x92, 0xc1, 0x42, 0x7d, 0x2f, - 0xa2, 0x25, 0xb6, 0x83, 0x45, 0xa9, 0x41, 0x4b, 0x51, 0x11, 0x1f, 0x96, 0x9a, 0x81, 0xbf, 0xe3, - 0xb1, 0x60, 0x08, 0xec, 0x98, 0xce, 0xd7, 0xf4, 0x65, 0x69, 0xba, 0xc9, 0xd3, 0x67, 0x08, 0x3f, - 0xce, 0x75, 0x49, 0xc1, 0xcc, 0x5b, 0xa9, 0x8d, 0x05, 0xe6, 0x30, 0x6f, 0xe3, 0xdb, 0x39, 0xb8, - 0x78, 0x28, 0xc7, 0xa3, 0xa6, 0x3d, 0x7f, 0x15, 0x80, 0xd3, 0xd2, 0x1e, 0x90, 0x8c, 0x70, 0xa2, - 0x32, 0x4e, 0xd0, 0x33, 0x25, 0x14, 0x72, 0x1f, 0x9e, 0x11, 0xbf, 0xda, 0x6d, 0xb7, 0x1f, 0x85, - 0xb4, 0x1e, 0x3c, 0x21, 0x42, 0x1c, 0xd9, 0xb1, 0xb0, 0xfe, 0xfc, 0x77, 0x3f, 0x5b, 0xbd, 0x18, - 0xf3, 0x60, 0x98, 0xec, 0x41, 0x8c, 0xc8, 0xad, 0x80, 0xa6, 0xc0, 0xf1, 0xbc, 0xc8, 0xa5, 0x64, - 0x25, 0x4d, 0x24, 0x46, 0x7d, 0xb1, 0x92, 0x92, 0xf5, 0xb3, 0x09, 0x84, 0x33, 0xa2, 0x64, 0x1b, - 0xf2, 0xed, 0x3e, 0x93, 0x9a, 0xa2, 0x26, 0xcc, 0xb4, 0xc8, 0xca, 0xcd, 0x0c, 0x1a, 0xe3, 0xe7, - 0xd9, 0xc2, 0xce, 0x94, 0x0e, 0xe4, 0x21, 0x2c, 0xd6, 0xd8, 0x86, 0x29, 0x3a, 0x9b, 0xcb, 0x95, - 0x6b, 0x87, 0x0f, 0x74, 0x35, 0xe2, 0x2f, 0x15, 0xad, 0xc1, 0xfe, 0xbe, 0x13, 0x1c, 0xac, 0x9f, - 0x16, 0xf9, 0xbb, 0xc5, 0x0e, 0x1d, 0x8f, 0xbd, 0x99, 0xfe, 0x8a, 0xf1, 0xa7, 0x39, 0x78, 0xe9, - 0x31, 0x78, 0x93, 0x26, 0xcc, 0xa0, 0x85, 0x03, 0x75, 0xe3, 0xc3, 0x2d, 0x24, 0x27, 0xf9, 0xde, - 0xc8, 0x03, 0x80, 0xc5, 0x9a, 0x71, 0xc2, 0x84, 0xdc, 0xa4, 0xd3, 0xa9, 0x83, 0xfc, 0x0e, 0xb7, - 0x96, 0x2c, 0x0b, 0xc3, 0xa1, 0xdb, 0xeb, 0x24, 0xdc, 0x04, 0x03, 0x29, 0xb6, 0x69, 0x7e, 0x64, - 0x6c, 0xd3, 0x37, 0x60, 0xae, 0x22, 0x39, 0x24, 0xf3, 0xe1, 0xc7, 0x7b, 0x14, 0xc5, 0x7b, 0xd9, - 0x54, 0xd0, 0xc8, 0xe7, 0x61, 0x81, 0xf9, 0x53, 0xf0, 0xde, 0x61, 0xde, 0x7e, 0x93, 0x3c, 0x63, - 0x13, 0x96, 0x88, 0xae, 0x0e, 0xcd, 0x14, 0x2a, 0x5d, 0x58, 0x27, 0xa9, 0x56, 0xd2, 0x75, 0xc3, - 0xb0, 0x38, 0x88, 0xf6, 0xe8, 0xae, 0xc3, 0x2c, 0x17, 0xe4, 0x2d, 0x98, 0xda, 0x7b, 0xbc, 0xfb, - 0x4a, 0x86, 0x4e, 0x08, 0xe0, 0xd9, 0x47, 0x3c, 0x90, 0xa7, 0x7f, 0x93, 0xb7, 0x61, 0x12, 0xcd, - 0xee, 0xfc, 0x88, 0x21, 0x4c, 0x43, 0xd9, 0x9f, 0x46, 0xa3, 0xbc, 0xc9, 0x08, 0xe8, 0x6a, 0x4d, - 0x12, 0x93, 0xf3, 0xfd, 0x58, 0x98, 0xa3, 0xe3, 0xdc, 0xe4, 0xe6, 0xcc, 0xfe, 0x8e, 0xc3, 0xb3, - 0x7d, 0xaf, 0xc1, 0x92, 0x90, 0xbd, 0x7d, 0x91, 0xfd, 0x88, 0x7b, 0x22, 0x2d, 0xf2, 0x30, 0x71, - 0x7d, 0x91, 0x01, 0xe9, 0x39, 0x58, 0x08, 0xc3, 0x3d, 0x9b, 0x07, 0x27, 0xbd, 0x2f, 0x12, 0x2b, - 0x9a, 0x73, 0x61, 0xb8, 0xc7, 0xa2, 0x94, 0xde, 0x72, 0x0f, 0x28, 0x16, 0xbe, 0xfd, 0x48, 0xb0, - 0x0a, 0x0c, 0x2b, 0xea, 0x86, 0x31, 0x16, 0x8f, 0xab, 0x0b, 0x09, 0x96, 0xf1, 0x21, 0xcc, 0x5a, - 0x6d, 0xbf, 0x2f, 0x5e, 0x9b, 0x2d, 0xc3, 0x64, 0x48, 0x7f, 0x8a, 0x4b, 0x74, 0xfc, 0x41, 0x5e, - 0x86, 0x89, 0xfd, 0xc4, 0x4f, 0x65, 0xe5, 0x8a, 0x48, 0x90, 0x71, 0x05, 0x8b, 0xc3, 0x2b, 0x0f, - 0xae, 0x5e, 0xd9, 0x42, 0x4f, 0x76, 0x8a, 0x65, 0xfc, 0xf3, 0x3c, 0xcc, 0xdc, 0x71, 0xa2, 0xf6, - 0x1e, 0xda, 0xbd, 0xc7, 0xdb, 0xd1, 0xaf, 0xc1, 0x6c, 0xcd, 0x77, 0xb8, 0x1f, 0x04, 0x0f, 0x2b, - 0xc4, 0xde, 0x9f, 0x75, 0x7d, 0x47, 0xb8, 0x54, 0x84, 0xa6, 0x8c, 0x74, 0x48, 0xac, 0xda, 0x9b, - 0x30, 0xc5, 0xda, 0xc2, 0xaf, 0x74, 0x84, 0x8d, 0x28, 0xae, 0xd1, 0x15, 0x56, 0x2c, 0x5d, 0xd4, - 0x33, 0xc1, 0x23, 0x1b, 0x2c, 0x78, 0x6f, 0x48, 0x56, 0xfe, 0xc9, 0xa3, 0x59, 0xf9, 0xa5, 0xc4, - 0x38, 0x53, 0x47, 0x4a, 0x8c, 0xd3, 0x54, 0xfa, 0x9f, 0xbb, 0xc6, 0x93, 0xd8, 0x2a, 0x19, 0x97, - 0xb0, 0xcd, 0x15, 0xfb, 0xdb, 0x1e, 0xaa, 0xaf, 0xcc, 0xe2, 0xcc, 0x75, 0x98, 0x95, 0x5a, 0xf8, - 0x58, 0x46, 0xa8, 0xaf, 0xe6, 0x60, 0x1e, 0xfb, 0x29, 0x16, 0xad, 0x3f, 0x98, 0xb7, 0x20, 0xef, - 0x28, 0xb7, 0x20, 0x2b, 0xf2, 0x0c, 0xe0, 0xbe, 0x5a, 0xa3, 0xaf, 0x3f, 0x6e, 0xc2, 0xd2, 0x10, - 0x22, 0x79, 0x03, 0x26, 0x69, 0xf5, 0x85, 0xd5, 0x58, 0x4f, 0xcf, 0xa9, 0x24, 0x2d, 0x23, 0x6d, - 0x78, 0x68, 0x32, 0x6c, 0xe3, 0xbf, 0x69, 0x30, 0xc7, 0x93, 0xf5, 0xf7, 0x76, 0xfc, 0x43, 0xbb, - 0xf3, 0x52, 0xba, 0x3b, 0x59, 0x2c, 0x77, 0xde, 0x9d, 0x7f, 0xd5, 0x9d, 0x78, 0x5d, 0xe9, 0xc4, - 0x53, 0x71, 0x92, 0x26, 0xd1, 0x9c, 0x31, 0x7d, 0xf8, 0x2d, 0x4c, 0x5b, 0xa8, 0x22, 0x92, 0x1f, - 0x82, 0x99, 0xba, 0xfb, 0x50, 0x31, 0xbe, 0x5e, 0x1a, 0xc1, 0xf4, 0x4a, 0x8c, 0xc8, 0x56, 0x29, - 0x7b, 0x55, 0xea, 0x3e, 0xb4, 0x87, 0x5c, 0x6a, 0x12, 0x96, 0x67, 0xde, 0x85, 0x05, 0x95, 0xec, - 0x71, 0xa6, 0x3e, 0x0f, 0xed, 0x88, 0xe9, 0x09, 0xbe, 0x99, 0x07, 0x48, 0xa2, 0xe2, 0xd1, 0x25, - 0xad, 0x78, 0xf1, 0x89, 0x7b, 0x6b, 0x04, 0xc9, 0x73, 0x5c, 0x38, 0xf7, 0x5d, 0xe2, 0xf7, 0xab, - 0xb9, 0xd1, 0x49, 0xb4, 0xf0, 0xa6, 0xf5, 0x36, 0x8f, 0x70, 0xd6, 0x71, 0xbb, 0xce, 0x01, 0x8f, - 0xd5, 0xf0, 0x16, 0xe6, 0x4c, 0x8c, 0xa1, 0x4a, 0xc2, 0xa2, 0xb3, 0xe5, 0x81, 0x50, 0x7c, 0x02, - 0xaf, 0xb7, 0xbb, 0xe1, 0x07, 0x52, 0xec, 0x3e, 0x7c, 0xcf, 0x50, 0xa6, 0x34, 0x43, 0xc1, 0x27, - 0x27, 0x1e, 0x2f, 0xf8, 0x64, 0x13, 0x66, 0xbc, 0xde, 0x03, 0xb7, 0x17, 0xf9, 0xc1, 0x01, 0xde, - 0x33, 0x27, 0x17, 0x58, 0xf4, 0x7b, 0x55, 0x51, 0xc6, 0x86, 0x06, 0x35, 0xe4, 0x18, 0x5f, 0x1e, - 0x99, 0x18, 0x18, 0xfb, 0x60, 0x4d, 0xea, 0x53, 0x2c, 0x66, 0xdd, 0xcd, 0x89, 0x42, 0x41, 0x9f, - 0xb9, 0x39, 0x51, 0x98, 0xd1, 0xc1, 0x94, 0xdc, 0x46, 0x62, 0xb7, 0x10, 0xc9, 0x87, 0x43, 0xf5, - 0xcf, 0x30, 0xfe, 0x28, 0x0f, 0x64, 0xb8, 0x1a, 0xe4, 0x1d, 0x98, 0x65, 0x52, 0xd1, 0x0e, 0xc2, - 0x4f, 0xf8, 0x43, 0x5b, 0xf6, 0x3a, 0x5e, 0x02, 0xcb, 0x79, 0x1f, 0x18, 0xd8, 0x0c, 0x3f, 0xe9, - 0x92, 0x3e, 0x1c, 0xc7, 0x1e, 0xe7, 0x21, 0x32, 0x30, 0xab, 0x9f, 0xd3, 0xe5, 0x61, 0x36, 0xbe, - 0xf0, 0xdd, 0xcf, 0x56, 0x9f, 0xc9, 0x28, 0x3e, 0xfa, 0xc8, 0x60, 0xf4, 0xb9, 0x26, 0x12, 0x37, - 0x19, 0x2d, 0xd9, 0x05, 0x5d, 0x66, 0x89, 0xa1, 0x7a, 0xd8, 0xf8, 0xbf, 0x47, 0x4f, 0x39, 0xe9, - 0xb2, 0xa3, 0x7f, 0x6b, 0x21, 0xf9, 0xd6, 0xc6, 0xa0, 0xdb, 0x25, 0x6f, 0x01, 0xf8, 0x3d, 0x7b, - 0xdf, 0xc3, 0x18, 0x61, 0xf2, 0x13, 0xed, 0x04, 0x2a, 0x0f, 0x99, 0xdf, 0xdb, 0x62, 0x40, 0xf2, - 0x39, 0xc0, 0xa0, 0xd2, 0x18, 0x6d, 0x9d, 0xeb, 0x6a, 0xec, 0x2c, 0x24, 0x80, 0x6a, 0x3c, 0xd1, - 0x5d, 0x17, 0x23, 0xb1, 0xbf, 0x03, 0xb3, 0x4c, 0x07, 0x62, 0x81, 0x35, 0xa7, 0x92, 0x51, 0x90, - 0xc0, 0xf2, 0x28, 0x30, 0xb0, 0x14, 0x74, 0xf3, 0x8b, 0xb0, 0xc4, 0x75, 0xc6, 0x3b, 0x5e, 0xb4, - 0xc7, 0x6d, 0x51, 0x4f, 0x62, 0xc8, 0x92, 0x0c, 0x49, 0xbf, 0x35, 0x09, 0x50, 0xbc, 0x63, 0x89, - 0x24, 0x28, 0x2f, 0xc2, 0x24, 0xfd, 0xb0, 0xb8, 0xbb, 0x40, 0x55, 0x14, 0xf9, 0xca, 0x37, 0xbf, - 0x88, 0x41, 0x45, 0x80, 0x89, 0x0f, 0x56, 0xc5, 0xbd, 0x05, 0x8a, 0x00, 0xf6, 0x86, 0x55, 0xc9, - 0x5a, 0xc9, 0xb1, 0x48, 0x0d, 0x20, 0x49, 0x4b, 0xc2, 0xed, 0x3d, 0x4b, 0x49, 0x7c, 0x7f, 0x5e, - 0xc0, 0xb3, 0x71, 0x27, 0x51, 0x09, 0xe4, 0xae, 0x49, 0xd0, 0xc8, 0x2d, 0x98, 0x68, 0x39, 0x71, - 0xd4, 0xc9, 0x11, 0xc9, 0x5a, 0x2e, 0xd0, 0xd6, 0x2b, 0x09, 0x5b, 0x16, 0x22, 0x67, 0x57, 0xae, - 0x1d, 0x32, 0x21, 0x15, 0x98, 0x6a, 0x3a, 0x81, 0xb3, 0x1f, 0x8e, 0x4a, 0xf2, 0xc5, 0x4a, 0x45, - 0x2e, 0x50, 0x04, 0xca, 0xaa, 0x11, 0x2b, 0x26, 0xd7, 0x20, 0x6f, 0x59, 0x5b, 0xfc, 0x29, 0xcd, - 0x7c, 0x72, 0xce, 0xb2, 0xac, 0x2d, 0x76, 0x1c, 0x08, 0xc3, 0x7d, 0x89, 0x8c, 0x22, 0x93, 0xcf, - 0xc3, 0xac, 0x74, 0x7c, 0xe3, 0xc1, 0xfd, 0xb1, 0x0f, 0xa4, 0x90, 0x08, 0xb2, 0x58, 0x92, 0xb0, - 0x49, 0x0d, 0xf4, 0x5b, 0x83, 0x7b, 0x6e, 0xb1, 0xdf, 0xc7, 0x10, 0x6f, 0x0f, 0xdc, 0x80, 0xa9, - 0xb8, 0x85, 0x24, 0x8d, 0x26, 0xbe, 0x3f, 0xee, 0x88, 0x52, 0xd9, 0x50, 0x94, 0xa6, 0x24, 0x4d, - 0x58, 0xb2, 0xdc, 0x68, 0xd0, 0x67, 0x9e, 0xaf, 0x1b, 0xcc, 0xd0, 0xc0, 0x52, 0x01, 0x60, 0xc6, - 0xc1, 0x90, 0x16, 0x0a, 0xa7, 0xe3, 0x9d, 0x21, 0x63, 0xc3, 0x30, 0x31, 0xf9, 0x72, 0xca, 0x24, - 0x02, 0x69, 0xab, 0x9c, 0x5c, 0x2a, 0xf2, 0xfd, 0x1c, 0xdd, 0x62, 0xf2, 0x1f, 0x99, 0xc5, 0x24, - 0x83, 0x09, 0xa9, 0xc0, 0x82, 0x0c, 0x8e, 0xed, 0x42, 0x18, 0xf3, 0x42, 0x09, 0x34, 0xad, 0x18, - 0x7a, 0x52, 0x44, 0xe4, 0x11, 0x1c, 0x97, 0x21, 0x4e, 0x77, 0xbb, 0xe7, 0x45, 0xe2, 0x61, 0x9a, - 0x91, 0xdd, 0x0e, 0x44, 0x11, 0x8d, 0xc1, 0x8e, 0xf3, 0x15, 0x16, 0xf6, 0x80, 0x22, 0x48, 0x1f, - 0xcd, 0xfa, 0x84, 0xf1, 0xa3, 0x18, 0xd4, 0x63, 0x14, 0x5f, 0x96, 0xa6, 0x16, 0x83, 0x42, 0xc8, - 0xd7, 0x8c, 0x3c, 0x64, 0x84, 0x9a, 0xa6, 0x16, 0x41, 0x94, 0xa0, 0xc2, 0xa2, 0x48, 0xc8, 0xcb, - 0x95, 0x07, 0x96, 0x90, 0x09, 0x38, 0x96, 0xf1, 0x0d, 0x4d, 0x5e, 0xaf, 0xb2, 0x79, 0x43, 0x1b, - 0x67, 0xde, 0x78, 0x35, 0x23, 0xd7, 0x10, 0x1a, 0x69, 0xa4, 0x5c, 0x43, 0x72, 0x86, 0x21, 0xf2, - 0x22, 0x14, 0x28, 0xad, 0x74, 0x7a, 0x61, 0x09, 0x9f, 0x7c, 0x6e, 0x02, 0x31, 0xe3, 0x62, 0xe3, - 0x2f, 0x27, 0xa5, 0xcc, 0x78, 0x7c, 0xcd, 0xbd, 0x07, 0x70, 0xd3, 0xf7, 0x7a, 0x5b, 0x6e, 0xb4, - 0xe7, 0x77, 0xa4, 0x41, 0x9e, 0xfd, 0xd8, 0xf7, 0x7a, 0xf6, 0x3e, 0x82, 0xff, 0xe2, 0xb3, 0x55, - 0x09, 0xc9, 0x94, 0xfe, 0x26, 0x2f, 0xc3, 0x0c, 0xfd, 0xd5, 0x4a, 0xfc, 0xb4, 0xd9, 0x75, 0x36, - 0x52, 0xb3, 0x24, 0xf6, 0x09, 0x02, 0xb9, 0x0e, 0xc0, 0x72, 0xf4, 0x49, 0xb5, 0xc5, 0xb5, 0xca, - 0xdc, 0xf2, 0xd2, 0x51, 0x4d, 0x24, 0x64, 0xb2, 0x19, 0x57, 0xbd, 0xc5, 0x0f, 0x8a, 0x2c, 0x87, - 0x8e, 0x08, 0x8d, 0x82, 0x45, 0xb6, 0x38, 0x44, 0xca, 0xa1, 0x51, 0x52, 0x64, 0x58, 0x09, 0x6b, - 0xb3, 0xcc, 0xad, 0x77, 0x93, 0x52, 0x25, 0xc2, 0xbd, 0x0e, 0xb7, 0xc5, 0x29, 0x95, 0x88, 0x91, - 0xc9, 0x3a, 0x2c, 0xb2, 0x73, 0x6f, 0x33, 0xf0, 0x1f, 0x1d, 0xd0, 0xc3, 0x34, 0xdf, 0x8f, 0x84, - 0xe1, 0xe9, 0xd1, 0x01, 0x9e, 0xbc, 0xe5, 0xcf, 0xa7, 0x08, 0xc8, 0x06, 0x4c, 0xa2, 0x25, 0x96, - 0x1f, 0xcb, 0xce, 0xca, 0x77, 0x01, 0x69, 0x79, 0x89, 0xfb, 0x07, 0xde, 0x02, 0xc8, 0xfb, 0x07, - 0xa2, 0x92, 0x2f, 0x02, 0x54, 0x7a, 0x81, 0xdf, 0xed, 0x62, 0xe2, 0xd0, 0x82, 0x12, 0x41, 0x89, - 0xf3, 0x41, 0x2e, 0x09, 0x12, 0x66, 0xcf, 0x3d, 0xe1, 0xe2, 0x6f, 0x5b, 0x4d, 0x2f, 0xba, 0xa2, - 0x99, 0x12, 0x37, 0xf2, 0x32, 0x4c, 0x59, 0x83, 0x9d, 0x1d, 0xef, 0x11, 0x17, 0x5f, 0x2c, 0x6b, - 0x26, 0x42, 0x64, 0xb1, 0xcd, 0x70, 0xc8, 0xbb, 0x30, 0xbb, 0xdd, 0xef, 0x38, 0x91, 0x8b, 0x37, - 0xda, 0x3c, 0x15, 0x33, 0x4a, 0xa1, 0x01, 0x82, 0xd9, 0xa3, 0x18, 0x59, 0x06, 0x4b, 0xe8, 0xc4, - 0x83, 0xa5, 0xcd, 0x56, 0xab, 0x89, 0x1d, 0x24, 0x02, 0x2f, 0xf0, 0x10, 0x45, 0xe2, 0x90, 0x35, - 0x54, 0xbe, 0x7e, 0x91, 0x6a, 0x50, 0x7b, 0x51, 0xd4, 0xb7, 0x59, 0xa7, 0x8b, 0x20, 0x7a, 0xb2, - 0x38, 0x1d, 0xa2, 0x32, 0x7e, 0x53, 0xcb, 0xf8, 0x16, 0x79, 0x13, 0x66, 0x62, 0xa0, 0x9c, 0x65, - 0x3c, 0x61, 0x2f, 0xab, 0x33, 0x31, 0x2a, 0x9d, 0x47, 0xf4, 0x87, 0xc5, 0x08, 0xa5, 0xac, 0x60, - 0x94, 0x30, 0x1c, 0xa2, 0x94, 0x90, 0xe9, 0x19, 0xb7, 0xee, 0x33, 0x3a, 0xc9, 0x82, 0xdf, 0xf3, - 0x87, 0x88, 0x04, 0x9a, 0x51, 0x85, 0x29, 0xb6, 0x11, 0x62, 0x62, 0x64, 0xbf, 0x3d, 0xd8, 0x77, - 0x7b, 0x91, 0x94, 0x56, 0x97, 0x25, 0x46, 0xe6, 0xf0, 0xe1, 0xc4, 0xc8, 0x12, 0x81, 0x71, 0x0b, - 0x96, 0xb3, 0x26, 0x9b, 0x62, 0xcf, 0xd7, 0x8e, 0x6a, 0xcf, 0xff, 0xa3, 0x3c, 0xcc, 0x21, 0x37, - 0x21, 0x58, 0x8b, 0x30, 0x6f, 0x0d, 0xee, 0xc5, 0x49, 0x80, 0x84, 0x26, 0x84, 0xf5, 0x0b, 0xe5, - 0x02, 0xd9, 0x25, 0x4d, 0xa1, 0xa0, 0x7b, 0x8f, 0xd0, 0xc2, 0x6e, 0x88, 0x07, 0xd7, 0x71, 0x4e, - 0x62, 0xf1, 0x10, 0x9d, 0xbf, 0xb4, 0x92, 0xf7, 0x1e, 0x95, 0x28, 0xd1, 0xc5, 0xf2, 0x8f, 0xa3, - 0x8b, 0x4d, 0x1c, 0x49, 0x17, 0xfb, 0x08, 0xe6, 0xc4, 0xd7, 0x50, 0x8b, 0x9a, 0x7c, 0x32, 0x2d, - 0x4a, 0x61, 0x46, 0x6a, 0xb1, 0x36, 0x35, 0x35, 0x56, 0x9b, 0x42, 0x3f, 0x3f, 0x21, 0xf9, 0xfa, - 0x08, 0xcb, 0x50, 0xaa, 0x9e, 0x44, 0x41, 0x32, 0xfe, 0x24, 0x0f, 0x70, 0xa3, 0xd4, 0xfc, 0x1e, - 0xd4, 0xdb, 0x37, 0x60, 0xa6, 0xe6, 0x0b, 0xff, 0x30, 0xc9, 0x31, 0xa7, 0x2b, 0x80, 0xf2, 0xa2, - 0x8a, 0x31, 0x63, 0xb5, 0x34, 0xff, 0x34, 0xd4, 0xd2, 0xeb, 0x78, 0xe1, 0xf1, 0xb1, 0xdb, 0x8e, - 0xaa, 0x65, 0x31, 0xb2, 0xd8, 0x72, 0x11, 0xc9, 0x5f, 0xf5, 0x0f, 0x92, 0x90, 0xe9, 0x76, 0xc3, - 0x5d, 0xcf, 0x45, 0xe0, 0x36, 0x7e, 0xc5, 0x80, 0xdb, 0x8d, 0x88, 0x7e, 0x27, 0x62, 0xc1, 0xc9, - 0xf2, 0x3e, 0x45, 0xf6, 0x94, 0x47, 0xf3, 0xc3, 0xf8, 0x0d, 0xd1, 0xf4, 0xb8, 0x1e, 0x32, 0x86, - 0x7a, 0x68, 0xe4, 0xcb, 0x21, 0xe3, 0x77, 0x35, 0x39, 0x9b, 0xfc, 0xf7, 0x30, 0xd4, 0x6f, 0x03, - 0xc4, 0x0e, 0xba, 0x62, 0xac, 0xe3, 0x98, 0x5d, 0x0c, 0x2a, 0xf7, 0x72, 0x82, 0x2b, 0xb5, 0x26, - 0xff, 0xb4, 0x5a, 0xd3, 0x82, 0xd9, 0xc6, 0xfd, 0xc8, 0x49, 0x3c, 0xba, 0xc1, 0x8a, 0x8f, 0xaf, - 0x3c, 0x14, 0xe6, 0xf3, 0xb8, 0xd9, 0x27, 0xe7, 0x61, 0xe5, 0x28, 0x5c, 0x10, 0x47, 0x61, 0x53, - 0x22, 0x34, 0xfe, 0x52, 0x83, 0x45, 0x39, 0x8e, 0xeb, 0x41, 0xaf, 0x4d, 0xde, 0x67, 0xb9, 0x2a, - 0x35, 0xc5, 0x9a, 0x21, 0x21, 0x51, 0x79, 0x7d, 0xd0, 0x6b, 0xb3, 0x93, 0x8b, 0xf3, 0x50, 0xae, - 0x2c, 0x25, 0x24, 0xf7, 0x60, 0xae, 0xe9, 0x77, 0xbb, 0x22, 0xec, 0x6e, 0x9c, 0xf4, 0x32, 0x7d, - 0xd7, 0x22, 0x2a, 0xb4, 0xfe, 0x2c, 0xb7, 0x8a, 0x9d, 0xea, 0xd3, 0x0d, 0x5c, 0x84, 0x02, 0x4e, - 0xd8, 0x7e, 0x8a, 0xf1, 0x58, 0x64, 0x9e, 0x89, 0xb2, 0xa1, 0x66, 0x45, 0x97, 0x6b, 0x49, 0x8b, - 0xb1, 0x9e, 0x63, 0x94, 0x0d, 0xe3, 0xff, 0xd7, 0xe0, 0xc2, 0x70, 0xd3, 0x4a, 0x5d, 0x7f, 0xd0, - 0x69, 0x05, 0x8e, 0xd7, 0xad, 0xf9, 0xbb, 0x21, 0xcb, 0xf1, 0xb7, 0x9b, 0x5c, 0xdd, 0xf2, 0x1c, - 0x7f, 0xbb, 0x5e, 0x3a, 0xc7, 0x1f, 0x86, 0x69, 0x7a, 0x1d, 0x0a, 0xd6, 0x87, 0xd6, 0x87, 0x03, - 0x57, 0x58, 0xce, 0x98, 0x7c, 0x08, 0x3f, 0x09, 0xed, 0x4f, 0x28, 0x50, 0xde, 0x6e, 0x04, 0xa2, - 0xb1, 0x0f, 0xe7, 0x87, 0xab, 0x51, 0xb9, 0x65, 0x15, 0x07, 0x1d, 0x2f, 0xc2, 0x4a, 0x08, 0x01, - 0xa2, 0x3d, 0x05, 0x01, 0x62, 0xfc, 0x93, 0x3c, 0x90, 0xe1, 0xef, 0xc9, 0xdb, 0x85, 0xf6, 0xbf, - 0xe1, 0xe8, 0x9e, 0x92, 0xe8, 0x13, 0x8f, 0x75, 0xe4, 0xfd, 0x04, 0xf4, 0x36, 0x1d, 0x36, 0x3b, - 0xa2, 0xe3, 0x66, 0x77, 0xfd, 0x78, 0xf7, 0x7a, 0x61, 0xe4, 0x14, 0x56, 0xc7, 0x99, 0x89, 0xc0, - 0x34, 0x13, 0x79, 0x23, 0x6e, 0xab, 0xf3, 0xc2, 0x83, 0x05, 0xf7, 0x7e, 0x68, 0x3b, 0x74, 0x8c, - 0xd8, 0x07, 0xa7, 0x14, 0x0f, 0xfb, 0xf1, 0x23, 0xca, 0x04, 0xa3, 0xca, 0x40, 0xde, 0x3a, 0xdd, - 0xfb, 0x61, 0x8c, 0x6b, 0x7c, 0x53, 0x83, 0xe5, 0xac, 0xc9, 0x4d, 0x75, 0x0a, 0x59, 0xc9, 0x50, - 0xcf, 0xb3, 0xb2, 0x5e, 0x92, 0x3a, 0xcf, 0xaa, 0x44, 0xe9, 0xae, 0xcf, 0x3d, 0xd6, 0x66, 0xfa, - 0xa7, 0x79, 0x98, 0x63, 0x6e, 0x61, 0x9b, 0xae, 0xd3, 0x8d, 0xf6, 0xe8, 0x3c, 0xe2, 0x17, 0x72, - 0xf2, 0xeb, 0x25, 0x7e, 0x75, 0xa7, 0x64, 0xd1, 0xe4, 0xd7, 0x76, 0xd7, 0xa0, 0xd0, 0xa4, 0x62, - 0xa1, 0xed, 0x77, 0xe5, 0xcb, 0x91, 0x3e, 0x87, 0xc9, 0x4b, 0x46, 0xe0, 0xa1, 0x2e, 0x2f, 0xdf, - 0xc0, 0x32, 0x5d, 0x1e, 0x21, 0x8a, 0x2e, 0xcf, 0xee, 0x62, 0x1f, 0xc1, 0xf1, 0xc4, 0xd5, 0x30, - 0xbe, 0xe5, 0x3d, 0xc2, 0x43, 0xed, 0x35, 0x7e, 0xc5, 0x7d, 0x3e, 0xf1, 0x5e, 0xc4, 0xeb, 0x60, - 0x2c, 0x4d, 0xb9, 0xa2, 0x65, 0x7d, 0x82, 0xdc, 0x02, 0x3d, 0x01, 0xf3, 0x5c, 0xa1, 0xec, 0x70, - 0x86, 0xa1, 0x5a, 0x25, 0xb6, 0x43, 0x69, 0x43, 0x87, 0x08, 0xe9, 0xf6, 0x9d, 0xc0, 0x2a, 0x49, - 0x4c, 0x06, 0xe1, 0xf1, 0x11, 0xf3, 0xc2, 0x0b, 0x66, 0x79, 0xfb, 0x4e, 0x91, 0xd1, 0x31, 0x12, - 0xf7, 0xd2, 0xd3, 0xc9, 0x18, 0xf1, 0x1b, 0x69, 0x79, 0x8c, 0x38, 0xd6, 0xda, 0x37, 0x34, 0x58, - 0xac, 0x16, 0xb7, 0x58, 0x68, 0x3c, 0xde, 0xab, 0x17, 0xe1, 0x99, 0x6a, 0x71, 0xcb, 0x6e, 0x36, - 0x6a, 0xd5, 0xd2, 0x5d, 0x3b, 0x33, 0x4b, 0xf5, 0x33, 0x70, 0x7a, 0x18, 0x25, 0xf1, 0xca, 0x3c, - 0x07, 0x2b, 0xc3, 0xc5, 0x22, 0x93, 0x75, 0x36, 0xb1, 0x48, 0x7a, 0x9d, 0x5f, 0xfb, 0x00, 0x16, - 0x45, 0xd6, 0xe6, 0x56, 0xcd, 0xc2, 0x13, 0xde, 0x22, 0xcc, 0xde, 0xae, 0x98, 0xd5, 0x8d, 0xbb, - 0xf6, 0xc6, 0x76, 0xad, 0xa6, 0x1f, 0x23, 0xf3, 0x30, 0xc3, 0x01, 0xa5, 0xa2, 0xae, 0x91, 0x39, - 0x28, 0x54, 0xeb, 0x56, 0xa5, 0xb4, 0x6d, 0x56, 0xf4, 0xdc, 0xda, 0xdf, 0xd5, 0x60, 0x9e, 0x9d, - 0xd9, 0x02, 0xde, 0xa2, 0xf3, 0x70, 0x66, 0xbb, 0x59, 0x2e, 0xb6, 0x2a, 0x66, 0x76, 0x73, 0x4e, - 0xc0, 0x52, 0xaa, 0xbc, 0x71, 0x4b, 0xd7, 0xc8, 0x59, 0x38, 0x95, 0x02, 0x97, 0xab, 0x56, 0x71, - 0x9d, 0xb5, 0xe2, 0x34, 0x9c, 0x48, 0x15, 0x36, 0xab, 0xf5, 0x7a, 0xa5, 0xac, 0xe7, 0x69, 0x03, - 0x87, 0x3e, 0x67, 0x56, 0x8a, 0x65, 0x4a, 0xaa, 0x4f, 0xac, 0x7d, 0x00, 0x0b, 0xcd, 0xf8, 0x09, - 0x2a, 0x7a, 0x6e, 0x4e, 0x43, 0xde, 0x2c, 0xde, 0xd1, 0x8f, 0x11, 0x80, 0xa9, 0xe6, 0xad, 0x92, - 0x75, 0xf5, 0xaa, 0xae, 0x91, 0x59, 0x98, 0xbe, 0x51, 0x6a, 0xda, 0xb7, 0xb6, 0x2c, 0x3d, 0x47, - 0x7f, 0x14, 0xef, 0x58, 0xf8, 0x23, 0xbf, 0xf6, 0x1a, 0x3a, 0xf6, 0x3c, 0x3a, 0xa8, 0x79, 0x61, - 0xe4, 0xf6, 0xdc, 0x00, 0xfb, 0x68, 0x0e, 0x0a, 0x96, 0x4b, 0x35, 0xb1, 0xc8, 0x65, 0x1d, 0xb4, - 0x35, 0xe8, 0x46, 0x5e, 0xbf, 0xeb, 0x3e, 0xd2, 0xb5, 0xb5, 0xeb, 0xb0, 0x68, 0xfa, 0x03, 0x7a, - 0x82, 0xb4, 0x22, 0x8a, 0xb1, 0x7b, 0x80, 0x6d, 0xae, 0x17, 0xb7, 0xd6, 0xab, 0x37, 0xb6, 0x1b, - 0xdb, 0x96, 0xbd, 0x55, 0x6c, 0x95, 0x36, 0x99, 0xdf, 0xe8, 0x56, 0xc3, 0x6a, 0xd9, 0x66, 0xa5, - 0x54, 0xa9, 0xb7, 0x74, 0x6d, 0xed, 0x67, 0xf0, 0x26, 0xab, 0xed, 0xf7, 0x3a, 0x1b, 0x4e, 0x3b, - 0xf2, 0x03, 0xac, 0xb0, 0x01, 0xe7, 0xad, 0x4a, 0xa9, 0x51, 0x2f, 0xdb, 0x1b, 0xc5, 0x52, 0xab, - 0x61, 0x66, 0xa5, 0x71, 0x3f, 0x03, 0x27, 0x33, 0x70, 0x1a, 0xad, 0xa6, 0xae, 0x91, 0x55, 0x38, - 0x9b, 0x51, 0x76, 0xa7, 0xb2, 0x5e, 0xdc, 0x6e, 0x6d, 0xd6, 0xf5, 0xdc, 0x08, 0x62, 0xcb, 0x6a, - 0xe8, 0xf9, 0xb5, 0xbf, 0xa5, 0xc1, 0xc2, 0x76, 0xc8, 0xdf, 0xbf, 0x6f, 0xa3, 0x77, 0xc5, 0x05, - 0x38, 0xb7, 0x6d, 0x55, 0x4c, 0xbb, 0xd5, 0xb8, 0x55, 0xa9, 0xdb, 0xdb, 0x56, 0xf1, 0x46, 0xba, - 0x36, 0xab, 0x70, 0x56, 0xc2, 0x30, 0x2b, 0xa5, 0xc6, 0xed, 0x8a, 0x69, 0x37, 0x8b, 0x96, 0x75, - 0xa7, 0x61, 0x96, 0x75, 0x8d, 0x7e, 0x31, 0x03, 0x61, 0x6b, 0xa3, 0xc8, 0x6a, 0xa3, 0x94, 0xd5, - 0x2b, 0x77, 0x8a, 0x35, 0x7b, 0xbd, 0xd1, 0xd2, 0xf3, 0x6b, 0x5b, 0xf4, 0x70, 0x85, 0xc9, 0x94, - 0xd9, 0x2b, 0xc5, 0x02, 0x4c, 0xd4, 0x1b, 0xf5, 0x4a, 0xda, 0x51, 0x79, 0x0e, 0x0a, 0xc5, 0x66, - 0xd3, 0x6c, 0xdc, 0xc6, 0xc9, 0x03, 0x30, 0x55, 0xae, 0xd4, 0xab, 0x38, 0x5b, 0xe6, 0xa0, 0xd0, - 0x34, 0x1b, 0x5b, 0x8d, 0x56, 0xa5, 0xac, 0x4f, 0xac, 0x15, 0x61, 0x89, 0x6d, 0x09, 0x9c, 0x29, - 0x5e, 0x77, 0xce, 0xc3, 0xcc, 0x76, 0xbd, 0x5c, 0xd9, 0xa8, 0xd6, 0xb1, 0x2d, 0x0b, 0x00, 0xd6, - 0x66, 0xc3, 0x6c, 0xd9, 0xad, 0x8a, 0xb9, 0xc5, 0xb2, 0xe3, 0xd7, 0x1a, 0xf5, 0x1b, 0xec, 0x67, - 0x6e, 0xcd, 0x14, 0x6a, 0x80, 0xa8, 0x17, 0x7a, 0x30, 0xcc, 0xc2, 0x74, 0xb9, 0xb2, 0x51, 0xdc, - 0xae, 0xb5, 0xf8, 0x28, 0xdf, 0xb5, 0xcd, 0xca, 0x87, 0xdb, 0x15, 0xab, 0x65, 0xe9, 0x1a, 0xd1, - 0x61, 0xae, 0x5e, 0xa9, 0x94, 0x2d, 0xdb, 0xac, 0xdc, 0xae, 0x56, 0xee, 0xe8, 0x39, 0x5a, 0x2d, - 0xf6, 0x37, 0xad, 0xe4, 0xda, 0xaf, 0x68, 0x40, 0x58, 0x2e, 0xeb, 0x4d, 0x3f, 0x8c, 0x68, 0xef, - 0xe3, 0xa4, 0x3b, 0x0f, 0x67, 0x36, 0xe9, 0x6c, 0xc1, 0xde, 0xd9, 0x6a, 0x94, 0xd3, 0xbd, 0x7e, - 0x12, 0x48, 0xaa, 0xbc, 0xb1, 0xb1, 0x81, 0x2b, 0xeb, 0x78, 0x0a, 0x5e, 0x36, 0x1b, 0x4d, 0x3d, - 0x77, 0x26, 0x57, 0xd0, 0xc8, 0xa9, 0xa1, 0xc2, 0x5b, 0x95, 0x4a, 0x53, 0xcf, 0xd3, 0x51, 0x4e, - 0x15, 0x88, 0x55, 0xcf, 0xc8, 0x27, 0xd6, 0xbe, 0xa6, 0xc1, 0x49, 0x56, 0x4d, 0x21, 0x42, 0xe2, - 0xaa, 0x9e, 0x83, 0x15, 0x9e, 0xa1, 0x3f, 0xab, 0xa2, 0xcb, 0xa0, 0x2b, 0xa5, 0xac, 0x9a, 0x27, - 0x60, 0x49, 0x81, 0x62, 0x3d, 0x72, 0x54, 0x40, 0x2a, 0xe0, 0xf5, 0x8a, 0xd5, 0xb2, 0x2b, 0x1b, - 0x1b, 0x74, 0x48, 0xb0, 0x22, 0xf9, 0xb5, 0xbf, 0xa7, 0xc1, 0xca, 0x1d, 0xf7, 0x5e, 0xcb, 0x0d, - 0xf6, 0xbd, 0x9e, 0xd3, 0x2d, 0x75, 0xbd, 0xfe, 0x3d, 0xdf, 0x09, 0x3a, 0x58, 0x95, 0x97, 0xe0, - 0x85, 0x3b, 0x95, 0x75, 0x1c, 0xae, 0x6a, 0xbd, 0x58, 0xb3, 0x4b, 0xb5, 0x6a, 0x73, 0xbd, 0x51, - 0x34, 0xcb, 0x59, 0x35, 0x7b, 0x19, 0x2e, 0x8f, 0x47, 0x36, 0x2b, 0x56, 0xcb, 0xac, 0x96, 0xe8, - 0xf4, 0xd1, 0xc8, 0x0b, 0xf0, 0xec, 0x38, 0xec, 0x7a, 0xc3, 0x2e, 0x35, 0x9a, 0x77, 0xf5, 0xdc, - 0x9a, 0x01, 0x4b, 0x25, 0x37, 0x88, 0x2a, 0x8f, 0x22, 0xb7, 0x17, 0x7a, 0x7e, 0x0f, 0x2b, 0x36, - 0x0f, 0x33, 0x95, 0xff, 0xa7, 0x55, 0xa9, 0x5b, 0xd5, 0x46, 0x5d, 0x3f, 0xb6, 0x76, 0x2e, 0x85, - 0x23, 0x64, 0x95, 0x65, 0x6d, 0xea, 0xc7, 0xd6, 0x1c, 0x98, 0x17, 0x4f, 0xd7, 0xd9, 0xcc, 0x3f, - 0x0f, 0x67, 0xc4, 0x7a, 0x42, 0xb9, 0x97, 0x6e, 0xc9, 0x0a, 0x2c, 0x0f, 0x97, 0x57, 0x5a, 0xba, - 0x46, 0xa7, 0x49, 0xaa, 0x84, 0xc2, 0x73, 0x6b, 0xbf, 0xa8, 0xc1, 0x7c, 0xec, 0x3d, 0x84, 0x2b, - 0x61, 0x15, 0xce, 0x6e, 0x6d, 0x14, 0xed, 0x72, 0xe5, 0x76, 0xb5, 0x54, 0xb1, 0x6f, 0x55, 0xeb, - 0xe5, 0xd4, 0x47, 0x4e, 0xc3, 0x89, 0x0c, 0x04, 0xfc, 0xca, 0x0a, 0x2c, 0xa7, 0x8b, 0x5a, 0x54, - 0x1c, 0xe5, 0xe8, 0xdc, 0x48, 0x97, 0xc4, 0xb2, 0x28, 0x4f, 0xe7, 0x63, 0xba, 0x94, 0x0a, 0xa2, - 0x89, 0xb5, 0x2f, 0xc1, 0xe2, 0x1d, 0xf7, 0x1e, 0x77, 0x0b, 0x64, 0x82, 0xe8, 0x22, 0x3c, 0x43, - 0xfb, 0xdf, 0xaa, 0x58, 0xb4, 0x0f, 0x33, 0x25, 0xd1, 0x25, 0x30, 0x86, 0x51, 0x8a, 0xb8, 0xfb, - 0xd9, 0x37, 0xcc, 0x62, 0x73, 0xd3, 0x2e, 0x36, 0xab, 0xba, 0xb6, 0x76, 0x1b, 0x16, 0xac, 0xe2, - 0x56, 0x6d, 0xc3, 0x0f, 0xda, 0x6e, 0x71, 0x10, 0xed, 0xf5, 0xe8, 0x7e, 0xb4, 0xd1, 0x30, 0x4b, - 0x15, 0x1b, 0x6b, 0x96, 0x62, 0x7b, 0x1c, 0x16, 0xe5, 0xc2, 0xbb, 0x15, 0xba, 0xac, 0x09, 0x2c, - 0xc8, 0xc0, 0x7a, 0x43, 0xcf, 0xad, 0xd9, 0xb0, 0xcc, 0x5f, 0xef, 0x95, 0xdd, 0xf0, 0x7e, 0xe4, - 0xf7, 0x79, 0x68, 0x7a, 0x5a, 0xaf, 0x6a, 0xbd, 0xdc, 0xb8, 0x63, 0xd9, 0xe5, 0x8a, 0x75, 0xab, - 0xd5, 0x68, 0xda, 0x56, 0x63, 0x9b, 0x12, 0x0f, 0x49, 0xd2, 0x11, 0x78, 0xb5, 0x72, 0xb1, 0xa9, - 0x6b, 0x6b, 0x5f, 0x82, 0x39, 0xde, 0x27, 0x6c, 0x5e, 0x9c, 0x82, 0xe3, 0xf2, 0xef, 0xa6, 0xdb, - 0xeb, 0x78, 0xbd, 0x5d, 0xfd, 0x58, 0xba, 0xc0, 0x1c, 0xf4, 0x7a, 0xb4, 0x00, 0x65, 0xb1, 0x5c, - 0xc0, 0x57, 0x51, 0xe4, 0x76, 0xf4, 0xdc, 0xda, 0x15, 0x98, 0x57, 0xd2, 0xe2, 0xd3, 0x09, 0x59, - 0x6b, 0xf0, 0xcd, 0x73, 0xab, 0x52, 0xae, 0x6e, 0x6f, 0xe9, 0x93, 0x54, 0x0a, 0x6f, 0x56, 0x6f, - 0x6c, 0xea, 0xb0, 0xf6, 0x73, 0x1a, 0x2c, 0x50, 0x41, 0xe8, 0x05, 0xee, 0xd6, 0x46, 0x51, 0x4c, - 0x61, 0xba, 0xbe, 0x51, 0x42, 0xf3, 0x61, 0x60, 0x4a, 0x8b, 0x18, 0x93, 0x62, 0xbd, 0x6c, 0x6f, - 0x16, 0xcd, 0xf2, 0x9d, 0xa2, 0x49, 0x17, 0xfd, 0x5d, 0x3d, 0x87, 0x92, 0x4c, 0x82, 0xd8, 0xad, - 0xc6, 0x76, 0x69, 0x53, 0xcf, 0x53, 0xc1, 0xa1, 0xc0, 0x9b, 0xd5, 0xba, 0x3e, 0x81, 0x72, 0x71, - 0x08, 0x1b, 0xd9, 0xd2, 0xf2, 0xc9, 0xb5, 0xef, 0x68, 0x70, 0xca, 0xf2, 0x76, 0x7b, 0x4e, 0x34, - 0x08, 0xdc, 0x62, 0x77, 0xd7, 0x0f, 0xbc, 0x68, 0x6f, 0xdf, 0x1a, 0x78, 0x91, 0x4b, 0x5e, 0x84, - 0xe7, 0xad, 0xea, 0x8d, 0x7a, 0xb1, 0x45, 0xe5, 0x5a, 0xb1, 0x76, 0xa3, 0x61, 0x56, 0x5b, 0x9b, - 0x5b, 0xb6, 0xb5, 0x5d, 0x1d, 0x5a, 0x51, 0xcf, 0xc1, 0x85, 0xd1, 0xa8, 0xb5, 0xca, 0x8d, 0x62, - 0xe9, 0xae, 0xae, 0x8d, 0x67, 0xb8, 0x5e, 0xac, 0x15, 0xeb, 0xa5, 0x4a, 0xd9, 0xbe, 0x7d, 0x55, - 0xcf, 0x91, 0xe7, 0xe1, 0xe2, 0x68, 0xd4, 0x8d, 0x6a, 0xd3, 0xa2, 0x68, 0xf9, 0xf1, 0xdf, 0xdd, - 0xb4, 0xb6, 0x28, 0xd6, 0xc4, 0xda, 0x37, 0x35, 0x58, 0x19, 0x95, 0x14, 0x89, 0xce, 0xb6, 0x4a, - 0xbd, 0x65, 0x16, 0xab, 0x65, 0xbb, 0x64, 0x56, 0xca, 0x95, 0x7a, 0xab, 0x5a, 0xac, 0x59, 0xc9, - 0x8c, 0x13, 0xef, 0x58, 0x9e, 0x85, 0xd5, 0x31, 0x78, 0x8d, 0x6a, 0xb9, 0xa4, 0x6b, 0xe4, 0x2a, - 0xbc, 0x32, 0x06, 0xc9, 0xba, 0x6b, 0xb5, 0x2a, 0x5b, 0x72, 0x89, 0x9e, 0x5b, 0xfb, 0x2f, 0x1a, - 0x3c, 0x33, 0x36, 0xbb, 0x91, 0xcc, 0x94, 0xaf, 0xce, 0x5a, 0xd5, 0x6a, 0xd9, 0x8d, 0x3b, 0xf5, - 0x8a, 0x69, 0x65, 0x2f, 0x8d, 0x97, 0xe1, 0xf2, 0xe1, 0x24, 0xcd, 0xda, 0xf6, 0x8d, 0x2a, 0x9d, - 0x70, 0xaf, 0xc0, 0x8b, 0x87, 0x63, 0x73, 0x0c, 0x3d, 0x47, 0xde, 0x86, 0xcf, 0x1d, 0x95, 0x39, - 0xce, 0x34, 0x41, 0x99, 0xc7, 0x5d, 0x31, 0x3b, 0x4d, 0x05, 0x1d, 0x49, 0x2c, 0x19, 0x3f, 0x08, - 0x17, 0xe1, 0x99, 0x91, 0x58, 0x7c, 0x08, 0x9e, 0x85, 0xd5, 0x91, 0x28, 0x6c, 0x00, 0xf4, 0xdc, - 0xda, 0x47, 0x70, 0x66, 0x74, 0xa8, 0x74, 0xaa, 0x94, 0xa8, 0xdd, 0x59, 0x80, 0x89, 0x32, 0x55, - 0xa5, 0x34, 0xe9, 0x19, 0x17, 0xae, 0x44, 0xb3, 0x52, 0xdd, 0x6a, 0xd2, 0xdd, 0x96, 0x6b, 0x30, - 0xa8, 0xa2, 0xfc, 0x98, 0x16, 0xe7, 0x75, 0x4a, 0x78, 0xa6, 0x5f, 0x8e, 0x99, 0xdb, 0xf5, 0x3a, - 0x53, 0xc8, 0x16, 0x61, 0xb6, 0xd1, 0xda, 0xac, 0x98, 0x76, 0xc5, 0x34, 0x1b, 0xa6, 0x9e, 0xa3, - 0x2a, 0xd0, 0x76, 0x9d, 0xca, 0xc9, 0x86, 0x59, 0xfd, 0x22, 0x6a, 0x66, 0x2b, 0xb0, 0x6c, 0xd5, - 0x8a, 0xa5, 0x5b, 0x76, 0xbd, 0xd1, 0xb2, 0xab, 0x75, 0xbb, 0xb4, 0x59, 0xac, 0xd7, 0x2b, 0x35, - 0x1d, 0xa8, 0x62, 0xd0, 0xb8, 0xd5, 0x2a, 0xda, 0xa5, 0x46, 0x7d, 0xa3, 0x7a, 0x83, 0xb3, 0x58, - 0xc6, 0x19, 0x3f, 0x2a, 0x76, 0x19, 0x9d, 0x1c, 0x48, 0xc3, 0x87, 0xc8, 0xba, 0x5b, 0x2f, 0x89, - 0xe3, 0x41, 0x69, 0x78, 0xdb, 0xbf, 0x0c, 0xcf, 0x8d, 0xc5, 0x16, 0xe7, 0x25, 0x8d, 0xae, 0xa4, - 0xb1, 0x98, 0xbc, 0x7d, 0x6b, 0xbf, 0xa7, 0xc1, 0xd9, 0x31, 0x9e, 0xbc, 0x74, 0x3a, 0x6e, 0x56, - 0x8a, 0xe5, 0x1a, 0x9d, 0x59, 0xb4, 0x1b, 0xe8, 0x20, 0xb2, 0xd7, 0x63, 0x99, 0xbb, 0xf9, 0x8b, - 0xf0, 0xfc, 0x78, 0xf4, 0x44, 0xf7, 0xbd, 0x0c, 0xcf, 0x8d, 0x47, 0xe5, 0xba, 0x70, 0x8e, 0xac, - 0xc1, 0xa5, 0xf1, 0x98, 0xb1, 0x0e, 0x9d, 0x5f, 0xfb, 0xba, 0x06, 0x27, 0xb3, 0xaf, 0x0e, 0x69, - 0xdd, 0xaa, 0x75, 0xab, 0x55, 0xac, 0xd5, 0xec, 0x66, 0xd1, 0x2c, 0x6e, 0xd9, 0x95, 0xba, 0xd9, - 0xa8, 0xd5, 0xb2, 0xd4, 0xab, 0xe7, 0xe0, 0xc2, 0x68, 0x54, 0xab, 0x64, 0x56, 0x9b, 0x54, 0x75, - 0x30, 0xe0, 0xfc, 0x68, 0xac, 0x4a, 0xb5, 0x54, 0xd1, 0x73, 0xeb, 0xef, 0xfd, 0xce, 0x9f, 0x9c, - 0x3f, 0xf6, 0x3b, 0xdf, 0x39, 0xaf, 0xfd, 0xc1, 0x77, 0xce, 0x6b, 0x7f, 0xfc, 0x9d, 0xf3, 0xda, - 0x17, 0x5f, 0x62, 0x6f, 0x97, 0xae, 0xb4, 0xfd, 0xfd, 0x57, 0x77, 0x03, 0xe7, 0x81, 0x17, 0xf1, - 0x4b, 0xff, 0x57, 0xc5, 0xcd, 0xef, 0xab, 0x4e, 0xdf, 0x7b, 0x15, 0xed, 0x4a, 0xf7, 0xa6, 0xd0, - 0x4c, 0xf1, 0xfa, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x5c, 0xe9, 0x4a, 0x84, 0x7c, 0x1f, 0x02, - 0x00, + // 37399 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xfd, 0x6b, 0x90, 0x1c, 0x49, + 0x72, 0x20, 0x06, 0x6f, 0x55, 0xbf, 0xaa, 0xbd, 0x5f, 0xd5, 0x81, 0x57, 0xa3, 0x67, 0x30, 0x8d, + 0xc9, 0x99, 0xc1, 0x00, 0xb3, 0x33, 0xc0, 0x4c, 0x63, 0x07, 0xbb, 0xf3, 0xd8, 0x9d, 0xa9, 0x7e, + 0x00, 0x68, 0xa0, 0x1b, 0xa8, 0xc9, 0x6a, 0xa0, 0x77, 0xb9, 0x8f, 0x9c, 0xec, 0xaa, 0xe8, 0xee, + 0x1c, 0x54, 0x65, 0xd6, 0x64, 0x66, 0x35, 0xd0, 0xb7, 0xbc, 0xc7, 0xc7, 0xdb, 0xbd, 0x3b, 0x1e, + 0x3f, 0x8a, 0x8f, 0x23, 0x8f, 0x3c, 0x99, 0x48, 0x3b, 0x93, 0xd9, 0xe9, 0x1e, 0x14, 0x65, 0x26, + 0x1e, 0x4f, 0xa2, 0xec, 0x74, 0x14, 0x29, 0xbb, 0x87, 0x74, 0x26, 0xf1, 0xce, 0x8c, 0x27, 0xe9, + 0x44, 0x5b, 0x35, 0x4d, 0x22, 0x25, 0x3b, 0x41, 0x66, 0x67, 0x27, 0xfd, 0xa0, 0x99, 0xf6, 0x4c, + 0x26, 0x59, 0x78, 0x78, 0x64, 0x46, 0x64, 0x66, 0xf5, 0x63, 0x06, 0x43, 0x12, 0x6b, 0xfc, 0x03, + 0x74, 0x79, 0xb8, 0x7b, 0x44, 0xc6, 0xc3, 0xc3, 0xc3, 0xc3, 0xdd, 0x03, 0x9e, 0x8f, 0x79, 0x9b, + 0x77, 0x83, 0x30, 0xbe, 0xd2, 0xe6, 0xdb, 0x6e, 0x73, 0xef, 0x4a, 0xbc, 0xd7, 0xe5, 0x91, 0xfc, + 0xf7, 0x72, 0x37, 0x0c, 0xe2, 0x80, 0x0d, 0xe1, 0x8f, 0xd9, 0x93, 0xdb, 0xc1, 0x76, 0x80, 0x90, + 0x2b, 0xe2, 0x2f, 0x59, 0x38, 0xfb, 0xdc, 0x76, 0x10, 0x6c, 0xb7, 0xf9, 0x15, 0xfc, 0xb5, 0xd9, + 0xdb, 0xba, 0xd2, 0xea, 0x85, 0x6e, 0xec, 0x05, 0x3e, 0x95, 0x3f, 0x9b, 0x2d, 0x8f, 0xe2, 0xb0, + 0xd7, 0x8c, 0xa9, 0x74, 0x2e, 0x5b, 0x1a, 0x7b, 0x1d, 0x1e, 0xc5, 0x6e, 0xa7, 0x4b, 0x08, 0x97, + 0x92, 0xe6, 0xb9, 0x71, 0x2c, 0x4a, 0x04, 0xeb, 0x2b, 0xbb, 0x6f, 0xe8, 0x3f, 0x09, 0xf5, 0x5a, + 0x82, 0xda, 0x0c, 0x3a, 0xdd, 0xc0, 0xe7, 0x7e, 0xbc, 0xc5, 0xdd, 0xb8, 0x17, 0xf2, 0x48, 0x10, + 0x24, 0x40, 0x47, 0x41, 0x89, 0xee, 0xc5, 0xe2, 0x1e, 0xe8, 0xf0, 0xd8, 0x6d, 0xb9, 0xb1, 0x4b, + 0x58, 0x17, 0xfa, 0x60, 0x6d, 0xb9, 0x4e, 0x8b, 0xef, 0x7a, 0x4d, 0x4e, 0x78, 0x2f, 0x15, 0xe3, + 0x85, 0x3c, 0x0a, 0x7a, 0x61, 0x33, 0xa9, 0xf4, 0x5a, 0x9f, 0x6e, 0x0f, 0x7b, 0x51, 0xcc, 0x5b, + 0xc4, 0xd2, 0x09, 0xf9, 0xc7, 0x3d, 0x2f, 0xe4, 0x1d, 0xee, 0xab, 0x0e, 0x7b, 0xad, 0x98, 0xee, + 0x61, 0xe8, 0x76, 0xbb, 0x3c, 0x4c, 0xff, 0x50, 0xfd, 0x9b, 0xa0, 0x47, 0xcd, 0xa0, 0x2b, 0x3b, + 0x42, 0xfe, 0x25, 0x11, 0xac, 0x5f, 0x1d, 0x80, 0xd1, 0xdb, 0x9c, 0x77, 0x6b, 0x6d, 0x6f, 0x97, + 0xb3, 0x17, 0x60, 0xf0, 0x8e, 0xdb, 0xe1, 0x33, 0xa5, 0xf3, 0xa5, 0x8b, 0xa3, 0x0b, 0x53, 0x8f, + 0xf7, 0xe7, 0xc6, 0x22, 0x1e, 0xee, 0xf2, 0xd0, 0xf1, 0xdd, 0x0e, 0xb7, 0xb1, 0x90, 0x7d, 0x1e, + 0x46, 0xc5, 0xff, 0x51, 0xd7, 0x6d, 0xf2, 0x99, 0x32, 0x62, 0x4e, 0x3c, 0xde, 0x9f, 0x1b, 0xf5, + 0x15, 0xd0, 0x4e, 0xcb, 0xd9, 0x0a, 0x8c, 0x2c, 0x3f, 0xea, 0x7a, 0x21, 0x8f, 0x66, 0x06, 0xcf, + 0x97, 0x2e, 0x8e, 0xcd, 0xcf, 0x5e, 0x96, 0x43, 0x7e, 0x59, 0x0d, 0xf9, 0xe5, 0x75, 0x35, 0xe4, + 0x0b, 0x27, 0xfe, 0xab, 0xfd, 0xb9, 0xcf, 0x3d, 0xde, 0x9f, 0x1b, 0xe1, 0x92, 0xe4, 0x27, 0x7f, + 0x77, 0xae, 0x64, 0x2b, 0x7a, 0xf6, 0x2e, 0x0c, 0xae, 0xef, 0x75, 0xf9, 0xcc, 0xe8, 0xf9, 0xd2, + 0xc5, 0xc9, 0xf9, 0xe7, 0x2e, 0xcb, 0x29, 0x9a, 0x34, 0x3e, 0xfd, 0x4b, 0x60, 0x2d, 0x54, 0x1e, + 0xef, 0xcf, 0x0d, 0x0a, 0x14, 0x1b, 0xa9, 0xd8, 0x6b, 0x30, 0x7c, 0x33, 0x88, 0xe2, 0x95, 0xa5, + 0x19, 0xc0, 0x26, 0x9f, 0x7a, 0xbc, 0x3f, 0x37, 0xbd, 0x13, 0x44, 0xb1, 0xe3, 0xb5, 0x5e, 0x0d, + 0x3a, 0x5e, 0xcc, 0x3b, 0xdd, 0x78, 0xcf, 0x26, 0x24, 0x76, 0x12, 0x86, 0xb0, 0x9f, 0x66, 0xc6, + 0x04, 0xb6, 0x2d, 0x7f, 0x58, 0x8f, 0x60, 0xc2, 0xa8, 0x85, 0x8d, 0xc1, 0xc8, 0xbd, 0x3b, 0xb7, + 0xef, 0xdc, 0xdd, 0xb8, 0x53, 0xfd, 0x1c, 0xab, 0xc0, 0xe0, 0x9d, 0xbb, 0x4b, 0xcb, 0xd5, 0x12, + 0x1b, 0x81, 0x81, 0x5a, 0xbd, 0x5e, 0x2d, 0xb3, 0x71, 0xa8, 0x2c, 0xd5, 0xd6, 0x6b, 0x0b, 0xb5, + 0xc6, 0x72, 0x75, 0x80, 0x9d, 0x80, 0xa9, 0x8d, 0x95, 0x3b, 0x4b, 0x77, 0x37, 0x1a, 0xce, 0xd2, + 0x72, 0xe3, 0xf6, 0xfa, 0xdd, 0x7a, 0x75, 0x90, 0x4d, 0x02, 0xdc, 0xbe, 0xb7, 0xb0, 0x6c, 0xdf, + 0x59, 0x5e, 0x5f, 0x6e, 0x54, 0x87, 0xd8, 0x49, 0xa8, 0x2a, 0x12, 0xa7, 0xb1, 0x6c, 0xdf, 0x5f, + 0x59, 0x5c, 0xae, 0x0e, 0xdf, 0x1a, 0xac, 0x0c, 0x54, 0x07, 0xed, 0x91, 0x55, 0xee, 0x46, 0x7c, + 0x65, 0xc9, 0xfa, 0xe9, 0x41, 0xa8, 0xd8, 0x81, 0x9c, 0xfe, 0xec, 0x12, 0x0c, 0x35, 0x62, 0x37, + 0x56, 0xc3, 0x76, 0xe2, 0xf1, 0xfe, 0xdc, 0x94, 0x58, 0x1a, 0x5c, 0xfb, 0x2e, 0x89, 0x21, 0x50, + 0xeb, 0x3b, 0x6e, 0xa4, 0xc6, 0x0d, 0x51, 0xbb, 0x02, 0xa0, 0xa3, 0x22, 0x06, 0xbb, 0x00, 0x83, + 0x6b, 0x41, 0x8b, 0xcf, 0x0c, 0x20, 0x26, 0x7b, 0xbc, 0x3f, 0x37, 0xd9, 0x09, 0x5a, 0x3a, 0x22, + 0x96, 0xb3, 0x57, 0x61, 0x74, 0xb1, 0x17, 0x86, 0xdc, 0x17, 0x7d, 0x3b, 0x88, 0xc8, 0x93, 0x8f, + 0xf7, 0xe7, 0xa0, 0x29, 0x81, 0x8e, 0xd7, 0xb2, 0x53, 0x04, 0xd6, 0x80, 0x91, 0x46, 0xec, 0x86, + 0x31, 0x6f, 0xcd, 0x0c, 0x1d, 0x3a, 0x1f, 0xce, 0xd1, 0x7c, 0x98, 0x8e, 0x24, 0x49, 0x5a, 0xb7, + 0x9c, 0x19, 0xc4, 0x89, 0xdd, 0x84, 0xb1, 0x1b, 0xa1, 0xdb, 0xe4, 0x75, 0x1e, 0x7a, 0x41, 0x6b, + 0x66, 0xf8, 0x7c, 0xe9, 0xe2, 0xc0, 0xc2, 0x85, 0xc7, 0xfb, 0x73, 0xa7, 0xb7, 0x05, 0xd8, 0xe9, + 0x22, 0x3c, 0xa5, 0xfe, 0xfe, 0xfe, 0x5c, 0x65, 0x89, 0xc4, 0x94, 0xad, 0x93, 0xb2, 0x0f, 0x61, + 0x6c, 0xd5, 0x8d, 0x62, 0xec, 0x5a, 0xde, 0x9a, 0x19, 0x39, 0xb4, 0x89, 0x16, 0x35, 0xf1, 0x74, + 0xdb, 0x8d, 0x62, 0x27, 0x94, 0x74, 0x99, 0x76, 0xea, 0x2c, 0xd9, 0x5d, 0xa8, 0x34, 0x9a, 0x3b, + 0xbc, 0xd5, 0x6b, 0xf3, 0x99, 0x0a, 0xb2, 0x3f, 0x43, 0x33, 0x59, 0x8d, 0xa7, 0x2a, 0x5e, 0x98, + 0x25, 0xde, 0x2c, 0x22, 0x88, 0xd6, 0xf7, 0x09, 0x93, 0xb7, 0x2b, 0x3f, 0xff, 0xd7, 0xe7, 0x3e, + 0xf7, 0xe7, 0xbe, 0x77, 0xfe, 0x73, 0xd6, 0x7f, 0x5a, 0x86, 0x6a, 0x96, 0x09, 0xdb, 0x82, 0x89, + 0x7b, 0xdd, 0x96, 0x1b, 0xf3, 0xc5, 0xb6, 0xc7, 0xfd, 0x38, 0xc2, 0x49, 0x72, 0xf0, 0x37, 0xbd, + 0x48, 0xf5, 0xce, 0xf4, 0x90, 0xd0, 0x69, 0x4a, 0xca, 0xcc, 0x57, 0x99, 0x6c, 0xd3, 0x7a, 0x1a, + 0x28, 0x30, 0x22, 0x9c, 0x61, 0xc7, 0xab, 0x47, 0x8a, 0x9a, 0x3e, 0xf5, 0x10, 0x5b, 0x9a, 0x40, + 0x7e, 0x6b, 0x73, 0x0f, 0x67, 0xe6, 0xd1, 0x27, 0x90, 0x20, 0x29, 0x98, 0x40, 0x02, 0x6c, 0xfd, + 0x7e, 0x09, 0x26, 0x6d, 0x92, 0xd0, 0x37, 0xb9, 0xdb, 0xe2, 0xa1, 0x98, 0xfe, 0xb7, 0x3d, 0xbf, + 0x45, 0x6b, 0x0a, 0xa7, 0xff, 0x03, 0xcf, 0xd7, 0x45, 0x05, 0x96, 0xb3, 0xd7, 0x61, 0xa4, 0xd1, + 0xdb, 0x44, 0x54, 0xb9, 0xa6, 0x4e, 0xe3, 0x88, 0xf5, 0x36, 0x9d, 0x0c, 0xba, 0x42, 0x63, 0x57, + 0x60, 0xe4, 0x3e, 0x0f, 0x23, 0x2f, 0xf0, 0x69, 0x6d, 0xa1, 0x28, 0xda, 0x95, 0x20, 0x9d, 0x80, + 0xb0, 0xd8, 0x0d, 0xa8, 0xac, 0xd1, 0x66, 0x44, 0x42, 0x74, 0x8a, 0xa6, 0x8c, 0x02, 0xa7, 0x53, + 0x45, 0xed, 0x5a, 0xfa, 0x54, 0x51, 0x58, 0xd6, 0xbf, 0x2e, 0x43, 0x75, 0xc9, 0x8d, 0xdd, 0x4d, + 0x37, 0xa2, 0xfe, 0xbc, 0x7f, 0x95, 0x3d, 0x6b, 0x7c, 0x28, 0x8a, 0x4d, 0xd1, 0xf2, 0x4f, 0xfc, + 0x79, 0x2f, 0x65, 0x3f, 0x6f, 0x4c, 0x48, 0x74, 0xfa, 0xbc, 0xf4, 0xa3, 0xbe, 0x7c, 0xf8, 0x47, + 0x55, 0xe9, 0xa3, 0x2a, 0xea, 0xa3, 0xd2, 0x4f, 0x61, 0x5f, 0x86, 0xc1, 0x46, 0x97, 0x37, 0x49, + 0x88, 0x3c, 0x43, 0xa4, 0xe6, 0xc7, 0x09, 0x84, 0xfb, 0x57, 0x17, 0xc6, 0x89, 0xcd, 0x60, 0xd4, + 0xe5, 0x4d, 0x1b, 0xc9, 0xd8, 0x32, 0x0c, 0x0b, 0x81, 0xd8, 0x8b, 0x50, 0x58, 0x8c, 0xcd, 0x9f, + 0x2b, 0x66, 0x80, 0x28, 0xf7, 0xaf, 0x2e, 0x4c, 0x12, 0x8b, 0xe1, 0x08, 0x21, 0x36, 0x11, 0xa7, + 0xbb, 0xc4, 0x88, 0xb6, 0x4b, 0x68, 0x2b, 0xf2, 0x0f, 0x06, 0xe0, 0x64, 0x51, 0x9b, 0xf4, 0x4e, + 0x1a, 0x3e, 0xa0, 0x93, 0x2e, 0x42, 0x45, 0xec, 0x47, 0x62, 0x67, 0x95, 0x55, 0x2c, 0x8c, 0x8b, + 0xfe, 0xd8, 0x21, 0x98, 0x9d, 0x94, 0xb2, 0x17, 0x92, 0xed, 0xad, 0x92, 0xf2, 0xa3, 0xed, 0x2d, + 0xd9, 0xd4, 0x6e, 0xa4, 0x9b, 0x06, 0xee, 0x82, 0x69, 0x9f, 0x2b, 0x70, 0x3a, 0x91, 0x42, 0x82, + 0xe8, 0x13, 0x29, 0xd9, 0x71, 0x96, 0xa1, 0xa2, 0x3e, 0x6b, 0x66, 0x1c, 0x19, 0x4d, 0x67, 0x3a, + 0xf0, 0xfe, 0x55, 0x39, 0x53, 0x5a, 0xf4, 0x5b, 0x67, 0xa3, 0x70, 0xd8, 0x55, 0xa8, 0xd4, 0xc3, + 0xe0, 0xd1, 0xde, 0xca, 0x52, 0x34, 0x33, 0x71, 0x7e, 0xe0, 0xe2, 0xe8, 0xc2, 0x99, 0xc7, 0xfb, + 0x73, 0x27, 0xba, 0x02, 0xe6, 0x78, 0xad, 0x48, 0x27, 0x52, 0x88, 0x6c, 0x0e, 0xc6, 0x42, 0xde, + 0x76, 0xf7, 0x9c, 0xed, 0x30, 0xe8, 0x75, 0x67, 0x26, 0xb1, 0xe7, 0x01, 0x41, 0x37, 0x04, 0x84, + 0x3d, 0x03, 0xa3, 0x12, 0xc1, 0x6b, 0x45, 0x33, 0x53, 0x82, 0xad, 0x5d, 0x41, 0xc0, 0x4a, 0x2b, + 0xba, 0x35, 0x58, 0x29, 0x55, 0xcb, 0xb7, 0x06, 0x2b, 0xe5, 0xea, 0x80, 0xdc, 0x53, 0x6f, 0x0d, + 0x56, 0x06, 0xab, 0x43, 0xb7, 0x06, 0x2b, 0x43, 0xb8, 0xcb, 0x8e, 0x56, 0xe1, 0xd6, 0x60, 0x65, + 0xac, 0x3a, 0x6e, 0x8f, 0x2d, 0xf1, 0xa8, 0x19, 0x7a, 0x5d, 0xdc, 0x24, 0x44, 0xf5, 0x71, 0xd0, + 0x0c, 0xda, 0xf6, 0xc0, 0x3d, 0x7b, 0xc5, 0x1e, 0x5e, 0xac, 0x2d, 0xf2, 0x30, 0xb6, 0x07, 0x6a, + 0x1b, 0x0d, 0x7b, 0x62, 0x69, 0xcf, 0x77, 0x3b, 0x5e, 0x73, 0xd5, 0xdd, 0xe4, 0xed, 0xc8, 0x1e, + 0xb8, 0xb1, 0x58, 0xb7, 0x7c, 0x38, 0x5d, 0x3c, 0x95, 0xd8, 0x3a, 0x8c, 0xaf, 0xbb, 0xe1, 0x36, + 0x8f, 0x6f, 0x72, 0xb7, 0x1d, 0xef, 0x60, 0xfb, 0xc7, 0xe6, 0x4f, 0x50, 0xf7, 0xe9, 0x45, 0x0b, + 0xcf, 0x3c, 0xde, 0x9f, 0x3b, 0x13, 0x23, 0xc4, 0xd9, 0x41, 0x90, 0xd6, 0x21, 0x06, 0x17, 0xab, + 0x06, 0x93, 0x69, 0xcf, 0xaf, 0x7a, 0x51, 0xcc, 0xae, 0xc0, 0xa8, 0x82, 0x08, 0x99, 0x3f, 0x50, + 0x38, 0x46, 0x76, 0x8a, 0x63, 0xfd, 0xe3, 0x32, 0x40, 0x5a, 0xf2, 0x94, 0x8a, 0x85, 0x2f, 0x1a, + 0x62, 0xe1, 0x54, 0x76, 0x55, 0xf7, 0x17, 0x08, 0xef, 0x65, 0x04, 0xc2, 0x99, 0x2c, 0xe9, 0x21, + 0xa2, 0x40, 0x5b, 0xf4, 0xbf, 0x38, 0x92, 0x0e, 0x06, 0x2d, 0xf7, 0x8b, 0x90, 0x4c, 0x20, 0xea, + 0x50, 0x5c, 0xc7, 0x5d, 0x35, 0xa9, 0x92, 0x52, 0x76, 0x16, 0xc4, 0x04, 0xa3, 0x4e, 0x1d, 0x79, + 0xbc, 0x3f, 0x37, 0xd0, 0x0b, 0x3d, 0x9c, 0x74, 0xec, 0x0a, 0xd0, 0xb4, 0xa3, 0x0e, 0x14, 0x6b, + 0x65, 0xba, 0xe9, 0x3a, 0x4d, 0x1e, 0xc6, 0x69, 0x8f, 0xcf, 0x94, 0xd4, 0xec, 0x64, 0x5d, 0x30, + 0xa7, 0xe6, 0xcc, 0x20, 0x4e, 0x83, 0x8b, 0x85, 0xbd, 0x72, 0xd9, 0x40, 0x5d, 0xf6, 0xe3, 0x70, + 0x6f, 0xe1, 0xbc, 0xda, 0xa0, 0x5b, 0xb2, 0xcc, 0x69, 0x63, 0xa1, 0x36, 0xbc, 0x66, 0x05, 0xec, + 0x2a, 0x88, 0x15, 0x41, 0xbd, 0x0f, 0x54, 0x4f, 0x6d, 0xa3, 0xb1, 0x70, 0x8a, 0x38, 0x4d, 0xb8, + 0x0f, 0x75, 0x72, 0x81, 0xcd, 0xde, 0x01, 0xb1, 0x64, 0xa8, 0xdf, 0x19, 0x11, 0xdd, 0x58, 0xac, + 0x2f, 0xb6, 0x83, 0x5e, 0xab, 0xf1, 0xc1, 0x6a, 0x4a, 0xbc, 0xdd, 0xec, 0xea, 0xc4, 0x37, 0x16, + 0xeb, 0xec, 0x1d, 0x18, 0xaa, 0xfd, 0xa9, 0x5e, 0xc8, 0x49, 0x55, 0x1b, 0x57, 0x75, 0x0a, 0xd8, + 0xc2, 0x19, 0x22, 0x9c, 0x72, 0xc5, 0x4f, 0x5d, 0xc5, 0xc5, 0x72, 0x51, 0xf3, 0xfa, 0x6a, 0x83, + 0xd4, 0x30, 0x96, 0xe9, 0x96, 0xf5, 0x55, 0xad, 0xd9, 0xb1, 0xf1, 0xd5, 0x82, 0x8a, 0x5d, 0x81, + 0x72, 0x6d, 0x09, 0x0f, 0x23, 0x63, 0xf3, 0xa3, 0xaa, 0xda, 0xa5, 0x85, 0x93, 0x44, 0x32, 0xee, + 0xea, 0xcb, 0xa0, 0x5c, 0x5b, 0x62, 0x0b, 0x30, 0xb4, 0xb6, 0xd7, 0xf8, 0x60, 0x95, 0x44, 0xaf, + 0x5a, 0xf2, 0x08, 0xbb, 0x8b, 0x62, 0x26, 0x4a, 0x5b, 0xdc, 0xd9, 0x8b, 0x3e, 0x6e, 0xeb, 0x2d, + 0x46, 0x34, 0x56, 0x87, 0xd1, 0x5a, 0xab, 0xe3, 0xf9, 0xf7, 0x22, 0x1e, 0xe2, 0xd1, 0x64, 0x6c, + 0x7e, 0x26, 0xd3, 0xee, 0xa4, 0x7c, 0x61, 0xe6, 0xf1, 0xfe, 0xdc, 0x49, 0x57, 0xfc, 0x74, 0x7a, + 0x11, 0x0f, 0x35, 0x6e, 0x29, 0x13, 0x56, 0x07, 0x58, 0x0b, 0xfc, 0xed, 0xa0, 0x16, 0xb7, 0xdd, + 0x28, 0x23, 0xcc, 0xd3, 0x82, 0x44, 0x93, 0x3a, 0xd5, 0x11, 0x30, 0xc7, 0x15, 0x40, 0x8d, 0xa1, + 0xc6, 0x83, 0x5d, 0x87, 0xe1, 0xbb, 0xa1, 0xdb, 0x6c, 0xf3, 0x99, 0x09, 0xe4, 0x76, 0x92, 0xb8, + 0x49, 0xa0, 0xfa, 0xd2, 0x19, 0x62, 0x58, 0x0d, 0x10, 0xac, 0x1f, 0xc1, 0x24, 0xe2, 0xec, 0x06, + 0xb0, 0xfc, 0x9c, 0x64, 0x55, 0x18, 0x78, 0xc0, 0xf7, 0xe4, 0x2a, 0xb2, 0xc5, 0x9f, 0xec, 0xf3, + 0x30, 0xb4, 0xeb, 0xb6, 0x7b, 0x9c, 0x34, 0x4e, 0xb5, 0xe8, 0x17, 0x83, 0x4e, 0xc7, 0xf5, 0x5b, + 0x48, 0x7b, 0x7f, 0xde, 0x96, 0x38, 0x6f, 0x97, 0xbf, 0x54, 0xb2, 0x3e, 0x86, 0xe9, 0x5c, 0x67, + 0x09, 0x79, 0xa7, 0x1d, 0x7d, 0x51, 0xde, 0x69, 0x67, 0xde, 0xaf, 0xc0, 0xd4, 0x12, 0xdf, 0x72, + 0x7b, 0xed, 0x38, 0xd9, 0xf7, 0xe4, 0x12, 0x3d, 0x29, 0x3e, 0xa1, 0x25, 0x8b, 0x1c, 0xb5, 0xd9, + 0xd9, 0x59, 0x64, 0xeb, 0x9f, 0x95, 0x60, 0xc2, 0xf8, 0x7e, 0x76, 0x0d, 0x46, 0x6b, 0xbd, 0x96, + 0x17, 0xe3, 0x48, 0xca, 0x4a, 0xe5, 0x78, 0x09, 0x60, 0x7e, 0xbc, 0x14, 0x2a, 0x7b, 0x0b, 0xc0, + 0xe6, 0x71, 0xb8, 0xb7, 0x18, 0xf4, 0xfc, 0x18, 0x1b, 0x31, 0xb4, 0x70, 0x56, 0x0c, 0x4c, 0x28, + 0xa0, 0x4e, 0x53, 0x80, 0xf5, 0x81, 0x49, 0x91, 0xd9, 0x6d, 0xa8, 0x36, 0x76, 0x7a, 0x5b, 0x5b, + 0x6d, 0xae, 0xd4, 0x86, 0x08, 0x45, 0x49, 0x65, 0x61, 0xee, 0xf1, 0xfe, 0xdc, 0x33, 0x91, 0x2c, + 0x73, 0x94, 0x76, 0xa1, 0x8f, 0x6f, 0x8e, 0xd0, 0xfa, 0x3d, 0x5d, 0x97, 0x54, 0x9b, 0xdb, 0x6b, + 0x89, 0x88, 0x2a, 0xa5, 0x9a, 0x6d, 0x4e, 0x44, 0x25, 0x02, 0xea, 0x92, 0x14, 0x17, 0xe5, 0x9c, + 0xb8, 0x18, 0xa3, 0xc9, 0x31, 0xe0, 0x3e, 0x8c, 0xa4, 0x90, 0x48, 0x16, 0xcf, 0xc0, 0x27, 0x5f, + 0x3c, 0xef, 0xc1, 0xf8, 0x9a, 0xeb, 0xbb, 0xdb, 0xbc, 0x25, 0x7a, 0x52, 0x8a, 0xc3, 0x51, 0xb9, + 0xcb, 0x76, 0x24, 0x1c, 0xfb, 0x5d, 0xff, 0x6e, 0x83, 0x80, 0xbd, 0xa1, 0x84, 0xcd, 0x50, 0x81, + 0xb0, 0x99, 0xa0, 0xda, 0x87, 0x50, 0xd8, 0x28, 0x11, 0xf3, 0x65, 0x18, 0xbb, 0x7f, 0x87, 0xc7, + 0x4b, 0x77, 0x1a, 0x38, 0xbb, 0xa4, 0xb2, 0x87, 0x55, 0xee, 0xfa, 0x3c, 0x76, 0x5a, 0x7e, 0x84, + 0xa6, 0x15, 0xad, 0x4a, 0x1d, 0xdf, 0xfa, 0x75, 0xc0, 0x2e, 0x62, 0xaf, 0xc2, 0xb0, 0xcd, 0xb7, + 0xc5, 0xe6, 0x59, 0x4a, 0xa7, 0x5d, 0x88, 0x10, 0xbd, 0x5f, 0x25, 0x0e, 0xea, 0x79, 0xbc, 0x15, + 0xed, 0x78, 0x5b, 0x31, 0x75, 0x6e, 0xa2, 0xe7, 0x11, 0x58, 0xd3, 0xf3, 0x08, 0x62, 0xe8, 0x79, + 0x04, 0x13, 0xf2, 0xdc, 0x5e, 0x6a, 0x50, 0x9f, 0xab, 0x01, 0xb2, 0x97, 0x34, 0xc1, 0x18, 0x1a, + 0x5a, 0x9a, 0xc0, 0xc6, 0x99, 0xdd, 0xc4, 0x79, 0x98, 0x18, 0x04, 0xe4, 0xcc, 0x96, 0x40, 0xd3, + 0xde, 0x92, 0xa2, 0xb2, 0x06, 0x8c, 0x2d, 0x8b, 0x53, 0xb4, 0xb7, 0xe8, 0x36, 0x77, 0x54, 0x1f, + 0x2b, 0xa9, 0xac, 0x95, 0xa4, 0xb2, 0x88, 0x23, 0xb0, 0x29, 0x80, 0x7a, 0x07, 0x6a, 0xb8, 0x6c, + 0x1d, 0xc6, 0x1a, 0xbc, 0x19, 0xf2, 0xb8, 0x11, 0x07, 0x21, 0xcf, 0x6c, 0x32, 0x5a, 0xc9, 0xc2, + 0x73, 0xea, 0x20, 0x1f, 0x21, 0xd0, 0x89, 0x04, 0x54, 0xe7, 0xaa, 0x21, 0xcb, 0x13, 0x59, 0x27, + 0x08, 0xf7, 0x96, 0x16, 0x68, 0xe3, 0x49, 0xb5, 0x14, 0x09, 0xd6, 0x4f, 0x64, 0x02, 0xd2, 0xda, + 0x34, 0x4f, 0x64, 0x12, 0x0b, 0x47, 0x6a, 0xa9, 0x81, 0xba, 0x2d, 0x6d, 0x43, 0x53, 0x69, 0x2f, + 0x23, 0x58, 0x1b, 0xa9, 0x56, 0x84, 0x9a, 0xb1, 0x31, 0x52, 0x84, 0xc5, 0xba, 0xc0, 0xd4, 0xa8, + 0x49, 0x85, 0xb3, 0xcd, 0xa3, 0x88, 0x76, 0xa7, 0xb3, 0x99, 0xc1, 0x4f, 0x11, 0x16, 0x5e, 0x22, + 0xe6, 0xe7, 0xd4, 0x34, 0xa0, 0x43, 0xb8, 0x28, 0xd4, 0xea, 0x29, 0xe0, 0x2d, 0x04, 0xd1, 0xf2, + 0xa3, 0x98, 0x87, 0xbe, 0xdb, 0x4e, 0x8c, 0x6a, 0x28, 0x88, 0x38, 0x41, 0xcd, 0x81, 0xd6, 0x90, + 0xd9, 0x22, 0x4c, 0xd4, 0xa2, 0xa8, 0xd7, 0xe1, 0x76, 0xd0, 0xe6, 0x35, 0xfb, 0x8e, 0x34, 0xb2, + 0x2d, 0x9c, 0x7b, 0xbc, 0x3f, 0x77, 0xd6, 0xc5, 0x02, 0x27, 0x0c, 0xda, 0xdc, 0x71, 0x43, 0x7d, + 0x76, 0x9b, 0x34, 0xec, 0x2e, 0xc0, 0xdd, 0x2e, 0xf7, 0x1b, 0xdc, 0x0d, 0x9b, 0x3b, 0x99, 0x8d, + 0x2b, 0x2d, 0x58, 0x78, 0x96, 0xbe, 0xf0, 0x64, 0xd0, 0xe5, 0x7e, 0x84, 0x30, 0xbd, 0x55, 0x29, + 0x26, 0xdb, 0x80, 0xa9, 0x95, 0xda, 0x5a, 0x3d, 0x68, 0x7b, 0xcd, 0x3d, 0xd2, 0x05, 0x27, 0xd1, + 0xd4, 0x78, 0x9a, 0xb8, 0x66, 0x4a, 0xa5, 0x74, 0xf3, 0xdc, 0x8e, 0xd3, 0x45, 0xa8, 0x43, 0x1a, + 0x61, 0x96, 0x0b, 0xfb, 0x9a, 0x98, 0x83, 0x91, 0x50, 0x6f, 0xd7, 0xdd, 0x6d, 0x79, 0x24, 0x49, + 0x8f, 0xac, 0xb5, 0x8d, 0xc6, 0x65, 0xad, 0x54, 0x2a, 0x5e, 0xb3, 0x72, 0x22, 0x22, 0xd4, 0x89, + 0xdd, 0xed, 0xc8, 0x9c, 0x88, 0x09, 0x36, 0xbb, 0x05, 0xb0, 0x14, 0x34, 0x7b, 0x1d, 0xee, 0xc7, + 0x4b, 0x0b, 0x33, 0x55, 0xf3, 0x28, 0x96, 0x14, 0xa4, 0x92, 0xb1, 0x15, 0x34, 0x8d, 0x99, 0xa8, + 0x51, 0xb3, 0x1f, 0x86, 0x53, 0xda, 0xca, 0xd1, 0x66, 0xd1, 0x34, 0xb2, 0x7d, 0x36, 0xbf, 0x12, + 0xb5, 0x89, 0x74, 0x91, 0x6a, 0x38, 0xaf, 0xad, 0xc9, 0xe2, 0xb9, 0x54, 0x5c, 0xc9, 0xec, 0x57, + 0xa0, 0x9a, 0xed, 0x86, 0x82, 0xbd, 0xfe, 0xa4, 0xbe, 0xd7, 0x8f, 0x6a, 0x9b, 0xfa, 0xad, 0xc1, + 0xca, 0x44, 0x75, 0x52, 0xeb, 0xfb, 0xe5, 0x47, 0x5e, 0x14, 0x47, 0xd6, 0xb7, 0x8d, 0xf5, 0x2f, + 0x64, 0xd3, 0x6d, 0xbe, 0x57, 0x0f, 0xf9, 0x96, 0xf7, 0x48, 0xdf, 0x75, 0x1f, 0xf0, 0x3d, 0xa7, + 0x8b, 0x50, 0x5d, 0x36, 0x25, 0xa8, 0xec, 0x0b, 0x50, 0xb9, 0xbd, 0xd6, 0xb8, 0xcd, 0xf7, 0x56, + 0x96, 0x68, 0xe3, 0x97, 0x64, 0x9d, 0xc8, 0x11, 0xa4, 0xc6, 0x4c, 0x4f, 0x30, 0xad, 0x85, 0x54, + 0x0e, 0x8b, 0x9a, 0x17, 0xdb, 0xbd, 0x28, 0xe6, 0xe1, 0xca, 0x92, 0x5e, 0x73, 0x53, 0x02, 0x33, + 0x52, 0x31, 0x41, 0xb5, 0xfe, 0xdf, 0x32, 0xca, 0x60, 0xb1, 0xdc, 0x56, 0xfc, 0x28, 0x76, 0xfd, + 0x26, 0x4f, 0x18, 0xe0, 0x72, 0xf3, 0x08, 0x9a, 0x59, 0x6e, 0x29, 0xb2, 0x59, 0x75, 0xf9, 0xc8, + 0x55, 0x4b, 0x55, 0x43, 0x1a, 0xc5, 0x56, 0x96, 0xe8, 0xd0, 0x41, 0xaa, 0x86, 0x84, 0x66, 0xaa, + 0x4c, 0x91, 0xd9, 0x05, 0x18, 0x59, 0xa9, 0xad, 0xd5, 0x7a, 0xf1, 0x0e, 0xee, 0x00, 0x15, 0x79, + 0xde, 0x11, 0x6b, 0xc5, 0xed, 0xc5, 0x3b, 0xb6, 0x2a, 0x64, 0x57, 0xf0, 0x1c, 0xe9, 0xf3, 0x38, + 0x9a, 0x19, 0xc2, 0xdd, 0x18, 0xd7, 0x54, 0x24, 0x41, 0x99, 0x63, 0xa4, 0x00, 0xb1, 0x57, 0x60, + 0xe8, 0x7e, 0x7d, 0x71, 0x65, 0x89, 0x76, 0x52, 0xdc, 0x07, 0x77, 0xbb, 0x4d, 0xb3, 0x25, 0x12, + 0x85, 0x2d, 0xc3, 0x64, 0x83, 0x37, 0x7b, 0xa1, 0x17, 0x4b, 0xcb, 0x40, 0x34, 0x33, 0x82, 0x75, + 0xa0, 0x9c, 0x89, 0xa8, 0x44, 0x9a, 0x11, 0xf4, 0xba, 0x32, 0x44, 0xd6, 0x6f, 0x96, 0x52, 0x21, + 0xcd, 0x2e, 0x18, 0x6a, 0x22, 0x9a, 0x05, 0x33, 0xfb, 0xb7, 0x54, 0x18, 0x6d, 0x60, 0x8b, 0xbd, + 0x28, 0x0e, 0x3a, 0xcb, 0x7e, 0xab, 0x1b, 0x78, 0x7e, 0x8c, 0x54, 0xb2, 0xf3, 0xad, 0xc7, 0xfb, + 0x73, 0xcf, 0x35, 0xb1, 0xd4, 0xe1, 0x54, 0x9c, 0xd5, 0x02, 0x0a, 0xa8, 0x3f, 0xc5, 0x78, 0x58, + 0xff, 0xb4, 0x6c, 0x6c, 0xae, 0xa2, 0x79, 0x36, 0xef, 0xb6, 0xbd, 0x26, 0xda, 0x73, 0xf0, 0x43, + 0x93, 0x59, 0x85, 0xcd, 0x0b, 0xd3, 0x52, 0xd9, 0x43, 0x26, 0xef, 0x02, 0x6a, 0xf6, 0x3e, 0x8c, + 0x0b, 0x35, 0x89, 0x7e, 0x46, 0x33, 0x65, 0xec, 0xec, 0x67, 0xd1, 0xc0, 0x1b, 0xf1, 0x30, 0x61, + 0x63, 0xe8, 0x57, 0x3a, 0x05, 0x6b, 0xc1, 0xcc, 0x7a, 0xe8, 0xfa, 0x91, 0x17, 0x2f, 0xfb, 0xcd, + 0x70, 0x0f, 0xd5, 0xba, 0x65, 0xdf, 0xdd, 0x6c, 0xf3, 0x16, 0x29, 0xaa, 0x17, 0x1f, 0xef, 0xcf, + 0xbd, 0x18, 0x4b, 0x1c, 0x87, 0x27, 0x48, 0x0e, 0x97, 0x58, 0x1a, 0xe7, 0xbe, 0x9c, 0x84, 0x1a, + 0xa8, 0xba, 0x15, 0xef, 0x93, 0x06, 0x53, 0x9d, 0x2c, 0x19, 0x0d, 0x21, 0xea, 0xf4, 0x66, 0xea, + 0x04, 0xd6, 0xdd, 0x3e, 0x82, 0x12, 0x17, 0x9a, 0x00, 0x69, 0x33, 0x44, 0x2e, 0x34, 0x94, 0x86, + 0x99, 0x11, 0x4e, 0x51, 0xad, 0x3f, 0x28, 0xa5, 0xfa, 0x04, 0x7b, 0x17, 0xc6, 0x68, 0x09, 0x6a, + 0x6c, 0x70, 0x43, 0x50, 0xeb, 0x35, 0xab, 0x30, 0x6a, 0xe8, 0xec, 0x75, 0x18, 0xa9, 0x2d, 0xae, + 0x6a, 0x93, 0x0d, 0x0d, 0x33, 0x6e, 0xb3, 0x9d, 0xa5, 0x52, 0x68, 0x62, 0x56, 0xad, 0xaf, 0x36, + 0xcc, 0x6e, 0xc6, 0x59, 0x15, 0xb7, 0xa3, 0x82, 0x7e, 0xd5, 0x90, 0x3f, 0x7d, 0x4f, 0xfe, 0x0f, + 0xa5, 0x22, 0xb5, 0x85, 0x2d, 0xc0, 0xc4, 0x46, 0x10, 0x3e, 0xc0, 0x09, 0xa3, 0x75, 0x02, 0x4e, + 0xa5, 0x87, 0xaa, 0x20, 0xfb, 0x41, 0x26, 0x89, 0xde, 0x36, 0xad, 0x37, 0xcc, 0xb6, 0x65, 0x38, + 0x18, 0x04, 0x62, 0x1c, 0x12, 0x8e, 0xc9, 0x72, 0xc3, 0x71, 0x48, 0x9b, 0x60, 0xac, 0x09, 0x1d, + 0xdd, 0xfa, 0x07, 0x25, 0x5d, 0x3d, 0x11, 0x9d, 0xbc, 0x14, 0x74, 0x5c, 0xcf, 0xd7, 0x3e, 0x07, + 0x3b, 0xb9, 0x85, 0xd0, 0x6c, 0x4b, 0x34, 0x64, 0x76, 0x15, 0x2a, 0xf2, 0x57, 0x22, 0xbc, 0xd1, + 0x48, 0x4a, 0x84, 0xe6, 0xce, 0xa3, 0x10, 0x73, 0x23, 0x33, 0x70, 0xdc, 0x91, 0xf9, 0x8d, 0x92, + 0xae, 0x59, 0x7c, 0xd2, 0xdd, 0x2b, 0xb3, 0x6b, 0x95, 0x8f, 0xb3, 0x6b, 0x7d, 0xea, 0x4f, 0xf8, + 0x47, 0x25, 0x18, 0xd3, 0xcc, 0x48, 0xe2, 0x1b, 0xea, 0x61, 0xf0, 0x11, 0x6f, 0xc6, 0xe6, 0x37, + 0x74, 0x25, 0x30, 0xf3, 0x0d, 0x09, 0xea, 0xa7, 0xf9, 0x86, 0x45, 0x18, 0xa9, 0xb5, 0xdb, 0x81, + 0x38, 0x26, 0xc8, 0x33, 0xd4, 0xa4, 0xd2, 0xfa, 0x24, 0x74, 0xe1, 0xac, 0xba, 0xa0, 0x72, 0x05, + 0xc0, 0x50, 0xcd, 0x14, 0xa5, 0xf5, 0x73, 0xa5, 0x84, 0x4b, 0xae, 0x53, 0x4a, 0xc7, 0xec, 0x14, + 0x76, 0x1b, 0xaa, 0xea, 0xf7, 0xdd, 0x5d, 0x1e, 0x86, 0x5e, 0x4b, 0x2d, 0x0d, 0xb4, 0x01, 0x24, + 0x4c, 0x02, 0x2a, 0xd4, 0x6d, 0x00, 0x59, 0x42, 0xeb, 0xff, 0x2c, 0xd1, 0x81, 0xf8, 0xc8, 0xdb, + 0xa2, 0xb9, 0x85, 0x95, 0x8f, 0xa3, 0x52, 0xbc, 0x0f, 0x43, 0x36, 0x6f, 0x79, 0x11, 0xf5, 0xe4, + 0xb4, 0x7e, 0xf8, 0xc6, 0x82, 0x54, 0xcb, 0x0d, 0xc5, 0x4f, 0x5d, 0x1f, 0xc0, 0x72, 0x71, 0xec, + 0x58, 0x89, 0xae, 0xb7, 0xf9, 0x23, 0x4f, 0xca, 0x1a, 0x52, 0x4d, 0x50, 0x1d, 0xf0, 0x22, 0x67, + 0x4b, 0x94, 0x90, 0xce, 0xaa, 0xcb, 0x15, 0x83, 0xc6, 0xfa, 0x1a, 0x40, 0x5a, 0xa5, 0xe8, 0x4e, + 0x9a, 0xec, 0x9e, 0xbf, 0x2d, 0x15, 0x4f, 0xea, 0x03, 0xec, 0xce, 0x66, 0x52, 0x46, 0x67, 0x04, + 0xbd, 0x3b, 0xb3, 0x84, 0xd6, 0xff, 0x36, 0x00, 0xe5, 0x1a, 0xce, 0xb7, 0xdb, 0x7c, 0x2f, 0x76, + 0x37, 0xaf, 0x7b, 0x6d, 0x43, 0x56, 0x3c, 0x40, 0xa8, 0xb3, 0xe5, 0x19, 0xe6, 0x32, 0x0d, 0x59, + 0xc8, 0x8a, 0xdb, 0xe1, 0xe6, 0x9b, 0x48, 0xa8, 0xc9, 0x8a, 0x07, 0xe1, 0xe6, 0x9b, 0x59, 0xb2, + 0x04, 0x91, 0x59, 0x30, 0x2c, 0xe5, 0x06, 0x2d, 0x31, 0x78, 0xbc, 0x3f, 0x37, 0x2c, 0xc5, 0x8b, + 0x4d, 0x25, 0xec, 0x2c, 0x0c, 0x34, 0xea, 0x77, 0x48, 0xc0, 0xa3, 0x59, 0x3a, 0xea, 0xfa, 0xb6, + 0x80, 0x89, 0x3a, 0x57, 0x97, 0x6a, 0x75, 0xb4, 0xfa, 0x0c, 0xa5, 0x75, 0xb6, 0x5b, 0x6e, 0x37, + 0x6b, 0xf7, 0x49, 0x10, 0xd9, 0x97, 0x61, 0xec, 0xf6, 0xd2, 0xe2, 0xcd, 0x20, 0x8a, 0xb3, 0x66, + 0x91, 0x07, 0xad, 0x26, 0x5a, 0xa0, 0x72, 0xbb, 0x9c, 0x86, 0xcf, 0x1c, 0x38, 0x2d, 0x58, 0x89, + 0x21, 0xf1, 0x9a, 0x9c, 0x4c, 0x08, 0x77, 0xd2, 0x5b, 0xb2, 0x97, 0x1f, 0xef, 0xcf, 0xbd, 0x80, + 0x2d, 0x88, 0x24, 0x8a, 0xa3, 0x8c, 0x0f, 0x19, 0xae, 0x7d, 0xd8, 0xb0, 0x6f, 0xc0, 0xa9, 0x7c, + 0x49, 0x23, 0xb9, 0x5d, 0xbb, 0xf0, 0x78, 0x7f, 0xce, 0x2a, 0xe4, 0x1f, 0x19, 0xf3, 0xb7, 0x98, + 0x89, 0xf5, 0xdd, 0x32, 0x8c, 0x69, 0x66, 0x66, 0xf6, 0x05, 0x72, 0xb5, 0x28, 0x19, 0xc7, 0x4d, + 0x0d, 0x43, 0x94, 0x4a, 0x9b, 0x64, 0x27, 0x68, 0x71, 0x72, 0xbc, 0x48, 0x8d, 0x6d, 0xe5, 0xa3, + 0x18, 0xdb, 0xde, 0x02, 0x90, 0x53, 0x18, 0xfb, 0x49, 0xd3, 0x1e, 0x35, 0x0f, 0x1f, 0x7d, 0x5a, + 0xa5, 0xc8, 0xec, 0x3e, 0x9c, 0x58, 0x0f, 0x7b, 0x51, 0xdc, 0xd8, 0x8b, 0x62, 0xde, 0x11, 0xdc, + 0xea, 0x41, 0xd0, 0xa6, 0xe5, 0xf3, 0xa2, 0x38, 0xf5, 0xa1, 0xdf, 0x92, 0x13, 0x61, 0x39, 0x36, + 0xc0, 0xe9, 0x06, 0x81, 0x6e, 0x82, 0x2b, 0x62, 0x60, 0xd9, 0x30, 0xae, 0x1b, 0xf0, 0xc4, 0xbe, + 0x4f, 0xd7, 0xd2, 0x74, 0x53, 0xa4, 0xed, 0xfb, 0xd4, 0xca, 0xfc, 0x35, 0xb9, 0x49, 0x62, 0x7d, + 0x41, 0xb7, 0x67, 0x1f, 0x55, 0x2e, 0x59, 0x3f, 0x52, 0x4a, 0x85, 0xfc, 0xfd, 0x37, 0xd8, 0x3b, + 0x30, 0x2c, 0xdd, 0x00, 0xc8, 0x5b, 0xe2, 0x54, 0x62, 0x41, 0xd1, 0x7d, 0x04, 0xe4, 0x45, 0xd2, + 0x3f, 0xdf, 0x9f, 0x2b, 0x3d, 0xde, 0x9f, 0xfb, 0x9c, 0x4d, 0x24, 0xc9, 0x1d, 0x94, 0x69, 0x8e, + 0x56, 0xdc, 0xf1, 0xb6, 0xe5, 0x8d, 0xa2, 0x3b, 0x28, 0xeb, 0x1f, 0x0d, 0xc1, 0xa4, 0x89, 0xa6, + 0xfb, 0x0a, 0x94, 0x8e, 0xe4, 0x2b, 0xf0, 0x3e, 0x54, 0x68, 0xbe, 0x29, 0x05, 0xfc, 0x45, 0xbc, + 0x99, 0x23, 0x98, 0xe1, 0x03, 0x03, 0x72, 0x38, 0xec, 0xa0, 0xcd, 0xed, 0x84, 0x8a, 0xcd, 0x6b, + 0x77, 0xce, 0x03, 0xa9, 0x0a, 0xa9, 0xac, 0xc2, 0xfa, 0x72, 0x4e, 0x6e, 0x9f, 0x5f, 0x83, 0x61, + 0x71, 0x9c, 0x4b, 0xec, 0x7d, 0xd8, 0x4a, 0x71, 0xd2, 0xcb, 0x38, 0x57, 0x49, 0x24, 0xb6, 0x01, + 0x95, 0x55, 0x37, 0x8a, 0x1b, 0x9c, 0xfb, 0x47, 0xf0, 0x02, 0x9a, 0xa3, 0xae, 0x3a, 0x81, 0x2e, + 0x36, 0x11, 0xe7, 0x7e, 0xc6, 0x8d, 0x23, 0x61, 0xc6, 0xbe, 0x09, 0xb0, 0x18, 0xf8, 0x71, 0x18, + 0xb4, 0x57, 0x83, 0xed, 0x99, 0x61, 0x34, 0xb4, 0x3c, 0x97, 0x19, 0x80, 0x14, 0x41, 0xda, 0x5a, + 0x12, 0x6b, 0x62, 0x53, 0x16, 0x38, 0xed, 0x60, 0x5b, 0x5f, 0x07, 0x29, 0x3e, 0xbb, 0x0e, 0x55, + 0x65, 0xc5, 0xba, 0xd7, 0xdd, 0x0e, 0x71, 0x82, 0x8c, 0xa4, 0x7a, 0x21, 0x7f, 0x14, 0x3b, 0x3d, + 0x82, 0x1b, 0xfb, 0x66, 0x86, 0x86, 0x7d, 0x03, 0xce, 0x64, 0x61, 0x6a, 0x94, 0x2b, 0xe9, 0x11, + 0x4c, 0x67, 0x57, 0x30, 0xef, 0xfb, 0xb1, 0x60, 0x37, 0x60, 0x4a, 0x74, 0xc8, 0x1a, 0x77, 0xa3, + 0x9e, 0xf4, 0x1d, 0x24, 0x3b, 0xa0, 0x72, 0x72, 0xa0, 0x55, 0xd8, 0x0e, 0x9a, 0x0f, 0x34, 0x24, + 0x3b, 0x4b, 0xc5, 0xae, 0xc1, 0x98, 0xf4, 0xbd, 0x09, 0x57, 0xfc, 0xad, 0x80, 0xae, 0xad, 0xd4, + 0x6d, 0x0e, 0x95, 0xdc, 0x9f, 0x17, 0x65, 0xb6, 0x8e, 0x68, 0xed, 0x97, 0xe1, 0x74, 0x71, 0x1d, + 0xec, 0xcf, 0xc2, 0x29, 0xea, 0xcf, 0x36, 0x0f, 0x35, 0x9c, 0x23, 0x78, 0x25, 0xbd, 0x46, 0xe3, + 0xf4, 0x7c, 0x33, 0x61, 0x90, 0x08, 0x1c, 0xc1, 0x22, 0x33, 0x29, 0x8a, 0xeb, 0x61, 0x1f, 0xc2, + 0x98, 0x5e, 0x6d, 0xf9, 0xe8, 0x0e, 0x5e, 0x07, 0xd4, 0xa5, 0xb3, 0x64, 0x2e, 0x4c, 0xd9, 0xfc, + 0xe3, 0x1e, 0x8f, 0x62, 0xe5, 0x62, 0x46, 0x1a, 0xcb, 0xd9, 0x5c, 0x2d, 0x0a, 0x21, 0x31, 0x52, + 0x56, 0x43, 0x49, 0xe9, 0x28, 0x27, 0xda, 0x9f, 0x17, 0xec, 0xb3, 0xfc, 0xac, 0xef, 0x97, 0xe1, + 0x4c, 0x9f, 0xe9, 0x2c, 0x24, 0x9e, 0xa6, 0x19, 0xa2, 0xc4, 0xcb, 0x28, 0x84, 0xd2, 0x1f, 0xf2, + 0x3c, 0x94, 0x49, 0x03, 0x1b, 0x5c, 0xa8, 0x3e, 0xde, 0x9f, 0x1b, 0x37, 0x56, 0x6a, 0x79, 0x65, + 0x89, 0xdd, 0x82, 0x41, 0xd1, 0x0d, 0x47, 0x70, 0xb3, 0x52, 0x26, 0xea, 0xc9, 0xd8, 0xd3, 0x05, + 0x04, 0xf6, 0x0d, 0xf2, 0x60, 0x5f, 0x80, 0x81, 0xf5, 0xf5, 0x55, 0x94, 0x0e, 0x03, 0x38, 0xbb, + 0x27, 0xe2, 0xb8, 0x6d, 0x08, 0xa3, 0x09, 0x41, 0x9b, 0xf4, 0x88, 0x2d, 0xd0, 0xd9, 0x57, 0x61, + 0x98, 0x6e, 0xae, 0x87, 0x70, 0x29, 0xbf, 0x72, 0xf0, 0x52, 0xbe, 0xac, 0xdf, 0x5d, 0xa3, 0x65, + 0x28, 0x77, 0x5f, 0x4d, 0xfc, 0x66, 0xdf, 0x82, 0xb1, 0x83, 0x2f, 0x15, 0xfb, 0x1a, 0x1a, 0xad, + 0x1f, 0x2f, 0x29, 0x4f, 0x37, 0x9a, 0xfc, 0xec, 0xbc, 0x5a, 0x27, 0x68, 0xc8, 0x20, 0x2e, 0x3a, + 0x88, 0x3d, 0x07, 0x20, 0x7f, 0xde, 0xbb, 0x47, 0x9d, 0x3e, 0x6e, 0x6b, 0x10, 0xf6, 0x76, 0xc2, + 0x92, 0x0c, 0xcf, 0x03, 0xa8, 0x09, 0x64, 0xd6, 0x9a, 0x2c, 0xb3, 0x4d, 0x54, 0xeb, 0xd7, 0xcb, + 0xe9, 0xae, 0x71, 0xdd, 0x6b, 0xc7, 0x3c, 0x64, 0xb3, 0x72, 0x13, 0x48, 0x0f, 0x6b, 0x76, 0xf2, + 0x9b, 0xcd, 0xa4, 0x3b, 0x8a, 0xfc, 0xb4, 0x64, 0xeb, 0x78, 0x45, 0xdb, 0x3a, 0x06, 0x70, 0xeb, + 0x98, 0xec, 0xbb, 0x49, 0xbc, 0x52, 0x20, 0x09, 0x51, 0xf4, 0x17, 0x48, 0xbb, 0x17, 0x61, 0xe2, + 0x4e, 0xb0, 0xfc, 0x28, 0x4e, 0x10, 0x85, 0xc8, 0xaf, 0xd8, 0x26, 0x50, 0x70, 0xbc, 0xdb, 0x6e, + 0xf1, 0x70, 0x7d, 0xc7, 0xf5, 0x0d, 0xd7, 0x28, 0x3b, 0x07, 0x17, 0xb8, 0x77, 0xf8, 0x43, 0x13, + 0x57, 0x7a, 0x60, 0xe5, 0xe0, 0xd9, 0xc1, 0xa9, 0xe4, 0x06, 0xc7, 0xfa, 0xb9, 0xb2, 0xea, 0xae, + 0xfb, 0xf3, 0x4f, 0xa9, 0xdb, 0xcb, 0x9b, 0x86, 0xdb, 0xcb, 0x89, 0xe4, 0x7a, 0x2b, 0xf1, 0x38, + 0x9b, 0x2f, 0x74, 0x7a, 0x49, 0xdc, 0xd7, 0x86, 0x8b, 0xdd, 0xd7, 0xbe, 0x57, 0x81, 0x71, 0x9d, + 0x89, 0xe8, 0x9d, 0x5a, 0xab, 0x15, 0xea, 0xbd, 0xe3, 0xb6, 0x5a, 0xa1, 0x8d, 0x50, 0xc3, 0x5b, + 0x6d, 0xe0, 0x40, 0x6f, 0xb5, 0x6f, 0xc1, 0xe8, 0x62, 0xa7, 0x65, 0x78, 0xa5, 0x58, 0x05, 0x8d, + 0xbe, 0x9c, 0x20, 0xc9, 0x35, 0x9d, 0xdc, 0xe5, 0x34, 0x3b, 0xad, 0xbc, 0x2f, 0x4a, 0xca, 0xd2, + 0x70, 0x74, 0x1b, 0xfa, 0x34, 0x8e, 0x6e, 0xd7, 0x60, 0xf4, 0x5e, 0xc4, 0xd7, 0x7b, 0xbe, 0xcf, + 0xdb, 0xd8, 0x4b, 0x15, 0x69, 0x33, 0xe8, 0x45, 0xdc, 0x89, 0x11, 0xaa, 0x37, 0x20, 0x41, 0xd5, + 0x87, 0x7d, 0xe4, 0x80, 0x61, 0xbf, 0x0a, 0x95, 0x3a, 0xe7, 0x21, 0xf6, 0xe9, 0x58, 0x7a, 0x76, + 0xea, 0x72, 0x1e, 0x3a, 0xa2, 0x63, 0x0d, 0x07, 0x38, 0x42, 0x34, 0xbc, 0xe6, 0xc6, 0x8f, 0xea, + 0x35, 0xf7, 0x3c, 0x8c, 0x77, 0x7b, 0x9b, 0x6d, 0xaf, 0x89, 0x7c, 0xc9, 0xdd, 0xce, 0x1e, 0x93, + 0x30, 0xc1, 0x36, 0x62, 0x5f, 0x85, 0x09, 0xb4, 0x95, 0x24, 0x13, 0x71, 0xd2, 0xd8, 0xf0, 0x8d, + 0x32, 0xa9, 0x93, 0x37, 0x05, 0xc8, 0x29, 0x70, 0x39, 0x35, 0x19, 0xb1, 0x5b, 0x30, 0xb2, 0xed, + 0xc5, 0xce, 0x4e, 0x6f, 0x73, 0x66, 0xca, 0xf0, 0xd7, 0xbc, 0xe1, 0xc5, 0x37, 0x7b, 0x9b, 0x72, + 0xc8, 0x13, 0xd6, 0x28, 0xb9, 0xb7, 0xbd, 0x78, 0xa7, 0xa7, 0x9b, 0x43, 0x86, 0xb7, 0x11, 0x37, + 0xeb, 0xfe, 0x57, 0x3d, 0xd8, 0xfd, 0x6f, 0xda, 0x74, 0xff, 0x63, 0x0e, 0xb0, 0x7c, 0x1c, 0xc8, + 0x0c, 0xc3, 0x46, 0xbd, 0x7e, 0x59, 0x05, 0x4b, 0x5c, 0xce, 0x05, 0x90, 0x5c, 0xde, 0x7d, 0xe3, + 0xf2, 0xa2, 0x02, 0x5e, 0x27, 0xa0, 0x3d, 0xdd, 0xcc, 0x82, 0x58, 0x03, 0xaa, 0x5e, 0xa7, 0xd3, + 0x8b, 0xdd, 0xcd, 0x36, 0xa7, 0x29, 0x3a, 0x73, 0xc2, 0x70, 0xbb, 0x32, 0x26, 0xf8, 0x8a, 0xc2, + 0xd5, 0xa6, 0xb9, 0x3d, 0xe5, 0x99, 0xd0, 0xd9, 0x06, 0x4c, 0x9a, 0x2b, 0xe1, 0x09, 0x78, 0xc1, + 0xcc, 0x2e, 0xc0, 0xc9, 0xa2, 0xda, 0x8f, 0x79, 0xe9, 0x26, 0xfd, 0x28, 0x2b, 0xd5, 0xd1, 0x5b, + 0x83, 0x15, 0xa8, 0x8e, 0x49, 0x0f, 0x4a, 0x1b, 0xea, 0xc9, 0xbc, 0xb2, 0xd9, 0xed, 0xde, 0x26, + 0x0f, 0x7d, 0x1e, 0xf3, 0x88, 0x2c, 0x1e, 0x91, 0x3d, 0x58, 0xeb, 0x76, 0x23, 0xeb, 0x57, 0xcb, + 0x30, 0x52, 0xdb, 0x68, 0xe0, 0x1e, 0xfa, 0xaa, 0xee, 0x34, 0x50, 0x4a, 0xa3, 0x08, 0x52, 0xa7, + 0x01, 0xdd, 0x55, 0xe0, 0x4a, 0x81, 0x49, 0x0e, 0xa3, 0x55, 0x34, 0x93, 0x9c, 0x61, 0x88, 0x4b, + 0xfd, 0x27, 0x06, 0x8e, 0xe0, 0x3f, 0x91, 0x5c, 0x32, 0x0d, 0x1e, 0x7e, 0xc9, 0xf4, 0x0e, 0x8c, + 0xad, 0xf8, 0x31, 0xdf, 0x0e, 0x53, 0x69, 0x93, 0x98, 0x07, 0x13, 0xb0, 0x6e, 0xc7, 0xd0, 0xb0, + 0xc5, 0x52, 0x96, 0x17, 0x5b, 0xc9, 0x85, 0x16, 0x2e, 0x65, 0x79, 0xff, 0x95, 0xb1, 0xed, 0x2a, + 0x44, 0x6b, 0x29, 0xb3, 0x4e, 0x95, 0xd7, 0x5d, 0xc9, 0xb4, 0x30, 0xca, 0x8e, 0x5d, 0x98, 0x2e, + 0xf6, 0xba, 0xb3, 0xfe, 0x4a, 0x09, 0x4e, 0x16, 0x2d, 0x3f, 0xf6, 0x15, 0x18, 0x0f, 0xc2, 0x6d, + 0xd7, 0xf7, 0xfe, 0x94, 0xfc, 0x22, 0xed, 0x02, 0x42, 0x87, 0xeb, 0x16, 0x46, 0x1d, 0x2e, 0x3a, + 0x44, 0xfb, 0x72, 0xd3, 0x5e, 0x5a, 0xd8, 0x21, 0x1a, 0xd8, 0xfa, 0x85, 0x32, 0x8c, 0xd5, 0xba, + 0xdd, 0xa7, 0xdc, 0x39, 0xfd, 0x4b, 0xc6, 0x76, 0xac, 0xec, 0x39, 0xc9, 0x77, 0x1d, 0xe0, 0x86, + 0x7a, 0xd8, 0x8e, 0xfc, 0xdf, 0x0e, 0xc0, 0x54, 0x86, 0x8f, 0xfe, 0x4d, 0xa5, 0x23, 0xfa, 0x92, + 0x97, 0x8f, 0xe8, 0x4b, 0x3e, 0x70, 0x34, 0x5f, 0xf2, 0xc1, 0x4f, 0xb3, 0xc5, 0xbe, 0x0c, 0x03, + 0xb5, 0x6e, 0x37, 0xeb, 0x52, 0xd5, 0xed, 0xde, 0xbf, 0x2a, 0x0d, 0x8d, 0x6e, 0xb7, 0x6b, 0x0b, + 0x0c, 0x63, 0xdf, 0x1b, 0xfe, 0x84, 0xde, 0xe2, 0x23, 0x07, 0x6f, 0x17, 0x95, 0x23, 0x6d, 0x17, + 0xa3, 0x4f, 0x6c, 0xbb, 0xb0, 0x5e, 0x83, 0x51, 0xfc, 0x54, 0x74, 0xd9, 0x3e, 0x0f, 0x28, 0x17, + 0xc9, 0x5b, 0xdb, 0xe8, 0x0a, 0x92, 0x98, 0x3f, 0x55, 0x86, 0x21, 0xfc, 0xfd, 0x94, 0x2e, 0x8c, + 0x79, 0x63, 0x61, 0x54, 0xb5, 0x85, 0xf1, 0xc9, 0x97, 0xc4, 0xdf, 0x19, 0x00, 0x58, 0xbc, 0x6b, + 0x37, 0xa4, 0x11, 0x9d, 0x5d, 0x87, 0x29, 0xb7, 0xdd, 0x0e, 0x1e, 0xf2, 0x96, 0x13, 0x84, 0xde, + 0xb6, 0xe7, 0xcb, 0xfe, 0x54, 0xde, 0x45, 0x66, 0x91, 0x7e, 0xeb, 0x4f, 0x45, 0x77, 0x65, 0x89, + 0xce, 0xa7, 0xc3, 0xe3, 0x9d, 0xa0, 0xa5, 0xec, 0x69, 0x06, 0x1f, 0x2a, 0x2a, 0xe0, 0xb3, 0x26, + 0x4b, 0x74, 0x3e, 0x3b, 0x68, 0x1f, 0x54, 0x87, 0x2b, 0x83, 0x0f, 0x15, 0x15, 0xf0, 0x91, 0x46, + 0xc5, 0x88, 0xad, 0x02, 0xde, 0x11, 0x3d, 0x74, 0x9a, 0x21, 0x6f, 0x71, 0x3f, 0xf6, 0xdc, 0x76, + 0x44, 0x16, 0x58, 0xbc, 0x6a, 0xc8, 0x15, 0xea, 0x16, 0x28, 0x2c, 0x5c, 0x4c, 0xcb, 0xd8, 0x65, + 0x18, 0xe9, 0xb8, 0x8f, 0x1c, 0x77, 0x5b, 0xfa, 0xd9, 0x4d, 0x48, 0x8b, 0x1d, 0x81, 0xf4, 0x1d, + 0xb1, 0xe3, 0x3e, 0xaa, 0x6d, 0x73, 0xf1, 0x15, 0xfc, 0x51, 0x37, 0x88, 0xb4, 0xaf, 0x18, 0x4e, + 0xbf, 0x22, 0x53, 0xa4, 0x7f, 0x05, 0x15, 0xd1, 0x57, 0x58, 0xbf, 0x5c, 0x82, 0x67, 0x56, 0xb0, + 0x15, 0xf1, 0xde, 0x22, 0xf7, 0x63, 0x1e, 0xd6, 0x79, 0xd8, 0xf1, 0xd0, 0xef, 0xa7, 0xc1, 0x63, + 0xf6, 0x02, 0x0c, 0xd4, 0xec, 0x3b, 0x34, 0xab, 0xe5, 0xd6, 0x65, 0xf8, 0x80, 0x89, 0xd2, 0xc4, + 0xa8, 0x5b, 0x3e, 0xe4, 0xb2, 0xa9, 0x06, 0xe3, 0xb5, 0x28, 0xf2, 0xb6, 0xfd, 0x8e, 0x0c, 0x4e, + 0x1c, 0x30, 0xbc, 0xcc, 0x08, 0x9e, 0xbb, 0x04, 0xd6, 0x49, 0xac, 0xff, 0xa4, 0x04, 0xd3, 0xb5, + 0x6e, 0xd7, 0x6c, 0xb2, 0xe9, 0xe1, 0x58, 0x3a, 0xba, 0x87, 0xa3, 0x07, 0x93, 0xc6, 0xe7, 0xca, + 0x29, 0x95, 0x9e, 0x7d, 0x0e, 0xe8, 0x19, 0xd9, 0xec, 0x6e, 0x02, 0x72, 0x22, 0xd3, 0x41, 0x26, + 0xc3, 0xd8, 0xfa, 0x77, 0x47, 0x51, 0xb2, 0xd0, 0x16, 0x41, 0x51, 0x05, 0xa5, 0x82, 0xa8, 0x82, + 0xb7, 0x40, 0x53, 0xd6, 0xf4, 0xdd, 0x5a, 0x3b, 0x2e, 0xe8, 0xe6, 0xd0, 0x14, 0x99, 0x3d, 0xc8, + 0xc6, 0x17, 0x0c, 0xe0, 0xd7, 0xbc, 0x90, 0x5d, 0xd6, 0x4f, 0x24, 0xb4, 0xe0, 0x26, 0xb0, 0x15, + 0x1f, 0x9d, 0x76, 0x78, 0xe3, 0x81, 0xd7, 0xbd, 0xcf, 0x43, 0x6f, 0x6b, 0x8f, 0x16, 0x00, 0x76, + 0xbe, 0x47, 0xa5, 0x4e, 0xf4, 0xc0, 0xeb, 0x3a, 0xbb, 0x58, 0x6e, 0x17, 0xd0, 0xb0, 0xf7, 0x60, + 0xc4, 0xe6, 0x0f, 0x43, 0x2f, 0x56, 0x3e, 0xa6, 0x93, 0x89, 0x75, 0x1f, 0xa1, 0x72, 0x2d, 0x84, + 0xf2, 0x87, 0x2e, 0x2b, 0xa9, 0x9c, 0xcd, 0x4b, 0x7d, 0x4b, 0xfa, 0x92, 0x4e, 0xa4, 0x5f, 0x5b, + 0xdb, 0x68, 0xf4, 0x53, 0xb7, 0xd8, 0x25, 0x18, 0x42, 0xa5, 0x8d, 0x8e, 0x83, 0x18, 0x78, 0x8b, + 0xc7, 0x27, 0x5d, 0xa3, 0x44, 0x0c, 0x34, 0x16, 0x29, 0xaf, 0x18, 0xb5, 0x25, 0x69, 0x90, 0xac, + 0xc6, 0x39, 0x7a, 0x2c, 0x8d, 0x73, 0x15, 0xaa, 0xb6, 0x0c, 0x2a, 0x6f, 0xd5, 0xba, 0xe8, 0x29, + 0x11, 0xcd, 0x00, 0xae, 0xe4, 0xf3, 0x8f, 0xf7, 0xe7, 0x9e, 0xa5, 0x80, 0xf3, 0x96, 0xe3, 0x76, + 0xa5, 0x83, 0x85, 0x21, 0x46, 0xb2, 0x94, 0xec, 0x2d, 0x18, 0x14, 0xa2, 0x97, 0x22, 0x11, 0xd4, + 0x95, 0x6c, 0x2a, 0x8d, 0xe5, 0xe2, 0x6c, 0x06, 0x86, 0x4c, 0x40, 0x12, 0xe6, 0xc0, 0xa4, 0x39, + 0xdd, 0xc9, 0x85, 0x73, 0x26, 0xed, 0x4f, 0xb3, 0x9c, 0xee, 0x69, 0x09, 0xe6, 0x34, 0x11, 0xa8, + 0xaf, 0x80, 0xcc, 0x22, 0x5d, 0x86, 0xca, 0xfa, 0x62, 0xbd, 0x1e, 0x84, 0xb1, 0x3c, 0xed, 0xa6, + 0xfb, 0x8d, 0x80, 0xd9, 0xae, 0xbf, 0xcd, 0xa5, 0x02, 0x11, 0x37, 0xbb, 0x8e, 0xd8, 0xc6, 0x0d, + 0x05, 0x42, 0x91, 0xb2, 0x6f, 0xc2, 0xa9, 0x7b, 0x11, 0xaf, 0xf9, 0x7b, 0xa8, 0x52, 0x68, 0x4b, + 0x65, 0x12, 0xa7, 0x1e, 0xde, 0x34, 0xf6, 0x22, 0xee, 0xb8, 0xfe, 0x9e, 0x23, 0x55, 0x91, 0xe2, + 0x85, 0x53, 0xcc, 0x85, 0x5d, 0x81, 0x81, 0xb5, 0xc5, 0x3a, 0x1d, 0x8b, 0x95, 0x87, 0xf5, 0xda, + 0x62, 0x5d, 0x4e, 0xa4, 0x8e, 0x19, 0xf0, 0xb2, 0xb6, 0x58, 0x67, 0x6f, 0xc2, 0xc0, 0xea, 0xea, + 0x1a, 0xb9, 0x7a, 0x2a, 0x82, 0xd5, 0xd5, 0x35, 0xf9, 0x2d, 0x9e, 0xbf, 0xc5, 0x43, 0xee, 0x37, + 0xf5, 0x79, 0x2b, 0xf0, 0xc5, 0x9c, 0x5d, 0x5f, 0x6d, 0x90, 0x2b, 0xa7, 0x36, 0x67, 0xd7, 0x57, + 0x69, 0xce, 0xe6, 0x23, 0x5c, 0x3e, 0xbb, 0x00, 0x8c, 0x6f, 0xe0, 0x47, 0xb3, 0x19, 0x18, 0x69, + 0x4a, 0x1c, 0xe2, 0xa6, 0x7e, 0x32, 0x06, 0x83, 0x6e, 0xb8, 0x4d, 0x3b, 0xae, 0x8d, 0x7f, 0xb3, + 0x97, 0xa1, 0x1a, 0xf6, 0x7c, 0xc7, 0x8d, 0xe4, 0xf5, 0x70, 0x2f, 0xe2, 0xa1, 0x94, 0xe8, 0xf6, + 0x44, 0xd8, 0xf3, 0x6b, 0x91, 0xd0, 0x4b, 0xc5, 0x2a, 0xb1, 0xfe, 0x5e, 0x09, 0xb4, 0xa5, 0x5a, + 0xb1, 0x79, 0xcb, 0x0b, 0x79, 0x33, 0x26, 0x35, 0xe0, 0x34, 0x39, 0xbd, 0x23, 0x2c, 0xe3, 0xf4, + 0x8e, 0x30, 0xf6, 0x15, 0x18, 0xa1, 0xed, 0x8a, 0xc4, 0xb3, 0xea, 0x2e, 0xba, 0xf5, 0x93, 0x99, + 0x03, 0x72, 0x5b, 0x9d, 0x22, 0x12, 0xbb, 0xc3, 0xad, 0x8d, 0xf5, 0xc5, 0xb6, 0xeb, 0x75, 0x22, + 0xda, 0x73, 0x50, 0x40, 0x7d, 0xf4, 0x30, 0x76, 0x9a, 0x08, 0xd5, 0x77, 0x87, 0x04, 0xd5, 0xba, + 0xa1, 0x2e, 0x1d, 0x0f, 0x89, 0x45, 0x99, 0x83, 0xa1, 0xfb, 0xe9, 0x71, 0x7c, 0x61, 0xf4, 0xf1, + 0xfe, 0x9c, 0xec, 0x5b, 0x5b, 0xc2, 0x2d, 0x0e, 0xa3, 0xc9, 0x14, 0x17, 0xbc, 0xc4, 0x0f, 0xe4, + 0x35, 0x21, 0x79, 0x89, 0xc9, 0x6e, 0x23, 0x54, 0x28, 0x8a, 0xcb, 0x7e, 0x0b, 0x11, 0xca, 0x88, + 0x80, 0xdd, 0xc3, 0xfd, 0x16, 0xae, 0x08, 0xfd, 0xeb, 0x08, 0x4d, 0x53, 0xbc, 0xfe, 0x9b, 0x12, + 0x4e, 0x45, 0x76, 0x1a, 0x86, 0xb7, 0x82, 0xb0, 0xe3, 0x52, 0xd0, 0x87, 0x4d, 0xbf, 0xd8, 0x2c, + 0x54, 0xba, 0x61, 0xb0, 0xeb, 0xb5, 0x38, 0xed, 0x2b, 0x76, 0xf2, 0x9b, 0x5d, 0x84, 0xe1, 0x4e, + 0xd0, 0x4a, 0xf7, 0x8c, 0x6a, 0x3a, 0x91, 0x2f, 0xaf, 0x89, 0x02, 0x9b, 0xca, 0xd9, 0x4b, 0x30, + 0xb9, 0xe5, 0xb6, 0xdb, 0x9b, 0x6e, 0xf3, 0x81, 0x83, 0x20, 0xb2, 0x33, 0x4f, 0x28, 0x28, 0xa2, + 0xcf, 0xbe, 0x0f, 0x43, 0xf8, 0x87, 0x98, 0x3a, 0x7e, 0xd2, 0x77, 0xf6, 0x20, 0x1d, 0x68, 0x26, + 0x54, 0xcd, 0x4e, 0x7a, 0xfe, 0xb1, 0xc7, 0x15, 0x10, 0xaf, 0x80, 0x7f, 0xa5, 0x04, 0xc3, 0x72, + 0x41, 0x08, 0x1e, 0x1d, 0x75, 0x1f, 0x3f, 0x6a, 0xe3, 0xdf, 0xe2, 0x20, 0xa1, 0x5d, 0x94, 0x13, + 0x07, 0x88, 0xd2, 0x4b, 0xf2, 0x8b, 0x50, 0x25, 0x84, 0xa8, 0xeb, 0x6d, 0x6d, 0x71, 0xc7, 0x6b, + 0xd1, 0xfc, 0x9c, 0x8c, 0xe8, 0xb8, 0x26, 0xc0, 0x2b, 0x2d, 0xc1, 0x4a, 0xe9, 0x7d, 0x4d, 0x97, + 0xc2, 0x50, 0x6c, 0x20, 0xd0, 0xa2, 0x1b, 0x09, 0x56, 0x32, 0xe6, 0x5e, 0x5e, 0xa5, 0x63, 0x5b, + 0x86, 0x24, 0x2b, 0x09, 0x5f, 0xe4, 0x61, 0x2c, 0x3e, 0xd7, 0xfa, 0xb1, 0x12, 0x4c, 0x9a, 0xeb, + 0x8c, 0x5d, 0x86, 0x61, 0xca, 0x83, 0x50, 0xc2, 0xdb, 0x16, 0x31, 0xa2, 0xc3, 0x32, 0x03, 0x82, + 0x91, 0xf7, 0x80, 0xb0, 0x84, 0xe6, 0x4f, 0x1c, 0x48, 0xc1, 0x45, 0xcd, 0x9f, 0x56, 0xa2, 0xad, + 0xca, 0x98, 0x05, 0xc3, 0x36, 0x8f, 0x7a, 0xed, 0x58, 0xf7, 0x12, 0x09, 0x11, 0x62, 0x53, 0x89, + 0xf5, 0x2f, 0x65, 0x17, 0x8a, 0xfd, 0xce, 0x8c, 0x0e, 0x28, 0x1d, 0x27, 0x3a, 0xe0, 0xdb, 0x70, + 0xd2, 0x0e, 0xda, 0x3c, 0xaa, 0xf9, 0x7b, 0x0f, 0x77, 0x78, 0xc8, 0xeb, 0x61, 0xb0, 0xa5, 0x1c, + 0x5a, 0xc6, 0xe6, 0x9f, 0x37, 0xf6, 0xdb, 0x22, 0x44, 0xe9, 0x91, 0x10, 0x8a, 0x12, 0x21, 0x95, + 0xb1, 0x48, 0x88, 0xe6, 0x8c, 0x03, 0x4c, 0x61, 0x25, 0x62, 0x32, 0x87, 0x9a, 0xa1, 0xc8, 0xa6, + 0x5f, 0xd6, 0x2f, 0x95, 0x60, 0xb6, 0x7f, 0x95, 0xa8, 0x45, 0xc9, 0x3f, 0x53, 0xf5, 0x55, 0x6a, + 0x51, 0x12, 0x9a, 0x09, 0x65, 0xd0, 0x90, 0x99, 0x0d, 0xa7, 0x6a, 0xcd, 0x26, 0xef, 0xc6, 0x82, + 0x31, 0xb9, 0xc0, 0x27, 0xea, 0x6d, 0x45, 0x1a, 0x5a, 0x5d, 0x44, 0x90, 0x41, 0x11, 0x2a, 0x2c, + 0x00, 0x25, 0x42, 0x31, 0xa9, 0xf5, 0xfb, 0x25, 0x80, 0x46, 0xe3, 0xe6, 0x6d, 0xbe, 0x57, 0x77, + 0x3d, 0xd4, 0x57, 0xe5, 0x96, 0x73, 0x9b, 0x04, 0xf7, 0x38, 0x79, 0xbe, 0xc9, 0x9d, 0xea, 0x01, + 0xdf, 0x33, 0x3c, 0xdf, 0x14, 0xaa, 0xfc, 0x2a, 0x6f, 0xd7, 0x8d, 0xb9, 0x20, 0xc4, 0x6b, 0x2b, + 0xf5, 0x55, 0x08, 0xcd, 0x50, 0x6a, 0xc8, 0xec, 0x9b, 0x30, 0x99, 0xfe, 0x4a, 0xfc, 0xf7, 0x26, + 0x93, 0xcd, 0xc1, 0x2c, 0x5c, 0x78, 0xee, 0xf1, 0xfe, 0xdc, 0xac, 0xc6, 0x35, 0xeb, 0xc4, 0x96, + 0x61, 0xf6, 0xf6, 0xe0, 0xbf, 0xfa, 0xeb, 0x73, 0x25, 0x74, 0xb2, 0x5c, 0x5f, 0x6d, 0xa8, 0xcf, + 0xbc, 0x00, 0x83, 0x49, 0xec, 0xd9, 0x38, 0xa9, 0x1e, 0xa6, 0x2f, 0x0c, 0x96, 0x8b, 0x43, 0x46, + 0xfa, 0x3d, 0xb8, 0xf9, 0x99, 0xdf, 0x21, 0x4a, 0xd9, 0x0d, 0x18, 0x39, 0x52, 0xcb, 0x51, 0x60, + 0x16, 0xb4, 0x58, 0x51, 0x0b, 0xdd, 0x7b, 0xd1, 0x96, 0x57, 0x9f, 0xe3, 0x52, 0xf7, 0x6e, 0x86, + 0x6d, 0x5b, 0xc0, 0xac, 0xfd, 0x12, 0xc0, 0xad, 0x8d, 0xf5, 0x1f, 0xd8, 0x61, 0xb2, 0x7e, 0xaa, + 0x0c, 0xd3, 0xa9, 0x03, 0xb6, 0xfa, 0xce, 0x2f, 0x02, 0xa4, 0x9f, 0x74, 0xf8, 0x87, 0x76, 0x93, + 0x0f, 0x7d, 0x1b, 0xc6, 0xb4, 0xca, 0x8f, 0xf0, 0xa5, 0xdd, 0xf4, 0x4b, 0x1d, 0xa8, 0x66, 0x1b, + 0xfe, 0x29, 0xbf, 0xb5, 0x6b, 0xe0, 0x8b, 0xd9, 0xb7, 0xe3, 0x46, 0x32, 0xde, 0x61, 0x42, 0xce, + 0x3e, 0xf1, 0x5b, 0x9f, 0x7d, 0xe2, 0xb7, 0x55, 0x83, 0x6a, 0x6d, 0x9b, 0x1b, 0xbd, 0xc2, 0x5e, + 0x2b, 0xe8, 0x11, 0x34, 0x7f, 0xa7, 0x50, 0xad, 0x1f, 0xac, 0x9f, 0x28, 0xc3, 0x94, 0x98, 0xc9, + 0xb5, 0x5e, 0xbc, 0x13, 0x84, 0x5e, 0xbc, 0xf7, 0xd4, 0x5e, 0x5c, 0xbe, 0x6b, 0x18, 0x84, 0x66, + 0x95, 0xe6, 0xa8, 0x7f, 0x5b, 0xff, 0xfb, 0x4b, 0x5d, 0x17, 0x19, 0x82, 0x13, 0x05, 0x54, 0xec, + 0x55, 0xc3, 0x1b, 0x62, 0x46, 0xe5, 0x84, 0xfa, 0xfe, 0xfe, 0xdc, 0xb8, 0x42, 0x5f, 0x4f, 0x73, + 0x44, 0xcd, 0x9b, 0xae, 0xf7, 0xb2, 0xa7, 0xd0, 0x39, 0x42, 0x77, 0xbd, 0x37, 0x1d, 0xee, 0x2f, + 0xc1, 0x10, 0xee, 0x08, 0x14, 0xbf, 0x82, 0x07, 0x3b, 0xdc, 0x7b, 0x0c, 0xff, 0x53, 0x01, 0x60, + 0x37, 0x61, 0x44, 0xfc, 0xb1, 0xe6, 0x76, 0xc9, 0x35, 0x89, 0x25, 0x16, 0x53, 0x84, 0x76, 0x3d, + 0x7f, 0x5b, 0x37, 0x9a, 0xb6, 0xb9, 0xd3, 0x71, 0xbb, 0xc6, 0x09, 0x54, 0x22, 0x1a, 0xc6, 0xd7, + 0x4a, 0x7f, 0xe3, 0x6b, 0xe9, 0x50, 0xe3, 0xeb, 0x16, 0x40, 0xc3, 0xdb, 0xf6, 0x3d, 0x7f, 0xbb, + 0xd6, 0xde, 0xa6, 0xcc, 0x5a, 0x97, 0xfa, 0x8f, 0xc2, 0xe5, 0x14, 0x19, 0xd7, 0xc8, 0x33, 0xe8, + 0x3f, 0x28, 0x61, 0x8e, 0xdb, 0xde, 0x36, 0xd2, 0x10, 0x68, 0x9c, 0xd9, 0x1d, 0x80, 0x5a, 0x33, + 0xf6, 0x76, 0xc5, 0x6a, 0x89, 0xe8, 0xb8, 0xa8, 0x9a, 0xbc, 0x58, 0xbb, 0xcd, 0xf7, 0xd0, 0xc4, + 0xa1, 0x3c, 0xb1, 0x5c, 0x44, 0x15, 0xb3, 0xde, 0x88, 0x31, 0x4f, 0x39, 0xb0, 0x2e, 0x9c, 0xaa, + 0xb5, 0x5a, 0x9e, 0xf8, 0x06, 0xb7, 0xbd, 0x2e, 0x93, 0xa6, 0x21, 0xeb, 0xf1, 0x62, 0xd6, 0x97, + 0x94, 0xf3, 0x90, 0x9b, 0x50, 0x39, 0x2a, 0xd7, 0x5a, 0xa6, 0x9a, 0x62, 0xc6, 0x56, 0x03, 0x26, + 0xcd, 0x8f, 0x37, 0x73, 0x7f, 0x8d, 0x43, 0xc5, 0x6e, 0xd4, 0x9c, 0xc6, 0xcd, 0xda, 0x1b, 0xd5, + 0x12, 0xab, 0xc2, 0x38, 0xfd, 0x9a, 0x77, 0xe6, 0xdf, 0xbc, 0x56, 0x2d, 0x1b, 0x90, 0x37, 0xdf, + 0x98, 0xaf, 0x0e, 0xcc, 0x96, 0x67, 0x4a, 0x99, 0x0c, 0x24, 0x23, 0xd5, 0x8a, 0xbc, 0x45, 0x13, + 0xba, 0x68, 0x45, 0xb5, 0x9d, 0x5d, 0x83, 0x81, 0x46, 0xe3, 0x66, 0x26, 0x87, 0x47, 0xba, 0xbb, + 0xcb, 0x1d, 0x2c, 0x32, 0x24, 0x8d, 0x20, 0x10, 0x74, 0xe2, 0xc8, 0x57, 0x36, 0xe8, 0xd2, 0xed, + 0xb2, 0xdf, 0xb1, 0x4f, 0xd0, 0xdd, 0xda, 0x58, 0x27, 0xc5, 0x5c, 0xd1, 0xa5, 0xdb, 0x94, 0xa4, + 0xfb, 0xe8, 0xa1, 0xbe, 0xaf, 0x0a, 0x02, 0xcb, 0x86, 0x31, 0x6d, 0x22, 0x4b, 0x85, 0xb1, 0x13, + 0x24, 0x69, 0xc9, 0x48, 0x61, 0x14, 0x10, 0x9b, 0x4a, 0xc4, 0x51, 0x66, 0x35, 0x68, 0xba, 0x6d, + 0xd2, 0x3c, 0xf1, 0x28, 0xd3, 0x16, 0x00, 0x5b, 0xc2, 0xad, 0xdf, 0x2c, 0x41, 0xb5, 0x2e, 0xb4, + 0x74, 0x0c, 0x0c, 0x0c, 0x1e, 0x70, 0xff, 0xfe, 0x1b, 0xec, 0x35, 0xb5, 0xe4, 0x4a, 0xc9, 0x2d, + 0xc0, 0x10, 0x2e, 0xb9, 0x8c, 0xbb, 0x0a, 0x2d, 0xbb, 0x46, 0x9a, 0x82, 0xae, 0x7c, 0xf4, 0x8c, + 0x51, 0x94, 0x82, 0x2e, 0x9b, 0x31, 0x4a, 0x25, 0xa3, 0x9b, 0x83, 0x21, 0x6c, 0x0e, 0x09, 0x47, + 0x6c, 0x79, 0x2c, 0x00, 0xb6, 0x84, 0x6b, 0xb2, 0x69, 0xbf, 0x9c, 0xfb, 0x86, 0xf9, 0x1f, 0xa8, + 0xac, 0x4b, 0xe6, 0xc7, 0x1d, 0xe0, 0x6f, 0x72, 0xbb, 0x4f, 0xd6, 0xa5, 0x0c, 0x03, 0x99, 0x4d, + 0x60, 0x5e, 0xde, 0xbb, 0xca, 0xa0, 0x5a, 0xdd, 0x24, 0x9d, 0x4b, 0xb8, 0xf2, 0x35, 0x38, 0x99, + 0xed, 0x5f, 0xbc, 0x4f, 0xa9, 0xc1, 0x94, 0x09, 0x57, 0x57, 0x2b, 0x67, 0x0a, 0xeb, 0xbd, 0x3f, + 0x6f, 0x67, 0xf1, 0xad, 0x5f, 0x1b, 0x80, 0x51, 0xfc, 0xd3, 0xee, 0x49, 0x2d, 0xbf, 0xb6, 0xd1, + 0x20, 0x7b, 0xae, 0xae, 0xe5, 0xbb, 0x0f, 0x23, 0xe5, 0x01, 0x6e, 0x08, 0xac, 0x04, 0x99, 0x48, + 0xe5, 0xfd, 0xb2, 0xba, 0x49, 0x48, 0x48, 0xe5, 0x19, 0x23, 0xca, 0x90, 0x12, 0x32, 0x86, 0x74, + 0xc9, 0x63, 0x87, 0xee, 0x8f, 0x8b, 0x74, 0x41, 0xdb, 0x0c, 0xe9, 0x92, 0x68, 0xe8, 0x8e, 0xbb, + 0xd1, 0x10, 0x27, 0x11, 0xdd, 0x1d, 0x57, 0xb4, 0xd1, 0x38, 0x85, 0x10, 0x12, 0xbb, 0x0b, 0xd3, + 0xb5, 0x8d, 0xc6, 0x5d, 0xed, 0x12, 0x77, 0x65, 0x89, 0x2e, 0xb2, 0x9f, 0x7f, 0xbc, 0x3f, 0x77, + 0x4e, 0x50, 0xea, 0x57, 0xbc, 0xe6, 0xd1, 0x2d, 0x4f, 0xcb, 0x7e, 0xa4, 0x04, 0xa7, 0x33, 0x50, + 0xb7, 0x7d, 0xcf, 0xf7, 0x62, 0x35, 0xf0, 0x56, 0x7a, 0x49, 0xad, 0x23, 0x21, 0xca, 0x9a, 0x1b, + 0x37, 0x77, 0x78, 0x28, 0x7d, 0xec, 0xb3, 0x55, 0xbb, 0x6d, 0xa7, 0x27, 0x90, 0x74, 0x17, 0xfe, + 0xe2, 0x9a, 0xac, 0xbf, 0x3d, 0x95, 0x9d, 0x16, 0xa4, 0x13, 0x1c, 0x53, 0x7c, 0xbc, 0x03, 0x43, + 0x35, 0x71, 0x34, 0x27, 0x41, 0xaa, 0x2c, 0x15, 0xc9, 0xac, 0x90, 0x5b, 0x3e, 0x9e, 0xde, 0x8d, + 0x0c, 0x33, 0x02, 0xc0, 0x16, 0x61, 0xb4, 0xb6, 0xd1, 0x58, 0x59, 0x59, 0x5a, 0x5f, 0x97, 0xa9, + 0x2b, 0x06, 0x16, 0x5e, 0x52, 0xa3, 0xee, 0x79, 0x2d, 0x27, 0xeb, 0x05, 0x99, 0x1e, 0xcf, 0x53, + 0x3a, 0xf6, 0x65, 0x80, 0x5b, 0x81, 0xe7, 0xcb, 0x1b, 0x25, 0x1a, 0xd2, 0x73, 0x8f, 0xf7, 0xe7, + 0xc6, 0x3e, 0x0a, 0x3c, 0x9f, 0xae, 0xa0, 0x44, 0xdb, 0x53, 0x24, 0x5b, 0xfb, 0x5b, 0xcc, 0x9f, + 0x85, 0x40, 0x46, 0x47, 0x0c, 0xa5, 0xf3, 0x67, 0x33, 0xc8, 0x5d, 0x7d, 0x28, 0x34, 0xd6, 0x81, + 0xa9, 0x46, 0x6f, 0x7b, 0x9b, 0x8b, 0xcd, 0x8f, 0x4c, 0xfb, 0xc3, 0x64, 0xda, 0x4b, 0xf2, 0x8b, + 0x4a, 0x73, 0x83, 0xdb, 0xee, 0xf1, 0x68, 0xe1, 0x55, 0xb1, 0xd6, 0x7f, 0x67, 0x7f, 0x8e, 0xbc, + 0x2b, 0x85, 0xe2, 0x1c, 0x29, 0xfa, 0xbc, 0x61, 0x3f, 0xcb, 0x9b, 0xdd, 0x85, 0x61, 0xe9, 0x89, + 0x40, 0xb9, 0x14, 0x9e, 0x3f, 0x40, 0xae, 0x48, 0xc4, 0x7e, 0x3e, 0x42, 0xb2, 0x94, 0x6d, 0x40, + 0x65, 0xd1, 0x0b, 0x9b, 0x6d, 0xbe, 0xb8, 0x42, 0xea, 0xd1, 0x0b, 0x07, 0xb0, 0x54, 0xa8, 0xb2, + 0x5f, 0x9a, 0xf8, 0xab, 0xe9, 0xe9, 0xea, 0x92, 0xc2, 0x60, 0x7f, 0xa5, 0x04, 0xcf, 0x24, 0xad, + 0xaf, 0x6d, 0x73, 0x3f, 0xa6, 0xd9, 0x4a, 0xbd, 0x34, 0x7a, 0x50, 0x2f, 0xbd, 0x9d, 0xeb, 0xa5, + 0x8b, 0x69, 0x2f, 0xb9, 0x82, 0x99, 0xd3, 0x91, 0xdc, 0xf2, 0x7d, 0x76, 0x50, 0xad, 0xcc, 0x01, + 0x48, 0x7d, 0x6c, 0xc8, 0x4d, 0xfb, 0xa5, 0x03, 0x3e, 0x38, 0x45, 0xa6, 0x28, 0xf6, 0xe4, 0xb7, + 0x11, 0x56, 0x94, 0x40, 0xd9, 0x6d, 0x95, 0xf7, 0x44, 0x2a, 0x6e, 0xe7, 0x0f, 0xe0, 0x2d, 0x73, + 0xa1, 0x9c, 0x38, 0x20, 0xe9, 0x92, 0x1c, 0xed, 0x55, 0x77, 0x93, 0x74, 0xb5, 0x43, 0x46, 0x7b, + 0xd5, 0x4d, 0x47, 0xbb, 0xed, 0x66, 0x47, 0x7b, 0xd5, 0xdd, 0x64, 0x8b, 0x32, 0x7f, 0x94, 0x4c, + 0x36, 0xf4, 0xdc, 0x41, 0xdc, 0x94, 0x59, 0xbd, 0x20, 0x8f, 0xd4, 0xd7, 0x61, 0xb4, 0xd1, 0x75, + 0x9b, 0xbc, 0xed, 0x6d, 0xc5, 0xe4, 0xf8, 0xf6, 0xe2, 0x01, 0xac, 0x12, 0x5c, 0x72, 0xd8, 0x51, + 0x3f, 0xf5, 0x73, 0x6b, 0x82, 0x23, 0x5a, 0xb8, 0x5e, 0x5f, 0x23, 0x23, 0xff, 0x41, 0x2d, 0x5c, + 0xaf, 0xaf, 0x91, 0x5a, 0xd6, 0xed, 0x18, 0x6a, 0x59, 0x7d, 0x8d, 0x75, 0x61, 0x72, 0x9d, 0x87, + 0xa1, 0xbb, 0x15, 0x84, 0x1d, 0x79, 0x95, 0x24, 0xef, 0x00, 0x2e, 0x1d, 0xc4, 0xcf, 0x20, 0x90, + 0x37, 0x28, 0xb1, 0x82, 0x39, 0xd9, 0xfb, 0xa7, 0x0c, 0x7f, 0xd1, 0x27, 0x0b, 0x5e, 0xbc, 0xd9, + 0x6b, 0x3e, 0xe0, 0x31, 0xdd, 0x1c, 0x1c, 0xd4, 0x27, 0x09, 0xae, 0xec, 0x93, 0x4d, 0xf5, 0x53, + 0xef, 0x93, 0x04, 0x47, 0x4c, 0x03, 0xca, 0x12, 0xc5, 0x0e, 0x9d, 0x06, 0x12, 0x51, 0x4e, 0x83, + 0x7e, 0xe9, 0xa2, 0xd8, 0x0e, 0x8c, 0x2f, 0x04, 0x3d, 0x5f, 0x68, 0xeb, 0x5d, 0xd7, 0x0b, 0x67, + 0x4e, 0x20, 0xdb, 0x97, 0x0f, 0x6a, 0xb0, 0x86, 0x2e, 0x83, 0xcf, 0x36, 0x05, 0x44, 0x1c, 0x08, + 0x04, 0x48, 0xbf, 0x14, 0xd6, 0x51, 0x59, 0x0b, 0xc6, 0x70, 0x2a, 0x2f, 0xf1, 0xdd, 0xa0, 0x1b, + 0xcd, 0x9c, 0xc4, 0x8a, 0x2e, 0x1c, 0xb6, 0x28, 0x24, 0xb6, 0x74, 0xa4, 0xc2, 0xa5, 0xe1, 0xb4, + 0x10, 0xa2, 0xdf, 0xd4, 0x69, 0x88, 0xec, 0x06, 0x0c, 0x2e, 0xfb, 0xbb, 0xaf, 0xcf, 0x9c, 0x42, + 0xf6, 0x73, 0x07, 0xb0, 0x17, 0x68, 0xd2, 0xe0, 0xc0, 0xfd, 0xdd, 0xd7, 0x75, 0x83, 0x83, 0x28, + 0xc9, 0xde, 0x17, 0x9e, 0x3e, 0xd6, 0x7d, 0x61, 0x0b, 0xc6, 0x6e, 0x70, 0x9f, 0x87, 0x5e, 0xf3, + 0xee, 0xca, 0xd2, 0xe2, 0xcc, 0x99, 0x43, 0xbf, 0x55, 0xc3, 0x96, 0xdf, 0xba, 0x2d, 0x01, 0x4e, + 0xe0, 0xb5, 0x9a, 0x7a, 0x2d, 0x1a, 0xa2, 0xf5, 0xab, 0x43, 0x30, 0x77, 0x48, 0xc7, 0xb1, 0xfb, + 0x6a, 0x1f, 0x96, 0x3a, 0xdc, 0xe7, 0x8f, 0xd6, 0xdf, 0x97, 0x0f, 0xdd, 0xa2, 0xdf, 0x81, 0xc9, + 0x8c, 0xea, 0xa3, 0xe5, 0x46, 0xce, 0xa8, 0x3d, 0x76, 0x06, 0x75, 0xf6, 0xdf, 0x0e, 0xc0, 0x20, + 0xaa, 0x86, 0x2f, 0xc0, 0x40, 0xa3, 0xb7, 0xa9, 0x3b, 0x2e, 0x44, 0xc6, 0xd6, 0x24, 0x4a, 0xd9, + 0xbb, 0x30, 0x46, 0x61, 0xc5, 0x9a, 0x7d, 0x01, 0x3b, 0x49, 0xc5, 0x20, 0x67, 0x83, 0x1e, 0x35, + 0x74, 0xf6, 0x1e, 0x8c, 0xd7, 0xbd, 0x2e, 0x6f, 0x7b, 0x3e, 0xd7, 0x42, 0xf8, 0x70, 0xde, 0x76, + 0x09, 0x9e, 0x73, 0x66, 0xd0, 0x09, 0xcc, 0x00, 0xe8, 0xc1, 0xa3, 0x07, 0x40, 0xbf, 0x07, 0xe3, + 0x4b, 0x7c, 0xcb, 0xf3, 0x3d, 0x43, 0x35, 0xc4, 0x8a, 0x5b, 0x09, 0xdc, 0xa4, 0x36, 0x08, 0xd8, + 0x02, 0x4c, 0xd8, 0xbc, 0x1b, 0x44, 0x5e, 0x1c, 0x84, 0x7b, 0xf7, 0xec, 0x15, 0xf2, 0x75, 0x44, + 0xd3, 0x76, 0x98, 0x14, 0x38, 0xbd, 0x50, 0xdf, 0x75, 0x4d, 0x12, 0x76, 0x07, 0xa6, 0x53, 0x80, + 0xe9, 0x5b, 0x4d, 0x37, 0xd7, 0x09, 0x9f, 0x7c, 0xac, 0x54, 0x9e, 0xd4, 0x6c, 0x93, 0xcd, 0xb7, + 0x28, 0xf2, 0x2a, 0xdb, 0xa6, 0x90, 0x6f, 0x15, 0xb7, 0xc9, 0xe6, 0x5b, 0xd6, 0xdf, 0x1f, 0x80, + 0x33, 0x7d, 0xc4, 0x38, 0xbb, 0x63, 0x4e, 0xd7, 0x17, 0x0e, 0x96, 0xfa, 0x87, 0x4f, 0xd3, 0x55, + 0xa8, 0x2e, 0xdf, 0x46, 0x93, 0x0c, 0xdd, 0x13, 0xd5, 0xd4, 0x31, 0x02, 0x3f, 0x9f, 0x3f, 0xc0, + 0xab, 0x22, 0xed, 0x5e, 0xc9, 0x88, 0x40, 0xcb, 0x50, 0xce, 0xfe, 0xff, 0xca, 0x34, 0x6f, 0xdf, + 0x01, 0x3d, 0xcf, 0xa9, 0x91, 0x99, 0x20, 0x05, 0xeb, 0x33, 0x52, 0xc3, 0x66, 0xef, 0xc3, 0xf8, + 0xf2, 0x6d, 0x69, 0x87, 0xbe, 0xa9, 0x4c, 0x9f, 0xd4, 0x85, 0xfc, 0x81, 0xba, 0x07, 0xcf, 0x18, + 0x41, 0x0d, 0x0a, 0x76, 0x0f, 0x4e, 0xc8, 0xb6, 0x79, 0x5b, 0x5e, 0x53, 0x66, 0x79, 0xf6, 0xdc, + 0x36, 0xcd, 0xb0, 0x17, 0x1e, 0xef, 0xcf, 0xcd, 0xf1, 0x07, 0x78, 0x09, 0x46, 0xe5, 0x4e, 0x84, + 0x08, 0x7a, 0x60, 0x69, 0x01, 0xbd, 0x9e, 0xdf, 0xd5, 0x1e, 0xc5, 0x0a, 0x45, 0x6d, 0xa2, 0x6e, + 0x81, 0x2b, 0x91, 0xac, 0xff, 0x63, 0x18, 0x66, 0xfb, 0xab, 0x98, 0xec, 0x03, 0x73, 0x00, 0x2f, + 0x1c, 0xaa, 0x94, 0x1e, 0x3e, 0x86, 0x5f, 0x85, 0x93, 0xcb, 0x7e, 0xcc, 0xc3, 0x6e, 0xe8, 0xa9, + 0xbc, 0xb0, 0x37, 0x83, 0x48, 0xc5, 0xef, 0xe2, 0xb5, 0x15, 0x4f, 0xca, 0x29, 0x12, 0x1d, 0x6f, + 0xbb, 0xf5, 0x6b, 0xab, 0x22, 0x0e, 0x6c, 0x19, 0x26, 0x35, 0x78, 0xbb, 0xb7, 0xad, 0x3b, 0x3b, + 0xe9, 0x3c, 0xdb, 0x3d, 0x3d, 0xb8, 0x31, 0x43, 0x84, 0x31, 0xc2, 0xb1, 0x1b, 0x7b, 0xcd, 0x5b, + 0x1b, 0xb7, 0x1b, 0x34, 0x9c, 0x32, 0x46, 0x18, 0xa1, 0xce, 0x47, 0x0f, 0x1f, 0x18, 0x3a, 0x62, + 0x8a, 0x3c, 0xfb, 0x0b, 0x83, 0xc7, 0x91, 0x84, 0x5f, 0x02, 0x48, 0x97, 0x92, 0x9e, 0x93, 0x28, + 0x5d, 0x7a, 0x66, 0x1a, 0x00, 0x05, 0x65, 0x37, 0x61, 0x2a, 0xfd, 0x75, 0xf7, 0xa1, 0xaf, 0xbc, + 0x00, 0xa4, 0xbd, 0x5e, 0x5b, 0xb9, 0x81, 0x28, 0xd3, 0x8f, 0x1d, 0x19, 0x32, 0x36, 0x0f, 0x95, + 0x8d, 0x20, 0x7c, 0xb0, 0x25, 0xc6, 0x78, 0x30, 0x3d, 0x18, 0x3d, 0x24, 0x98, 0x7e, 0x00, 0x50, + 0x78, 0x62, 0xb9, 0x2c, 0xfb, 0xbb, 0x5e, 0x18, 0xa0, 0x83, 0x98, 0xee, 0xed, 0xcd, 0x53, 0xb0, + 0x91, 0x8b, 0x2e, 0x05, 0xb3, 0x4b, 0x30, 0x54, 0x6b, 0xc6, 0x41, 0x48, 0xe2, 0x4f, 0xce, 0x14, + 0x01, 0x30, 0x66, 0x8a, 0x00, 0x88, 0x4e, 0x14, 0x32, 0x69, 0x24, 0xed, 0x44, 0x53, 0x10, 0x89, + 0x52, 0x71, 0xb0, 0xb3, 0xf9, 0x16, 0xda, 0xb7, 0x2b, 0x69, 0xfb, 0x43, 0xbe, 0x95, 0xbb, 0x8b, + 0x22, 0x34, 0xd1, 0xed, 0xe9, 0x88, 0x93, 0xe7, 0x10, 0x76, 0x7b, 0x3a, 0x45, 0x8c, 0x4b, 0xd9, + 0x04, 0x2a, 0x13, 0x4f, 0xa8, 0x5f, 0x49, 0xbe, 0x37, 0x4a, 0x3c, 0x91, 0x4c, 0x2f, 0x73, 0x0f, + 0xd0, 0x09, 0xac, 0x3f, 0x0f, 0x7d, 0x57, 0x9b, 0x50, 0xe2, 0x8f, 0xb7, 0xda, 0x56, 0xdd, 0x23, + 0xac, 0xb6, 0x57, 0x93, 0xbc, 0x06, 0x7a, 0xaa, 0x4e, 0x84, 0xe8, 0xea, 0x23, 0x65, 0x38, 0x30, + 0xa7, 0xfe, 0xc0, 0x71, 0xa6, 0xfe, 0xdf, 0xaa, 0x1c, 0x67, 0xea, 0xd3, 0xd0, 0x96, 0x8f, 0x3a, + 0xb4, 0x03, 0x47, 0x1b, 0xda, 0x05, 0x98, 0x48, 0x5e, 0xdd, 0xa8, 0xbb, 0xb1, 0x21, 0x8c, 0x93, + 0x97, 0x39, 0x9c, 0xae, 0x6b, 0x24, 0x91, 0x36, 0x49, 0x34, 0xfd, 0x04, 0x39, 0x0c, 0xe5, 0xf5, + 0x93, 0x0c, 0xbd, 0x8e, 0x2e, 0x64, 0x90, 0x52, 0x37, 0x1a, 0x18, 0x25, 0x4f, 0xf3, 0x5c, 0x7a, + 0x2e, 0x2a, 0x0d, 0x45, 0x06, 0xd0, 0x1b, 0x77, 0x86, 0x06, 0x51, 0x76, 0x89, 0x8d, 0x1c, 0x6b, + 0x89, 0xc9, 0x78, 0xad, 0x70, 0x35, 0xd8, 0xf6, 0x54, 0x2c, 0xb5, 0x8a, 0xd7, 0x0a, 0x9d, 0xb6, + 0x80, 0x66, 0xe2, 0xb5, 0x24, 0x2a, 0x7b, 0x0d, 0x86, 0xc5, 0x8f, 0x95, 0x25, 0x5a, 0x14, 0x68, + 0x31, 0x43, 0x22, 0x33, 0x80, 0x5d, 0x22, 0xa9, 0x6a, 0x96, 0x3b, 0xae, 0xd7, 0xa6, 0xa5, 0x90, + 0x56, 0xc3, 0x05, 0x34, 0x5b, 0x0d, 0xa2, 0xb2, 0x26, 0x8c, 0xdb, 0x7c, 0xab, 0x1e, 0x06, 0x31, + 0x6f, 0xc6, 0xbc, 0x45, 0xe7, 0x69, 0x65, 0x52, 0x5a, 0x08, 0x02, 0x69, 0x2b, 0xc0, 0x58, 0xe7, + 0xd2, 0xef, 0xec, 0xcf, 0x81, 0x00, 0xc9, 0xec, 0x08, 0x62, 0xa5, 0x89, 0xf1, 0xef, 0x2a, 0x62, + 0x7d, 0xa5, 0xe9, 0x4c, 0xd9, 0xb7, 0xc5, 0x2e, 0x93, 0x74, 0x49, 0x5a, 0xd9, 0x78, 0x9f, 0xca, + 0xde, 0x2c, 0xac, 0x6c, 0x4e, 0xeb, 0xed, 0xc2, 0x4a, 0x0b, 0x2b, 0x61, 0x5f, 0x86, 0xb1, 0xc5, + 0x95, 0xc5, 0xc0, 0xdf, 0xf2, 0xb6, 0x1b, 0x37, 0x6b, 0x78, 0x28, 0x27, 0x31, 0xd1, 0xf4, 0x9c, + 0x26, 0xc2, 0x9d, 0x68, 0xc7, 0x35, 0xd2, 0x57, 0xa5, 0xf8, 0xec, 0x06, 0x4c, 0xaa, 0x9f, 0x36, + 0xdf, 0x12, 0xaa, 0xe2, 0xa4, 0x96, 0x4d, 0x25, 0xe1, 0x20, 0x3a, 0xc2, 0xd4, 0x16, 0x33, 0x64, + 0x62, 0x32, 0x2e, 0xf1, 0x6e, 0x3b, 0xd8, 0x13, 0xcd, 0x5b, 0xf7, 0x78, 0x88, 0xa7, 0x6f, 0x9a, + 0x8c, 0xad, 0xa4, 0xc4, 0x89, 0x3d, 0xd3, 0x89, 0xd0, 0x24, 0x12, 0x5a, 0x27, 0x4d, 0xf1, 0xfb, + 0x5e, 0xe4, 0x6d, 0x7a, 0x6d, 0x2f, 0xde, 0x93, 0x31, 0x67, 0x52, 0xed, 0x52, 0xeb, 0x62, 0x37, + 0x29, 0xd5, 0xb5, 0xce, 0x1c, 0xa9, 0xf5, 0x2b, 0x65, 0x78, 0xf6, 0x20, 0x1b, 0x14, 0x6b, 0x98, + 0x72, 0xf0, 0xe2, 0x11, 0xec, 0x56, 0x87, 0x4b, 0xc2, 0xe5, 0x3e, 0x47, 0x1c, 0xec, 0x8c, 0xfe, + 0x96, 0xdd, 0xec, 0x61, 0x67, 0x97, 0xc4, 0xdc, 0x27, 0x4d, 0xa4, 0x74, 0x0d, 0x46, 0x17, 0x03, + 0x3f, 0xe6, 0x8f, 0xe2, 0x4c, 0x1e, 0x42, 0x09, 0xcc, 0x26, 0x91, 0x52, 0xa8, 0xd6, 0x6f, 0x0f, + 0xc0, 0xb9, 0x03, 0x8d, 0x30, 0x6c, 0xdd, 0xec, 0xb5, 0x4b, 0x47, 0xb1, 0xdc, 0x1c, 0xde, 0x6d, + 0xf3, 0xb9, 0x88, 0x99, 0xc3, 0x33, 0x61, 0xd8, 0xc0, 0x64, 0x76, 0xb4, 0x1b, 0xed, 0x60, 0x13, + 0xcd, 0x74, 0x9e, 0xbf, 0x4d, 0x59, 0xd5, 0x64, 0x56, 0x07, 0x2c, 0x75, 0xb6, 0xdb, 0xc1, 0xa6, + 0x34, 0xf7, 0x79, 0xbe, 0xae, 0x91, 0x15, 0x50, 0xcf, 0xfe, 0x76, 0x89, 0x3a, 0xfe, 0x75, 0x18, + 0xc1, 0xe6, 0x27, 0xdd, 0x2e, 0xaf, 0x85, 0x50, 0xb2, 0x7b, 0xe6, 0xb5, 0x90, 0x44, 0x63, 0x57, + 0xa1, 0xb2, 0xe8, 0xb6, 0xdb, 0x5a, 0xe6, 0x47, 0xb4, 0xcd, 0x34, 0x11, 0x96, 0x09, 0x30, 0x53, + 0x88, 0x62, 0x2b, 0x94, 0x7f, 0x6b, 0xfb, 0x0f, 0x0a, 0x60, 0x22, 0xcb, 0x6c, 0x41, 0x1a, 0x32, + 0x3e, 0x45, 0x84, 0xc1, 0x1a, 0x83, 0xda, 0x53, 0x44, 0x02, 0x60, 0x3c, 0x45, 0x84, 0x6f, 0x29, + 0x7d, 0x6f, 0x18, 0x9e, 0x3b, 0xd8, 0x3a, 0xc9, 0xee, 0x99, 0xc3, 0xfa, 0xca, 0x91, 0x6c, 0x9a, + 0x87, 0x8f, 0xab, 0x7a, 0x48, 0x4a, 0x76, 0xc8, 0xc5, 0x7c, 0x8a, 0x84, 0xef, 0xef, 0xcf, 0x69, + 0x51, 0x8b, 0xb7, 0x02, 0xcf, 0xd7, 0x9c, 0x04, 0x3e, 0xce, 0x29, 0x0a, 0x63, 0xf3, 0xd7, 0x8e, + 0xd6, 0xb2, 0x94, 0x4e, 0xca, 0xaa, 0x23, 0x2a, 0x18, 0xec, 0xab, 0x30, 0x88, 0xd6, 0x97, 0x41, + 0x15, 0x78, 0x74, 0x94, 0xca, 0x04, 0x05, 0x55, 0x83, 0xb6, 0xa1, 0x8c, 0xfd, 0x05, 0x39, 0xce, + 0xbe, 0x0d, 0xd5, 0x6c, 0xa3, 0xd8, 0x05, 0x18, 0xc4, 0x4f, 0xd3, 0x32, 0x48, 0x64, 0xda, 0x86, + 0xe5, 0xb3, 0x3f, 0x53, 0x02, 0x48, 0x2b, 0x11, 0xea, 0xd6, 0x4a, 0x14, 0xf5, 0x92, 0x6c, 0xe6, + 0xa8, 0x6e, 0x79, 0x08, 0xd1, 0x77, 0x50, 0x89, 0xc3, 0xbe, 0x8a, 0x19, 0x2c, 0xd0, 0x35, 0x1f, + 0x07, 0xe5, 0xe6, 0xfa, 0x7a, 0x9d, 0xc8, 0xa5, 0xdf, 0x1b, 0xaa, 0xf3, 0x89, 0x4f, 0xbf, 0x8c, + 0x6e, 0xd9, 0x89, 0xe3, 0xae, 0x23, 0x59, 0xda, 0xfd, 0xc8, 0x67, 0xbf, 0xa3, 0x8e, 0xb6, 0x98, + 0xfe, 0x53, 0x4f, 0xbd, 0x44, 0x0d, 0xa3, 0xf4, 0x9f, 0x46, 0xde, 0x26, 0x33, 0xfd, 0xa7, 0x4e, + 0x24, 0x56, 0x74, 0x41, 0x9e, 0x29, 0x2d, 0x93, 0xe7, 0x21, 0x29, 0xa6, 0x0a, 0xa8, 0x99, 0x0b, + 0x67, 0xf2, 0x50, 0xf9, 0xa0, 0xda, 0x40, 0x9a, 0xc0, 0xaa, 0x88, 0x31, 0x22, 0xe9, 0x59, 0x60, + 0xfa, 0xf0, 0xb1, 0x7e, 0xa9, 0x0c, 0x67, 0xfb, 0xda, 0xc6, 0x58, 0xdd, 0x5c, 0x59, 0x2f, 0x1d, + 0x66, 0x4c, 0x3b, 0x74, 0x51, 0xcd, 0xfe, 0x7d, 0x25, 0xa4, 0xbe, 0x02, 0xe3, 0x8d, 0xde, 0x66, + 0xd6, 0xa4, 0x20, 0x33, 0x1a, 0x6b, 0x70, 0x5d, 0x7d, 0xd1, 0xf1, 0xc5, 0xb0, 0xa9, 0x34, 0x4b, + 0x14, 0x02, 0xa1, 0xc5, 0x5d, 0x25, 0x69, 0xe2, 0xf2, 0x59, 0x5b, 0x4d, 0x22, 0x31, 0x1d, 0x63, + 0xee, 0xbb, 0x7e, 0xac, 0x47, 0xfc, 0x4a, 0x88, 0x3e, 0x1d, 0x25, 0xc4, 0xfa, 0x8f, 0xca, 0xc5, + 0x96, 0x9c, 0x1b, 0x8b, 0xf5, 0xe3, 0x58, 0x72, 0x6e, 0x2c, 0xd6, 0x0f, 0xef, 0xa9, 0xff, 0x52, + 0xf5, 0x94, 0x74, 0x1a, 0x95, 0x9b, 0xa3, 0xba, 0x8d, 0x54, 0x4e, 0xa3, 0xb4, 0x91, 0x46, 0x19, + 0xa7, 0x51, 0x42, 0x66, 0x6f, 0xc2, 0xe8, 0x6a, 0x20, 0xd3, 0xaf, 0xaa, 0xfe, 0x91, 0x59, 0xd7, + 0x14, 0x50, 0xdf, 0x49, 0x13, 0x4c, 0x71, 0x78, 0x36, 0xe7, 0x8b, 0x0a, 0x46, 0xc3, 0xd5, 0x96, + 0x99, 0x6f, 0xe6, 0x9d, 0x9d, 0x49, 0x66, 0xfd, 0xc7, 0x43, 0x60, 0x1d, 0x7e, 0xe3, 0xc0, 0xbe, + 0x66, 0xf6, 0xdd, 0xe5, 0x23, 0xdf, 0x55, 0x1c, 0x69, 0x77, 0xae, 0xf5, 0x5a, 0x1e, 0xf7, 0x9b, + 0x66, 0xaa, 0x53, 0x82, 0xe9, 0x3b, 0x9b, 0xc2, 0xfb, 0x24, 0xb9, 0xad, 0x66, 0xff, 0xc9, 0x40, + 0x2a, 0x4f, 0x32, 0x5a, 0x54, 0xe9, 0x13, 0x68, 0x51, 0xec, 0x36, 0x54, 0x75, 0x88, 0x26, 0x4d, + 0x50, 0xc9, 0x35, 0x18, 0x65, 0x1a, 0x95, 0x23, 0x34, 0x55, 0xb1, 0x81, 0xa3, 0xab, 0x62, 0x19, + 0x4b, 0xf4, 0xe0, 0xf1, 0x2c, 0xd1, 0x94, 0x1a, 0x35, 0x22, 0x5d, 0x64, 0xc8, 0x4c, 0x8d, 0x5a, + 0xa0, 0x8f, 0xe8, 0xe8, 0x2a, 0xbb, 0x2b, 0xfe, 0xd4, 0xb2, 0xff, 0x25, 0xd9, 0x5d, 0x25, 0x7d, + 0x51, 0x76, 0xd7, 0x84, 0x44, 0xe8, 0x35, 0x76, 0xcf, 0x97, 0x4f, 0x19, 0x8e, 0xa4, 0x7a, 0x4d, + 0xd8, 0xf3, 0x9d, 0xec, 0x73, 0x86, 0x09, 0xa2, 0xf5, 0x6b, 0x83, 0xc5, 0x7a, 0x64, 0x7a, 0x29, + 0x75, 0x0c, 0x3d, 0x32, 0x21, 0xfa, 0x6c, 0x66, 0xea, 0x3d, 0x38, 0xa1, 0xe2, 0x99, 0xea, 0x14, + 0xa5, 0x70, 0xcf, 0x5e, 0xa5, 0x21, 0x46, 0xc3, 0x68, 0x12, 0x09, 0x95, 0x84, 0x36, 0xf4, 0x42, + 0xc3, 0x30, 0x5a, 0x40, 0x3f, 0xfb, 0xf7, 0xd4, 0xe6, 0xa8, 0x0f, 0x02, 0x26, 0x25, 0x2a, 0x15, + 0x0d, 0x42, 0xaf, 0x67, 0x0c, 0xa3, 0x49, 0x22, 0x25, 0x75, 0x62, 0xa3, 0xbf, 0x67, 0x1e, 0x2b, + 0x74, 0xbb, 0xbe, 0xc9, 0x25, 0x43, 0xc4, 0xb6, 0xe1, 0x6c, 0x7a, 0xea, 0xd2, 0x0e, 0x95, 0xc8, + 0x51, 0x7e, 0xf0, 0xa5, 0xc7, 0xfb, 0x73, 0x2f, 0x69, 0xa7, 0x36, 0xfd, 0x6c, 0x9a, 0xe1, 0xde, + 0x9f, 0x97, 0x90, 0xb7, 0x0b, 0xa1, 0xeb, 0x37, 0x77, 0xb4, 0x39, 0x8f, 0xf2, 0x76, 0x13, 0xa1, + 0xb9, 0x0c, 0x88, 0x29, 0xb2, 0xf5, 0x3b, 0xe5, 0x62, 0xeb, 0x15, 0xdd, 0x3d, 0x1e, 0xc3, 0x7a, + 0x25, 0x29, 0x0e, 0xdf, 0x25, 0xfe, 0x95, 0xda, 0x25, 0x5e, 0x82, 0x91, 0x75, 0xb1, 0x49, 0x25, + 0x99, 0x45, 0xd1, 0xb3, 0x2b, 0x96, 0x20, 0x5b, 0x95, 0xb1, 0x0f, 0x80, 0xd5, 0xdd, 0x90, 0xfb, + 0xf1, 0x62, 0xd0, 0xe9, 0xba, 0x61, 0xdc, 0xc1, 0xc7, 0x1e, 0xe5, 0xd6, 0x80, 0x5e, 0x3c, 0x5d, + 0x2c, 0x75, 0x9a, 0x5a, 0xb1, 0xae, 0xa5, 0xe4, 0x89, 0xd9, 0x15, 0x18, 0x51, 0x0e, 0x4b, 0x03, + 0x69, 0x72, 0xf6, 0xbc, 0xb3, 0x92, 0xc2, 0x12, 0xbb, 0x92, 0xca, 0xb9, 0xa1, 0x5e, 0x57, 0xa1, + 0xa8, 0x34, 0x02, 0xea, 0xc2, 0x28, 0xc1, 0xb4, 0xfe, 0xf3, 0x61, 0x98, 0xe9, 0x77, 0xaf, 0xc9, + 0xee, 0x9a, 0x5d, 0xfb, 0xe2, 0x21, 0xf7, 0xa0, 0x87, 0x77, 0xec, 0x6f, 0x0d, 0x3d, 0x59, 0x79, + 0x6e, 0x88, 0xe0, 0xf2, 0x27, 0x16, 0xc1, 0x03, 0xc7, 0x13, 0xc1, 0x6f, 0x01, 0xac, 0xf3, 0x4e, + 0xb7, 0xed, 0xc6, 0x3c, 0xb9, 0xcc, 0x93, 0x59, 0xbb, 0x09, 0x9a, 0x89, 0xaf, 0x49, 0x91, 0xd9, + 0x7b, 0x30, 0xae, 0x7e, 0x69, 0x4e, 0x41, 0xf2, 0xb1, 0x39, 0x45, 0x9c, 0xbd, 0x47, 0xd4, 0x09, + 0x84, 0xec, 0xd0, 0x96, 0x56, 0x92, 0xba, 0x44, 0xde, 0xfb, 0x68, 0x2b, 0xd3, 0x94, 0x1d, 0x06, + 0x89, 0xd0, 0x44, 0x34, 0x80, 0x96, 0xba, 0x15, 0x35, 0x11, 0x9d, 0x4b, 0xa6, 0x29, 0x59, 0xb2, + 0xfc, 0x76, 0x52, 0x39, 0xfe, 0x76, 0x62, 0x5a, 0x8b, 0xd4, 0x23, 0xc3, 0x05, 0xd6, 0xa2, 0x4c, + 0x08, 0x80, 0x49, 0x24, 0x9a, 0x22, 0x21, 0xa6, 0x69, 0x10, 0x9b, 0xd2, 0xa2, 0x82, 0x9c, 0x79, + 0xd0, 0x24, 0x91, 0x11, 0x79, 0xbb, 0xaf, 0xaf, 0xbb, 0xdb, 0x94, 0x11, 0x8a, 0x22, 0xf2, 0x76, + 0x5f, 0x77, 0x62, 0x77, 0xdb, 0x8c, 0xc8, 0x43, 0x34, 0xeb, 0x0f, 0x46, 0x8b, 0x2f, 0xcf, 0xb5, + 0x0b, 0x76, 0x66, 0x65, 0x0e, 0x67, 0xe8, 0x8c, 0x4b, 0x27, 0xa9, 0xcf, 0xfc, 0x48, 0x66, 0x6c, + 0x80, 0x03, 0x47, 0xdc, 0x00, 0x3f, 0xf9, 0x55, 0x14, 0x7b, 0x05, 0x86, 0xd6, 0x57, 0x1b, 0x8b, + 0x35, 0x9a, 0xd9, 0x52, 0xf3, 0x6f, 0x47, 0x4e, 0x53, 0x37, 0x3b, 0x4a, 0x14, 0xb6, 0x09, 0x53, + 0x6b, 0xbd, 0x48, 0x3a, 0x54, 0x5d, 0xf7, 0x78, 0xbb, 0x95, 0x3e, 0x00, 0x98, 0x75, 0x12, 0x6e, + 0xe0, 0xc3, 0xe5, 0xa8, 0xa5, 0x4d, 0x77, 0x7a, 0x11, 0x79, 0x6b, 0x39, 0x5b, 0x48, 0xf5, 0xfd, + 0xfd, 0xb9, 0x61, 0x89, 0x60, 0x67, 0x19, 0xb2, 0xfb, 0x50, 0xc1, 0x1e, 0xa9, 0xf9, 0x7b, 0xf8, + 0x02, 0xc5, 0xc1, 0xce, 0x0b, 0xda, 0xb0, 0x49, 0x61, 0x86, 0x8f, 0x6b, 0xcb, 0xde, 0x76, 0x7d, + 0xd1, 0x45, 0xc4, 0x6b, 0xf6, 0x32, 0x8c, 0x2d, 0x06, 0xbe, 0x74, 0x56, 0x5f, 0xfe, 0x38, 0x8d, + 0x0b, 0x2d, 0x15, 0xc7, 0x85, 0xce, 0xbe, 0x01, 0x93, 0x09, 0xfe, 0x9d, 0x20, 0x3e, 0x1a, 0x49, + 0x5a, 0xc5, 0x8a, 0x2f, 0xa6, 0x91, 0x74, 0x66, 0xa3, 0xd3, 0x0a, 0x4e, 0x23, 0x24, 0x88, 0x6c, + 0x2a, 0x99, 0xfd, 0x82, 0x59, 0xcb, 0x11, 0xa9, 0x7e, 0x64, 0x00, 0x8d, 0x83, 0x92, 0x8c, 0x7d, + 0x1e, 0x46, 0x6b, 0x71, 0x1c, 0x7a, 0x9b, 0xbd, 0xc4, 0x7d, 0x5c, 0xf6, 0x82, 0x02, 0xda, 0x69, + 0x39, 0xbb, 0x05, 0xe5, 0xe5, 0x8f, 0xc9, 0xb5, 0x7b, 0xfe, 0x88, 0x1d, 0xab, 0xf5, 0xdb, 0xc2, + 0xf0, 0xe3, 0xfd, 0xb9, 0x32, 0xff, 0xd8, 0x2e, 0x2f, 0x7f, 0xcc, 0x36, 0x60, 0x08, 0x7b, 0x86, + 0xec, 0x3a, 0x6f, 0x1e, 0x97, 0x1d, 0x12, 0xcb, 0x0f, 0xf4, 0x83, 0xd8, 0xe1, 0x1f, 0xdb, 0x92, + 0x9f, 0x68, 0xe4, 0x8a, 0x4a, 0x94, 0x73, 0xec, 0x46, 0xae, 0xf8, 0xb2, 0x91, 0x9e, 0x6f, 0x97, + 0x57, 0x7c, 0x6a, 0xe4, 0x8a, 0x4a, 0x6d, 0xf7, 0x49, 0x1a, 0xb9, 0xe2, 0xa7, 0x8d, 0xf4, 0x7c, + 0x5b, 0xf2, 0x9b, 0xfd, 0x0f, 0x95, 0xce, 0xf1, 0x11, 0xe6, 0xd1, 0x95, 0xd8, 0xca, 0x69, 0xfa, + 0xf5, 0xe3, 0x56, 0x93, 0x18, 0x77, 0x89, 0x4f, 0x26, 0xa9, 0x2e, 0x41, 0xf1, 0x4e, 0xf2, 0x51, + 0x37, 0x94, 0xd1, 0x8b, 0xfa, 0x06, 0xca, 0x13, 0xa8, 0x19, 0x28, 0xaa, 0xa0, 0xd6, 0x3f, 0x19, + 0x82, 0xf3, 0x87, 0xf9, 0x75, 0xb1, 0x2d, 0x80, 0xbb, 0xfe, 0x66, 0xe0, 0x86, 0x2d, 0xcf, 0xdf, + 0xa6, 0x1c, 0x59, 0xd7, 0x8e, 0xe8, 0x14, 0x76, 0x39, 0xa5, 0x14, 0x85, 0x32, 0x24, 0x2b, 0x48, + 0x60, 0xb6, 0xc6, 0x99, 0x7d, 0x0b, 0x2a, 0x36, 0x6f, 0x06, 0xbb, 0x9c, 0xee, 0xb3, 0xc7, 0xe6, + 0xbf, 0x70, 0xd4, 0x5a, 0x14, 0x1d, 0xd6, 0x81, 0x49, 0x99, 0x42, 0x82, 0xd8, 0x09, 0x4f, 0xf6, + 0x21, 0x8c, 0xc9, 0x27, 0xc4, 0x6b, 0x5b, 0x31, 0xdd, 0x79, 0x1f, 0x9e, 0xb8, 0xb6, 0x24, 0xd4, + 0x09, 0xf9, 0x28, 0xb9, 0xe3, 0x6e, 0x19, 0xf9, 0x12, 0x64, 0xe2, 0x5a, 0x8d, 0xe5, 0xec, 0xdf, + 0x2e, 0xc3, 0xa4, 0xf9, 0xc1, 0x6c, 0x15, 0xaa, 0x2b, 0xbe, 0x17, 0x7b, 0x6e, 0xdb, 0x0c, 0x4c, + 0xa4, 0xdb, 0x0f, 0x4f, 0x96, 0x39, 0x85, 0x71, 0x7b, 0x39, 0x4a, 0xa1, 0xa2, 0x0a, 0x4d, 0x31, + 0x8a, 0xa5, 0x6f, 0x99, 0x7c, 0x97, 0x89, 0x46, 0xfc, 0x79, 0xf9, 0x08, 0x59, 0x5a, 0xea, 0xc8, + 0x77, 0xd8, 0xcc, 0x37, 0x67, 0xb2, 0xc4, 0x6c, 0x17, 0x98, 0x90, 0xb6, 0xb2, 0x84, 0x87, 0x0b, + 0x7c, 0x2b, 0x08, 0x8f, 0x92, 0xb1, 0xf6, 0x15, 0xea, 0x9c, 0xe7, 0x50, 0x90, 0x87, 0x44, 0xee, + 0x6c, 0x22, 0x7d, 0xa6, 0x93, 0x0a, 0x6a, 0x98, 0x5d, 0x83, 0x71, 0x7d, 0xd4, 0x30, 0x90, 0xc5, + 0xeb, 0x78, 0x2a, 0xcc, 0x5e, 0x06, 0xb2, 0x08, 0x80, 0x2d, 0xe1, 0xec, 0x59, 0x4a, 0xf1, 0x5e, + 0x4e, 0xe3, 0x3d, 0xd2, 0x54, 0xee, 0xd6, 0x77, 0x4a, 0x70, 0xba, 0x38, 0x08, 0x82, 0x7d, 0x94, + 0x71, 0x6b, 0x2c, 0x1d, 0xe4, 0xf4, 0xa9, 0x22, 0x27, 0x3e, 0x99, 0x63, 0xa3, 0xf5, 0x0f, 0x06, + 0x73, 0x46, 0x9d, 0x02, 0x8e, 0xec, 0x46, 0xe1, 0x38, 0x96, 0xb4, 0x63, 0x78, 0x7e, 0x1c, 0x0b, + 0x47, 0xef, 0x5d, 0x98, 0x44, 0xc6, 0xe9, 0xe4, 0xd2, 0x6e, 0xea, 0x65, 0x93, 0xb5, 0x00, 0xc8, + 0x0c, 0x2e, 0x5b, 0x01, 0x86, 0x90, 0x05, 0x21, 0xbb, 0x92, 0x64, 0x80, 0xda, 0x75, 0x85, 0xe4, + 0xb0, 0x89, 0x22, 0x2e, 0x4d, 0x0b, 0x58, 0x40, 0xc4, 0xbe, 0x04, 0x13, 0x6a, 0x38, 0xe5, 0xbb, + 0x9a, 0x5a, 0x10, 0xa7, 0x5a, 0x8b, 0xf2, 0x69, 0x4d, 0xdb, 0x44, 0x64, 0x1d, 0x99, 0x6c, 0x9b, + 0x80, 0xbc, 0x55, 0x8b, 0x8f, 0x90, 0xd1, 0xfc, 0x65, 0x9a, 0x7d, 0xcf, 0x60, 0x46, 0xf3, 0x50, + 0xd1, 0x3a, 0x6e, 0x9c, 0x99, 0x7a, 0x59, 0xde, 0x6c, 0x1b, 0x26, 0x10, 0x84, 0xcb, 0x56, 0x54, + 0x36, 0x7c, 0x68, 0x65, 0x2f, 0x51, 0x65, 0x67, 0x65, 0x65, 0x92, 0x32, 0x5f, 0x95, 0xc9, 0x97, + 0x5d, 0x85, 0x31, 0xec, 0x27, 0x4a, 0x04, 0xa7, 0x39, 0xa2, 0xc8, 0x5e, 0x55, 0xe9, 0xe0, 0x74, + 0x2c, 0xeb, 0xc7, 0xcb, 0x30, 0x29, 0xf5, 0x30, 0x19, 0x3e, 0xf3, 0xd4, 0xc6, 0x39, 0xbd, 0x63, + 0xc4, 0x39, 0xa9, 0xf7, 0x13, 0xf5, 0x4f, 0x3b, 0x52, 0x54, 0xea, 0x0e, 0xb0, 0x3c, 0x0d, 0xb3, + 0x61, 0x5c, 0x87, 0x1e, 0x1c, 0x93, 0xf4, 0x46, 0xfa, 0xd4, 0x26, 0x29, 0xbe, 0x18, 0x65, 0x16, + 0xd9, 0x06, 0x0f, 0xeb, 0xc7, 0xca, 0x30, 0xa1, 0x45, 0xa5, 0x3e, 0xb5, 0x1d, 0xff, 0xb6, 0xd1, + 0xf1, 0x33, 0x49, 0xea, 0xd9, 0xe4, 0xcb, 0x8e, 0xd4, 0xef, 0x3d, 0x98, 0xce, 0x91, 0x64, 0x83, + 0x7b, 0x4b, 0x47, 0x09, 0xee, 0x7d, 0x35, 0xff, 0x72, 0x1e, 0xea, 0x00, 0xe9, 0xb3, 0x47, 0xfa, + 0x53, 0x7d, 0x3f, 0x51, 0x86, 0x93, 0xf4, 0x0b, 0xdf, 0xeb, 0x95, 0x17, 0x63, 0x4f, 0xed, 0x58, + 0xd4, 0x8c, 0xb1, 0x98, 0x33, 0xc7, 0x42, 0xfb, 0xc0, 0xfe, 0x43, 0x62, 0x7d, 0x07, 0x60, 0xa6, + 0x1f, 0xc1, 0x91, 0x33, 0xd5, 0xa7, 0xb9, 0x5b, 0xcb, 0x47, 0xc8, 0xdd, 0xba, 0x0a, 0x55, 0xac, + 0x8a, 0xd2, 0x61, 0x44, 0xf7, 0xec, 0x15, 0xea, 0x24, 0x54, 0x59, 0xe4, 0xf3, 0xca, 0x94, 0x43, + 0x23, 0xca, 0x38, 0x91, 0xe4, 0x28, 0xd9, 0xbf, 0x5f, 0x82, 0x49, 0x04, 0x2e, 0xef, 0x72, 0x3f, + 0x46, 0x66, 0x83, 0x94, 0xd4, 0x33, 0x89, 0xf1, 0x69, 0xc4, 0xa1, 0xe7, 0x6f, 0x53, 0x90, 0xcf, + 0x26, 0x05, 0xf9, 0xbc, 0x2b, 0x83, 0x93, 0x2e, 0x37, 0x83, 0xce, 0x95, 0xed, 0xd0, 0xdd, 0xf5, + 0x62, 0x0a, 0x4e, 0xbb, 0xa2, 0xb2, 0x46, 0x5e, 0x71, 0xbb, 0xde, 0x15, 0xec, 0xd3, 0x2b, 0x19, + 0x56, 0x18, 0x40, 0x25, 0x1b, 0xca, 0xb1, 0xda, 0xac, 0xaf, 0x8b, 0xd9, 0x22, 0xf6, 0x43, 0x70, + 0x46, 0x7a, 0x0d, 0x2c, 0x06, 0x7e, 0xec, 0xf9, 0xbd, 0xa0, 0x17, 0x2d, 0xb8, 0xcd, 0x07, 0xbd, + 0x6e, 0x44, 0x29, 0xab, 0xf1, 0xcb, 0x9b, 0x49, 0xa1, 0xb3, 0x29, 0x4b, 0x8d, 0xc7, 0x24, 0x8a, + 0x19, 0xb0, 0x9b, 0x30, 0x2d, 0x8b, 0x6a, 0xbd, 0x38, 0x68, 0x34, 0xdd, 0xb6, 0xd0, 0xa2, 0x47, + 0x90, 0xab, 0x8c, 0x64, 0xe8, 0xc5, 0x81, 0x13, 0x49, 0xb8, 0xee, 0xfa, 0x92, 0x23, 0x62, 0x2b, + 0x30, 0x65, 0x73, 0xb7, 0xb5, 0xe6, 0x3e, 0x5a, 0x74, 0xbb, 0x6e, 0xd3, 0x8b, 0xe5, 0x8b, 0xb7, + 0x03, 0xf2, 0xda, 0x23, 0xe4, 0x6e, 0xcb, 0xe9, 0xb8, 0x8f, 0x9c, 0x26, 0x15, 0x9a, 0x9e, 0x9b, + 0x06, 0x5d, 0xc2, 0xca, 0xf3, 0x13, 0x56, 0xa3, 0x59, 0x56, 0x9e, 0xdf, 0x9f, 0x55, 0x4a, 0xa7, + 0x58, 0xad, 0xbb, 0xe1, 0x36, 0x8f, 0xe5, 0x59, 0x18, 0xce, 0x97, 0x2e, 0x96, 0x34, 0x56, 0x31, + 0x96, 0x39, 0x78, 0x60, 0xcd, 0xb2, 0xd2, 0xe8, 0xc4, 0xcc, 0xdb, 0x08, 0xbd, 0x98, 0xeb, 0x5f, + 0x38, 0x86, 0xcd, 0xc2, 0xfe, 0xc7, 0x84, 0x54, 0xfd, 0x3e, 0x31, 0x47, 0x99, 0x72, 0xd3, 0x3e, + 0x72, 0x3c, 0xc7, 0xad, 0xf8, 0x2b, 0x73, 0x94, 0x09, 0x37, 0xfd, 0x3b, 0x27, 0xf0, 0x3b, 0x35, + 0x6e, 0x7d, 0x3e, 0x34, 0x47, 0xc9, 0xee, 0x88, 0x4e, 0x8b, 0xb9, 0x2f, 0x66, 0x34, 0xe5, 0x1a, + 0x9a, 0xc4, 0xa6, 0xbd, 0x48, 0xba, 0x46, 0x35, 0x54, 0xc5, 0x4e, 0x41, 0xe6, 0xa1, 0x2c, 0x31, + 0xfb, 0x61, 0x98, 0xba, 0x17, 0xf1, 0xeb, 0x2b, 0xf5, 0x86, 0x7a, 0x62, 0x0c, 0xbd, 0xb5, 0x26, + 0xe7, 0xdf, 0x38, 0x44, 0xe8, 0x5c, 0xd6, 0x69, 0xc4, 0x86, 0xc8, 0xe5, 0xb8, 0xf5, 0x22, 0xee, + 0x6c, 0x79, 0xdd, 0x28, 0x79, 0x5e, 0x53, 0x1f, 0xb7, 0x4c, 0x55, 0xd6, 0x4d, 0x98, 0xce, 0xb1, + 0x61, 0x93, 0x00, 0x02, 0xe8, 0xdc, 0xbb, 0xd3, 0x58, 0x5e, 0xaf, 0x7e, 0x8e, 0x55, 0x61, 0x1c, + 0x7f, 0x2f, 0xdf, 0xa9, 0x2d, 0xac, 0x2e, 0x2f, 0x55, 0x4b, 0x6c, 0x1a, 0x26, 0x10, 0xb2, 0xb4, + 0xd2, 0x90, 0xa0, 0xf2, 0xad, 0xc1, 0xca, 0x50, 0x75, 0xd8, 0xae, 0xca, 0xa5, 0x2b, 0x53, 0x80, + 0x8b, 0x3d, 0xc5, 0xfa, 0x6b, 0x65, 0x38, 0xab, 0xb6, 0x15, 0x1e, 0x3f, 0x0c, 0xc2, 0x07, 0x9e, + 0xbf, 0xfd, 0x94, 0xef, 0x0e, 0xd7, 0x8d, 0xdd, 0xe1, 0xc5, 0xcc, 0x4e, 0x9d, 0xf9, 0xca, 0x03, + 0xb6, 0x88, 0xdf, 0x18, 0x85, 0x73, 0x07, 0x52, 0xb1, 0x0f, 0xc4, 0x6e, 0xee, 0x71, 0x3f, 0x5e, + 0x69, 0xb5, 0xb9, 0xd0, 0x6b, 0x83, 0x5e, 0x4c, 0xb9, 0xad, 0x5e, 0x40, 0x67, 0x26, 0x99, 0x2a, + 0xcb, 0x6b, 0xb5, 0xb9, 0x13, 0xcb, 0x62, 0x63, 0xba, 0xe5, 0xa9, 0x05, 0xcb, 0xdb, 0x9c, 0x77, + 0x6b, 0x6d, 0x6f, 0x97, 0xaf, 0xf8, 0x31, 0x0f, 0x77, 0x31, 0x07, 0x41, 0xc2, 0xf2, 0x01, 0xe7, + 0x5d, 0xc7, 0x15, 0xa5, 0x8e, 0x47, 0xc5, 0x26, 0xcb, 0x1c, 0x35, 0xbb, 0xae, 0xb1, 0xc4, 0xa3, + 0xc1, 0x9a, 0xfb, 0x88, 0x22, 0x7e, 0xe9, 0x85, 0xe1, 0x84, 0xa5, 0xf4, 0xe6, 0xe8, 0xb8, 0x8f, + 0xec, 0x3c, 0x09, 0xfb, 0x26, 0x9c, 0xa2, 0x0d, 0x88, 0x5e, 0x37, 0x51, 0x5f, 0x2c, 0xdf, 0x4e, + 0x79, 0x59, 0x9c, 0xe6, 0x54, 0x0a, 0x28, 0xf5, 0x62, 0x51, 0xd1, 0x57, 0x17, 0x73, 0x61, 0xeb, + 0x62, 0x43, 0xce, 0x74, 0xc7, 0x1a, 0x8f, 0x22, 0x95, 0x09, 0x96, 0x6c, 0x2f, 0x7a, 0x67, 0x3a, + 0x1d, 0x59, 0x6e, 0xf7, 0xa5, 0x64, 0x37, 0x61, 0x72, 0x83, 0x6f, 0xea, 0xe3, 0x33, 0x9c, 0x88, + 0xaa, 0xea, 0x43, 0xbe, 0xd9, 0x7f, 0x70, 0x32, 0x74, 0xcc, 0x43, 0x87, 0xcb, 0x47, 0x7b, 0xab, + 0xe2, 0xb4, 0xed, 0xf3, 0x10, 0x0f, 0xcd, 0x23, 0x28, 0x0c, 0x66, 0x52, 0x0d, 0xd9, 0x2c, 0xa7, + 0xfb, 0x2d, 0x4c, 0xbe, 0xd8, 0x26, 0x38, 0x26, 0x4d, 0xcb, 0xf8, 0x62, 0x9a, 0x54, 0xec, 0x43, + 0x98, 0xb2, 0x83, 0x5e, 0xec, 0xf9, 0xdb, 0x0d, 0x71, 0x2c, 0xe5, 0xdb, 0x72, 0x43, 0x4a, 0x1f, + 0x60, 0xcb, 0x94, 0x52, 0x84, 0x81, 0x04, 0x3a, 0x11, 0x41, 0x8d, 0x1d, 0xc1, 0x24, 0x60, 0xdf, + 0x82, 0x49, 0xf9, 0x1e, 0x44, 0x52, 0xc1, 0x28, 0xd9, 0x96, 0x29, 0x86, 0xdc, 0x28, 0xbc, 0xff, + 0x06, 0x05, 0x68, 0x22, 0xb4, 0xa8, 0x82, 0x0c, 0x37, 0xf6, 0x75, 0xea, 0xac, 0xba, 0xe7, 0x6f, + 0x27, 0xd3, 0x18, 0xb0, 0xe7, 0x5f, 0x4b, 0xbb, 0xa4, 0x2b, 0x9a, 0xab, 0xa6, 0x71, 0x9f, 0x68, + 0xf3, 0x3c, 0x1f, 0x16, 0xc3, 0xb9, 0x5a, 0x14, 0x79, 0x51, 0x4c, 0x19, 0xe0, 0x96, 0x1f, 0xf1, + 0x66, 0x4f, 0x20, 0x6f, 0x04, 0xe1, 0x03, 0x1e, 0xca, 0x2c, 0x2e, 0x43, 0x0b, 0x97, 0x1f, 0xef, + 0xcf, 0xbd, 0xe2, 0x22, 0xa2, 0x43, 0x49, 0xe3, 0x1c, 0xae, 0x50, 0x9d, 0x87, 0x12, 0x57, 0xfb, + 0x86, 0x83, 0x99, 0xb2, 0x6f, 0xc1, 0xe9, 0x45, 0x37, 0xe2, 0x2b, 0x7e, 0xc4, 0xfd, 0xc8, 0x8b, + 0xbd, 0x5d, 0x4e, 0x9d, 0x8a, 0x9b, 0x5f, 0x45, 0x66, 0x05, 0x68, 0xba, 0x91, 0x58, 0x98, 0x09, + 0x8a, 0x43, 0x83, 0xa2, 0x67, 0x05, 0x28, 0xe6, 0xc2, 0x6c, 0x98, 0x6c, 0x34, 0x6e, 0x2e, 0x79, + 0x6e, 0xb2, 0xae, 0x26, 0xb0, 0xbf, 0x5e, 0x41, 0x07, 0x98, 0x68, 0xc7, 0x69, 0x79, 0x6e, 0xb2, + 0xa0, 0xfa, 0x74, 0x56, 0x86, 0x83, 0xb5, 0x5f, 0x82, 0x6a, 0x76, 0x28, 0xd9, 0x57, 0x61, 0x54, + 0x86, 0x6a, 0xf3, 0x68, 0x87, 0x8c, 0x36, 0x2a, 0xf2, 0x37, 0x81, 0x9b, 0x44, 0x94, 0x43, 0x58, + 0x06, 0x82, 0x73, 0x3d, 0x68, 0xea, 0xe6, 0xe7, 0xec, 0x94, 0x19, 0x6b, 0xc1, 0xb8, 0x1c, 0x2d, + 0x8e, 0x8f, 0x47, 0x66, 0xf2, 0xe2, 0xe9, 0x45, 0x19, 0xfe, 0x32, 0x54, 0x50, 0xce, 0x09, 0x89, + 0x60, 0x54, 0x61, 0x70, 0x5d, 0x00, 0xa8, 0x28, 0x42, 0xeb, 0x2c, 0x9c, 0xe9, 0xd3, 0x66, 0xeb, + 0xd7, 0x4a, 0x78, 0x31, 0xde, 0xa7, 0x4a, 0xf6, 0x55, 0x38, 0x89, 0x94, 0x8b, 0x81, 0xef, 0xf3, + 0x66, 0x8c, 0xf2, 0x48, 0x79, 0xe2, 0x0d, 0xc8, 0x88, 0x27, 0xf9, 0xc1, 0xcd, 0x04, 0xc1, 0xc9, + 0x3a, 0xe4, 0x15, 0x72, 0x60, 0xef, 0xc3, 0xb3, 0x2d, 0x2f, 0x22, 0x32, 0x27, 0xde, 0x09, 0x79, + 0xb4, 0x13, 0xb4, 0x5b, 0x4e, 0xc4, 0x9b, 0x81, 0xdf, 0x92, 0xa9, 0x5e, 0x26, 0xec, 0xd9, 0x14, + 0x67, 0x5d, 0xa1, 0x34, 0x24, 0x86, 0xf5, 0xaf, 0xcb, 0x30, 0x43, 0x42, 0xd2, 0xe6, 0xcd, 0x00, + 0xad, 0x9e, 0x4f, 0xf9, 0xa6, 0xbc, 0x6c, 0x6c, 0xca, 0x2f, 0x24, 0x2f, 0x8e, 0x14, 0x7d, 0xe4, + 0x01, 0x79, 0x5a, 0xee, 0x65, 0xf2, 0xb4, 0x1c, 0xc2, 0x08, 0x51, 0x17, 0x66, 0x94, 0xee, 0xd7, + 0x2f, 0x63, 0x8b, 0xb5, 0x02, 0x95, 0xdb, 0x7c, 0x0f, 0x93, 0x16, 0x88, 0xfe, 0x8d, 0xd3, 0xc3, + 0x5f, 0x45, 0x25, 0xe6, 0xb2, 0xf1, 0x5f, 0x36, 0x07, 0x43, 0x98, 0x02, 0x41, 0x4f, 0x71, 0x8a, + 0x00, 0x5b, 0xfe, 0x67, 0xfd, 0xef, 0x25, 0x38, 0xbb, 0xe6, 0xfa, 0x3d, 0xb7, 0x7d, 0x9b, 0xef, + 0xad, 0xb9, 0xbe, 0xbb, 0x8d, 0xef, 0xcb, 0x91, 0x2b, 0xea, 0x15, 0x18, 0xa1, 0x87, 0xab, 0x91, + 0x7f, 0x45, 0x3a, 0x2e, 0xe4, 0xdf, 0xb2, 0x56, 0x58, 0xec, 0x36, 0x8c, 0x69, 0x39, 0xa7, 0x28, + 0xd3, 0x87, 0xea, 0x79, 0xd5, 0x66, 0xca, 0xd7, 0x52, 0x9c, 0x9b, 0xca, 0x4d, 0x73, 0x53, 0xdd, + 0x81, 0x71, 0x65, 0x65, 0x43, 0x6e, 0x03, 0xc5, 0xdc, 0x66, 0x53, 0xdb, 0x7c, 0x2e, 0x07, 0xd5, + 0x18, 0xc1, 0x31, 0xf3, 0xd4, 0x6f, 0x95, 0xe0, 0x7c, 0xb6, 0xe7, 0xd3, 0xf4, 0x71, 0x9f, 0xf4, + 0x93, 0xf7, 0xe0, 0x54, 0x07, 0x3b, 0x10, 0x73, 0xdb, 0x75, 0x92, 0x2e, 0x24, 0x71, 0xa2, 0x72, + 0x3c, 0xf4, 0xed, 0x64, 0xe9, 0x84, 0x54, 0xc8, 0x42, 0x77, 0x42, 0xea, 0xe4, 0xe9, 0xad, 0x9f, + 0x2d, 0xc3, 0xb3, 0x07, 0xcd, 0xc9, 0xc4, 0x58, 0x5e, 0x2a, 0x32, 0x96, 0xb3, 0x2e, 0x9c, 0x40, + 0x89, 0xb3, 0xb8, 0xc3, 0x9b, 0x0f, 0x30, 0xed, 0xef, 0x6d, 0x39, 0x68, 0xc5, 0xe1, 0x2d, 0xaf, + 0x16, 0x86, 0xb7, 0x9c, 0x96, 0x72, 0xb0, 0x89, 0x3c, 0xa4, 0x35, 0x53, 0x8c, 0x81, 0x5d, 0xc4, + 0x9a, 0x71, 0x80, 0xf4, 0xad, 0x79, 0xba, 0x5d, 0x78, 0xb9, 0xcf, 0x9a, 0xc8, 0x8e, 0x0c, 0x3e, + 0x5a, 0x55, 0x92, 0x31, 0x76, 0xaa, 0x44, 0x9f, 0x38, 0x29, 0xd4, 0xea, 0xf5, 0xed, 0x16, 0x5c, + 0x3f, 0xec, 0x1e, 0x4c, 0x69, 0x4f, 0xde, 0xe3, 0xdc, 0x32, 0x6d, 0x87, 0xd9, 0xbc, 0x82, 0xd2, + 0x0f, 0x26, 0x43, 0x63, 0x4f, 0x72, 0x1d, 0x07, 0x1f, 0x95, 0xa8, 0xd6, 0x7a, 0xf1, 0x4e, 0x3d, + 0xe4, 0x94, 0x3c, 0xfa, 0x07, 0x2c, 0x3f, 0x95, 0xf9, 0x71, 0x47, 0xb2, 0x20, 0xfe, 0xe6, 0x14, + 0x9c, 0x2c, 0x22, 0x13, 0xfd, 0xb2, 0x5e, 0x28, 0xb7, 0xd0, 0x54, 0xf5, 0xdd, 0x12, 0x8c, 0xcb, + 0xed, 0xe5, 0x3a, 0x06, 0x76, 0x52, 0xef, 0xb8, 0x52, 0x69, 0x17, 0x70, 0x67, 0x2b, 0x13, 0xf1, + 0xf9, 0xfd, 0xfd, 0xb9, 0xf7, 0x8f, 0x66, 0x2b, 0x6a, 0x06, 0x78, 0x61, 0x11, 0x8b, 0x39, 0x97, + 0x56, 0x21, 0x5b, 0x63, 0x1b, 0xd5, 0xb2, 0x05, 0x98, 0xa0, 0xfd, 0x32, 0xd0, 0x9f, 0xfb, 0x95, + 0x8f, 0x76, 0xa9, 0x82, 0x9c, 0x4f, 0x8c, 0x41, 0xc2, 0xae, 0xc2, 0xc0, 0xbd, 0xf9, 0xeb, 0x34, + 0x0a, 0x2a, 0xd1, 0xf8, 0xbd, 0xf9, 0xeb, 0x68, 0x90, 0x16, 0x13, 0x7a, 0xa2, 0x37, 0x6f, 0xc4, + 0x2d, 0xde, 0x9b, 0xbf, 0xce, 0xfe, 0x2c, 0x9c, 0x5a, 0x4a, 0x76, 0x5c, 0x99, 0x2b, 0xad, 0x85, + 0x39, 0x59, 0x87, 0xfb, 0xac, 0xce, 0x2f, 0x16, 0xae, 0xce, 0xe7, 0xb5, 0xad, 0x5d, 0x26, 0x62, + 0x6b, 0x65, 0x9f, 0x35, 0x2e, 0xae, 0x87, 0x7d, 0x04, 0x93, 0xe8, 0x1f, 0x81, 0xe9, 0xe3, 0xc4, + 0x08, 0x52, 0x4e, 0xa1, 0x7c, 0xcd, 0xaf, 0x17, 0xd6, 0x3c, 0x2b, 0x1d, 0x2d, 0x30, 0x09, 0x9d, + 0xe3, 0xf6, 0x8c, 0x68, 0xc7, 0x0c, 0x67, 0x76, 0x0b, 0xa6, 0xe8, 0x00, 0x74, 0x77, 0x6b, 0x7d, + 0x87, 0x2f, 0xb9, 0x7b, 0xe4, 0x7b, 0x84, 0x36, 0x15, 0x3a, 0x35, 0x39, 0xc1, 0x96, 0x13, 0xef, + 0x70, 0xa7, 0xe5, 0x1a, 0x47, 0x85, 0x0c, 0x21, 0xfb, 0x36, 0x8c, 0xad, 0x06, 0x4d, 0x71, 0xf6, + 0x45, 0xc9, 0x27, 0xdd, 0x8f, 0xbe, 0x26, 0x04, 0x55, 0x5b, 0x82, 0x33, 0x07, 0x9a, 0xef, 0xef, + 0xcf, 0xbd, 0x73, 0xdc, 0x69, 0xa3, 0x55, 0x60, 0xeb, 0xb5, 0xb1, 0x45, 0xa8, 0x6c, 0xf0, 0x4d, + 0xf1, 0xb5, 0x3e, 0xa5, 0x0f, 0x52, 0xab, 0x4e, 0x81, 0x29, 0x34, 0x9a, 0x7e, 0x19, 0xa1, 0xd1, + 0x04, 0x63, 0x21, 0x4c, 0x63, 0xff, 0xd4, 0xdd, 0x28, 0x7a, 0x18, 0x84, 0xad, 0x36, 0x8f, 0xa2, + 0xbe, 0x01, 0x8e, 0xf3, 0x85, 0x9d, 0xff, 0xac, 0xec, 0xfc, 0xae, 0xc6, 0xc1, 0x48, 0x34, 0x96, + 0x65, 0xcf, 0x3e, 0x84, 0x49, 0x7a, 0x93, 0x60, 0xed, 0x7a, 0x0d, 0x57, 0xc2, 0xb8, 0x91, 0xd2, + 0xd5, 0x2c, 0x54, 0x97, 0xcc, 0x08, 0x4b, 0xf2, 0x28, 0x77, 0xb6, 0x5c, 0xd3, 0x39, 0x55, 0x27, + 0x61, 0x75, 0x18, 0x5b, 0xe2, 0xbb, 0x5e, 0x93, 0x63, 0x2e, 0x48, 0x4a, 0x32, 0xa4, 0xf2, 0x76, + 0x6a, 0x25, 0x72, 0x3b, 0x6f, 0x21, 0x40, 0x66, 0x96, 0x34, 0x93, 0x26, 0x24, 0x88, 0xec, 0x1a, + 0x0c, 0xac, 0x2c, 0xd5, 0x29, 0xc7, 0xd0, 0x74, 0xf2, 0xf2, 0x47, 0x9d, 0x1e, 0xc1, 0x96, 0x97, + 0x6c, 0x5e, 0xcb, 0xc8, 0x50, 0xb4, 0xb2, 0x54, 0x67, 0x5b, 0x30, 0x21, 0xfd, 0xaa, 0xb8, 0x2b, + 0xfb, 0x76, 0xaa, 0x4f, 0xdf, 0x5e, 0x2e, 0xec, 0xdb, 0x19, 0xf2, 0xd7, 0x22, 0x6a, 0x7d, 0xdd, + 0x1b, 0x6c, 0xc5, 0xa1, 0x72, 0x89, 0x6f, 0xb9, 0xbd, 0xb6, 0x32, 0x84, 0xaf, 0xaf, 0xaf, 0x62, + 0xc8, 0x23, 0x1d, 0x2a, 0x5b, 0xb2, 0x30, 0xe9, 0xbf, 0xfe, 0x29, 0xcc, 0xf2, 0x7c, 0xd8, 0xdb, + 0x30, 0x78, 0xf7, 0x41, 0xec, 0x52, 0x36, 0x21, 0xd5, 0x8f, 0x02, 0xa4, 0x3e, 0x5f, 0xc6, 0x2a, + 0x3d, 0x30, 0x9e, 0x13, 0x44, 0x1a, 0x31, 0x14, 0x37, 0xdd, 0xb0, 0xf5, 0xd0, 0x0d, 0x31, 0xcf, + 0xf1, 0x09, 0x83, 0x85, 0x56, 0x22, 0x87, 0x62, 0x87, 0x00, 0x19, 0xaf, 0x04, 0x9d, 0x05, 0xfb, + 0x21, 0x38, 0x1b, 0x79, 0xdb, 0x3e, 0xbe, 0xc5, 0xe4, 0xb8, 0xed, 0xed, 0x20, 0xf4, 0xe2, 0x9d, + 0x8e, 0x13, 0xf5, 0xbc, 0x98, 0x63, 0x5a, 0x9f, 0xc9, 0xe4, 0xcc, 0xd6, 0x50, 0x78, 0x35, 0x85, + 0xd6, 0x10, 0x58, 0xf6, 0x99, 0xa8, 0xb8, 0x80, 0x7d, 0x1d, 0x26, 0x74, 0x91, 0x1c, 0xcd, 0x9c, + 0x3a, 0x3f, 0x70, 0x71, 0x32, 0xd9, 0xaa, 0xb3, 0x22, 0x5c, 0x3d, 0x72, 0xae, 0xed, 0x11, 0x91, + 0xf9, 0xc8, 0xb9, 0xc6, 0x8b, 0xd9, 0x70, 0x26, 0x92, 0xf6, 0xc5, 0x9e, 0xef, 0x3d, 0xc2, 0xb7, + 0x10, 0x28, 0x34, 0x16, 0xd3, 0xfb, 0xa4, 0x5b, 0x5f, 0x03, 0xb1, 0xee, 0xdd, 0x59, 0xf9, 0xea, + 0xbd, 0x88, 0x87, 0x14, 0x21, 0x7b, 0x52, 0xd2, 0xde, 0xf3, 0xbd, 0x47, 0x29, 0x94, 0x7d, 0xa7, + 0x04, 0xa7, 0x71, 0x26, 0x2c, 0xae, 0xae, 0x08, 0xf1, 0x76, 0xdf, 0x73, 0x17, 0xc2, 0xe0, 0x61, + 0xc4, 0xc3, 0x99, 0x99, 0x3e, 0x33, 0xed, 0xed, 0xc2, 0x99, 0xf6, 0x22, 0x3d, 0x45, 0xd4, 0xf6, + 0x50, 0x80, 0x3a, 0xbb, 0x9e, 0xeb, 0x6c, 0x4a, 0x4e, 0x46, 0xda, 0xbe, 0xc2, 0xba, 0xa4, 0x0d, + 0xf5, 0xd6, 0x60, 0x85, 0x55, 0x4f, 0xdc, 0x1a, 0xac, 0x9c, 0xa9, 0xce, 0xd8, 0xd3, 0xb4, 0x24, + 0x69, 0x1a, 0xad, 0x5d, 0xaf, 0xd9, 0x23, 0xf5, 0x95, 0xfb, 0x8d, 0x76, 0x10, 0xdb, 0xb3, 0xc8, + 0x85, 0x68, 0x05, 0x27, 0xee, 0xc7, 0x9e, 0x8c, 0x76, 0xb1, 0x76, 0xe0, 0x64, 0xd1, 0xe7, 0xb3, + 0x99, 0x8c, 0xaa, 0x9c, 0xea, 0xc4, 0xcf, 0xc0, 0xe8, 0x96, 0x17, 0x46, 0xb1, 0xd3, 0xf3, 0xa4, + 0x62, 0x33, 0x64, 0x57, 0x10, 0x70, 0xcf, 0x6b, 0xb1, 0xb3, 0x50, 0xc1, 0x1b, 0xf4, 0x1e, 0xe5, + 0xf5, 0x1f, 0xb2, 0x47, 0xc4, 0xef, 0x7b, 0x5e, 0xcb, 0xfa, 0xcf, 0x4a, 0xb8, 0x57, 0xb2, 0x57, + 0xf0, 0x75, 0xae, 0xc4, 0x91, 0x18, 0x2f, 0xaa, 0xdc, 0x6e, 0x37, 0xf3, 0x6e, 0x20, 0xa2, 0xb0, + 0x57, 0x61, 0xf8, 0xba, 0xdb, 0xe4, 0x89, 0x8f, 0x36, 0x22, 0x6f, 0x21, 0x44, 0x3f, 0x3a, 0x49, + 0x1c, 0x71, 0x8e, 0x96, 0x32, 0xa4, 0x16, 0xc7, 0x3c, 0x92, 0x82, 0x7e, 0xb1, 0xa6, 0xfc, 0xb2, + 0xf1, 0x1c, 0x4d, 0xb2, 0xc7, 0x4d, 0x11, 0x32, 0x59, 0x60, 0x0a, 0x39, 0x58, 0xff, 0xa6, 0x94, + 0x0a, 0x7f, 0xf6, 0x32, 0x0c, 0xda, 0xf5, 0xa4, 0xfd, 0x32, 0x97, 0x71, 0xa6, 0xf9, 0x88, 0xc0, + 0xbe, 0x0e, 0xa7, 0x34, 0x3e, 0xb9, 0x94, 0x34, 0x2f, 0x61, 0xaa, 0x5d, 0xad, 0x25, 0xc5, 0x79, + 0x69, 0x8a, 0x79, 0xa0, 0xd1, 0x20, 0x2d, 0x58, 0xe2, 0xbe, 0x27, 0x79, 0x6b, 0x1f, 0xab, 0xf3, + 0x6e, 0x21, 0x42, 0xf6, 0x63, 0x8b, 0x38, 0xc8, 0x4c, 0xbb, 0xd6, 0xaf, 0x97, 0x0c, 0xa1, 0xce, + 0x2e, 0x18, 0xc7, 0x0d, 0x14, 0x40, 0x19, 0xeb, 0xa1, 0x3c, 0x78, 0xbc, 0x05, 0x50, 0xeb, 0xc5, + 0xc1, 0xb2, 0x1f, 0x06, 0xed, 0x36, 0xf9, 0xc4, 0xca, 0x33, 0x61, 0x2f, 0x0e, 0x1c, 0x8e, 0x60, + 0x23, 0x87, 0x67, 0x82, 0x5c, 0x98, 0xbd, 0x67, 0xe0, 0x93, 0x66, 0xef, 0x11, 0x07, 0x28, 0x43, + 0x8e, 0xbd, 0x0e, 0x6a, 0x41, 0xe8, 0x71, 0xc1, 0x5d, 0x6f, 0xd7, 0x89, 0xda, 0x81, 0xf1, 0x8a, + 0x07, 0xa1, 0xb1, 0xbf, 0x54, 0x82, 0xd3, 0x32, 0x0d, 0xce, 0x9d, 0x5e, 0x67, 0x93, 0x87, 0xf7, + 0xdd, 0xb6, 0xd7, 0x4a, 0x1f, 0x6c, 0x4c, 0x03, 0xcf, 0xb5, 0x6a, 0x8a, 0xf1, 0xa5, 0x45, 0x4e, + 0xa6, 0xe5, 0x71, 0x7c, 0x2c, 0x74, 0x76, 0x93, 0x52, 0x7d, 0xc1, 0x17, 0xd3, 0xb3, 0x15, 0x18, + 0xab, 0x7b, 0xfe, 0xa2, 0xdb, 0xdc, 0xe1, 0x69, 0x92, 0xcc, 0x97, 0x65, 0x56, 0x2b, 0x31, 0x85, + 0x9b, 0x3b, 0xfc, 0x80, 0x3d, 0x46, 0xa7, 0xb5, 0x7e, 0xa5, 0x04, 0xcf, 0x1f, 0xda, 0x60, 0x71, + 0x54, 0x5e, 0x3e, 0xd2, 0x51, 0x99, 0xb0, 0xd8, 0x37, 0xe0, 0x94, 0xce, 0x6a, 0x3d, 0x74, 0x3d, + 0x3d, 0x08, 0xb4, 0xa0, 0x03, 0x62, 0x81, 0x92, 0xd5, 0xaf, 0x8b, 0x99, 0x58, 0xff, 0x77, 0x09, + 0x46, 0x93, 0xb0, 0xcd, 0xa7, 0xf4, 0xdc, 0x75, 0xcd, 0x38, 0x77, 0xa9, 0x17, 0x83, 0x93, 0xaf, + 0x92, 0x8e, 0x8d, 0x05, 0x97, 0x3f, 0x53, 0x5a, 0xd2, 0x12, 0x04, 0xfc, 0xc5, 0x32, 0x8c, 0x09, + 0x51, 0x2d, 0x9d, 0x5f, 0x7e, 0xb0, 0xde, 0xff, 0x4c, 0xbe, 0xab, 0xff, 0x63, 0x87, 0xda, 0x09, + 0xf4, 0x7f, 0x2e, 0xe1, 0xa5, 0xa8, 0x4e, 0x21, 0x7a, 0x43, 0x80, 0xf4, 0xde, 0x10, 0x5b, 0xbf, + 0x8d, 0x50, 0xf9, 0xc8, 0xdb, 0x2a, 0xf5, 0x04, 0x3d, 0xf2, 0xd6, 0xb6, 0x05, 0x8c, 0xbd, 0x0f, + 0x43, 0xf7, 0xf0, 0x8a, 0xc7, 0x7c, 0xf1, 0x20, 0xe1, 0x8f, 0x85, 0x52, 0xde, 0xf7, 0x22, 0xf3, + 0x05, 0x40, 0x49, 0xc8, 0x1a, 0x30, 0xb2, 0x18, 0x72, 0x37, 0xe6, 0x2d, 0xea, 0x90, 0x23, 0x25, + 0xd1, 0x6e, 0x4a, 0x92, 0x6c, 0x12, 0x6d, 0xe2, 0x24, 0xe4, 0x18, 0x4b, 0xbf, 0x11, 0x7d, 0x02, + 0xa3, 0xa7, 0x76, 0xd0, 0xdf, 0x33, 0x06, 0xfd, 0x5c, 0x6e, 0xd0, 0xe5, 0xe7, 0x1d, 0x69, 0xec, + 0x7f, 0xb3, 0x04, 0xa7, 0x8b, 0x09, 0xd9, 0x0b, 0x30, 0x7c, 0x77, 0xbd, 0x9e, 0xfa, 0xe1, 0xe2, + 0xa7, 0x04, 0x5d, 0xb4, 0x5e, 0xd9, 0x54, 0xc4, 0x5e, 0x83, 0xe1, 0x0f, 0xec, 0xc5, 0xd4, 0xdd, + 0x14, 0x05, 0xdc, 0xc7, 0x42, 0x45, 0x34, 0x76, 0x35, 0x42, 0xd2, 0xc7, 0x76, 0xe0, 0x89, 0x8d, + 0xed, 0x4f, 0x94, 0x61, 0xaa, 0xd6, 0x6c, 0xf2, 0x28, 0x12, 0xea, 0x1b, 0x8f, 0xe2, 0xa7, 0x76, + 0x60, 0x8b, 0xdf, 0xa8, 0x30, 0xbe, 0xed, 0x48, 0xa3, 0xfa, 0x8f, 0x4b, 0xf2, 0xa5, 0x1f, 0x41, + 0xb5, 0xeb, 0xf1, 0x87, 0xc9, 0x85, 0x44, 0xf2, 0xa0, 0x65, 0xe9, 0x90, 0x07, 0x2d, 0x85, 0xce, + 0xe8, 0xb5, 0x63, 0xf5, 0x9e, 0x16, 0xe9, 0x8c, 0x08, 0x31, 0x74, 0x46, 0x84, 0x88, 0x7d, 0xae, + 0xd6, 0xed, 0x86, 0xc1, 0xae, 0x5c, 0xf6, 0xf4, 0xc6, 0xa7, 0x2b, 0x41, 0x46, 0xda, 0x70, 0x09, + 0x12, 0xcd, 0x58, 0xe2, 0xfe, 0x9e, 0xee, 0xfc, 0xda, 0xe2, 0xbe, 0x7e, 0x7a, 0xc2, 0x72, 0xab, + 0x01, 0xac, 0x1e, 0x06, 0x9d, 0x20, 0xe6, 0x2d, 0xf9, 0x3d, 0x98, 0x6d, 0xfd, 0xd0, 0x47, 0xcb, + 0xd6, 0xbd, 0xb8, 0x6d, 0x3c, 0x5a, 0x16, 0x0b, 0x80, 0x2d, 0xe1, 0xd6, 0x4f, 0x0d, 0xc0, 0x39, + 0xa3, 0x4f, 0x97, 0xc2, 0x3d, 0xbb, 0xe7, 0x2f, 0xfb, 0xa1, 0xd7, 0xdc, 0xc1, 0xdc, 0x4e, 0x77, + 0x00, 0x6c, 0xee, 0x46, 0x81, 0xaf, 0x29, 0x6b, 0x97, 0x85, 0xfa, 0x15, 0x22, 0x34, 0x6f, 0x20, + 0x99, 0x26, 0x4e, 0x29, 0x95, 0xad, 0x71, 0x60, 0x35, 0x98, 0x90, 0xbf, 0xc4, 0xc7, 0x74, 0x13, + 0x45, 0xfc, 0x19, 0x99, 0x69, 0x09, 0x59, 0x76, 0xb1, 0xc4, 0x4c, 0x00, 0xa9, 0x51, 0xb0, 0x6f, + 0xc3, 0xb8, 0x58, 0xa7, 0x4b, 0x5e, 0xd4, 0x6d, 0xbb, 0x89, 0xa9, 0xff, 0x5a, 0xd1, 0x14, 0xc9, + 0x7e, 0xce, 0x65, 0x9d, 0x50, 0xbe, 0x89, 0x39, 0x47, 0xd3, 0xe7, 0x0c, 0x9e, 0xe7, 0x5a, 0x54, + 0xa6, 0x7b, 0x6b, 0xeb, 0x34, 0xb3, 0x1f, 0xc2, 0x74, 0x8e, 0x47, 0xc1, 0xeb, 0x7c, 0x6f, 0x9a, + 0xaf, 0xf3, 0xcd, 0x15, 0x35, 0x4e, 0xe3, 0xa3, 0xbf, 0xd3, 0xf7, 0x23, 0x25, 0x98, 0xe9, 0x87, + 0x27, 0xa6, 0x57, 0x3d, 0xf4, 0x3a, 0x6e, 0xa8, 0x44, 0x11, 0x4e, 0xaf, 0xae, 0x04, 0x19, 0x3a, + 0xa7, 0x04, 0xb1, 0x37, 0x61, 0x54, 0x9e, 0x62, 0xdd, 0x24, 0xe5, 0x9f, 0x4c, 0x9e, 0xac, 0x80, + 0x46, 0xf2, 0x64, 0x05, 0xb4, 0xfe, 0xaf, 0x61, 0x18, 0xd7, 0x97, 0x0d, 0xb3, 0x60, 0x58, 0x3e, + 0x4b, 0xa2, 0xc7, 0xb2, 0xb9, 0x08, 0xb1, 0xa9, 0x24, 0x7d, 0x95, 0xa5, 0x7c, 0xe8, 0xab, 0x2c, + 0x1b, 0x30, 0x51, 0x0f, 0x03, 0x7c, 0xa5, 0x16, 0x5d, 0x7f, 0x68, 0x8f, 0x3c, 0xa1, 0x99, 0x90, + 0xc4, 0x0a, 0x47, 0xe7, 0x22, 0x3c, 0xa4, 0x77, 0x09, 0xdb, 0x11, 0xc7, 0x0b, 0xc3, 0x80, 0x6a, + 0xf0, 0x91, 0x7e, 0x8b, 0x62, 0xb6, 0xe8, 0xcf, 0xc8, 0xcb, 0x89, 0x65, 0xfa, 0x2d, 0x0a, 0x88, + 0x2e, 0x84, 0x87, 0x9e, 0x94, 0x10, 0x66, 0x3f, 0x5b, 0x82, 0xb1, 0x9a, 0xef, 0xd3, 0x6b, 0x2f, + 0x87, 0x64, 0x71, 0xff, 0x06, 0xb9, 0x2e, 0xbe, 0xf3, 0x89, 0x5c, 0x17, 0x51, 0xa1, 0x8d, 0xf0, + 0x34, 0x94, 0x56, 0x68, 0xe4, 0x36, 0x4e, 0xc1, 0xec, 0x1d, 0xa8, 0x26, 0xd2, 0x6f, 0xc5, 0x6f, + 0xf1, 0x47, 0x3c, 0xc2, 0xc8, 0xb8, 0x09, 0x7a, 0xc5, 0x5f, 0x3f, 0xfd, 0x64, 0x11, 0xd9, 0x3a, + 0x80, 0x9b, 0x88, 0x1d, 0xf2, 0xa8, 0x38, 0x9b, 0xde, 0x7e, 0x67, 0xe4, 0x92, 0xba, 0xb5, 0x13, + 0xbf, 0xd1, 0x3b, 0xc4, 0xbc, 0xb5, 0x4b, 0xc4, 0x57, 0x07, 0xa6, 0x6a, 0x51, 0xd4, 0xeb, 0xf0, + 0x46, 0xec, 0x86, 0xb1, 0xe8, 0x6a, 0x32, 0x85, 0x1e, 0xc9, 0x91, 0xdf, 0x45, 0x52, 0x31, 0x23, + 0xc2, 0x18, 0xbd, 0x07, 0xb2, 0x8e, 0xfc, 0x19, 0xde, 0xec, 0x5d, 0x18, 0x6b, 0xf4, 0x36, 0x3b, + 0x5e, 0x1c, 0xf3, 0xd6, 0xc2, 0x1e, 0x85, 0x7a, 0xaa, 0x24, 0x29, 0x12, 0xec, 0x6c, 0x1a, 0x86, + 0x2b, 0x0d, 0x9d, 0xbd, 0x0f, 0xe7, 0x52, 0xb4, 0xc0, 0x77, 0x36, 0xf9, 0x8e, 0xdb, 0xde, 0x72, + 0x82, 0x2d, 0x47, 0xae, 0x03, 0xe9, 0x2c, 0x61, 0x9f, 0x4d, 0x90, 0xee, 0xfa, 0x0b, 0x88, 0x72, + 0x77, 0x4b, 0x2e, 0x90, 0x5b, 0x83, 0x95, 0x4a, 0x75, 0xd4, 0x3e, 0x93, 0xef, 0x2f, 0x29, 0x8d, + 0x7f, 0xa2, 0x04, 0xa7, 0xf5, 0x45, 0x87, 0x95, 0xa3, 0xf9, 0x85, 0x5d, 0x86, 0x51, 0x5a, 0x13, + 0x89, 0xa1, 0x00, 0xbd, 0xa8, 0xcd, 0x38, 0xe7, 0x04, 0x85, 0x2d, 0x8b, 0x65, 0x20, 0x78, 0x90, + 0x00, 0x3a, 0x91, 0x11, 0x40, 0xa2, 0x08, 0x2f, 0x8f, 0x3f, 0x27, 0xd7, 0x87, 0xf8, 0x6d, 0xae, + 0x0f, 0x01, 0xb1, 0xbe, 0x02, 0xd3, 0xe6, 0x4c, 0x68, 0xf0, 0x98, 0x5d, 0x82, 0x11, 0x35, 0x7d, + 0x4a, 0xc5, 0xd3, 0x47, 0x95, 0x5b, 0x1b, 0xc0, 0x72, 0xf4, 0x11, 0xfa, 0x38, 0xf3, 0x58, 0xdd, + 0xa3, 0x29, 0x0f, 0xa3, 0x1c, 0xe2, 0xc2, 0x09, 0x6a, 0xdf, 0x98, 0x91, 0x06, 0x09, 0x5f, 0x5a, + 0xfe, 0xf1, 0x13, 0x70, 0xa2, 0x40, 0x19, 0x38, 0x44, 0x59, 0x9f, 0x33, 0x05, 0xd4, 0x68, 0xf2, + 0x08, 0x85, 0x12, 0x4b, 0x5f, 0x81, 0xa1, 0x43, 0xc5, 0x91, 0x4c, 0xaf, 0x95, 0x91, 0x42, 0x92, + 0xec, 0x33, 0x51, 0xd8, 0xf5, 0xa7, 0x74, 0x86, 0x9e, 0xd8, 0x53, 0x3a, 0x98, 0x89, 0x59, 0xdb, + 0xa8, 0xcd, 0xec, 0xd0, 0x58, 0xe0, 0xe4, 0xc4, 0xa6, 0x49, 0x22, 0x79, 0x44, 0x41, 0x7b, 0x97, + 0x13, 0x8f, 0x11, 0x9d, 0x07, 0x16, 0x14, 0xf2, 0xd0, 0x48, 0xd8, 0x2f, 0x95, 0x80, 0x11, 0x44, + 0x97, 0x99, 0x95, 0x83, 0x64, 0x66, 0xeb, 0xc9, 0xc8, 0xcc, 0x73, 0xaa, 0x8d, 0xc5, 0xb2, 0xb3, + 0xa0, 0x59, 0xec, 0x6f, 0x95, 0x60, 0x5a, 0x3e, 0x56, 0xa2, 0x37, 0xf6, 0xc0, 0x07, 0x28, 0x9a, + 0x4f, 0xa6, 0xb1, 0xcf, 0x46, 0x58, 0x6d, 0x9f, 0xb6, 0xe6, 0x1b, 0xc5, 0x7e, 0x08, 0x20, 0x59, + 0x51, 0xf2, 0xb5, 0xe9, 0xb1, 0xf9, 0x67, 0x0b, 0xa4, 0x40, 0x82, 0x84, 0x77, 0xe6, 0x62, 0xea, + 0x9c, 0x4c, 0xfc, 0x7a, 0x0c, 0x67, 0x8b, 0x94, 0x1b, 0xfb, 0xb3, 0xf2, 0xb1, 0xd0, 0x04, 0x42, + 0xaf, 0x4f, 0xcd, 0x8c, 0x61, 0x2d, 0x5f, 0xe8, 0xaf, 0xac, 0x5f, 0x2e, 0x22, 0x93, 0x7a, 0x58, + 0x12, 0x71, 0x13, 0xc6, 0x9d, 0xec, 0x83, 0xa1, 0x59, 0x0a, 0x7c, 0xd4, 0x0d, 0x5b, 0x1f, 0xcd, + 0x8c, 0x63, 0x9d, 0x85, 0xf2, 0xed, 0xac, 0x5a, 0x0b, 0x52, 0xbe, 0x65, 0xb2, 0x67, 0x20, 0x88, + 0x7d, 0x00, 0x2c, 0x79, 0xe5, 0x43, 0xc2, 0x78, 0x28, 0x9f, 0xae, 0xa6, 0xf0, 0xc8, 0xf4, 0xb5, + 0x90, 0x50, 0x15, 0x1b, 0x79, 0xc6, 0x72, 0xc4, 0x8c, 0xc3, 0x49, 0xfa, 0x68, 0x01, 0x95, 0xf9, + 0xb1, 0x56, 0x96, 0xa2, 0x99, 0x49, 0xe3, 0x6d, 0xaf, 0xb4, 0x64, 0xe1, 0x39, 0x6a, 0xe7, 0xe9, + 0x24, 0xd1, 0x96, 0x99, 0x83, 0xaa, 0x90, 0x1d, 0xbb, 0x06, 0xa3, 0x98, 0x46, 0xf5, 0xa6, 0xf2, + 0xdc, 0x26, 0x2f, 0x52, 0x4c, 0xb8, 0xea, 0xec, 0x98, 0xfe, 0xd7, 0x29, 0xaa, 0x38, 0xa7, 0x4a, + 0xb5, 0x18, 0xef, 0x97, 0xc8, 0x10, 0xd7, 0x0a, 0xf7, 0x9c, 0xb0, 0x67, 0xa6, 0xe8, 0x45, 0x24, + 0xf6, 0x21, 0x8c, 0xad, 0xb9, 0x8f, 0x94, 0xe9, 0x8f, 0xee, 0x90, 0x0e, 0x0b, 0x81, 0xc5, 0xaf, + 0xe9, 0xb8, 0x8f, 0x9c, 0x56, 0x2f, 0xfb, 0xd2, 0x81, 0x0c, 0x81, 0xd5, 0x58, 0xb2, 0x6f, 0x02, + 0x68, 0x97, 0x5e, 0xec, 0xd0, 0x0a, 0x9e, 0x57, 0x2f, 0xd6, 0x15, 0x5e, 0x86, 0x21, 0x7f, 0x8d, + 0x61, 0x46, 0x73, 0x39, 0xf9, 0xd9, 0x69, 0x2e, 0xa7, 0x3e, 0x43, 0xcd, 0xe5, 0xc3, 0x34, 0x3f, + 0x1b, 0x4a, 0xf0, 0x3d, 0xba, 0x72, 0x3a, 0xa8, 0x36, 0xe5, 0x03, 0x53, 0xc5, 0xad, 0x60, 0x2f, + 0x53, 0x45, 0x86, 0x1f, 0x0b, 0xa1, 0x9a, 0x3d, 0x2d, 0xd1, 0xc3, 0x13, 0x2f, 0x1e, 0xe5, 0x64, + 0x25, 0x4d, 0xea, 0x34, 0x8d, 0x1c, 0x9e, 0xc0, 0x75, 0xa5, 0x32, 0x77, 0xb8, 0xbc, 0x0f, 0x63, + 0xc4, 0x0e, 0x0d, 0x10, 0x33, 0x86, 0xc7, 0xb1, 0x51, 0x9d, 0x28, 0x27, 0xe7, 0x2d, 0xda, 0x9c, + 0x32, 0xe6, 0x09, 0x9d, 0x11, 0xeb, 0x40, 0x75, 0x35, 0xf0, 0xb7, 0xd7, 0x79, 0xd8, 0xc1, 0xc4, + 0x74, 0x42, 0x36, 0x9d, 0x35, 0x0e, 0x62, 0xaa, 0xd8, 0xc8, 0x5f, 0xe7, 0xf9, 0xdb, 0xf2, 0x33, + 0xda, 0x81, 0xbf, 0xed, 0xc4, 0x3c, 0xec, 0xc8, 0x8c, 0x77, 0xa6, 0x93, 0x6b, 0x8e, 0x35, 0xfb, + 0x33, 0x30, 0x9b, 0x5b, 0xa1, 0xb2, 0xf5, 0x62, 0xd9, 0xcf, 0x1a, 0xde, 0x42, 0xd9, 0x72, 0x8c, + 0x14, 0xfd, 0x9c, 0xda, 0x8d, 0x70, 0xf1, 0xd3, 0xe4, 0x33, 0x65, 0xc0, 0x01, 0x35, 0xcc, 0x6e, + 0xc2, 0xd9, 0xbe, 0x72, 0xb5, 0xe0, 0x6c, 0x7a, 0xc5, 0x3c, 0x9b, 0x9e, 0xed, 0xa7, 0x7f, 0x45, + 0xda, 0xa9, 0xf4, 0xd6, 0x60, 0xe5, 0x44, 0xf5, 0x64, 0x7f, 0xd5, 0xf5, 0x77, 0xca, 0x19, 0x7d, + 0x8c, 0xcc, 0x21, 0xe7, 0xa1, 0x7c, 0x80, 0xc2, 0x5a, 0x5e, 0x59, 0x62, 0x17, 0x48, 0x63, 0x2b, + 0xa7, 0x66, 0x98, 0x9e, 0x79, 0x69, 0x29, 0x75, 0xb7, 0x4f, 0xab, 0x9a, 0xbd, 0x0b, 0x93, 0x0d, + 0xee, 0x86, 0xcd, 0x9d, 0xdb, 0x7c, 0xef, 0x61, 0x10, 0xb6, 0x54, 0xae, 0x24, 0xf9, 0xde, 0x1d, + 0x96, 0x98, 0xf9, 0x28, 0x75, 0x5c, 0xb6, 0xa4, 0x52, 0xac, 0x0e, 0x61, 0xed, 0x67, 0x0b, 0xb7, + 0x38, 0x81, 0x70, 0x50, 0xf6, 0x55, 0x71, 0x18, 0x57, 0xc3, 0xa8, 0xf2, 0xd1, 0x53, 0xe8, 0x36, + 0x01, 0x0b, 0x94, 0x79, 0x1e, 0x5a, 0xdf, 0x1b, 0x00, 0x26, 0x6b, 0x5a, 0x74, 0xbb, 0x2e, 0x26, + 0x35, 0xf6, 0xf0, 0x81, 0xa8, 0x2a, 0xe1, 0xb8, 0x9b, 0x6d, 0xae, 0xbf, 0xae, 0x46, 0x61, 0x54, + 0x49, 0x99, 0x93, 0x3d, 0x85, 0xe7, 0x08, 0xfb, 0xec, 0x83, 0xe5, 0x4f, 0xb3, 0x0f, 0x7e, 0x08, + 0xcf, 0xd4, 0xba, 0xdd, 0xb6, 0xd7, 0x4c, 0x6a, 0xb9, 0x1e, 0x84, 0x6a, 0xf6, 0x1a, 0x39, 0x10, + 0xdd, 0x04, 0x2d, 0xd7, 0xd2, 0x83, 0x58, 0x68, 0x4a, 0xac, 0xb4, 0x0d, 0xe9, 0xe9, 0xd7, 0x95, + 0x9c, 0x28, 0xb2, 0x26, 0x69, 0x24, 0x8a, 0x87, 0x17, 0x2a, 0x25, 0x76, 0x28, 0x7d, 0x01, 0x5c, + 0x79, 0xad, 0x14, 0x2b, 0xc2, 0x09, 0x89, 0x38, 0x57, 0xd6, 0x7a, 0x71, 0x40, 0x8c, 0x29, 0xfe, + 0x2f, 0x8d, 0xd4, 0xa3, 0xa6, 0x18, 0xe7, 0xf2, 0x14, 0xdd, 0xfa, 0xed, 0x41, 0x38, 0x9b, 0x1f, + 0x5e, 0x2a, 0x4d, 0xd6, 0x47, 0xe9, 0x90, 0xf5, 0x51, 0x34, 0x1b, 0xe4, 0x6d, 0xe9, 0x13, 0x9b, + 0x0d, 0x32, 0xa1, 0xf2, 0x27, 0x9c, 0x0d, 0x0d, 0x21, 0xeb, 0x53, 0x65, 0x68, 0xf0, 0x93, 0x2a, + 0x43, 0x3a, 0x17, 0x76, 0x09, 0x86, 0x64, 0xd6, 0xf9, 0xa1, 0xf4, 0xee, 0x3c, 0x9b, 0x70, 0x5e, + 0x62, 0xb0, 0x3f, 0x03, 0xe7, 0xa5, 0x4c, 0xca, 0x7e, 0xec, 0xc2, 0x9e, 0xe2, 0x48, 0x03, 0x37, + 0xff, 0x78, 0x7f, 0xee, 0xb2, 0x34, 0xf0, 0x3a, 0xb9, 0x6e, 0x73, 0x36, 0xf7, 0x1c, 0xd5, 0x32, + 0xad, 0x92, 0x43, 0x79, 0x33, 0x1f, 0x4e, 0x14, 0x48, 0x78, 0x4a, 0x4d, 0xf4, 0x69, 0x77, 0x87, + 0xe9, 0xd0, 0x24, 0x6c, 0x45, 0xd6, 0x0f, 0xc3, 0x8c, 0x7c, 0xef, 0xb5, 0x40, 0x72, 0x1c, 0xb2, + 0x32, 0x4b, 0x9f, 0x7a, 0x65, 0x8a, 0xfd, 0x60, 0xae, 0x5f, 0xf5, 0xc7, 0x9d, 0xd9, 0xb7, 0x60, + 0x42, 0x4a, 0xe3, 0x5a, 0xa4, 0x9f, 0xde, 0xd1, 0x25, 0x41, 0x3e, 0x6d, 0xeb, 0x48, 0xf9, 0xed, + 0xb8, 0x51, 0xae, 0x95, 0x26, 0x69, 0x76, 0x16, 0x0e, 0x3c, 0x91, 0x59, 0xd8, 0x67, 0x68, 0x07, + 0x3f, 0xab, 0xa1, 0x7d, 0x04, 0x67, 0x95, 0xb6, 0x93, 0x64, 0x9e, 0x4e, 0xe6, 0xd9, 0x05, 0xc0, + 0xeb, 0x1d, 0xbd, 0x57, 0x33, 0xaa, 0x12, 0x96, 0xb3, 0xab, 0x50, 0xa9, 0xd5, 0x57, 0x50, 0x87, + 0xd1, 0xed, 0xc2, 0x6e, 0xd7, 0x93, 0x4a, 0x8f, 0x91, 0x1f, 0x8c, 0x10, 0xad, 0x1f, 0x2d, 0xc3, + 0x78, 0xbd, 0xdd, 0xdb, 0xf6, 0xfc, 0x25, 0x37, 0x76, 0x9f, 0xda, 0xcb, 0xa5, 0xb7, 0x8c, 0xcb, + 0xa5, 0x24, 0x09, 0x44, 0xf2, 0x61, 0x47, 0xba, 0x59, 0xfa, 0x99, 0x12, 0x4c, 0xa5, 0x24, 0x52, + 0xd9, 0xba, 0x09, 0x83, 0xe2, 0x07, 0x59, 0xb6, 0xce, 0xe7, 0x18, 0x23, 0xd6, 0xe5, 0xe4, 0x2f, + 0xba, 0xee, 0x71, 0x4d, 0xbf, 0x3b, 0x51, 0x3c, 0xfb, 0x45, 0x18, 0x4d, 0xd9, 0xe6, 0x75, 0xb8, + 0x93, 0xba, 0x0e, 0x37, 0xaa, 0x5f, 0x1f, 0xfc, 0xdd, 0x12, 0x54, 0xb3, 0x5f, 0xc2, 0x6e, 0xc3, + 0x88, 0xe0, 0xe4, 0xf1, 0x28, 0x9b, 0xd1, 0x31, 0x83, 0x79, 0x99, 0xd0, 0x64, 0xf3, 0xb0, 0xf3, + 0xb9, 0x84, 0xd8, 0x8a, 0xc3, 0xac, 0x8d, 0xaf, 0xd2, 0x24, 0x58, 0x05, 0xad, 0x7b, 0xd5, 0xd4, + 0x30, 0x4f, 0x17, 0xf7, 0x83, 0xde, 0xea, 0x7f, 0xcf, 0x68, 0x35, 0x29, 0x8f, 0x17, 0x8c, 0xc9, + 0x55, 0x38, 0x95, 0x71, 0xd2, 0xcc, 0x43, 0x25, 0x11, 0xe1, 0x65, 0xfd, 0xe1, 0x96, 0x9c, 0x98, + 0x4e, 0xf0, 0xd8, 0xab, 0x30, 0x2c, 0xeb, 0xd3, 0x33, 0x55, 0x77, 0x11, 0xa2, 0x1f, 0x82, 0x25, + 0x8e, 0xf5, 0x0b, 0x03, 0x70, 0x3a, 0x6d, 0xde, 0xbd, 0x6e, 0xcb, 0x8d, 0x79, 0xdd, 0x0d, 0xdd, + 0x4e, 0x74, 0xc8, 0x0a, 0xb8, 0x98, 0x6b, 0x1a, 0xa5, 0x9a, 0x92, 0x30, 0xad, 0x41, 0x56, 0xa6, + 0x41, 0x78, 0xc1, 0x22, 0x1b, 0xa4, 0x9a, 0xc1, 0x6e, 0xc3, 0x40, 0x83, 0xc7, 0x24, 0x58, 0x2e, + 0xe4, 0x7a, 0x55, 0x6f, 0xd7, 0xe5, 0x06, 0x8f, 0xe5, 0x20, 0xca, 0x27, 0x71, 0x8c, 0xa4, 0x4e, + 0x82, 0x0b, 0xdb, 0x80, 0xe1, 0xe5, 0x47, 0x5d, 0xde, 0x8c, 0xf1, 0x11, 0x7d, 0x2d, 0xbb, 0x51, + 0x31, 0x3f, 0x89, 0x2b, 0x59, 0x9e, 0xa4, 0x53, 0x25, 0x6f, 0x1a, 0x69, 0xbd, 0x25, 0xca, 0xec, + 0x35, 0xa8, 0xa8, 0xca, 0x8f, 0x33, 0x73, 0x67, 0xdf, 0x82, 0x31, 0xad, 0x92, 0x63, 0x4d, 0xfa, + 0x5f, 0x2c, 0x01, 0x08, 0x01, 0x4f, 0x13, 0x67, 0x39, 0xa7, 0xeb, 0x97, 0xd2, 0x6c, 0xe6, 0xb4, + 0x57, 0x3c, 0xa0, 0xa2, 0x03, 0x94, 0xfe, 0x15, 0x98, 0x6a, 0x3c, 0xf0, 0xba, 0xe9, 0xeb, 0xc4, + 0x86, 0x46, 0x15, 0x3d, 0xf0, 0xba, 0x0e, 0x59, 0xe5, 0xb2, 0xbb, 0x4e, 0x96, 0xce, 0xfa, 0x7f, + 0x4a, 0x30, 0x2c, 0xfe, 0xba, 0x7f, 0xed, 0x29, 0x15, 0x99, 0x57, 0x0d, 0x91, 0x99, 0xec, 0x98, + 0x41, 0x5b, 0xc6, 0x68, 0x5c, 0x3b, 0x44, 0x58, 0xee, 0xd3, 0x00, 0x49, 0x64, 0x76, 0x03, 0x46, + 0xc8, 0xf5, 0x98, 0xa2, 0x34, 0x99, 0xc6, 0x50, 0x39, 0x25, 0x27, 0xe6, 0xbb, 0xa0, 0x9b, 0xb5, + 0x77, 0x2a, 0x6a, 0x71, 0x2e, 0x53, 0xef, 0x44, 0x4b, 0x1b, 0x6c, 0xca, 0x26, 0xcd, 0x76, 0xb7, + 0x70, 0x86, 0x38, 0xf5, 0xcb, 0x15, 0x5d, 0xa3, 0x3b, 0xf8, 0x81, 0x83, 0x98, 0x9c, 0x26, 0x26, + 0xc5, 0xd7, 0xf3, 0x1d, 0x38, 0xdd, 0x68, 0xdc, 0xc4, 0x30, 0x85, 0x7a, 0x10, 0xc6, 0xd7, 0x83, + 0xf0, 0x21, 0xe5, 0xa8, 0x6b, 0x98, 0x9e, 0x6f, 0x45, 0x2e, 0xbd, 0x2f, 0x17, 0xba, 0xf4, 0x1e, + 0xe0, 0x1d, 0x67, 0xf9, 0x70, 0xa6, 0xd1, 0xb8, 0x29, 0x35, 0xac, 0x3f, 0x8c, 0xfa, 0xfe, 0x6e, + 0x09, 0xa6, 0x1b, 0x8d, 0x9b, 0x99, 0xaa, 0x56, 0x85, 0xfa, 0xdd, 0x74, 0xdb, 0x54, 0x91, 0x72, + 0xbf, 0x29, 0xee, 0x08, 0x1c, 0x85, 0x92, 0xd4, 0xd0, 0x9b, 0xc6, 0x6b, 0x82, 0x92, 0x09, 0xab, + 0xc3, 0xb0, 0xfc, 0x20, 0x1a, 0xcc, 0xe7, 0x52, 0x76, 0x45, 0x1f, 0x9a, 0xc6, 0x3e, 0x4a, 0x25, + 0xd0, 0xbc, 0xbe, 0x12, 0x10, 0xeb, 0xb7, 0xce, 0xc0, 0x98, 0x36, 0xa3, 0xd8, 0x97, 0x61, 0x9c, + 0xe8, 0x31, 0xba, 0x95, 0x3c, 0x11, 0xcf, 0x0a, 0x01, 0xb9, 0x25, 0xe1, 0xf2, 0x49, 0xe8, 0xef, + 0xef, 0xcf, 0x0d, 0x8a, 0xae, 0xb1, 0x0d, 0x74, 0x76, 0x17, 0x26, 0xd6, 0xdc, 0x47, 0x9a, 0xad, + 0x52, 0x26, 0x2f, 0xb8, 0x24, 0xa4, 0x4a, 0xc7, 0x7d, 0x74, 0x04, 0xe7, 0x7c, 0x93, 0x9e, 0x3d, + 0x80, 0x49, 0xf3, 0x9b, 0x68, 0x06, 0xe6, 0x47, 0xec, 0x8d, 0xc2, 0x11, 0x3b, 0xdb, 0x0d, 0xc2, + 0xd8, 0xd9, 0x4a, 0xc8, 0xd3, 0x5a, 0x67, 0x4a, 0x76, 0x86, 0x35, 0xfb, 0x32, 0x4c, 0x6b, 0x6f, + 0x36, 0x5e, 0x0f, 0xc2, 0x8e, 0xab, 0x0e, 0xcc, 0x78, 0x81, 0x87, 0xce, 0xb0, 0x5b, 0x08, 0xb6, + 0xf3, 0x98, 0xec, 0xeb, 0x45, 0x09, 0x21, 0x86, 0xd2, 0x08, 0x85, 0x82, 0x84, 0x10, 0xfd, 0x22, + 0x14, 0xf2, 0xa9, 0x21, 0xb6, 0x0f, 0x8a, 0x60, 0xaa, 0xc8, 0xaf, 0x3f, 0x52, 0x84, 0x52, 0x32, + 0x72, 0x7d, 0x22, 0x95, 0xe6, 0x61, 0x60, 0xa1, 0x7e, 0x1d, 0x4f, 0x5d, 0xca, 0x0b, 0xd8, 0xdf, + 0x71, 0xfd, 0x26, 0x1e, 0x64, 0x29, 0xfc, 0x4f, 0xdf, 0x28, 0x17, 0xea, 0xd7, 0x99, 0x0b, 0x27, + 0xea, 0x3c, 0xec, 0x78, 0xf1, 0x57, 0xdf, 0x78, 0x43, 0x1b, 0xaa, 0x0a, 0x36, 0xed, 0x0a, 0x35, + 0x6d, 0xae, 0x8b, 0x28, 0xce, 0xa3, 0x37, 0xde, 0x28, 0x1c, 0x90, 0xa4, 0x61, 0x45, 0xbc, 0xc4, + 0x86, 0xb5, 0xe6, 0x3e, 0x4a, 0xa3, 0xad, 0x23, 0x4a, 0xad, 0x73, 0x4e, 0x4d, 0xad, 0x34, 0x52, + 0xdb, 0xd8, 0xb0, 0x4c, 0x22, 0xf6, 0x2e, 0xda, 0xea, 0x55, 0x2a, 0x25, 0x4a, 0x4a, 0x30, 0xab, + 0x6c, 0xf1, 0x2a, 0xff, 0x92, 0x7e, 0x8c, 0xd1, 0xd0, 0xd9, 0xbd, 0xc4, 0x9a, 0x22, 0x8f, 0x1a, + 0x74, 0x3f, 0x7e, 0x45, 0xb7, 0xa6, 0xc8, 0x23, 0x8a, 0xf1, 0x59, 0x53, 0x89, 0x09, 0x4e, 0x86, + 0x9f, 0xdb, 0x26, 0x97, 0xbc, 0x91, 0x66, 0xfc, 0xf8, 0x46, 0x1a, 0x0e, 0x83, 0xab, 0x41, 0xf3, + 0x01, 0xbd, 0x4b, 0xf6, 0x81, 0x90, 0xc2, 0xed, 0xa0, 0xf9, 0xe0, 0xc9, 0x45, 0x66, 0x21, 0x7b, + 0x76, 0x47, 0x66, 0x24, 0x0c, 0x5b, 0xd4, 0x27, 0x14, 0xed, 0x73, 0x32, 0x39, 0xc2, 0x69, 0x65, + 0x69, 0x9e, 0xc2, 0xb0, 0xa5, 0xba, 0xd6, 0x36, 0xc9, 0x19, 0x87, 0xea, 0x12, 0x8f, 0x1e, 0xc4, + 0x41, 0x77, 0xb1, 0xed, 0x75, 0x31, 0xc9, 0x27, 0xbd, 0x1f, 0x7e, 0x64, 0x99, 0xdc, 0x92, 0xf4, + 0x4e, 0x53, 0x31, 0xb0, 0x73, 0x2c, 0xd9, 0xd7, 0x61, 0x52, 0x4c, 0xee, 0xe5, 0x47, 0x31, 0xf7, + 0xe5, 0xc8, 0x4f, 0xa3, 0x46, 0xa7, 0xda, 0x6d, 0x14, 0xca, 0x39, 0x85, 0x8b, 0x9d, 0x27, 0x04, + 0xc6, 0x9b, 0x6e, 0x06, 0x2b, 0xd6, 0x82, 0x99, 0x35, 0xf7, 0x51, 0x7a, 0xd0, 0xd4, 0x27, 0x29, + 0xc3, 0x09, 0x76, 0xf1, 0xf1, 0xfe, 0xdc, 0x8b, 0x62, 0x82, 0xa5, 0x4f, 0xda, 0xf7, 0x99, 0xaf, + 0x7d, 0x39, 0xb1, 0x6f, 0xc3, 0x19, 0xfa, 0xac, 0x25, 0x2f, 0xc4, 0x70, 0xc8, 0xbd, 0xc6, 0x8e, + 0x8b, 0x99, 0x16, 0x4e, 0xf4, 0xe9, 0xb0, 0x2b, 0xc5, 0x22, 0x51, 0x75, 0x58, 0x4b, 0xf1, 0x71, + 0x22, 0xc9, 0xc8, 0xee, 0x57, 0x03, 0xfb, 0x08, 0x26, 0xe5, 0x5d, 0xfb, 0xcd, 0x40, 0x7a, 0x5b, + 0xd1, 0x2d, 0xd1, 0x91, 0xc3, 0x17, 0xe5, 0x05, 0xbe, 0x0c, 0x68, 0xce, 0x98, 0x30, 0x32, 0x9c, + 0xd9, 0x3b, 0xe8, 0x78, 0x2f, 0x5f, 0x5d, 0x5c, 0xa9, 0xe3, 0x9d, 0x11, 0xed, 0x40, 0x5d, 0xcf, + 0x77, 0x94, 0xad, 0xa1, 0x9b, 0x88, 0x0b, 0x1d, 0x9b, 0x6d, 0xc0, 0x58, 0xa3, 0x71, 0xf3, 0xba, + 0x27, 0xf4, 0x92, 0xae, 0xba, 0x02, 0xca, 0xb7, 0xf2, 0x85, 0xc2, 0x56, 0x4e, 0x44, 0xd1, 0x8e, + 0xb3, 0xe5, 0xb5, 0xb9, 0xd3, 0x0c, 0xba, 0x7b, 0xb6, 0xce, 0xa9, 0x20, 0xa4, 0xef, 0xcc, 0x13, + 0x0e, 0xe9, 0x5b, 0x81, 0x29, 0x2d, 0xfa, 0x03, 0x9d, 0x09, 0x67, 0xd2, 0xf7, 0x57, 0xf4, 0x10, + 0xbe, 0x6c, 0x12, 0x99, 0x2c, 0x9d, 0x8a, 0xe5, 0x3b, 0x7b, 0xdc, 0x58, 0x3e, 0x0f, 0xa6, 0xe5, + 0x60, 0xd0, 0x3c, 0xc0, 0x91, 0x9e, 0xed, 0xd3, 0x87, 0x97, 0x0a, 0xfb, 0xf0, 0x04, 0x8d, 0xb4, + 0x9a, 0x64, 0xe8, 0x5b, 0x92, 0xe7, 0xca, 0xb6, 0x80, 0x11, 0xd0, 0x8d, 0xdd, 0x4d, 0x37, 0xe2, + 0x58, 0xd7, 0x33, 0x7d, 0xea, 0x7a, 0xb1, 0xb0, 0xae, 0x49, 0x55, 0xd7, 0xa6, 0xac, 0xa6, 0x80, + 0x23, 0xf3, 0x55, 0x3d, 0x6a, 0x7e, 0x61, 0xc7, 0x3e, 0x6b, 0xdc, 0x51, 0xe4, 0x11, 0x64, 0x16, + 0x81, 0xec, 0xa4, 0xcd, 0xf6, 0x7b, 0x01, 0x67, 0xf6, 0x08, 0x4e, 0xe7, 0x5b, 0x81, 0x75, 0x9e, + 0xc3, 0x3a, 0xcf, 0x19, 0x75, 0x66, 0x91, 0xe4, 0xbc, 0x31, 0x3f, 0x2b, 0x5b, 0x6b, 0x1f, 0xfe, + 0xec, 0x3b, 0x25, 0x38, 0xb3, 0x76, 0xbd, 0x76, 0x9f, 0x87, 0x52, 0x2d, 0xc1, 0x04, 0xdd, 0x94, + 0x7c, 0xe7, 0x39, 0xba, 0xc7, 0xca, 0x5e, 0x85, 0x2a, 0x8d, 0x03, 0x45, 0x85, 0x50, 0xdd, 0x5f, + 0xe8, 0x6c, 0xb9, 0xce, 0xae, 0xc6, 0xa2, 0x20, 0x43, 0xcf, 0xcf, 0xff, 0xee, 0x5c, 0xc9, 0xee, + 0x57, 0x15, 0x6b, 0xc3, 0xac, 0xd9, 0x2d, 0x2a, 0xda, 0x72, 0x87, 0xb7, 0xdb, 0x33, 0x73, 0x38, + 0xa3, 0x5f, 0x7d, 0xbc, 0x3f, 0x77, 0x31, 0xd7, 0xbb, 0x49, 0x04, 0xa7, 0xc0, 0xd4, 0x2f, 0xf6, + 0xfa, 0xf3, 0x63, 0x9d, 0x02, 0xa5, 0x7b, 0xe6, 0xbc, 0x91, 0xa5, 0x33, 0x57, 0x9e, 0xa4, 0x9e, + 0x3d, 0x27, 0xd6, 0x7b, 0x5f, 0x05, 0xd1, 0x2e, 0x50, 0xe7, 0xff, 0x42, 0x09, 0x66, 0x36, 0xf8, + 0xe6, 0xba, 0x50, 0x50, 0x7c, 0xb7, 0x9d, 0x6c, 0x30, 0x38, 0xc0, 0xcf, 0xe3, 0x00, 0xcf, 0xa5, + 0x21, 0xca, 0x85, 0x68, 0xf2, 0xd1, 0x98, 0x87, 0x7c, 0x13, 0xaf, 0x4f, 0x45, 0x71, 0xba, 0x73, + 0x65, 0x87, 0xba, 0x6f, 0x5d, 0xb7, 0x06, 0x2b, 0x13, 0xd5, 0xc9, 0x82, 0x10, 0x44, 0xeb, 0x37, + 0xca, 0x99, 0x1d, 0x9a, 0xad, 0xc0, 0x08, 0x2d, 0xbc, 0xbe, 0xa7, 0x9d, 0x73, 0x85, 0xcb, 0x6b, + 0x84, 0xd6, 0xb0, 0xad, 0xe8, 0xd9, 0x43, 0xc1, 0x0a, 0x7b, 0x9f, 0x8e, 0xde, 0xdf, 0x94, 0x1b, + 0x30, 0x82, 0x0c, 0x55, 0x63, 0xe9, 0xf8, 0xb9, 0x03, 0xcc, 0x0c, 0x13, 0xa8, 0x73, 0xa8, 0xda, + 0xd8, 0x03, 0x18, 0x68, 0x34, 0x6e, 0xd2, 0xe9, 0xfd, 0x6b, 0x24, 0xaa, 0x3f, 0x83, 0x0a, 0x45, + 0x2d, 0xd6, 0xaf, 0x97, 0x60, 0xc2, 0xd8, 0xe2, 0xd9, 0x35, 0x2d, 0xbb, 0x42, 0x7a, 0xfd, 0x6e, + 0xe0, 0xa0, 0xd4, 0xcf, 0xe6, 0x5d, 0xb8, 0xa6, 0x65, 0xd7, 0xee, 0x43, 0x87, 0x53, 0x22, 0x9b, + 0x4a, 0x44, 0xf9, 0xac, 0x0f, 0xf4, 0xf3, 0x59, 0x97, 0x99, 0x12, 0x07, 0x8b, 0x5f, 0x47, 0xb0, + 0xfe, 0xa7, 0x17, 0x60, 0xd2, 0x3c, 0x9a, 0xb3, 0x57, 0x61, 0x18, 0x2f, 0x79, 0x94, 0x9d, 0x07, + 0xed, 0x53, 0x78, 0x0f, 0x64, 0x84, 0x75, 0x4a, 0x1c, 0xf6, 0x12, 0x40, 0x12, 0xff, 0xa4, 0x2e, + 0x13, 0x86, 0x1e, 0xef, 0xcf, 0x95, 0x5e, 0xb3, 0xb5, 0x02, 0xf6, 0x2d, 0x80, 0x3b, 0x41, 0x8b, + 0xa3, 0x2f, 0x57, 0x94, 0x98, 0x06, 0x0a, 0x7d, 0xbc, 0x5e, 0x26, 0x1f, 0xaf, 0x61, 0x89, 0xfc, + 0x78, 0x7f, 0xee, 0x94, 0x1f, 0xb4, 0xb8, 0x83, 0x69, 0x73, 0x0c, 0x27, 0xaa, 0x94, 0x23, 0x7b, + 0x1b, 0x86, 0xec, 0x5e, 0x9b, 0xab, 0x7b, 0x82, 0x31, 0xb5, 0xd5, 0xf6, 0xda, 0x3c, 0x35, 0x58, + 0x84, 0xbd, 0xac, 0xfb, 0xb4, 0x00, 0xb0, 0xf7, 0x00, 0x84, 0x1e, 0x45, 0x4f, 0xf5, 0x0d, 0xa5, + 0x97, 0xbe, 0x9a, 0x0a, 0x96, 0x7b, 0xac, 0x4f, 0x23, 0x61, 0x77, 0x61, 0x44, 0xbf, 0xad, 0xd4, + 0xd2, 0x64, 0xe9, 0x37, 0xda, 0x9a, 0xf5, 0x83, 0x5e, 0x21, 0xca, 0x5e, 0x64, 0x2a, 0x2e, 0xec, + 0x5d, 0x18, 0x15, 0xec, 0x85, 0x08, 0x8b, 0xe8, 0x78, 0x85, 0x17, 0x48, 0x5a, 0x83, 0x84, 0x18, + 0x34, 0x1e, 0x23, 0x4a, 0x08, 0xd8, 0xd7, 0x61, 0xb4, 0xd6, 0xed, 0x52, 0x57, 0x1f, 0xe8, 0xfb, + 0x77, 0x21, 0xd7, 0xd5, 0x27, 0xdd, 0x6e, 0x37, 0xdf, 0xd3, 0x29, 0x3f, 0xb6, 0x9d, 0x64, 0x69, + 0xa6, 0x0a, 0x0e, 0xf4, 0xd7, 0x7b, 0x25, 0x57, 0xc1, 0x8c, 0x4a, 0x3c, 0x9c, 0xab, 0xc4, 0xe4, + 0xcb, 0xba, 0x50, 0x4d, 0xb5, 0x5b, 0xaa, 0x0b, 0x0e, 0xaa, 0xeb, 0xb5, 0x5c, 0x5d, 0xfa, 0x00, + 0xe6, 0xaa, 0xcb, 0x71, 0x67, 0x2d, 0x98, 0x54, 0x3b, 0x25, 0xd5, 0x37, 0x76, 0x50, 0x7d, 0x2f, + 0xe5, 0xea, 0x3b, 0xd1, 0xda, 0xcc, 0xd7, 0x93, 0xe1, 0xc9, 0xde, 0x85, 0x09, 0x05, 0xc1, 0xf5, + 0x81, 0x3e, 0x77, 0x64, 0x99, 0x6c, 0x6d, 0xca, 0x17, 0x32, 0x8d, 0xb7, 0x75, 0x74, 0x64, 0x9d, + 0x5a, 0xce, 0x8e, 0x09, 0x83, 0x3a, 0x3b, 0x2b, 0x4c, 0x64, 0xf6, 0x35, 0x18, 0x5b, 0xe9, 0x88, + 0x0f, 0x09, 0x7c, 0x37, 0xe6, 0x94, 0xc0, 0x41, 0xf9, 0x31, 0x6a, 0x25, 0xda, 0x54, 0x45, 0x0f, + 0x2e, 0x2f, 0x2d, 0xd2, 0xcf, 0xbb, 0x1a, 0x85, 0xe8, 0x3c, 0x79, 0x3d, 0x4d, 0x73, 0x58, 0x25, + 0x77, 0x38, 0x57, 0xe0, 0x4b, 0xa8, 0xb1, 0xa7, 0x87, 0xd6, 0x04, 0x54, 0x5d, 0x0f, 0x67, 0x9e, + 0xc4, 0xd4, 0x79, 0xb2, 0x2f, 0xc3, 0x58, 0x6d, 0xa3, 0x21, 0x04, 0x56, 0xcd, 0xbe, 0x13, 0xcd, + 0x54, 0xd3, 0x70, 0x17, 0xf7, 0xa1, 0xbc, 0xae, 0x74, 0xdc, 0x30, 0xe3, 0xb4, 0x9f, 0xe2, 0xb3, + 0xaf, 0xc2, 0xc9, 0x0d, 0xcf, 0x6f, 0x05, 0x0f, 0x23, 0xda, 0xa6, 0x48, 0xd0, 0x4d, 0xa7, 0x77, + 0xa0, 0x0f, 0x65, 0x79, 0xa2, 0x94, 0xe6, 0x04, 0x5f, 0x21, 0x07, 0xf6, 0xa7, 0x73, 0x9c, 0xe5, + 0x0c, 0x62, 0x07, 0xcd, 0xa0, 0xf9, 0xdc, 0x0c, 0xca, 0x57, 0x9f, 0x9d, 0x4e, 0x85, 0xd5, 0xb0, + 0x00, 0x98, 0xb9, 0xbf, 0xdf, 0x0a, 0x3c, 0x7f, 0xe6, 0x04, 0xca, 0xc2, 0x67, 0xb2, 0x69, 0x9e, + 0x10, 0xaf, 0x1e, 0xb4, 0xbd, 0xe6, 0x9e, 0x7c, 0xfe, 0x35, 0x7b, 0xf8, 0xf8, 0x28, 0x30, 0xee, + 0x6d, 0x0a, 0x58, 0xb3, 0xaf, 0xc1, 0xb8, 0xf8, 0x3f, 0xb1, 0x8e, 0x9c, 0x34, 0xbc, 0xcf, 0x35, + 0x4c, 0xaa, 0x07, 0xc7, 0x48, 0xf0, 0x2d, 0x32, 0x9c, 0x18, 0xac, 0xd8, 0x5b, 0x00, 0x42, 0x7f, + 0x23, 0x71, 0x7c, 0x2a, 0x7d, 0x53, 0x14, 0xd5, 0xbf, 0xbc, 0x20, 0x4e, 0x91, 0xd9, 0xbb, 0x30, + 0x26, 0x7e, 0x35, 0x7a, 0xad, 0x40, 0xac, 0x8d, 0xd3, 0x48, 0x2b, 0x73, 0x69, 0x08, 0xda, 0x48, + 0xc2, 0x8d, 0x5c, 0x1a, 0x29, 0x3a, 0xbb, 0x09, 0x53, 0xf8, 0x52, 0x2c, 0xbd, 0x3a, 0xe8, 0xf1, + 0x68, 0xe6, 0x8c, 0x76, 0x79, 0x2f, 0x8a, 0x1c, 0x2f, 0x29, 0xd3, 0x0f, 0x55, 0x19, 0x32, 0x16, + 0xc1, 0x89, 0xfc, 0x65, 0x72, 0x34, 0x33, 0x83, 0x9d, 0xa4, 0x8e, 0x12, 0x79, 0x0c, 0x29, 0x8f, + 0xc5, 0x88, 0x68, 0x82, 0x4b, 0xdd, 0x6e, 0xe9, 0x15, 0x16, 0x71, 0x67, 0x36, 0xb0, 0x1b, 0x8b, + 0xf5, 0xec, 0xe3, 0xa8, 0x67, 0xf1, 0x0b, 0x70, 0x98, 0xb7, 0x9b, 0x5d, 0xe7, 0x80, 0x07, 0x52, + 0x0b, 0xa8, 0xd9, 0x9f, 0x82, 0x53, 0x4a, 0x82, 0x50, 0x11, 0xcd, 0xeb, 0xd9, 0x63, 0x4a, 0xe2, + 0xd6, 0x66, 0x52, 0x75, 0x6e, 0x4a, 0x17, 0x57, 0xc1, 0x5c, 0x18, 0xc3, 0x61, 0xa5, 0x1a, 0x9f, + 0x39, 0xa8, 0xc6, 0x8b, 0xb9, 0x1a, 0x4f, 0xe3, 0x44, 0xc9, 0x57, 0xa6, 0xf3, 0x94, 0xaf, 0x9d, + 0xe1, 0x3a, 0xa2, 0xd9, 0xf6, 0x2c, 0xf6, 0x16, 0xbd, 0x76, 0x26, 0x57, 0x60, 0x6e, 0xc2, 0x99, + 0x24, 0xba, 0x44, 0x96, 0xb7, 0x5a, 0xe7, 0x0c, 0x89, 0x9c, 0x73, 0xa1, 0x30, 0x90, 0x85, 0x44, + 0x4a, 0xb5, 0x18, 0xed, 0x11, 0xa1, 0xe7, 0x50, 0x5f, 0x43, 0x89, 0xa4, 0x29, 0x3f, 0x4e, 0xe1, + 0x83, 0x42, 0x85, 0x1c, 0xd8, 0x3d, 0x38, 0x91, 0xec, 0xda, 0x1a, 0xe3, 0xb9, 0xf4, 0xf9, 0xcd, + 0x74, 0xab, 0x2f, 0xe6, 0x5b, 0x44, 0xcf, 0x5c, 0x38, 0x63, 0xec, 0xd3, 0x1a, 0xeb, 0xf3, 0xe9, + 0xbb, 0xcf, 0xe6, 0x26, 0x5f, 0xcc, 0xbe, 0x1f, 0x1f, 0xf6, 0x11, 0xcc, 0x66, 0xf7, 0x66, 0xad, + 0x96, 0xe7, 0xb1, 0x96, 0x57, 0x1e, 0xef, 0xcf, 0x5d, 0xc8, 0x6d, 0xef, 0xc5, 0x15, 0x1d, 0xc0, + 0x8d, 0x7d, 0x0b, 0x66, 0xcc, 0xfd, 0x59, 0xab, 0xc9, 0x4a, 0x1f, 0xc8, 0x4e, 0x36, 0xf6, 0xe2, + 0x1a, 0xfa, 0xf2, 0x60, 0x31, 0xcc, 0x15, 0xce, 0x6e, 0xad, 0x9a, 0x17, 0xd2, 0x0f, 0xca, 0xad, + 0x92, 0xe2, 0xea, 0x0e, 0x63, 0xc9, 0x1e, 0xc2, 0x73, 0x45, 0xdb, 0x84, 0x56, 0xe9, 0x8b, 0x89, + 0x35, 0xfa, 0xf3, 0xc5, 0x5b, 0x4e, 0x71, 0xcd, 0x87, 0xb0, 0x65, 0x5f, 0x87, 0x53, 0xda, 0xfa, + 0xd2, 0xea, 0x7b, 0x09, 0xeb, 0xc3, 0xa4, 0x2a, 0xfa, 0xc2, 0x2c, 0xae, 0xa5, 0x98, 0x07, 0xeb, + 0xc0, 0x09, 0xf5, 0xe1, 0x68, 0xf6, 0xa7, 0xad, 0xe7, 0x82, 0x21, 0x55, 0xf3, 0x18, 0x0b, 0xe7, + 0x49, 0xaa, 0xce, 0xb4, 0x36, 0x9d, 0x6e, 0x4a, 0xa8, 0xcf, 0xf4, 0x02, 0xbe, 0xec, 0x26, 0x0c, + 0x37, 0xea, 0x2b, 0xd7, 0xaf, 0x2f, 0xcf, 0xbc, 0x8c, 0x35, 0xa8, 0xb0, 0x69, 0x09, 0x34, 0x0e, + 0x4d, 0xe4, 0xf7, 0xda, 0xf5, 0xb6, 0xb6, 0x8c, 0x9b, 0x33, 0x89, 0xca, 0xfe, 0x34, 0x7a, 0x9c, + 0x0a, 0x89, 0x5a, 0x8b, 0x22, 0x6f, 0xdb, 0x97, 0x0f, 0x9b, 0xbe, 0x62, 0x38, 0x1e, 0xa8, 0xa7, + 0x6e, 0x17, 0xb9, 0x1f, 0xf3, 0x30, 0x87, 0x9e, 0x3a, 0x38, 0xa9, 0xa7, 0xd4, 0xdd, 0x94, 0x95, + 0x2e, 0xc4, 0xf3, 0x15, 0x89, 0x7e, 0xdb, 0xf6, 0x62, 0x67, 0xa7, 0x67, 0x7c, 0xfe, 0xcc, 0xe7, + 0x0d, 0x8f, 0xaa, 0x1b, 0x5e, 0x7c, 0xb3, 0xb7, 0xa9, 0xf5, 0xda, 0x8b, 0x54, 0xe1, 0xb3, 0xf2, + 0xb4, 0xdc, 0xa7, 0xe7, 0xa6, 0xb7, 0x33, 0x74, 0x11, 0xfb, 0xf3, 0x25, 0x38, 0xbd, 0x11, 0x84, + 0x0f, 0xda, 0x81, 0xdb, 0x52, 0x5f, 0x45, 0x32, 0xfc, 0xd5, 0x83, 0x64, 0xf8, 0x17, 0x72, 0x32, + 0xdc, 0x7a, 0x48, 0x6c, 0x9c, 0xe4, 0xa5, 0xe0, 0x9c, 0x3c, 0xef, 0x53, 0x15, 0xfb, 0xd3, 0x70, + 0xbe, 0xb8, 0x44, 0x9b, 0x94, 0xaf, 0xe1, 0xa4, 0x7c, 0xe3, 0xf1, 0xfe, 0xdc, 0x6b, 0xfd, 0x6a, + 0x2a, 0x9e, 0xa0, 0x87, 0xb2, 0x66, 0x6f, 0xc3, 0xc0, 0xda, 0x62, 0x7d, 0xe6, 0xb2, 0x71, 0x07, + 0xbe, 0xb6, 0x58, 0xd7, 0x3a, 0x4a, 0x9a, 0x56, 0x3b, 0x4d, 0xc3, 0xb4, 0xba, 0xb6, 0x58, 0x17, + 0x1b, 0xf9, 0xaa, 0xe7, 0xf7, 0x1e, 0x99, 0x3a, 0xea, 0x95, 0x74, 0x23, 0x6f, 0x8b, 0xd2, 0xfe, + 0x1a, 0x6a, 0x01, 0x35, 0x7b, 0x98, 0xe1, 0x29, 0xc7, 0xe3, 0xf5, 0x83, 0xc6, 0xe3, 0xf5, 0xdc, + 0x78, 0x64, 0x2b, 0xce, 0x8e, 0x45, 0x41, 0x15, 0xec, 0x63, 0x78, 0x36, 0x0f, 0xd5, 0xc6, 0xe0, + 0x0d, 0x1c, 0x83, 0xd7, 0x1e, 0xef, 0xcf, 0x5d, 0x2a, 0xe2, 0x5e, 0xdc, 0xff, 0x07, 0xb2, 0x64, + 0xdf, 0x02, 0x58, 0xe0, 0x6e, 0x87, 0xbe, 0x71, 0xfe, 0x98, 0xb6, 0x86, 0x4d, 0xee, 0x76, 0x0a, + 0x6c, 0x0d, 0x29, 0x47, 0xb1, 0x67, 0xa7, 0xbf, 0xb4, 0x4f, 0xb9, 0x9a, 0xee, 0xd9, 0x1a, 0x93, + 0x3e, 0x7b, 0x76, 0x11, 0x87, 0x5b, 0x83, 0x95, 0x8b, 0xd5, 0x4b, 0xb7, 0x06, 0x2b, 0x97, 0xaa, + 0xaf, 0xd8, 0xcf, 0x36, 0x6a, 0x6b, 0xab, 0x2b, 0x2d, 0xa5, 0x92, 0xa9, 0x27, 0xb0, 0x25, 0x8d, + 0x7d, 0xe1, 0xa0, 0x52, 0xed, 0x81, 0xc1, 0xbf, 0x5a, 0x82, 0xb9, 0x43, 0x44, 0x8b, 0xd0, 0x82, + 0xd2, 0x69, 0xd9, 0x48, 0xde, 0x41, 0x93, 0x91, 0xe2, 0x49, 0x81, 0x63, 0x7a, 0x3d, 0x99, 0x24, + 0x98, 0xa9, 0x41, 0x32, 0xd6, 0x13, 0x76, 0x90, 0xac, 0x32, 0x32, 0x35, 0x48, 0x90, 0xb5, 0x0a, + 0xd5, 0xac, 0xc8, 0x61, 0x5f, 0x82, 0x09, 0xfd, 0xd1, 0x62, 0x65, 0x80, 0x92, 0xf9, 0xf4, 0xc2, + 0x6d, 0x43, 0x8d, 0x32, 0x10, 0xad, 0x0b, 0x30, 0x69, 0x2e, 0x2e, 0x76, 0x12, 0x86, 0xe2, 0x20, + 0x68, 0x13, 0x0f, 0x5b, 0xfe, 0xb0, 0x7e, 0xb1, 0x04, 0x27, 0x0a, 0xe4, 0x37, 0xbb, 0x00, 0x83, + 0x75, 0x37, 0xde, 0xd1, 0x9d, 0xe3, 0xba, 0xae, 0x91, 0x39, 0x13, 0xcb, 0xd9, 0xeb, 0x30, 0xb2, + 0x74, 0xa7, 0xd1, 0xa8, 0xdd, 0x51, 0xa6, 0x2e, 0xa9, 0xe6, 0xf9, 0x91, 0x13, 0xb9, 0xa6, 0x4f, + 0x0d, 0xa1, 0xb1, 0xd7, 0x60, 0x78, 0xa5, 0x8e, 0x04, 0xda, 0x03, 0xd4, 0x5e, 0x37, 0x8b, 0x4f, + 0x48, 0xd6, 0x8f, 0x97, 0x80, 0xe5, 0x37, 0x23, 0xf6, 0x06, 0x8c, 0xe9, 0x5b, 0x9e, 0xec, 0x17, + 0x74, 0x34, 0xd0, 0xc4, 0xb2, 0xad, 0xe3, 0xb0, 0x25, 0x18, 0xc2, 0x17, 0x5c, 0x13, 0x67, 0x9e, + 0xc2, 0x05, 0x70, 0x26, 0xb7, 0x00, 0x86, 0xf0, 0x4d, 0x58, 0x5b, 0x12, 0x5b, 0xff, 0xb6, 0x04, + 0xac, 0xd8, 0x2f, 0xf6, 0x48, 0xce, 0x84, 0x6f, 0x6a, 0x89, 0xa1, 0x74, 0xc7, 0x58, 0x5f, 0x01, + 0x75, 0x23, 0x53, 0x9a, 0x42, 0xea, 0x82, 0x61, 0xd4, 0xec, 0x9f, 0x4d, 0xe4, 0x12, 0x0c, 0xdd, + 0xe7, 0xe1, 0xa6, 0x8a, 0x3e, 0x41, 0x8f, 0xf5, 0x5d, 0x01, 0xd0, 0x8d, 0x7c, 0x88, 0x61, 0x78, + 0xe8, 0x0e, 0x1d, 0xd5, 0x43, 0xf7, 0xf7, 0x4a, 0x70, 0xb2, 0xe8, 0xd8, 0x7c, 0x48, 0xa6, 0x10, + 0x2b, 0x93, 0xe4, 0x04, 0xbd, 0x0f, 0xa5, 0x0f, 0x7c, 0x92, 0xda, 0x64, 0x0e, 0x86, 0x44, 0x0f, + 0xa9, 0x69, 0x81, 0x96, 0x59, 0xd1, 0x85, 0x91, 0x2d, 0xe1, 0x02, 0x21, 0x7d, 0xc8, 0x6f, 0x48, + 0x22, 0xc8, 0xf7, 0xfb, 0x24, 0x5c, 0x20, 0xac, 0x05, 0x2d, 0xae, 0x2c, 0x96, 0x88, 0xd0, 0x11, + 0x00, 0x5b, 0xc2, 0xd9, 0x05, 0x18, 0xb9, 0xeb, 0xaf, 0x72, 0x77, 0x97, 0x53, 0x78, 0x0c, 0x7a, + 0x4b, 0x06, 0xbe, 0xd3, 0x16, 0x30, 0x5b, 0x15, 0x5a, 0x3f, 0x53, 0x82, 0xe9, 0xdc, 0x89, 0xfd, + 0xf0, 0x64, 0x28, 0x07, 0x07, 0x7f, 0x1f, 0xe5, 0xfb, 0x64, 0xf3, 0x07, 0x8b, 0x9b, 0x6f, 0xfd, + 0xd7, 0xc3, 0x70, 0xa6, 0x8f, 0x01, 0x35, 0x4d, 0x8e, 0x51, 0x3a, 0x34, 0x39, 0xc6, 0x37, 0x60, + 0x62, 0xb1, 0xed, 0x7a, 0x9d, 0x68, 0x3d, 0x48, 0x5b, 0x9c, 0xc6, 0xb8, 0x62, 0x19, 0xc5, 0x78, + 0x25, 0xc1, 0x90, 0x67, 0x9b, 0x48, 0xe1, 0xc4, 0x41, 0xfe, 0xfc, 0x66, 0x30, 0xcb, 0xa5, 0xa7, + 0x18, 0xf8, 0x63, 0x92, 0x9e, 0xc2, 0x0c, 0x58, 0x1e, 0x7c, 0xa2, 0x01, 0xcb, 0xc5, 0xf1, 0x2c, + 0x43, 0x9f, 0x26, 0xba, 0x69, 0x31, 0x1b, 0x95, 0x30, 0x9c, 0x73, 0x31, 0x3d, 0x3c, 0x1c, 0xe1, + 0xa6, 0x19, 0x5c, 0x3b, 0x82, 0xfe, 0x14, 0x17, 0xfa, 0x07, 0xcf, 0x9a, 0x59, 0xf8, 0xf4, 0x20, + 0xda, 0x6f, 0xc3, 0xc9, 0x22, 0x0b, 0xcc, 0x4c, 0xc5, 0xf0, 0x44, 0xef, 0x1b, 0x36, 0x70, 0x74, + 0x3b, 0xce, 0x83, 0x42, 0x3b, 0x8e, 0x4a, 0xba, 0x32, 0xda, 0x3f, 0x62, 0x34, 0x5d, 0x0b, 0x12, + 0xf7, 0xe0, 0xd4, 0x2c, 0xd6, 0x5f, 0x2e, 0x65, 0x52, 0x13, 0x65, 0xe9, 0xd9, 0x3b, 0x46, 0x06, + 0xc9, 0x97, 0xf3, 0x19, 0x24, 0x8b, 0xb3, 0x11, 0xc9, 0x4b, 0xa8, 0x57, 0x61, 0x98, 0xdc, 0x91, + 0xb4, 0xac, 0x4e, 0x39, 0x37, 0x24, 0xc2, 0xb1, 0xbe, 0x57, 0x36, 0x33, 0x73, 0xfc, 0x71, 0x5c, + 0xd7, 0x97, 0x60, 0x68, 0x63, 0x87, 0x87, 0x6a, 0x0b, 0xc2, 0x86, 0x3c, 0x14, 0x00, 0xbd, 0x21, + 0x88, 0xc1, 0xae, 0xc3, 0x64, 0x5d, 0xce, 0x73, 0x35, 0x79, 0x07, 0x53, 0xab, 0x61, 0x97, 0x6c, + 0xdb, 0x05, 0xb3, 0x37, 0x43, 0x25, 0xf8, 0xc8, 0x04, 0x29, 0xd7, 0x83, 0x50, 0xda, 0xf6, 0x87, + 0x52, 0x3e, 0x32, 0x0d, 0x8a, 0xb3, 0x15, 0x84, 0x39, 0x1b, 0x7f, 0x86, 0xca, 0xba, 0x91, 0x19, + 0x6a, 0xca, 0xba, 0x29, 0x83, 0x4d, 0xa5, 0xb2, 0x33, 0x99, 0xc6, 0x5e, 0xa7, 0x32, 0xde, 0xce, + 0x40, 0xad, 0x2d, 0x78, 0xee, 0x40, 0x46, 0x42, 0xc7, 0x80, 0x6e, 0xf2, 0x2b, 0x13, 0x0c, 0x71, + 0x20, 0xa9, 0xad, 0xd1, 0x59, 0xab, 0x69, 0x38, 0xf6, 0xca, 0x12, 0xc6, 0x83, 0xbf, 0x0d, 0xe3, + 0x7a, 0xa8, 0x10, 0x71, 0x2e, 0x88, 0x2c, 0x1a, 0x14, 0x03, 0x6b, 0x8f, 0x29, 0xe4, 0x95, 0x56, + 0x64, 0xfd, 0x2f, 0x03, 0x30, 0xd3, 0x2f, 0x20, 0x99, 0xfd, 0x64, 0x92, 0xc0, 0x0c, 0xa3, 0x6d, + 0x03, 0x33, 0x4c, 0x6b, 0x6c, 0xfe, 0xed, 0x43, 0x22, 0x9a, 0x2f, 0x17, 0x12, 0x4b, 0x3f, 0xfe, + 0x24, 0xca, 0x09, 0xf5, 0x09, 0x4c, 0x7e, 0xe3, 0x68, 0xa1, 0xef, 0x76, 0x71, 0xc5, 0xec, 0x03, + 0x38, 0x65, 0xf3, 0x66, 0xd0, 0xe9, 0x70, 0xbf, 0xa5, 0x87, 0x02, 0xd3, 0x52, 0xa2, 0x5c, 0x5e, + 0x09, 0x82, 0xc9, 0xb2, 0x90, 0x92, 0xdd, 0x81, 0xe9, 0x34, 0x59, 0xa8, 0x7a, 0x1a, 0x4b, 0x7b, + 0x41, 0x32, 0x4d, 0x6e, 0xaa, 0x1e, 0xc6, 0xd2, 0x4f, 0xf4, 0x39, 0x52, 0x76, 0x05, 0x60, 0xd1, + 0xf5, 0xeb, 0x61, 0xd0, 0xe4, 0x94, 0x8b, 0xa5, 0x42, 0x5e, 0xae, 0x2e, 0x26, 0x18, 0x13, 0x60, + 0x5b, 0x43, 0x99, 0x75, 0x60, 0xb6, 0x7f, 0x47, 0x15, 0xc4, 0x22, 0x7c, 0xde, 0x0c, 0x71, 0x39, + 0x95, 0x1b, 0x68, 0xc1, 0x47, 0x0f, 0x51, 0xf8, 0x36, 0x8c, 0xeb, 0x0b, 0x1c, 0x95, 0x25, 0xf1, + 0x9b, 0xc4, 0x97, 0x54, 0x96, 0x04, 0xc0, 0x96, 0xf0, 0xf4, 0x22, 0xbc, 0x5c, 0x7c, 0x11, 0x9e, + 0x4a, 0x9e, 0x81, 0xc3, 0x24, 0x8f, 0xa8, 0x1c, 0xf7, 0x62, 0xad, 0x72, 0xfc, 0xad, 0x57, 0x8e, + 0xe9, 0x53, 0x6d, 0x09, 0x7f, 0xa2, 0x95, 0xff, 0x43, 0xf5, 0xc0, 0x3d, 0x86, 0x3f, 0x9b, 0x41, + 0x86, 0x14, 0xfe, 0x9c, 0xdf, 0x67, 0x52, 0xcc, 0x54, 0x65, 0x2e, 0x1f, 0xaa, 0x32, 0x1f, 0x43, + 0x06, 0xe2, 0xf1, 0x4f, 0x4a, 0x81, 0xc1, 0xf4, 0x98, 0xe3, 0xe6, 0x7c, 0x16, 0x15, 0x96, 0xf5, + 0xf3, 0x25, 0x38, 0x55, 0x78, 0xe1, 0x28, 0x6a, 0x95, 0xd2, 0x4f, 0xdb, 0x02, 0xb2, 0x22, 0x4f, + 0x62, 0x1c, 0x27, 0x45, 0xda, 0xd1, 0xbf, 0xc5, 0x7a, 0x1e, 0x46, 0x13, 0x77, 0x17, 0x71, 0x8c, + 0x94, 0x43, 0x27, 0xf3, 0x6c, 0x93, 0xd7, 0xc4, 0xdf, 0x2c, 0x03, 0x88, 0x26, 0x7c, 0x86, 0x11, + 0x32, 0xb2, 0x0f, 0xfa, 0x44, 0xc8, 0x64, 0xfb, 0x23, 0x4b, 0xc7, 0xfe, 0x42, 0x09, 0x86, 0xa5, + 0xbe, 0x78, 0xb0, 0x46, 0xba, 0xf1, 0x64, 0x34, 0xd2, 0x2a, 0x4e, 0x77, 0xe3, 0x3c, 0x2b, 0xcb, + 0xac, 0x7f, 0x58, 0x86, 0x61, 0xd1, 0xa4, 0xa7, 0xf6, 0x3d, 0x96, 0xe2, 0x50, 0x1d, 0xf1, 0x49, + 0x07, 0xbc, 0x3e, 0xb5, 0x9c, 0x79, 0x7d, 0xea, 0x84, 0x4e, 0xa6, 0xde, 0xa3, 0x4f, 0x12, 0x86, + 0xf5, 0x7b, 0x6d, 0x4a, 0x8b, 0xf8, 0xf9, 0x67, 0x25, 0x99, 0xa6, 0x31, 0x79, 0x53, 0xff, 0x6b, + 0x30, 0xa9, 0xde, 0x98, 0x90, 0x49, 0xfc, 0xc8, 0xe1, 0x48, 0x79, 0x29, 0xab, 0x37, 0x26, 0xf4, + 0xa4, 0x7f, 0x06, 0xbe, 0xae, 0xfa, 0x74, 0x75, 0x64, 0xd6, 0x02, 0xd6, 0xd9, 0x72, 0x9d, 0x87, + 0xdc, 0x7d, 0xc0, 0xa3, 0xd8, 0x91, 0xde, 0xa4, 0xe4, 0x97, 0xa4, 0xd8, 0xaf, 0x5d, 0xaf, 0x49, + 0x47, 0x52, 0x4c, 0x25, 0x22, 0x1f, 0x0b, 0xc9, 0xd1, 0xe8, 0xce, 0x16, 0x9d, 0x2d, 0x77, 0x43, + 0x16, 0x4a, 0x3a, 0xeb, 0xdf, 0x0c, 0xcb, 0x25, 0x44, 0x8f, 0xd2, 0x6c, 0xc2, 0xe4, 0xdd, 0x95, + 0xa5, 0x45, 0xed, 0xea, 0xd5, 0x7c, 0x17, 0x68, 0xf9, 0x51, 0xcc, 0x43, 0xdf, 0x6d, 0x2b, 0x5b, + 0x56, 0xaa, 0xd2, 0x05, 0x5e, 0xab, 0x59, 0x7c, 0x2d, 0x9b, 0xe1, 0x28, 0xea, 0x90, 0x56, 0xb3, + 0xa4, 0x8e, 0xf2, 0x11, 0xeb, 0x88, 0xdc, 0x4e, 0xbb, 0x4f, 0x1d, 0x26, 0x47, 0xb6, 0x83, 0x66, + 0xad, 0x9d, 0xde, 0xa6, 0x56, 0xcb, 0xc0, 0xc1, 0xb5, 0xbc, 0x40, 0xb5, 0x3c, 0x43, 0x86, 0xf6, + 0xc2, 0x7a, 0x72, 0x5c, 0x53, 0xe1, 0x37, 0x78, 0xa8, 0xf0, 0xd3, 0xa4, 0xc1, 0xd0, 0x1f, 0xa5, + 0x34, 0x60, 0x4b, 0x99, 0x75, 0xc1, 0x12, 0x75, 0x6b, 0xdb, 0xf3, 0xb5, 0x47, 0xd8, 0x0e, 0x5c, + 0x16, 0x6c, 0x25, 0x4d, 0xe1, 0x36, 0x72, 0x68, 0xde, 0x1e, 0x95, 0xf6, 0x6e, 0x84, 0x52, 0xb8, + 0x99, 0x89, 0xdb, 0x56, 0x61, 0x94, 0x12, 0xc3, 0x2d, 0xec, 0x91, 0xab, 0x54, 0xd5, 0xf0, 0xba, + 0x6d, 0x2d, 0xec, 0xa5, 0x27, 0x63, 0x4a, 0x2d, 0x67, 0x66, 0x35, 0x4c, 0x19, 0xb0, 0xbb, 0x30, + 0x9a, 0x3e, 0xda, 0x63, 0xbe, 0x93, 0x99, 0xc0, 0x29, 0x17, 0xb2, 0xca, 0x2e, 0x55, 0xf0, 0x46, + 0x4f, 0xca, 0x83, 0xad, 0x42, 0x15, 0x3d, 0xb5, 0x79, 0x4b, 0xae, 0x9a, 0x95, 0x25, 0x99, 0x7c, + 0x8c, 0xf4, 0xb8, 0x58, 0x96, 0xd1, 0x72, 0xcb, 0x84, 0xba, 0xe7, 0x28, 0xad, 0x9f, 0x2e, 0x43, + 0x35, 0x3b, 0xfb, 0xd8, 0xbb, 0x30, 0x96, 0x3c, 0x9a, 0x94, 0x24, 0x97, 0x41, 0x97, 0x89, 0xf4, + 0x95, 0x25, 0x23, 0xcd, 0x8c, 0x8e, 0xce, 0xe6, 0xa1, 0x22, 0x16, 0xb1, 0x9f, 0xa6, 0x92, 0x47, + 0xb1, 0xdd, 0x23, 0x98, 0x6e, 0x54, 0x53, 0x78, 0xac, 0x01, 0x27, 0xc4, 0xa2, 0x69, 0x78, 0xfe, + 0x76, 0x9b, 0xaf, 0x06, 0xdb, 0x41, 0x2f, 0xbe, 0x67, 0xaf, 0x92, 0x0c, 0x97, 0xf6, 0x03, 0xb7, + 0xd3, 0x36, 0x8a, 0x43, 0xdd, 0xc7, 0xb7, 0x88, 0x9a, 0xbd, 0x26, 0xb7, 0x99, 0x95, 0x25, 0xf2, + 0x74, 0x44, 0xfd, 0x03, 0x5d, 0x85, 0x8d, 0xc6, 0x13, 0x92, 0x26, 0x59, 0x7f, 0xb7, 0x0c, 0x63, + 0xda, 0xf4, 0x63, 0x97, 0xa0, 0xb2, 0x12, 0xad, 0x06, 0xcd, 0x07, 0x49, 0x6e, 0xfd, 0x89, 0xc7, + 0xfb, 0x73, 0xa3, 0x5e, 0xe4, 0xb4, 0x11, 0x68, 0x27, 0xc5, 0x6c, 0x01, 0x26, 0xe4, 0x5f, 0x4a, + 0xc5, 0x2e, 0xa7, 0x76, 0x73, 0x89, 0x5c, 0xa0, 0x5e, 0x9b, 0x24, 0xec, 0x9b, 0x00, 0x12, 0x80, + 0x49, 0xaf, 0x06, 0x8e, 0x9e, 0xae, 0x8b, 0x2a, 0x28, 0x48, 0x77, 0xa5, 0x31, 0x64, 0x1f, 0xca, + 0x37, 0x99, 0xd4, 0x72, 0x19, 0x3c, 0x7a, 0xbe, 0x31, 0xc1, 0xdf, 0x29, 0x4e, 0x7b, 0xa8, 0xb3, + 0xa4, 0x07, 0xa3, 0x67, 0xc5, 0x41, 0x64, 0x97, 0x87, 0x7b, 0xb5, 0x18, 0x11, 0x35, 0x0c, 0xeb, + 0x7f, 0x2c, 0x69, 0x8b, 0x8c, 0xdd, 0x81, 0xd1, 0x64, 0x02, 0x91, 0x93, 0x71, 0x72, 0x64, 0x57, + 0x70, 0x9b, 0x6f, 0x2d, 0x3c, 0x43, 0x8e, 0xd7, 0x27, 0x92, 0x69, 0x68, 0xac, 0x39, 0x05, 0x64, + 0xef, 0xc3, 0x20, 0x76, 0x5d, 0xf9, 0xd0, 0x4f, 0x53, 0xbb, 0xfc, 0xa0, 0xe8, 0x33, 0xfc, 0x10, + 0xa4, 0x64, 0xaf, 0x53, 0xe6, 0x0c, 0xd9, 0xf9, 0x93, 0xda, 0x56, 0x2d, 0xda, 0x91, 0x6c, 0xef, + 0x69, 0xe6, 0x4b, 0x6d, 0xf6, 0xfc, 0xd5, 0x32, 0x54, 0xb3, 0x4b, 0x9b, 0xbd, 0x07, 0xe3, 0x6a, + 0xfb, 0xbd, 0xe9, 0xd2, 0xd3, 0xa9, 0xe3, 0xf4, 0x74, 0xa9, 0xda, 0x83, 0x77, 0x5c, 0xdd, 0x29, + 0xd9, 0x36, 0x08, 0x84, 0x2e, 0xb4, 0x4e, 0x29, 0xd2, 0xb5, 0x45, 0x15, 0x07, 0x71, 0x37, 0xf3, + 0x14, 0x90, 0x42, 0x63, 0x6f, 0xc2, 0xc0, 0xda, 0xf5, 0x1a, 0xc5, 0x9e, 0x57, 0xb3, 0x9b, 0x34, + 0xdd, 0x34, 0x1a, 0x21, 0x25, 0x02, 0x9f, 0xad, 0x6a, 0xaf, 0x66, 0x0d, 0x1b, 0x9e, 0xf0, 0x0a, + 0x9c, 0x7c, 0xdc, 0xe1, 0xcf, 0x67, 0xdd, 0x1a, 0xac, 0x0c, 0x54, 0x07, 0xe9, 0x81, 0x92, 0x05, + 0x98, 0xce, 0xb1, 0xd0, 0x16, 0xa9, 0xec, 0x92, 0x83, 0x17, 0xa9, 0xe5, 0xc3, 0xb8, 0x3e, 0x25, + 0x0e, 0x79, 0xb8, 0xee, 0x34, 0x26, 0xc7, 0x92, 0xfd, 0x35, 0xfc, 0x78, 0x7f, 0xae, 0xec, 0xb5, + 0x30, 0x25, 0xd6, 0x45, 0xa8, 0x28, 0x61, 0x47, 0x32, 0x06, 0xcd, 0xe0, 0xea, 0x9e, 0xd7, 0x4e, + 0x4a, 0xad, 0x97, 0x61, 0x84, 0x46, 0xfd, 0x60, 0xe3, 0xb7, 0xf5, 0xdd, 0x32, 0x4c, 0xd9, 0x5c, + 0x68, 0x9a, 0x5c, 0x3e, 0x16, 0xfb, 0xd4, 0x6a, 0xb7, 0xc5, 0x89, 0xe1, 0x8d, 0x6f, 0x3b, 0xe0, + 0xe1, 0xf3, 0x5f, 0x2e, 0xc1, 0x89, 0x02, 0x5c, 0x36, 0x0f, 0x63, 0xea, 0x3d, 0xf4, 0xb4, 0x13, + 0x31, 0x61, 0x99, 0xf2, 0x0b, 0xc2, 0xce, 0xd4, 0x91, 0xd8, 0x35, 0x18, 0x5d, 0xf2, 0xdc, 0x76, + 0xad, 0xd5, 0x4a, 0x32, 0x65, 0xe1, 0x16, 0x89, 0x8f, 0x19, 0xbb, 0x02, 0xaa, 0xaf, 0xff, 0x04, + 0x95, 0xbd, 0x42, 0x93, 0x62, 0x20, 0xe9, 0x56, 0x9c, 0x14, 0xdf, 0xdf, 0x9f, 0x03, 0xd9, 0xa6, + 0xf5, 0x64, 0x8a, 0xe0, 0x63, 0x0d, 0x12, 0x98, 0x46, 0xcb, 0x3d, 0xb5, 0x43, 0x57, 0xfc, 0x58, + 0x43, 0xf6, 0xf3, 0x8e, 0xf4, 0x54, 0xe4, 0xbf, 0x53, 0x86, 0xd3, 0xc5, 0x84, 0x9f, 0x68, 0x28, + 0x5f, 0x85, 0x51, 0x7c, 0x82, 0x54, 0x7b, 0x60, 0x66, 0xf2, 0xf1, 0xfe, 0x1c, 0xc8, 0xf7, 0x4a, + 0x11, 0x3f, 0x45, 0x60, 0x5b, 0x30, 0xb1, 0xea, 0x46, 0xf1, 0x4d, 0xee, 0x86, 0xf1, 0x26, 0x77, + 0xe3, 0x23, 0x6c, 0x82, 0xca, 0x35, 0x65, 0x06, 0x5f, 0xb3, 0xda, 0x51, 0x94, 0x99, 0x6d, 0xca, + 0x64, 0x9b, 0x4c, 0x94, 0xc1, 0x23, 0x4c, 0x94, 0x8f, 0x61, 0xaa, 0xc1, 0x3b, 0x6e, 0x77, 0x27, + 0x08, 0x55, 0x16, 0x8c, 0xcb, 0x30, 0x91, 0x80, 0x0a, 0x67, 0x8b, 0x59, 0x6c, 0xe0, 0x6b, 0x1d, + 0x91, 0x8a, 0x12, 0xb3, 0xd8, 0xfa, 0x6b, 0x65, 0x38, 0x53, 0x6b, 0x92, 0x9f, 0x2d, 0x15, 0xa8, + 0x70, 0x80, 0xcf, 0xb8, 0x6e, 0x76, 0x05, 0x46, 0xd7, 0xdc, 0x47, 0xab, 0xdc, 0x8d, 0x78, 0x44, + 0xaf, 0x17, 0xc9, 0x1d, 0xc3, 0x7d, 0x94, 0x5e, 0x5b, 0xd8, 0x29, 0x8e, 0xae, 0x81, 0x0f, 0x7e, + 0x4a, 0x0d, 0xdc, 0x82, 0xe1, 0x9b, 0x41, 0xbb, 0xc5, 0x43, 0xba, 0x95, 0xc5, 0xbb, 0xd2, 0x1d, + 0x84, 0xd8, 0x54, 0x62, 0xfd, 0x5e, 0x09, 0x26, 0x93, 0x16, 0x63, 0x13, 0x3e, 0xf3, 0x2e, 0xb9, + 0x00, 0x23, 0x58, 0xd1, 0xca, 0x92, 0xbe, 0x69, 0xb4, 0x39, 0x3e, 0xdb, 0xde, 0xb2, 0x55, 0xa1, + 0xde, 0x13, 0x43, 0x9f, 0xae, 0x27, 0xac, 0xbf, 0x83, 0xd7, 0xb0, 0xfa, 0x57, 0x8a, 0x9d, 0x48, + 0x6b, 0x48, 0xe9, 0x88, 0x0d, 0x29, 0x3f, 0xb1, 0x21, 0x19, 0xe8, 0x3b, 0x24, 0x7f, 0xb1, 0x0c, + 0x63, 0x49, 0x63, 0x7f, 0xc0, 0x5e, 0x39, 0x4a, 0xbe, 0xeb, 0x48, 0x99, 0xab, 0x1a, 0x9a, 0xac, + 0xa0, 0x04, 0x51, 0xef, 0xc3, 0x30, 0x2d, 0xa6, 0x52, 0xc6, 0x2d, 0x3e, 0x33, 0xba, 0x0b, 0x93, + 0xc4, 0x7a, 0x18, 0x07, 0x34, 0xb2, 0x89, 0x0e, 0x53, 0x83, 0x6d, 0xf0, 0x4d, 0xba, 0x95, 0x7f, + 0x6a, 0xf7, 0xa8, 0xe2, 0xd4, 0x60, 0xe9, 0x87, 0x1d, 0x69, 0x77, 0xfa, 0xef, 0x47, 0xa1, 0x9a, + 0x25, 0x39, 0xfc, 0x1d, 0xa9, 0x7a, 0x6f, 0x13, 0xfb, 0x62, 0x5c, 0xbe, 0x23, 0xd5, 0xed, 0x6d, + 0xda, 0x02, 0x86, 0x9e, 0x3e, 0xa1, 0xb7, 0x8b, 0x5f, 0x3d, 0x4e, 0x9e, 0x3e, 0xa1, 0xb7, 0x6b, + 0x78, 0xfa, 0x84, 0xde, 0x2e, 0xea, 0xe0, 0xab, 0x0d, 0x4c, 0x9b, 0x31, 0x88, 0xa8, 0x52, 0x07, + 0x6f, 0x47, 0xd9, 0x57, 0x7c, 0x15, 0x9a, 0xd8, 0x2a, 0x17, 0xb8, 0x1b, 0xd2, 0x9b, 0x47, 0x24, + 0xce, 0x70, 0xab, 0xdc, 0x44, 0xb0, 0x13, 0x0b, 0xb8, 0xad, 0x23, 0xb1, 0x36, 0x30, 0xed, 0xa7, + 0x5a, 0xc0, 0xc3, 0x87, 0x2e, 0x60, 0xe5, 0xd2, 0x7a, 0x52, 0x67, 0xed, 0xe8, 0xab, 0xb9, 0x80, + 0xef, 0x93, 0x34, 0x9c, 0xd4, 0x29, 0x5f, 0x36, 0x9e, 0xbd, 0x2a, 0x87, 0x32, 0x53, 0xe9, 0x7e, + 0x40, 0xe6, 0xd3, 0x4e, 0x4e, 0x60, 0x29, 0x13, 0xf6, 0x15, 0x18, 0xd3, 0x93, 0xa1, 0xc8, 0x94, + 0x1d, 0xcf, 0xca, 0x2c, 0xb7, 0x69, 0x16, 0x14, 0xf3, 0xce, 0x5e, 0xcf, 0x7a, 0xb2, 0x09, 0x67, + 0x16, 0x03, 0x3f, 0xea, 0x75, 0xd4, 0x55, 0x58, 0xfa, 0xc4, 0x03, 0xe0, 0x50, 0x60, 0x66, 0x85, + 0x26, 0xa1, 0xa8, 0x6b, 0x34, 0x95, 0x33, 0xc3, 0x38, 0x80, 0xf4, 0x63, 0xc4, 0xd6, 0x61, 0x0c, + 0x8d, 0x0f, 0xe4, 0xf0, 0x37, 0x66, 0x8a, 0x8d, 0xb4, 0x64, 0x49, 0x2c, 0x0c, 0xf9, 0x14, 0x86, + 0xdb, 0x69, 0xab, 0x90, 0x17, 0xe3, 0x29, 0x8c, 0x14, 0x99, 0x7d, 0x13, 0x26, 0xe5, 0x39, 0x6d, + 0x83, 0x6f, 0xca, 0xb9, 0x33, 0x6e, 0x5c, 0x83, 0x99, 0x85, 0xf2, 0x3e, 0x98, 0x4c, 0x3e, 0x18, + 0x1b, 0x2c, 0xa0, 0x46, 0xc0, 0x99, 0x81, 0xcf, 0xee, 0xc1, 0x89, 0x9b, 0x6e, 0x24, 0x81, 0x5a, + 0x56, 0x8b, 0x09, 0x34, 0x6e, 0x60, 0x20, 0xc0, 0x8e, 0x1b, 0x29, 0x1b, 0x52, 0x61, 0x16, 0x8b, + 0x22, 0x7a, 0xf6, 0xdd, 0x12, 0xcc, 0x18, 0x26, 0x26, 0xf2, 0x90, 0xc2, 0x5c, 0xd7, 0x93, 0x46, + 0x7c, 0x73, 0x3f, 0x34, 0x39, 0x24, 0x19, 0x2b, 0x56, 0x98, 0x96, 0xeb, 0x1e, 0xf6, 0xfd, 0x78, + 0xd0, 0x42, 0xc5, 0x35, 0x3d, 0x65, 0x2e, 0xd4, 0xcc, 0xba, 0x56, 0x68, 0x6c, 0x11, 0x26, 0x96, + 0x16, 0x1a, 0x8b, 0xf5, 0xde, 0x66, 0xdb, 0x6b, 0x8a, 0x43, 0x76, 0x15, 0xe9, 0xf0, 0xb6, 0xa6, + 0xb5, 0x19, 0x35, 0x9d, 0x2e, 0x96, 0x64, 0xce, 0xda, 0x26, 0x0d, 0xab, 0xa9, 0xf7, 0xe8, 0xa6, + 0xf1, 0x53, 0x4f, 0xe7, 0x64, 0xdb, 0xa1, 0x0f, 0xd2, 0x59, 0xd7, 0xb2, 0xe3, 0xce, 0x26, 0xa1, + 0xec, 0x91, 0x8c, 0xb7, 0xcb, 0x5e, 0x4b, 0x3a, 0x31, 0x8a, 0x09, 0x41, 0x39, 0xda, 0xf0, 0x87, + 0xf5, 0xba, 0x2e, 0x0f, 0x49, 0x3d, 0x3d, 0x50, 0x1e, 0x5a, 0xff, 0xdd, 0x30, 0x4c, 0x65, 0xa6, + 0x27, 0x9d, 0x97, 0x4b, 0xb9, 0xf3, 0x72, 0x03, 0x40, 0x5a, 0x6b, 0x8e, 0x68, 0x56, 0x51, 0xd1, + 0xb5, 0x63, 0x14, 0xa4, 0x9f, 0xac, 0x6d, 0x8d, 0x8d, 0x60, 0x2a, 0x25, 0xc7, 0x11, 0xcd, 0x5c, + 0x09, 0x53, 0x29, 0x7c, 0x34, 0xa6, 0x29, 0x1b, 0x36, 0x07, 0x43, 0x98, 0x5d, 0x5b, 0x0f, 0x6e, + 0xf6, 0x04, 0xc0, 0x96, 0x70, 0xf6, 0x02, 0x0c, 0x0b, 0x65, 0x6e, 0x65, 0x89, 0x84, 0x31, 0xee, + 0x71, 0x42, 0xdb, 0x13, 0x9a, 0x13, 0x15, 0xb1, 0x6b, 0x30, 0x2e, 0xff, 0xa2, 0x24, 0x4e, 0xc3, + 0xa6, 0xcf, 0xa1, 0xe3, 0xb5, 0x54, 0x1e, 0x27, 0x03, 0x4f, 0x9c, 0x72, 0x1a, 0xbd, 0xcd, 0x8f, + 0x78, 0x53, 0x48, 0x98, 0x91, 0xf4, 0x94, 0x13, 0x49, 0xa0, 0xa8, 0x22, 0x45, 0x10, 0x3a, 0x15, + 0x85, 0x18, 0x55, 0xf0, 0x6c, 0x8b, 0x3a, 0x95, 0x0c, 0x2d, 0xb2, 0xa9, 0x84, 0x5d, 0x92, 0xc6, + 0x54, 0x54, 0x4f, 0xe5, 0xeb, 0xe9, 0x68, 0x7a, 0x44, 0x03, 0x09, 0xea, 0xa8, 0x49, 0xb1, 0xa8, + 0x5c, 0xfc, 0xbd, 0xdc, 0x71, 0xbd, 0x36, 0x89, 0x37, 0xac, 0x1c, 0x71, 0xb9, 0x80, 0xda, 0x29, + 0x02, 0x7b, 0x17, 0x26, 0xe5, 0xe3, 0xc1, 0x9d, 0x4e, 0xe0, 0x23, 0xfb, 0xb1, 0xd4, 0x4d, 0x87, + 0x5e, 0x5e, 0x16, 0x45, 0xb2, 0x96, 0x0c, 0xae, 0xd8, 0xd7, 0xf0, 0xa2, 0xa6, 0x27, 0xcd, 0xbc, + 0xe3, 0xe9, 0xbe, 0x86, 0xa4, 0x91, 0x84, 0xdb, 0x3a, 0x12, 0x7b, 0x0b, 0x26, 0xc4, 0xcf, 0x1b, + 0xde, 0x2e, 0x97, 0x15, 0x4e, 0xa4, 0xd7, 0xae, 0x48, 0xb5, 0x2d, 0x4a, 0x64, 0x7d, 0x26, 0x26, + 0xfb, 0x00, 0x4e, 0x21, 0xa7, 0x66, 0xd0, 0xe5, 0xad, 0xda, 0xd6, 0x96, 0xd7, 0xf6, 0xa4, 0x3f, + 0xd7, 0x64, 0xea, 0x0f, 0x21, 0x2b, 0x46, 0x0c, 0xc7, 0x4d, 0x51, 0xec, 0x62, 0x4a, 0xb6, 0x01, + 0xd5, 0xc5, 0x5e, 0x14, 0x07, 0x9d, 0x5a, 0x1c, 0x87, 0xde, 0x66, 0x2f, 0xe6, 0xd1, 0xcc, 0x94, + 0x91, 0xd4, 0x47, 0x2c, 0x8e, 0xa4, 0x50, 0xda, 0xa5, 0x9a, 0x48, 0xe1, 0xb8, 0x09, 0x89, 0x9d, + 0x63, 0x62, 0xfd, 0x8b, 0x12, 0x4c, 0x18, 0xa4, 0xec, 0x4d, 0x18, 0xbf, 0x1e, 0x7a, 0xdc, 0x6f, + 0xb5, 0xf7, 0xb4, 0x03, 0x33, 0x9e, 0xa6, 0xb6, 0x08, 0x2e, 0xbf, 0xda, 0x40, 0x4b, 0xec, 0x4d, + 0xe5, 0x42, 0x67, 0xcb, 0x2b, 0x32, 0xc6, 0x9e, 0x26, 0xe8, 0x40, 0x9a, 0x65, 0x0c, 0x27, 0x28, + 0xcd, 0x4e, 0x0d, 0x85, 0x7d, 0x19, 0x86, 0xe5, 0x95, 0x0e, 0x79, 0xfe, 0x9d, 0x2d, 0xfa, 0x4c, + 0x99, 0xcf, 0x01, 0x27, 0x22, 0x3a, 0x23, 0x44, 0x36, 0x11, 0x59, 0x3f, 0x5b, 0x02, 0x96, 0x47, + 0x3d, 0xc4, 0xfe, 0x76, 0xa8, 0x93, 0xc3, 0xfb, 0xc9, 0x6a, 0x1c, 0x30, 0xee, 0x52, 0x45, 0x4d, + 0xb2, 0x40, 0x76, 0x3c, 0xad, 0x3a, 0xdd, 0x20, 0x28, 0x8b, 0xad, 0xef, 0x94, 0x01, 0x52, 0x6c, + 0xf6, 0x25, 0xf9, 0xf4, 0xea, 0x07, 0x3d, 0xb7, 0xed, 0x6d, 0x79, 0x66, 0xee, 0x63, 0x64, 0xf2, + 0xb1, 0x2a, 0xb1, 0x4d, 0x44, 0xf6, 0x1e, 0x4c, 0x35, 0xea, 0x26, 0xad, 0xe6, 0xb5, 0x1e, 0x75, + 0x9d, 0x0c, 0x79, 0x16, 0x1b, 0x3d, 0x7c, 0xf5, 0xd1, 0x90, 0x1e, 0xbe, 0x72, 0x20, 0xa8, 0x44, + 0x08, 0x96, 0x46, 0x9d, 0x1c, 0xf3, 0x5b, 0xc9, 0xc5, 0x04, 0xb6, 0x2e, 0xea, 0x3a, 0x5d, 0xf2, + 0xd8, 0x17, 0x72, 0xc2, 0xc0, 0x4b, 0x3b, 0x72, 0xa8, 0x4f, 0xce, 0x86, 0x9f, 0x43, 0xf3, 0x63, + 0x27, 0x88, 0x39, 0x59, 0x5d, 0x9e, 0xda, 0xf3, 0x57, 0x7a, 0x1f, 0x38, 0x64, 0x84, 0xa2, 0x1b, + 0x5f, 0x47, 0x77, 0xde, 0x57, 0xd3, 0xc3, 0x92, 0xbc, 0x19, 0x2c, 0xb8, 0x26, 0xff, 0x1b, 0x25, + 0x38, 0x55, 0x48, 0xcb, 0x2e, 0x03, 0xa4, 0xb6, 0x2d, 0xea, 0x25, 0x94, 0x98, 0x69, 0x6e, 0x2d, + 0x5b, 0xc3, 0x60, 0xdf, 0xc8, 0x5a, 0xa5, 0x0e, 0xdf, 0x08, 0x67, 0x55, 0x4a, 0x4b, 0xd3, 0x2a, + 0x55, 0x60, 0x8b, 0xb2, 0x7e, 0x79, 0x00, 0xa6, 0xb5, 0xd4, 0x5d, 0xb2, 0xad, 0x87, 0x78, 0x5c, + 0x3f, 0x80, 0x71, 0xf1, 0x35, 0x5e, 0x93, 0xe2, 0x5a, 0xe4, 0xdd, 0xf5, 0x2b, 0xb9, 0x60, 0x62, + 0xe2, 0x76, 0x59, 0x47, 0x96, 0x0e, 0x6a, 0x28, 0x3a, 0xf1, 0x41, 0xf1, 0x66, 0x3e, 0xc0, 0xc5, + 0x60, 0xce, 0x22, 0x98, 0x58, 0xda, 0xf3, 0xdd, 0x4e, 0x52, 0x9b, 0xbc, 0xc3, 0xfe, 0x7c, 0xdf, + 0xda, 0x0c, 0x6c, 0x59, 0x5d, 0x1a, 0x76, 0x27, 0xcb, 0x0a, 0x32, 0x3e, 0x18, 0x54, 0xb3, 0xef, + 0xc1, 0x74, 0xae, 0xd1, 0xc7, 0xca, 0x79, 0xbb, 0x01, 0x2c, 0xdf, 0x8e, 0xa3, 0xbb, 0x9b, 0x89, + 0x3d, 0xcf, 0xf5, 0x5b, 0xf2, 0x46, 0x7c, 0x5e, 0x77, 0x37, 0xfb, 0x5f, 0xcb, 0x7a, 0x40, 0xf7, + 0xd3, 0xbe, 0xea, 0xde, 0x37, 0x4e, 0xe5, 0xcf, 0xf5, 0x1b, 0xd3, 0xfe, 0xd6, 0x0f, 0x76, 0x0d, + 0x68, 0x0d, 0x66, 0xf2, 0x9d, 0xe4, 0x79, 0xd0, 0x4a, 0x95, 0xd8, 0x62, 0xe8, 0x70, 0xd7, 0x96, + 0xfa, 0x92, 0x2d, 0x7f, 0x68, 0xeb, 0xf7, 0x77, 0x06, 0xe0, 0x4c, 0x9f, 0x76, 0xb0, 0xbd, 0xec, + 0x94, 0x94, 0xb6, 0x95, 0x37, 0x0e, 0x6e, 0xfe, 0x93, 0x98, 0x98, 0xec, 0x4b, 0x32, 0x41, 0x4c, + 0x33, 0xf0, 0xb7, 0xbc, 0x6d, 0xb2, 0x2a, 0xe0, 0xe5, 0xc4, 0x83, 0x04, 0x9a, 0xcd, 0x0c, 0x23, + 0xa1, 0xec, 0x3d, 0x18, 0xc2, 0xdc, 0x00, 0x99, 0x54, 0xa4, 0x02, 0x03, 0xe1, 0x5a, 0x32, 0x5d, + 0xf1, 0xd3, 0x48, 0xa6, 0x2b, 0x00, 0xec, 0x8b, 0x30, 0x50, 0xdb, 0x68, 0xd0, 0x28, 0x4f, 0xea, + 0xe4, 0x1b, 0x8d, 0xf4, 0x95, 0x2f, 0xd7, 0x78, 0x8e, 0x4b, 0x50, 0x08, 0xc2, 0x1b, 0x8b, 0x75, + 0x1a, 0x63, 0x9d, 0xf0, 0xc6, 0x62, 0x3d, 0x25, 0xdc, 0x36, 0xa3, 0x0f, 0x6f, 0x2c, 0xd6, 0x3f, + 0xbb, 0x45, 0xf4, 0xff, 0x2f, 0xcb, 0xac, 0x36, 0xf2, 0xc3, 0xde, 0x83, 0x71, 0xe5, 0x94, 0xa8, + 0x09, 0x3d, 0xf2, 0x76, 0x25, 0xd7, 0xdf, 0x8c, 0x0b, 0x81, 0x41, 0xa0, 0xde, 0xcb, 0x4b, 0xbc, + 0x73, 0xf5, 0xdb, 0xf7, 0x84, 0x43, 0x36, 0xb4, 0xc7, 0x24, 0x61, 0x57, 0xa1, 0xb2, 0xce, 0x7d, + 0xd7, 0x8f, 0x13, 0x33, 0x2f, 0xba, 0x50, 0xc6, 0x08, 0x33, 0x75, 0x90, 0x04, 0x11, 0xbd, 0xfd, + 0x7a, 0x9b, 0x51, 0x33, 0xf4, 0x30, 0xfb, 0x55, 0xb2, 0xb3, 0x4b, 0x6f, 0x3f, 0xad, 0xc4, 0x64, + 0x90, 0x21, 0xb2, 0x7e, 0xae, 0x04, 0x23, 0x34, 0x90, 0xf2, 0x9d, 0xd5, 0xed, 0x74, 0x67, 0x22, + 0x67, 0xfe, 0x6d, 0x2f, 0xeb, 0xcc, 0xbf, 0x2d, 0x53, 0x4c, 0x8d, 0x52, 0x14, 0x5d, 0x72, 0xe1, + 0x89, 0xb3, 0x51, 0x45, 0x06, 0x9b, 0xcf, 0x58, 0x26, 0xa8, 0x47, 0x8d, 0xaa, 0xb2, 0x7e, 0x81, + 0x5a, 0x76, 0x63, 0xb1, 0xce, 0xe6, 0xa1, 0xb2, 0x1a, 0xc8, 0xb4, 0x6d, 0xd4, 0x36, 0x14, 0x62, + 0x6d, 0x82, 0xe9, 0x1d, 0xa4, 0xf0, 0x44, 0xfb, 0xea, 0x61, 0x40, 0x27, 0x23, 0xad, 0x7d, 0x5d, + 0x09, 0xcc, 0xb4, 0x2f, 0x41, 0x3d, 0x72, 0xfb, 0x3e, 0x2c, 0x12, 0x12, 0x52, 0xc0, 0x2c, 0xc3, + 0x28, 0x66, 0xb5, 0xdd, 0xe5, 0xf4, 0xa6, 0xef, 0xd8, 0xfc, 0xcb, 0xfd, 0x04, 0xc4, 0x92, 0x42, + 0x24, 0x21, 0x95, 0x52, 0x5a, 0x1b, 0x70, 0xfe, 0x30, 0x74, 0x76, 0x15, 0x06, 0xdc, 0x87, 0x2a, + 0xe3, 0xf6, 0xf3, 0xfd, 0x2a, 0xa9, 0x6d, 0x34, 0x88, 0xbd, 0xc0, 0xb6, 0x7e, 0xad, 0xa4, 0xe7, + 0x36, 0xc8, 0xe2, 0xb0, 0x2b, 0x70, 0x32, 0xe2, 0x71, 0xaf, 0xab, 0xac, 0x53, 0x5b, 0x41, 0xe8, + 0xb8, 0x21, 0xf5, 0xbc, 0x3d, 0x8d, 0x65, 0xd2, 0x26, 0x75, 0x3d, 0x08, 0x6b, 0xa1, 0xcf, 0xce, + 0xc3, 0x98, 0xe7, 0xc7, 0x7c, 0x9b, 0x42, 0x5e, 0xe4, 0x8e, 0xa8, 0x83, 0xd8, 0x0d, 0x38, 0x9d, + 0x7c, 0x97, 0x23, 0x5f, 0x4a, 0x6b, 0x61, 0x28, 0x42, 0x46, 0x0b, 0x97, 0x6f, 0xa4, 0xd9, 0x41, + 0x9b, 0xdb, 0x27, 0x13, 0x02, 0x09, 0x6c, 0x09, 0xa8, 0xc5, 0x0b, 0x7a, 0xfd, 0xfe, 0x55, 0xf4, + 0x37, 0xbf, 0xa5, 0xc7, 0x08, 0x52, 0x91, 0x92, 0xcf, 0xb3, 0xfd, 0x7a, 0xe6, 0xfe, 0x55, 0xbb, + 0x80, 0xca, 0xfa, 0xfd, 0xb2, 0xce, 0xac, 0xc1, 0xc3, 0xdd, 0xa7, 0x78, 0xa7, 0x2d, 0xbe, 0xa3, + 0xcd, 0x7e, 0xde, 0x01, 0x1b, 0xed, 0x17, 0x33, 0x1b, 0xed, 0x5c, 0x3f, 0x16, 0xa4, 0xe7, 0x1e, + 0x79, 0xa7, 0xfd, 0xf1, 0x01, 0x38, 0x5d, 0xdc, 0x0e, 0xbd, 0x6b, 0x4a, 0x07, 0x74, 0xcd, 0x45, + 0xa8, 0xdc, 0x0c, 0xa2, 0x58, 0x73, 0xfd, 0xc2, 0x9b, 0xa9, 0x1d, 0x82, 0xd9, 0x49, 0x29, 0x7b, + 0x01, 0x86, 0xc5, 0xdf, 0x89, 0x8c, 0x45, 0x7e, 0x98, 0x90, 0xc7, 0x6b, 0xd9, 0x54, 0xc4, 0x6e, + 0x40, 0xc5, 0xa6, 0xe8, 0xb5, 0x4c, 0x4f, 0x2b, 0x70, 0xa2, 0x60, 0xb3, 0x90, 0x20, 0xc6, 0x5b, + 0x14, 0x04, 0x63, 0x35, 0x18, 0xa1, 0xc9, 0x94, 0xf1, 0x6a, 0x28, 0x98, 0x81, 0xb2, 0x29, 0x74, + 0x93, 0x6d, 0x2b, 0x3a, 0xb1, 0x2d, 0xe0, 0xfd, 0xf4, 0xca, 0x92, 0x0a, 0x44, 0xc3, 0x6d, 0x41, + 0xde, 0x5f, 0x9b, 0x5e, 0x76, 0x09, 0x22, 0x9b, 0x83, 0xb1, 0x90, 0xb7, 0xdd, 0x3d, 0xb9, 0xe7, + 0x50, 0xbf, 0x03, 0x82, 0xe4, 0x66, 0xf3, 0x0c, 0x8c, 0x4a, 0x04, 0xaf, 0x45, 0x56, 0x20, 0xbb, + 0x82, 0x80, 0x95, 0x56, 0x64, 0xad, 0xc3, 0x4c, 0xbf, 0x31, 0x15, 0xe7, 0xdc, 0xd8, 0x0d, 0xb7, + 0x39, 0x1e, 0x2a, 0xda, 0x14, 0xa5, 0x9c, 0xba, 0x4d, 0xad, 0x63, 0xd9, 0x4d, 0x2c, 0xb2, 0xc7, + 0x63, 0xed, 0x97, 0xf5, 0xdd, 0x32, 0x80, 0x32, 0x2e, 0x3e, 0xb5, 0x8b, 0xe8, 0x8b, 0xc6, 0x22, + 0x3a, 0xa5, 0xe5, 0xcc, 0x14, 0x9f, 0x75, 0xa4, 0x3b, 0xba, 0xbb, 0x30, 0x69, 0xe2, 0x1f, 0xfe, + 0xb4, 0xf1, 0x7a, 0x6a, 0x77, 0xa5, 0x88, 0x0e, 0xbc, 0xbd, 0x91, 0x70, 0x6b, 0x13, 0x4e, 0xde, + 0xe0, 0x71, 0x6a, 0x85, 0x55, 0x37, 0xf5, 0x07, 0xb3, 0x7d, 0x15, 0x46, 0x09, 0x3f, 0xd9, 0x18, + 0xa5, 0xc9, 0x90, 0xf2, 0x6e, 0xa1, 0xc9, 0x50, 0x21, 0x08, 0x81, 0xbb, 0xc4, 0xdb, 0x3c, 0xe6, + 0x9f, 0x6d, 0x35, 0x0d, 0x60, 0xf2, 0x53, 0xf0, 0xcb, 0x8e, 0x56, 0xc3, 0xa1, 0xfd, 0x73, 0x1f, + 0x4e, 0x25, 0x6d, 0x7f, 0x92, 0x7c, 0xaf, 0xc0, 0x94, 0xd2, 0xe0, 0x34, 0x8e, 0x07, 0xb8, 0x6a, + 0x7d, 0xaf, 0x04, 0xb3, 0x8a, 0x62, 0xc3, 0x4b, 0x7c, 0xf4, 0x8e, 0x44, 0xcc, 0xde, 0x85, 0x31, + 0x8d, 0x86, 0x82, 0x31, 0xf0, 0x5a, 0xe7, 0xa1, 0x17, 0xef, 0x38, 0x91, 0x84, 0xeb, 0xd7, 0x3a, + 0x1a, 0x3a, 0xdb, 0x84, 0xd9, 0x46, 0x6d, 0x6d, 0x35, 0x0d, 0xa7, 0xba, 0x13, 0x5c, 0x0f, 0xda, + 0xed, 0xe0, 0xe1, 0x3d, 0x7b, 0x55, 0x3d, 0xff, 0x86, 0xe9, 0x3c, 0xf0, 0x8e, 0x48, 0x8b, 0xc9, + 0xf2, 0x03, 0x67, 0x0b, 0x11, 0x9d, 0x5e, 0xd8, 0x8e, 0xec, 0x03, 0xb8, 0x58, 0xff, 0x45, 0x09, + 0x9e, 0x49, 0x62, 0x7b, 0x0a, 0xbe, 0x2f, 0xf3, 0x05, 0xa5, 0x27, 0xf9, 0x05, 0xe5, 0x27, 0xf2, + 0x05, 0x77, 0xd2, 0xf1, 0x59, 0xf1, 0x93, 0x0c, 0x00, 0xaa, 0xfd, 0x4c, 0x1f, 0x1f, 0x1a, 0x95, + 0x67, 0x73, 0x39, 0x05, 0xb4, 0xd4, 0x01, 0xd6, 0x3b, 0x5a, 0x87, 0x14, 0x30, 0x34, 0x88, 0x4b, + 0x59, 0xe2, 0xef, 0x96, 0x61, 0xea, 0xee, 0xca, 0xd2, 0x62, 0xe2, 0x76, 0xf8, 0xd4, 0x0a, 0xcd, + 0x62, 0xc7, 0x3e, 0xe3, 0xdb, 0xfa, 0x4b, 0x4e, 0xeb, 0x1e, 0x9c, 0xc8, 0x74, 0x03, 0xea, 0x79, + 0x5f, 0x91, 0x41, 0x1a, 0x09, 0x58, 0xe9, 0x78, 0xa7, 0x8b, 0xd8, 0xdf, 0xbf, 0x6a, 0x67, 0xb0, + 0xad, 0x7f, 0x39, 0x9e, 0xe1, 0x4b, 0xc2, 0xf8, 0x55, 0x18, 0x5d, 0x89, 0xa2, 0x1e, 0x0f, 0xef, + 0xd9, 0xab, 0xba, 0x6d, 0xce, 0x43, 0xa0, 0x98, 0x43, 0x76, 0x8a, 0xc0, 0x2e, 0x41, 0x85, 0xde, + 0xbc, 0x50, 0xd2, 0x0d, 0xaf, 0x49, 0x92, 0x27, 0x33, 0xec, 0xa4, 0x98, 0xbd, 0x09, 0xe3, 0xf2, + 0x6f, 0x39, 0xa3, 0xa9, 0xc3, 0xd1, 0x1a, 0x4f, 0xe8, 0x72, 0x05, 0xd8, 0x06, 0x1a, 0x7b, 0x05, + 0x06, 0x6a, 0x8b, 0x36, 0xd9, 0x5f, 0xe9, 0x68, 0x15, 0x3a, 0xd2, 0x48, 0x6e, 0x9c, 0xb3, 0x17, + 0x6d, 0x71, 0x40, 0x52, 0xc9, 0x57, 0xe8, 0xea, 0x08, 0x67, 0x80, 0x32, 0xef, 0x66, 0x54, 0x05, + 0x84, 0xb1, 0x2b, 0x30, 0xb2, 0xe4, 0x45, 0xdd, 0xb6, 0xbb, 0x47, 0x17, 0x47, 0xf2, 0xd5, 0x68, + 0x09, 0x32, 0x92, 0x88, 0x48, 0x10, 0xbb, 0xa4, 0x1e, 0x4c, 0xad, 0xa4, 0xb1, 0x1e, 0x7d, 0x5e, + 0x45, 0x4d, 0x43, 0xb1, 0x47, 0x0f, 0x0f, 0xc5, 0xce, 0x07, 0x51, 0xc3, 0x93, 0x0c, 0xa2, 0xde, + 0x84, 0x33, 0x37, 0xd0, 0x5c, 0x6a, 0xa6, 0x15, 0xbc, 0x67, 0xaf, 0xd0, 0x05, 0x14, 0xde, 0xff, + 0x4a, 0x8b, 0x6a, 0x36, 0x33, 0xa1, 0xd3, 0x0b, 0x3d, 0xfd, 0x4a, 0xbe, 0x0f, 0x23, 0xf6, 0x55, + 0x38, 0x59, 0x54, 0x44, 0xd7, 0x54, 0x98, 0x8c, 0xa7, 0xb8, 0x02, 0x3d, 0x19, 0x4f, 0x11, 0x07, + 0xb6, 0x0a, 0x55, 0x09, 0xaf, 0xb5, 0x3a, 0x9e, 0x2f, 0xaf, 0xda, 0x26, 0xd2, 0x20, 0x5a, 0xe2, + 0xea, 0x8a, 0x42, 0x79, 0xe5, 0x66, 0x84, 0xeb, 0x64, 0x28, 0xd9, 0x4f, 0x96, 0x60, 0xdc, 0xe6, + 0xf2, 0x1d, 0x05, 0x14, 0x9f, 0x93, 0xe4, 0x3c, 0x90, 0xc4, 0xcf, 0x34, 0xe2, 0xd0, 0xf3, 0xb7, + 0x29, 0x14, 0x67, 0x9d, 0x42, 0x71, 0xde, 0xfd, 0x44, 0xa1, 0x38, 0x92, 0x55, 0xf4, 0x78, 0x7f, + 0x6e, 0x3c, 0xa4, 0x3a, 0x71, 0x15, 0x19, 0x2d, 0x10, 0x5d, 0x87, 0x71, 0xd9, 0xf7, 0x7c, 0x99, + 0xc5, 0x9d, 0xb7, 0xe4, 0x47, 0x4e, 0xa1, 0x60, 0xc7, 0xae, 0x73, 0xa5, 0x10, 0x4f, 0x10, 0x72, + 0x1f, 0x5a, 0xc8, 0x81, 0x2d, 0xc8, 0xeb, 0x3f, 0xb1, 0x95, 0xca, 0xb0, 0xdc, 0x6a, 0x6a, 0x9b, + 0x51, 0xb1, 0x21, 0x0e, 0x4e, 0x23, 0x7d, 0xf2, 0x18, 0x24, 0xec, 0x0a, 0x0c, 0xaf, 0xb9, 0x8f, + 0x6a, 0x74, 0xc3, 0x3e, 0x36, 0x3f, 0xa1, 0xc4, 0x1f, 0x02, 0x17, 0x2a, 0xff, 0x5c, 0xc6, 0x07, + 0x7c, 0xce, 0x26, 0x34, 0xf6, 0xe7, 0x4a, 0x70, 0x5a, 0x2e, 0x63, 0xf5, 0x95, 0x0d, 0x1e, 0xc7, + 0xa2, 0x1f, 0x28, 0x0b, 0xeb, 0xf9, 0x24, 0xcb, 0xfb, 0xdd, 0x62, 0x3c, 0x4c, 0x16, 0x61, 0x91, + 0x64, 0x48, 0x3a, 0x2e, 0xa2, 0x52, 0x23, 0xaf, 0x7e, 0x21, 0x3d, 0x5b, 0x87, 0xb1, 0xb5, 0xeb, + 0xb5, 0xa4, 0xda, 0x13, 0xc6, 0x99, 0xcd, 0x90, 0x7c, 0x1a, 0x5a, 0x91, 0x77, 0xbe, 0xce, 0x06, + 0x8f, 0x23, 0xb7, 0x17, 0x97, 0x31, 0x3d, 0xc3, 0xc9, 0xd4, 0x4a, 0xd5, 0x7d, 0xd0, 0xe4, 0xd9, + 0x2c, 0xf0, 0x09, 0x22, 0x7b, 0x4f, 0x06, 0x0c, 0x62, 0x16, 0x1f, 0x71, 0x1a, 0x3f, 0x95, 0xa6, + 0xca, 0x95, 0x2f, 0x05, 0x50, 0x81, 0x6e, 0x5f, 0xd3, 0x09, 0xd8, 0x5d, 0x50, 0xe9, 0x1e, 0xee, + 0xe2, 0x25, 0x36, 0x56, 0x7f, 0x3a, 0x0d, 0xd2, 0x51, 0x9e, 0x33, 0x81, 0xbc, 0xee, 0xce, 0x34, + 0x24, 0x4f, 0xcb, 0xee, 0xc1, 0x0c, 0xf7, 0xe3, 0xd0, 0x75, 0xbc, 0x16, 0x25, 0xd5, 0x4c, 0x2e, + 0xc1, 0xe8, 0x6d, 0x74, 0x75, 0xfd, 0xb3, 0x2c, 0xd0, 0x56, 0x96, 0xe4, 0xb5, 0xb8, 0x92, 0x9a, + 0xf6, 0x29, 0xa4, 0x5e, 0x69, 0x99, 0x60, 0x8a, 0x37, 0xd8, 0x83, 0x53, 0x85, 0x54, 0x6c, 0x16, + 0x2a, 0x2d, 0x2f, 0x4a, 0x5f, 0xf0, 0xaa, 0xd8, 0xc9, 0x6f, 0x76, 0x0e, 0x40, 0x26, 0x23, 0x14, + 0xd5, 0x2a, 0x55, 0x02, 0x21, 0x78, 0xa3, 0xf9, 0x12, 0x4c, 0x6e, 0x87, 0x6e, 0x77, 0xc7, 0xe1, + 0x7e, 0xab, 0x1b, 0x78, 0x3e, 0xed, 0x1f, 0xf6, 0x04, 0x42, 0x97, 0x09, 0x68, 0x7d, 0x51, 0x4d, + 0x54, 0xf6, 0x9a, 0x1e, 0x2c, 0x3c, 0x80, 0xa3, 0x34, 0xd2, 0x71, 0x1f, 0x39, 0xee, 0x36, 0x37, + 0xfc, 0x96, 0xe8, 0x1e, 0xef, 0x67, 0x4a, 0x70, 0xb6, 0xef, 0x5c, 0x64, 0xd7, 0xe0, 0x8c, 0x2b, + 0xb3, 0x26, 0x38, 0x3b, 0x71, 0xdc, 0x8d, 0x1c, 0x75, 0xb2, 0x56, 0x29, 0xad, 0x4e, 0x51, 0xf1, + 0x4d, 0x51, 0xaa, 0x0e, 0xdb, 0x11, 0x7b, 0x0f, 0x9e, 0xf5, 0xfc, 0x88, 0x37, 0x7b, 0x21, 0x77, + 0x14, 0x83, 0xa6, 0xd7, 0x0a, 0x9d, 0xd0, 0xf5, 0xb7, 0x55, 0xac, 0xb4, 0x7d, 0x56, 0xe1, 0x50, + 0x66, 0x86, 0x45, 0xaf, 0x15, 0xda, 0x88, 0x60, 0xfd, 0x8d, 0x32, 0xcc, 0xf4, 0x9b, 0xab, 0x6c, + 0x06, 0x46, 0xb8, 0xaf, 0xf7, 0xa6, 0xfa, 0x29, 0x8e, 0xb7, 0xc9, 0x16, 0x4c, 0x7d, 0x59, 0x69, + 0xd2, 0xdb, 0x54, 0xec, 0x05, 0x30, 0x37, 0x5c, 0xea, 0xc9, 0xf1, 0xa6, 0xbe, 0xed, 0x9e, 0x03, + 0x48, 0xf7, 0x59, 0x69, 0x54, 0xb5, 0x47, 0xdd, 0x66, 0x28, 0x45, 0x22, 0x3b, 0x0d, 0xc3, 0x72, + 0x1f, 0x93, 0x1b, 0xb3, 0x4d, 0xbf, 0x84, 0x42, 0x45, 0x9d, 0x8c, 0x1b, 0xf0, 0xc0, 0xc2, 0xb8, + 0xd1, 0xd9, 0xc3, 0x1d, 0x39, 0x38, 0x85, 0xf3, 0x79, 0xe4, 0x93, 0xcf, 0x67, 0xeb, 0x2f, 0x4f, + 0x48, 0x65, 0xb1, 0xd6, 0x8b, 0x77, 0x94, 0x7a, 0x39, 0x5f, 0x14, 0x4d, 0x27, 0xdd, 0xe5, 0xb5, + 0x68, 0x3a, 0x33, 0x86, 0x4e, 0x5d, 0xab, 0x97, 0x0b, 0xaf, 0xd5, 0x5f, 0x85, 0xd1, 0xc5, 0x1d, + 0xde, 0x7c, 0x90, 0x84, 0x28, 0x55, 0xe8, 0xde, 0x52, 0x00, 0xe5, 0x5b, 0x26, 0x29, 0x02, 0xbb, + 0x02, 0x80, 0x41, 0xbc, 0xf2, 0x14, 0xa5, 0xbd, 0x47, 0x86, 0x31, 0xbf, 0xe4, 0x81, 0xa8, 0xa1, + 0x20, 0xfb, 0x86, 0x7d, 0x5d, 0x77, 0x59, 0x94, 0xec, 0xa3, 0x70, 0x8b, 0xd0, 0x53, 0x04, 0xf1, + 0x79, 0xda, 0x0e, 0x42, 0xfa, 0x4e, 0x35, 0xb7, 0xcd, 0xe8, 0x48, 0xec, 0x8b, 0x30, 0xb2, 0xc8, + 0xc3, 0x78, 0x7d, 0x7d, 0x15, 0xfd, 0x04, 0xe5, 0x33, 0x5c, 0x15, 0x7c, 0x32, 0x29, 0x8e, 0xdb, + 0xdf, 0xdf, 0x9f, 0x9b, 0x88, 0xbd, 0x0e, 0x4f, 0x9e, 0x17, 0xb1, 0x15, 0x36, 0x5b, 0x80, 0xaa, + 0xf4, 0x20, 0x4a, 0xcf, 0xbf, 0xa8, 0xd3, 0x54, 0xa4, 0x86, 0x45, 0xee, 0x46, 0x0f, 0xf9, 0x66, + 0xf2, 0x60, 0x54, 0x0e, 0x9f, 0x2d, 0xab, 0x77, 0xd6, 0xf4, 0x66, 0x43, 0x2a, 0x43, 0xb3, 0xb2, + 0x5e, 0xb4, 0x3e, 0x4f, 0xc1, 0x6a, 0x30, 0xb1, 0x18, 0x74, 0xba, 0x6e, 0xec, 0xe1, 0xb3, 0xcb, + 0x7b, 0xa4, 0xbe, 0xa0, 0x34, 0x6d, 0xea, 0x05, 0x86, 0x2e, 0xa4, 0x17, 0xb0, 0xeb, 0x30, 0x69, + 0x07, 0x3d, 0xd1, 0xed, 0xca, 0x3a, 0x25, 0x35, 0x14, 0xf4, 0xe6, 0x0b, 0x45, 0x89, 0x50, 0xa8, + 0xc8, 0x14, 0x65, 0x64, 0x40, 0x37, 0xa8, 0xd8, 0x9d, 0x82, 0x9b, 0x63, 0x5d, 0x2d, 0xd1, 0x9f, + 0x8d, 0xca, 0x31, 0x2b, 0xb8, 0x74, 0xbe, 0x0a, 0x63, 0x8d, 0xc6, 0xdd, 0x75, 0x1e, 0xc5, 0xd7, + 0xdb, 0xc1, 0x43, 0xd4, 0x4a, 0x2a, 0xf4, 0xca, 0x69, 0x14, 0x38, 0xb1, 0x58, 0x11, 0x5b, 0xed, + 0xe0, 0xa1, 0xad, 0x63, 0xb1, 0x6f, 0x89, 0xfe, 0xd0, 0x74, 0x78, 0xca, 0xf5, 0x7e, 0xd0, 0x31, + 0x03, 0xf7, 0xfe, 0x74, 0x11, 0x88, 0xc3, 0x86, 0xd9, 0x59, 0x1a, 0x3a, 0x46, 0xd0, 0x85, 0xc1, + 0xa3, 0xbd, 0x5a, 0xab, 0x15, 0xf2, 0x28, 0x22, 0xf5, 0x41, 0x46, 0xd0, 0xa1, 0x11, 0xce, 0x95, + 0x05, 0x46, 0x04, 0x9d, 0x46, 0xc0, 0x16, 0x85, 0x5e, 0x2b, 0x46, 0x11, 0xfd, 0x4b, 0x57, 0xea, + 0xa8, 0x01, 0xd0, 0x15, 0x0d, 0x8d, 0xb9, 0xf4, 0x44, 0xf5, 0xba, 0xa6, 0xfa, 0xaa, 0xd1, 0xb0, + 0x15, 0x98, 0x92, 0x00, 0xb1, 0xb4, 0xe4, 0x1b, 0x87, 0x27, 0xd2, 0x57, 0x96, 0x88, 0x0d, 0x6e, + 0xa6, 0xf8, 0xce, 0xa1, 0x9e, 0x8f, 0x21, 0x43, 0xc7, 0xde, 0x83, 0x49, 0x7c, 0x40, 0x26, 0x71, + 0x2c, 0xc4, 0x8d, 0x7c, 0x5c, 0xe6, 0x35, 0xa7, 0x92, 0x8c, 0xbf, 0xe1, 0x78, 0x14, 0xed, 0xa4, + 0xee, 0x86, 0xef, 0xc1, 0x24, 0xba, 0x34, 0xa6, 0x0c, 0x4e, 0xa5, 0x0c, 0xa8, 0x24, 0xcb, 0x20, + 0x6e, 0x47, 0x29, 0x83, 0x9f, 0x2e, 0xc1, 0x59, 0x51, 0x91, 0x1b, 0x8b, 0x31, 0x95, 0xc7, 0x74, + 0x14, 0x0a, 0xe8, 0xaf, 0x29, 0x1f, 0xbf, 0x7a, 0xed, 0xb2, 0x52, 0x2a, 0x2f, 0x6b, 0x68, 0x97, + 0x77, 0xdf, 0xb8, 0x5c, 0x4b, 0x7f, 0x36, 0x14, 0x91, 0xcc, 0xf4, 0xdc, 0x97, 0xa7, 0xae, 0xbc, + 0x47, 0xd1, 0x4e, 0x11, 0x07, 0x6c, 0x94, 0x68, 0x7c, 0x71, 0xa3, 0xce, 0x7c, 0xe2, 0x46, 0xf5, + 0xe5, 0xa9, 0x37, 0x2a, 0x6e, 0x47, 0x85, 0x8d, 0xba, 0x06, 0x13, 0xa8, 0x5a, 0x91, 0x4a, 0x1b, + 0xd2, 0xd3, 0x5a, 0xb8, 0x26, 0x8c, 0x02, 0x7b, 0x5c, 0xfc, 0xbc, 0x4f, 0xbf, 0xd8, 0x17, 0x81, + 0xbc, 0x9b, 0x77, 0x84, 0xaa, 0x70, 0x36, 0x3d, 0x3c, 0xa6, 0x50, 0xfd, 0xde, 0x0b, 0xa1, 0x37, + 0x3d, 0x3f, 0x4e, 0x6d, 0xf5, 0xb3, 0x9a, 0xad, 0xfe, 0xd6, 0x60, 0x65, 0xa4, 0x5a, 0xb9, 0x35, + 0x58, 0x99, 0xae, 0x32, 0x7b, 0x34, 0x19, 0x47, 0xfb, 0x54, 0xe1, 0x77, 0xa1, 0xe5, 0xa2, 0x51, + 0x5b, 0x5b, 0x4d, 0x8f, 0xdf, 0x3f, 0x58, 0x21, 0x89, 0xc6, 0xb7, 0x1d, 0x10, 0x92, 0x78, 0x4f, + 0x06, 0x97, 0x6b, 0xdd, 0xa0, 0x2c, 0x17, 0x06, 0x38, 0x6b, 0xb9, 0xc8, 0xd0, 0xd8, 0x19, 0x6c, + 0xeb, 0x6f, 0x4e, 0x64, 0xf8, 0x52, 0x18, 0x82, 0x05, 0xc3, 0xd2, 0x30, 0x41, 0x9d, 0x8c, 0x7e, + 0x60, 0xd2, 0x6c, 0x61, 0x53, 0x09, 0x3b, 0x0b, 0x03, 0x8d, 0xc6, 0x5d, 0xea, 0x64, 0x0c, 0x46, + 0x88, 0xa2, 0xc0, 0x16, 0x30, 0x31, 0x42, 0x18, 0x61, 0xa0, 0x3d, 0xde, 0x23, 0x36, 0x46, 0x1b, + 0xa1, 0xa2, 0xbf, 0x95, 0x99, 0x60, 0x30, 0xed, 0x6f, 0x32, 0x13, 0xa4, 0xc6, 0x81, 0x45, 0x98, + 0xa9, 0x45, 0x11, 0x0f, 0xc5, 0x8c, 0x20, 0xc7, 0xf5, 0x90, 0x8e, 0xb2, 0xb4, 0xa3, 0x63, 0xa5, + 0x6e, 0x33, 0xb2, 0xfb, 0x22, 0xb2, 0x8b, 0x50, 0xa9, 0xf5, 0x5a, 0x1e, 0xf7, 0x9b, 0x46, 0xb2, + 0x48, 0x97, 0x60, 0x76, 0x52, 0xca, 0x3e, 0x80, 0x53, 0x99, 0xa4, 0xb3, 0xd4, 0x03, 0x23, 0xa9, + 0x90, 0x56, 0x47, 0xed, 0xd4, 0xc9, 0x4d, 0x76, 0x49, 0x31, 0x25, 0xab, 0x41, 0x75, 0x19, 0x43, + 0x70, 0x97, 0xb8, 0xbc, 0x21, 0x0f, 0x42, 0xd4, 0x15, 0xc8, 0x30, 0x42, 0x09, 0x99, 0x5b, 0x49, + 0xa1, 0x9d, 0x43, 0x67, 0xb7, 0xe1, 0x44, 0x16, 0x26, 0xb6, 0x7a, 0x69, 0x03, 0x41, 0x21, 0x99, + 0xe3, 0x82, 0x9b, 0x7d, 0x11, 0x15, 0xdb, 0x84, 0xe9, 0xd4, 0xc9, 0xd3, 0xb4, 0x8c, 0xa8, 0x18, + 0x96, 0xa4, 0x5c, 0x59, 0x47, 0x9e, 0xa1, 0xc9, 0x78, 0x22, 0x75, 0x18, 0x4d, 0x2c, 0x24, 0x76, + 0x9e, 0x1d, 0x6b, 0xc1, 0x64, 0xc3, 0xdb, 0xf6, 0x3d, 0x7f, 0xfb, 0x36, 0xdf, 0xab, 0xbb, 0x5e, + 0x48, 0xd1, 0x04, 0x33, 0xc9, 0x7d, 0xec, 0x5e, 0xa7, 0xc3, 0xe3, 0x10, 0x57, 0xbd, 0x28, 0xc7, + 0xd0, 0xfc, 0x12, 0xe6, 0x82, 0x93, 0x74, 0x42, 0xdc, 0x3b, 0x5d, 0xd7, 0x33, 0xb4, 0x05, 0x93, + 0xa7, 0x61, 0x9d, 0x1a, 0x3f, 0xa2, 0x75, 0xaa, 0x0d, 0xd3, 0xcb, 0x7e, 0x33, 0xdc, 0x43, 0x47, + 0x05, 0xd5, 0xb8, 0x89, 0x43, 0x1a, 0xf7, 0x22, 0x35, 0xee, 0x59, 0x57, 0xcd, 0xb0, 0xa2, 0xe6, + 0xe5, 0x19, 0xb3, 0x06, 0x4c, 0xe3, 0x11, 0x64, 0x65, 0xa9, 0xbe, 0xe2, 0x7b, 0xb1, 0xe7, 0xc6, + 0xbc, 0x45, 0x5a, 0x48, 0xf2, 0xf6, 0x9a, 0xb4, 0x42, 0x78, 0xad, 0xae, 0xe3, 0x29, 0x14, 0x9d, + 0x69, 0x8e, 0xfe, 0x20, 0x53, 0xc0, 0xd4, 0x1f, 0x92, 0x29, 0x60, 0x05, 0xa6, 0xb2, 0x19, 0x2e, + 0xaa, 0xa9, 0xf2, 0x10, 0x61, 0x91, 0xd0, 0x41, 0x82, 0x1e, 0x6a, 0x9d, 0x46, 0x32, 0xa7, 0x4c, + 0x6e, 0x8b, 0x8c, 0x55, 0x61, 0xda, 0xb0, 0x2a, 0x18, 0x52, 0xe9, 0x38, 0x56, 0x85, 0x3a, 0xc0, + 0xf5, 0x20, 0x6c, 0xf2, 0x1a, 0x46, 0xff, 0x33, 0xe3, 0x85, 0x4a, 0xc1, 0x34, 0x2d, 0x94, 0xeb, + 0x67, 0x4b, 0xfc, 0x76, 0xb2, 0xd1, 0xff, 0x1a, 0x0f, 0xe6, 0xc2, 0x99, 0x7a, 0xc8, 0xb7, 0x78, + 0x18, 0xf2, 0x16, 0x1d, 0x88, 0x16, 0x3c, 0xbf, 0xa5, 0x9e, 0x1d, 0xa5, 0xa7, 0x21, 0xba, 0x0a, + 0x25, 0x89, 0xbd, 0xd9, 0x94, 0x48, 0xfa, 0xde, 0xdc, 0x87, 0x4f, 0xce, 0xaa, 0x71, 0xf2, 0xb8, + 0x56, 0x8d, 0x65, 0x98, 0x5c, 0xf1, 0x9b, 0xed, 0x5e, 0x8b, 0x93, 0x6f, 0x3e, 0x3d, 0x1a, 0x8a, + 0x9a, 0xa1, 0x27, 0x4b, 0x1c, 0x72, 0xe1, 0xd7, 0xd7, 0x95, 0x49, 0x74, 0xa0, 0x2d, 0xe3, 0xf4, + 0x27, 0xb6, 0x65, 0xb0, 0x1a, 0x8c, 0x35, 0x43, 0x8e, 0xf9, 0x08, 0xdc, 0x76, 0x44, 0x0a, 0x50, + 0xe1, 0x48, 0x2f, 0xa6, 0x68, 0xb6, 0x4e, 0x63, 0xdd, 0x85, 0x99, 0x7e, 0x88, 0xec, 0x2a, 0x0c, + 0x05, 0x62, 0x00, 0x33, 0x2f, 0x8c, 0xdf, 0x15, 0xc3, 0x27, 0xa7, 0xb1, 0xce, 0x56, 0xe2, 0x5a, + 0x3f, 0x04, 0xa7, 0x8b, 0x11, 0xcc, 0x13, 0x7f, 0xe9, 0xb0, 0x13, 0x7f, 0x39, 0x7f, 0xe2, 0xb7, + 0x7e, 0xac, 0x9c, 0x69, 0xed, 0xd1, 0x4c, 0x0d, 0x9f, 0x87, 0xfc, 0x9e, 0x40, 0xfc, 0xab, 0x3c, + 0xbb, 0x33, 0xcc, 0x43, 0xb1, 0xe8, 0x27, 0x13, 0xc4, 0x89, 0x2c, 0xc1, 0xbd, 0xb0, 0xcd, 0xae, + 0xc1, 0x98, 0x36, 0xdd, 0x71, 0xf7, 0xed, 0xb7, 0x38, 0x6c, 0xd8, 0x4a, 0x57, 0xc0, 0x69, 0xa0, + 0xcd, 0x5f, 0x99, 0x28, 0xe4, 0x2f, 0x56, 0x05, 0xb1, 0xe7, 0xcb, 0x8d, 0xd5, 0x16, 0x7f, 0x32, + 0x06, 0xb8, 0xd3, 0x93, 0x9b, 0x00, 0xfe, 0x6d, 0xfd, 0xa5, 0x09, 0xa9, 0xc3, 0xe9, 0x06, 0x85, + 0x7e, 0x51, 0x3a, 0x19, 0x43, 0x43, 0xf9, 0x38, 0x86, 0x86, 0x81, 0xc3, 0x0d, 0x0d, 0x83, 0x87, + 0x19, 0x1a, 0x32, 0x96, 0x80, 0xa1, 0x63, 0x5a, 0x02, 0x46, 0x8e, 0x65, 0x09, 0x30, 0x8c, 0x14, + 0x95, 0xc3, 0x8c, 0x14, 0x7f, 0x62, 0x37, 0x78, 0x5a, 0xed, 0x06, 0x45, 0x4a, 0xfe, 0xb1, 0xec, + 0x06, 0xb9, 0x63, 0xff, 0xf4, 0x93, 0x39, 0xf6, 0xb3, 0x27, 0x76, 0xec, 0x3f, 0xf1, 0x69, 0x8f, + 0xfd, 0x27, 0x9f, 0xe4, 0xb1, 0xff, 0xd4, 0x1f, 0xc7, 0x63, 0xff, 0xe9, 0x3f, 0x9a, 0x63, 0xff, + 0x55, 0xa8, 0xd4, 0x83, 0x28, 0xbe, 0x1e, 0x84, 0x1d, 0xdc, 0x78, 0xc7, 0xe9, 0x96, 0x25, 0x88, + 0x30, 0xed, 0x72, 0xc7, 0xd0, 0x95, 0x09, 0x91, 0x2d, 0xa8, 0x09, 0xa7, 0x0e, 0xc6, 0x33, 0xe9, + 0x45, 0x17, 0xcd, 0x14, 0x3a, 0x1f, 0xe7, 0xe7, 0x9b, 0x3a, 0x2f, 0xdf, 0x81, 0x69, 0x15, 0x27, + 0x88, 0x5b, 0x2b, 0xda, 0x1c, 0xce, 0xa6, 0x2b, 0x33, 0x0d, 0x28, 0x54, 0xa5, 0xc6, 0xc3, 0xc3, + 0x59, 0xd2, 0xbe, 0xe6, 0x84, 0xe1, 0xea, 0xc8, 0xad, 0xc1, 0x4a, 0xb5, 0x3a, 0x7d, 0x04, 0x73, + 0xc2, 0x0f, 0x43, 0x35, 0x7b, 0xc2, 0x39, 0xfc, 0x41, 0x80, 0x27, 0x96, 0xe3, 0x56, 0x9c, 0xbf, + 0xb2, 0x27, 0x0c, 0x76, 0x05, 0xa0, 0x1e, 0x7a, 0xbb, 0x6e, 0xcc, 0x6f, 0x2b, 0x47, 0x74, 0x7a, + 0x01, 0x43, 0x42, 0xc5, 0x32, 0xb0, 0x35, 0x94, 0xe4, 0x70, 0x5d, 0x2e, 0x3a, 0x5c, 0x5b, 0x3f, + 0x5a, 0x86, 0x69, 0x99, 0x53, 0xf1, 0xe9, 0x77, 0xf6, 0xf8, 0x8a, 0x61, 0x32, 0x79, 0x36, 0x7d, + 0xd1, 0x49, 0xff, 0xba, 0x03, 0xdc, 0x3d, 0xbe, 0x09, 0xa7, 0x72, 0x5d, 0x81, 0x66, 0x93, 0x25, + 0x95, 0xcd, 0x32, 0x67, 0x38, 0x99, 0x29, 0xae, 0xe4, 0xfe, 0x55, 0x3b, 0x47, 0x61, 0xfd, 0xca, + 0x50, 0x8e, 0x3f, 0x39, 0x7e, 0xe8, 0xae, 0x1c, 0xa5, 0xe3, 0xb9, 0x72, 0x94, 0x8f, 0xe6, 0xca, + 0x91, 0xd1, 0x53, 0x06, 0x8e, 0xa2, 0xa7, 0x7c, 0x00, 0x13, 0xeb, 0xdc, 0xed, 0x44, 0xeb, 0x01, + 0x3d, 0xbd, 0x24, 0x83, 0x28, 0x55, 0xb2, 0x4a, 0x51, 0xa6, 0x4e, 0xfd, 0x49, 0xf8, 0x46, 0x2c, + 0x08, 0xc4, 0x4e, 0x2c, 0x5f, 0x63, 0xb2, 0x4d, 0x0e, 0xba, 0x29, 0x67, 0xe8, 0x00, 0x53, 0x4e, + 0x03, 0xc6, 0x89, 0x2e, 0x7d, 0x05, 0x21, 0xb5, 0x39, 0x88, 0x22, 0x84, 0xab, 0xda, 0x55, 0x56, + 0x85, 0xc9, 0xa4, 0x76, 0x69, 0x6e, 0x30, 0x98, 0x88, 0x2e, 0x50, 0x77, 0x95, 0xa2, 0x0b, 0x46, + 0xd2, 0x2e, 0x50, 0xf7, 0x9a, 0xb2, 0x0b, 0x34, 0x24, 0xf6, 0x2e, 0x4c, 0xd6, 0xea, 0x2b, 0x3a, + 0x59, 0x25, 0xf5, 0x26, 0x71, 0xbb, 0x9e, 0x63, 0x90, 0x66, 0x70, 0x0f, 0x3a, 0x7e, 0x8f, 0xfe, + 0x21, 0x1d, 0xbf, 0xb3, 0x07, 0x45, 0x38, 0xe6, 0x41, 0xd1, 0xfa, 0x17, 0xe3, 0x4a, 0x3e, 0x7c, + 0xb6, 0xf7, 0x7b, 0xe6, 0x8d, 0xdd, 0xc0, 0x31, 0x6f, 0xec, 0x06, 0x0f, 0x53, 0x86, 0x35, 0x9d, + 0x7b, 0xf8, 0x53, 0xdf, 0xbe, 0x8d, 0x1c, 0x53, 0x8b, 0xce, 0x2c, 0xbe, 0xca, 0x51, 0x16, 0x5f, + 0xa1, 0xe6, 0x3d, 0xfa, 0xe9, 0x35, 0x6f, 0x38, 0xb6, 0xe6, 0xdd, 0x48, 0xb3, 0xa5, 0x8c, 0x1d, + 0x1a, 0xfc, 0x79, 0x8e, 0x6c, 0x55, 0xd3, 0xc5, 0x29, 0x33, 0x93, 0xbc, 0x29, 0x3f, 0x50, 0xea, + 0xfc, 0x87, 0xc5, 0xea, 0xfc, 0xc1, 0x1b, 0xd0, 0x9f, 0x28, 0xf4, 0x7f, 0xa2, 0xd0, 0xff, 0xa1, + 0x28, 0xf4, 0x77, 0x81, 0xb9, 0xbd, 0x78, 0x47, 0xe8, 0xc5, 0x4d, 0x4c, 0xdb, 0x2c, 0x86, 0x18, + 0x55, 0x7b, 0x5a, 0x23, 0xf9, 0x52, 0x7d, 0x8d, 0x18, 0xa5, 0x68, 0xf3, 0x48, 0x14, 0xeb, 0x19, + 0x53, 0xb1, 0x1e, 0xaa, 0x0e, 0x1f, 0x59, 0xb1, 0x0e, 0x71, 0x9d, 0x6d, 0xb8, 0xa1, 0x8f, 0xdb, + 0xd4, 0x15, 0x18, 0x51, 0xc9, 0x83, 0x4b, 0xe9, 0x85, 0x47, 0x3e, 0x6b, 0xb0, 0xc2, 0x62, 0xf3, + 0x50, 0x51, 0xc4, 0xfa, 0x0b, 0x74, 0x0f, 0x09, 0x66, 0xe4, 0x65, 0x25, 0x98, 0xf5, 0x1f, 0x0c, + 0x2a, 0x59, 0x2e, 0x3e, 0xa3, 0xee, 0x86, 0x6e, 0x07, 0x9f, 0x3e, 0x4e, 0x96, 0x9a, 0xa6, 0xd6, + 0x67, 0x56, 0x67, 0x26, 0xfe, 0xce, 0x24, 0xf9, 0x44, 0xd9, 0x9f, 0x5f, 0x85, 0x61, 0xd2, 0xaa, + 0xe4, 0x39, 0x00, 0x35, 0x8a, 0xdc, 0x13, 0x96, 0x84, 0xc3, 0xde, 0x92, 0x91, 0xb7, 0x94, 0x3a, + 0x65, 0x30, 0x7d, 0x0b, 0x5c, 0xc8, 0xb7, 0x82, 0xb7, 0xc0, 0x53, 0x64, 0x76, 0x4d, 0x46, 0x9b, + 0xea, 0x2f, 0xe9, 0x24, 0x31, 0xbb, 0xb9, 0x07, 0x14, 0x52, 0xd4, 0xf4, 0x9c, 0x32, 0x7c, 0x9c, + 0xbc, 0xea, 0x23, 0x7f, 0xa4, 0x79, 0xd5, 0x97, 0x01, 0x68, 0xcf, 0x4d, 0xdd, 0x6c, 0x5e, 0x42, + 0x91, 0x44, 0x21, 0x1f, 0x71, 0xdc, 0xee, 0xf3, 0xee, 0x95, 0x46, 0x68, 0xfd, 0x53, 0x06, 0xd3, + 0x8d, 0xc6, 0xdd, 0x25, 0xcf, 0xdd, 0xf6, 0x83, 0x28, 0xf6, 0x9a, 0x2b, 0xfe, 0x56, 0x20, 0x94, + 0xf4, 0x64, 0x5f, 0xd0, 0x32, 0x62, 0xa7, 0x7b, 0x42, 0x52, 0x2c, 0x0e, 0x81, 0xcb, 0x61, 0xa8, + 0x8c, 0xb1, 0xf2, 0x10, 0xc8, 0x05, 0xc0, 0x96, 0x70, 0xa1, 0x07, 0x37, 0x7a, 0x18, 0xc0, 0x48, + 0xbe, 0x4c, 0xa8, 0x07, 0x47, 0x12, 0x64, 0xab, 0x32, 0xc6, 0xf3, 0x13, 0x96, 0xce, 0x45, 0x67, + 0x8c, 0xec, 0xec, 0x69, 0xb1, 0x5c, 0xd1, 0xa4, 0x95, 0xa0, 0x2c, 0xef, 0x22, 0x5c, 0xf7, 0xc9, + 0xcd, 0xad, 0x81, 0x3d, 0x38, 0x65, 0xa4, 0x39, 0x39, 0xea, 0x5d, 0xdf, 0x2b, 0xa4, 0x77, 0x5b, + 0x18, 0xf7, 0x50, 0x70, 0xe1, 0xa7, 0xbf, 0x65, 0x5c, 0x58, 0x03, 0xfb, 0xd1, 0x12, 0x9c, 0x2b, + 0x2c, 0x49, 0x56, 0xf7, 0x98, 0x91, 0x21, 0x5f, 0x13, 0x1a, 0xff, 0x1f, 0x7b, 0xef, 0x1a, 0x23, + 0x47, 0x92, 0x1c, 0x06, 0xb3, 0xba, 0xe7, 0xd1, 0x13, 0xf3, 0xaa, 0x49, 0x0e, 0xc9, 0xe1, 0x63, + 0x39, 0x64, 0xed, 0x2e, 0x97, 0x3b, 0xbb, 0xcb, 0x5d, 0x72, 0x6f, 0x1f, 0xdc, 0xdb, 0xc7, 0xf5, + 0x74, 0xf7, 0x70, 0x9a, 0xec, 0xe9, 0xee, 0xad, 0xea, 0x21, 0x3f, 0xde, 0xde, 0xa9, 0x54, 0xec, + 0xae, 0x99, 0xa9, 0x65, 0x4f, 0x57, 0x6f, 0x55, 0x35, 0xc9, 0x39, 0xe8, 0x83, 0x74, 0xd2, 0xf7, + 0x9d, 0x00, 0x19, 0x27, 0x9d, 0x1e, 0x86, 0x17, 0x07, 0xd9, 0x96, 0x65, 0x59, 0xd0, 0x0f, 0xc3, + 0x16, 0xac, 0x1f, 0x16, 0x74, 0x80, 0x6d, 0xfd, 0x13, 0x20, 0xc8, 0x92, 0x1f, 0x90, 0xe1, 0xb3, + 0xb0, 0x90, 0x4e, 0x7f, 0xec, 0x83, 0xfc, 0x4b, 0xb0, 0x01, 0x0b, 0x90, 0x61, 0x64, 0x64, 0x66, + 0x55, 0x66, 0x75, 0x75, 0xcf, 0xf0, 0xc8, 0x93, 0x7d, 0xb2, 0xfe, 0x90, 0xd3, 0x91, 0x11, 0x51, + 0xf9, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x60, 0x59, 0x9b, 0x47, 0x7d, 0xda, 0xce, 0x10, 0x05, 0xe3, + 0xbf, 0x44, 0xbe, 0xa5, 0xc1, 0x29, 0x05, 0x23, 0x96, 0xa1, 0x61, 0x1c, 0x89, 0x2c, 0x73, 0x5e, + 0x7f, 0xfc, 0x74, 0xe6, 0xf5, 0xb3, 0x6a, 0x5b, 0x12, 0x69, 0x29, 0xb7, 0x61, 0x54, 0x0d, 0xc9, + 0x03, 0x58, 0xc2, 0x22, 0x71, 0xef, 0x48, 0xe7, 0x2c, 0xbf, 0xae, 0x5c, 0x4e, 0xaa, 0xcd, 0x42, + 0xf7, 0x50, 0x8d, 0x7b, 0xfd, 0xda, 0xb7, 0x3f, 0x5b, 0x9d, 0x57, 0xd0, 0x45, 0xcc, 0x79, 0x3b, + 0xb9, 0xbc, 0xf4, 0x7a, 0x3b, 0xbe, 0x62, 0x66, 0x49, 0x7f, 0x82, 0xfc, 0x4b, 0x8d, 0xdd, 0x5d, + 0xb0, 0x66, 0x6c, 0x04, 0xfe, 0x7e, 0x5c, 0x2e, 0x9c, 0xbb, 0x47, 0x74, 0x5b, 0xf7, 0xe9, 0x74, + 0xdb, 0xf3, 0x58, 0x65, 0x26, 0x13, 0xec, 0x9d, 0xc0, 0xdf, 0x4f, 0xaa, 0x2f, 0x77, 0xdc, 0xc8, + 0x4a, 0x92, 0xaf, 0x6a, 0x70, 0x5a, 0x31, 0xb9, 0xca, 0x59, 0x8d, 0x78, 0x80, 0xa4, 0xf8, 0x91, + 0xa1, 0x54, 0xb4, 0x7e, 0x85, 0xcf, 0xff, 0x4b, 0x58, 0x83, 0x64, 0xb7, 0xc0, 0xba, 0xd8, 0xfb, + 0x0c, 0x4b, 0xaa, 0xc2, 0xe8, 0xaf, 0x10, 0x0f, 0x96, 0xd0, 0x5b, 0x4c, 0x79, 0x84, 0xb0, 0x3c, + 0xfa, 0x11, 0x42, 0x9c, 0x59, 0x0f, 0xd3, 0x7e, 0x8c, 0x7e, 0x89, 0x30, 0xcc, 0x95, 0xfc, 0xbf, + 0x70, 0x7a, 0x08, 0x18, 0xaf, 0xb6, 0x13, 0x23, 0x57, 0xdb, 0x4b, 0xdf, 0xfd, 0x6c, 0xf5, 0x85, + 0xac, 0xaf, 0x65, 0xad, 0xb4, 0xd1, 0x5f, 0x20, 0x0e, 0x40, 0x52, 0xc8, 0x75, 0xa2, 0xec, 0x09, + 0xfa, 0x12, 0x9f, 0x1f, 0x12, 0x3e, 0x95, 0xe5, 0x52, 0x1d, 0xe4, 0x2d, 0x2f, 0x41, 0x22, 0x2e, + 0xcc, 0x49, 0x29, 0x4f, 0x0e, 0xf8, 0xfd, 0xe1, 0x88, 0x8f, 0x7c, 0xfb, 0xb3, 0x55, 0x05, 0x9b, + 0x9e, 0x8c, 0xe4, 0x5c, 0x2a, 0x8a, 0x0a, 0x2a, 0x23, 0x92, 0xdf, 0xd0, 0x60, 0x99, 0x02, 0x92, + 0x49, 0xc5, 0x1b, 0xb5, 0x32, 0x6e, 0xd6, 0xef, 0x3d, 0x9d, 0x59, 0x7f, 0x11, 0xeb, 0x28, 0xcf, + 0xfa, 0xa1, 0x2e, 0xc9, 0xac, 0x1c, 0xce, 0x76, 0xc5, 0x31, 0x51, 0x99, 0xed, 0xa7, 0x8f, 0x30, + 0xdb, 0xd9, 0x00, 0x1c, 0x3e, 0xdb, 0x47, 0x7e, 0x85, 0xb4, 0x60, 0x8e, 0x1f, 0x8a, 0x58, 0x87, + 0x9d, 0x57, 0x1e, 0xf2, 0xca, 0x45, 0xec, 0xa4, 0xca, 0x33, 0xc2, 0x0c, 0xb5, 0x50, 0xe1, 0x42, + 0x7a, 0x70, 0x9c, 0xfd, 0x56, 0xad, 0x56, 0xab, 0x23, 0xad, 0x56, 0x97, 0x79, 0x8b, 0x2e, 0x70, + 0xfe, 0x29, 0xe3, 0x95, 0x1c, 0xf9, 0x31, 0x83, 0x31, 0xe9, 0x03, 0x51, 0xc0, 0x6c, 0xd1, 0x5e, + 0x18, 0x6f, 0xab, 0x7a, 0x81, 0x7f, 0x73, 0x35, 0xfd, 0xcd, 0xf4, 0xca, 0xcd, 0xe0, 0x4d, 0x1c, + 0x58, 0xe4, 0x50, 0xff, 0xbe, 0xcb, 0x24, 0xfc, 0x45, 0x25, 0xf6, 0x66, 0xaa, 0x94, 0x9d, 0xec, + 0xc4, 0x97, 0x30, 0x36, 0x6a, 0x4a, 0xa0, 0xa7, 0xf9, 0x91, 0x06, 0x2c, 0x15, 0xfb, 0xfd, 0xae, + 0xe7, 0x76, 0xb0, 0x95, 0xe6, 0x80, 0xb6, 0xc9, 0x48, 0x72, 0x9a, 0x3a, 0xac, 0x90, 0x1f, 0x37, + 0x83, 0x41, 0x4a, 0xdc, 0x0c, 0xd1, 0x1a, 0x3f, 0xa9, 0x0d, 0x55, 0x9a, 0xbc, 0x0c, 0x33, 0xf8, + 0x43, 0x0a, 0xa3, 0x86, 0xb6, 0x1b, 0x56, 0x45, 0xb4, 0x0a, 0x25, 0x08, 0x54, 0x59, 0x92, 0x43, + 0x3a, 0xe7, 0x99, 0xb2, 0xc4, 0x0d, 0x0c, 0x89, 0x49, 0x61, 0x55, 0x3c, 0x0e, 0xcb, 0x27, 0x4a, + 0x17, 0x1e, 0x4d, 0xf8, 0x93, 0x30, 0xe3, 0xd7, 0x73, 0xea, 0xb4, 0x23, 0x97, 0x25, 0xbd, 0x5d, + 0x0a, 0x2a, 0x2d, 0xf4, 0x76, 0x49, 0x5b, 0xff, 0x47, 0x1a, 0x1c, 0x6f, 0x48, 0x99, 0xb6, 0x5b, + 0x3e, 0x8e, 0xcb, 0xf8, 0x9c, 0xd2, 0x4f, 0x2b, 0xcd, 0xad, 0x9c, 0xe2, 0x9b, 0xce, 0x14, 0x9c, + 0x32, 0x66, 0x56, 0x7d, 0xf0, 0xe1, 0x30, 0x56, 0x4c, 0xca, 0x36, 0xcc, 0xd0, 0x19, 0xfc, 0x31, + 0xf3, 0xc3, 0x18, 0x3f, 0x9d, 0x83, 0x59, 0x69, 0xc5, 0x90, 0xcf, 0xc1, 0x9c, 0xfc, 0x59, 0xd9, + 0xf0, 0x27, 0xd7, 0xd2, 0x54, 0xb0, 0xd0, 0xf2, 0xe7, 0x3a, 0xfb, 0x8a, 0xe5, 0x8f, 0xae, 0x0b, + 0x84, 0x3e, 0xe6, 0x49, 0xe8, 0x83, 0x8c, 0x93, 0x10, 0xce, 0x72, 0xc9, 0xd2, 0x33, 0xf6, 0x3c, + 0xf4, 0xee, 0xf0, 0x79, 0x08, 0x8d, 0x4e, 0x12, 0xfd, 0xe8, 0x53, 0x91, 0xf1, 0xb3, 0x1a, 0xe8, + 0xe9, 0x35, 0xfd, 0x7d, 0xe9, 0x95, 0xc7, 0xb8, 0x26, 0xfa, 0x7a, 0x2e, 0x4e, 0x8f, 0x24, 0x22, + 0x3a, 0xfc, 0xa0, 0x3a, 0xbd, 0xbe, 0xa7, 0xdc, 0xe0, 0x9c, 0x55, 0xe3, 0xe6, 0xca, 0x01, 0xad, + 0xb2, 0x83, 0x65, 0x4f, 0x7c, 0xfa, 0x4b, 0xab, 0xc7, 0x8c, 0xbb, 0xb0, 0x9c, 0xee, 0x0e, 0xbc, + 0xc5, 0x29, 0xc2, 0xa2, 0x0a, 0x4f, 0x27, 0x57, 0x4b, 0x53, 0x99, 0x69, 0x7c, 0xe3, 0x0f, 0x72, + 0x69, 0xde, 0xdc, 0x01, 0x96, 0xca, 0x28, 0xd9, 0x49, 0x87, 0xcb, 0x28, 0x06, 0x32, 0x45, 0xd9, + 0xe3, 0x64, 0x6a, 0x8c, 0x63, 0x00, 0xe4, 0xb3, 0x63, 0x00, 0x90, 0x37, 0x53, 0x8f, 0x03, 0xa4, + 0xb8, 0x8a, 0x0f, 0xdd, 0x7b, 0x76, 0xf2, 0x40, 0x60, 0xe8, 0x4d, 0xc0, 0xb2, 0x92, 0xac, 0x44, + 0xd0, 0x4f, 0x26, 0x36, 0xf7, 0x08, 0x0b, 0x18, 0x71, 0x26, 0x32, 0xd9, 0x84, 0x69, 0x5a, 0xcd, + 0x2d, 0xa7, 0xcf, 0x2f, 0x67, 0x48, 0x1c, 0xa5, 0xa4, 0x1b, 0x9f, 0x0f, 0xa5, 0x40, 0x25, 0x5d, + 0x97, 0x6a, 0x08, 0xf2, 0xc4, 0xe2, 0x88, 0xc6, 0x9f, 0x6b, 0x74, 0xfd, 0xb7, 0xef, 0xff, 0x35, + 0xcb, 0x8c, 0x48, 0x9b, 0x34, 0xc6, 0x3f, 0xfb, 0x4f, 0x72, 0x2c, 0xe1, 0x15, 0x9f, 0x3e, 0xd7, + 0x61, 0x8a, 0x85, 0x2f, 0xe1, 0x2e, 0x69, 0x32, 0x17, 0x56, 0x90, 0x44, 0x7d, 0x64, 0x91, 0x4d, + 0x4c, 0x4e, 0x20, 0x9b, 0xce, 0x72, 0x47, 0x32, 0x9d, 0x49, 0xf6, 0xfc, 0xfc, 0x53, 0xb3, 0xe7, + 0x7f, 0x31, 0xce, 0x6d, 0x55, 0x8c, 0x8e, 0x90, 0x0b, 0xe3, 0x42, 0x3a, 0x95, 0xdc, 0x50, 0xd6, + 0x92, 0x84, 0x1d, 0x79, 0x53, 0x4e, 0x4e, 0x27, 0x3d, 0x46, 0x3f, 0x24, 0x0d, 0x9d, 0xf1, 0xf5, + 0x49, 0xd6, 0xc7, 0xbc, 0xa3, 0x2e, 0x29, 0x21, 0x37, 0x70, 0x9d, 0xa4, 0x6c, 0x9d, 0x2c, 0xf8, + 0xc6, 0x25, 0x98, 0xa0, 0x73, 0x93, 0xf7, 0x26, 0xe2, 0xd1, 0xf9, 0x2b, 0xe3, 0xd1, 0x72, 0xba, + 0x96, 0x71, 0x4f, 0x92, 0x53, 0xa9, 0xe2, 0xb6, 0x25, 0xaf, 0x65, 0xc4, 0xa0, 0x2d, 0x88, 0x93, + 0x50, 0xc9, 0x2d, 0xd8, 0xdf, 0x71, 0x86, 0x73, 0x44, 0x26, 0xa8, 0xa4, 0x02, 0x0b, 0x77, 0xbc, + 0x5e, 0xc7, 0x7f, 0x18, 0x96, 0xdd, 0xf0, 0x7e, 0xe4, 0xf7, 0xb9, 0x4f, 0x3a, 0xda, 0xfd, 0x1f, + 0xb2, 0x12, 0xbb, 0xc3, 0x8a, 0xe4, 0x4b, 0x12, 0x95, 0x88, 0xac, 0xc3, 0xbc, 0x12, 0xc1, 0x9d, + 0xdf, 0x7d, 0xa2, 0x8d, 0x53, 0x8d, 0xff, 0x2e, 0xdb, 0x38, 0x15, 0x12, 0xba, 0x4b, 0xf3, 0xfa, + 0x4b, 0x37, 0xa0, 0x43, 0x75, 0xe7, 0x38, 0xe4, 0x75, 0x28, 0xb0, 0xe0, 0x3d, 0xd5, 0xb2, 0x7c, + 0x69, 0x15, 0x22, 0x2c, 0x15, 0x50, 0x4e, 0x20, 0x92, 0x12, 0xcc, 0xaf, 0xfb, 0x51, 0xb5, 0x17, + 0x46, 0x4e, 0xaf, 0xed, 0xc6, 0xf1, 0xea, 0xb1, 0xb1, 0xf7, 0xfc, 0xc8, 0xf6, 0x78, 0x89, 0x4a, + 0xaf, 0xd2, 0xd0, 0xae, 0xbe, 0xe9, 0x7b, 0x3d, 0x26, 0x3a, 0x67, 0x93, 0xae, 0xfe, 0xd8, 0xf7, + 0x7a, 0x43, 0x21, 0xe2, 0x13, 0x54, 0xf2, 0x01, 0xcc, 0xd5, 0xbc, 0xde, 0xe0, 0x91, 0xe8, 0xe8, + 0xb9, 0xe4, 0x9e, 0xac, 0x4b, 0xe1, 0x19, 0xdd, 0xac, 0x10, 0xbc, 0xb3, 0x4c, 0xf7, 0x9e, 0x3f, + 0xf8, 0x07, 0xab, 0xc7, 0xbe, 0xf1, 0xcb, 0xab, 0xc7, 0x3e, 0xfd, 0x65, 0x16, 0x71, 0x87, 0xbd, + 0xca, 0x35, 0x27, 0xea, 0x7e, 0xc7, 0x35, 0x78, 0x8e, 0x3b, 0x1e, 0xa9, 0xfc, 0x25, 0x98, 0x66, + 0x2b, 0x39, 0x9d, 0xd6, 0x3b, 0x99, 0xb2, 0xa6, 0xc0, 0x20, 0x06, 0xcc, 0x7b, 0x3d, 0x9b, 0xf9, + 0x62, 0xfa, 0xbd, 0x2e, 0xcb, 0x87, 0x56, 0x30, 0x67, 0xbd, 0x1e, 0x7a, 0x60, 0x36, 0x7a, 0xdd, + 0x03, 0xe3, 0x35, 0xd0, 0xb9, 0x74, 0x8e, 0xb3, 0x08, 0xa3, 0xef, 0x47, 0xb5, 0x6c, 0xca, 0x12, + 0xb5, 0xed, 0x75, 0x02, 0x13, 0xa1, 0xf8, 0xf0, 0xb6, 0xee, 0x46, 0x0f, 0xfd, 0xe0, 0xbe, 0xe9, + 0x86, 0x51, 0xe0, 0xb1, 0xa4, 0xc4, 0x28, 0x93, 0x3e, 0x47, 0xde, 0x85, 0x49, 0xf4, 0x28, 0x4f, + 0x6d, 0x92, 0xe9, 0x6f, 0xac, 0xcf, 0xf3, 0xb5, 0x3c, 0x89, 0xee, 0xe9, 0x26, 0x23, 0x22, 0xd7, + 0x61, 0xa2, 0xec, 0xf6, 0x0e, 0x52, 0xa9, 0x45, 0x87, 0x88, 0x63, 0xd9, 0xd8, 0x71, 0x7b, 0x07, + 0x26, 0x92, 0x18, 0x3f, 0x9b, 0x83, 0x13, 0x19, 0xd5, 0xba, 0xfd, 0xb9, 0x1f, 0xd0, 0x0d, 0x62, + 0x5d, 0xd9, 0x20, 0xc4, 0x9d, 0xff, 0xc8, 0x8e, 0xcf, 0xdc, 0x2f, 0x7e, 0x51, 0x83, 0x53, 0xea, + 0xaa, 0xe6, 0x4f, 0x48, 0x6e, 0xbf, 0x4e, 0x3e, 0x0f, 0x53, 0x9b, 0xae, 0xd3, 0x71, 0x45, 0x1e, + 0xc1, 0x74, 0xda, 0x70, 0x56, 0xc8, 0xd8, 0x26, 0xd1, 0x02, 0x18, 0x94, 0x94, 0x79, 0xe5, 0xd8, + 0x49, 0xc6, 0x10, 0x51, 0xa5, 0xb2, 0x3e, 0x35, 0xc6, 0x73, 0xe6, 0xd3, 0x1c, 0x9c, 0x1d, 0x43, + 0x43, 0x07, 0x8e, 0x0e, 0xbd, 0x3c, 0x70, 0xa8, 0x5c, 0x20, 0x94, 0xbc, 0x0f, 0x8b, 0x2d, 0x7e, + 0x12, 0x12, 0xc3, 0x91, 0x4b, 0x84, 0x8c, 0x38, 0x24, 0x09, 0x0f, 0x32, 0x33, 0x8d, 0xac, 0x84, + 0x60, 0xcb, 0x8f, 0x0d, 0xc1, 0x26, 0x47, 0x34, 0x9b, 0xf8, 0x1e, 0x23, 0x9a, 0x4d, 0x8e, 0x8f, + 0x68, 0x36, 0x95, 0x8a, 0x68, 0xf6, 0x4d, 0x0d, 0x96, 0xd5, 0xae, 0xe1, 0x02, 0x20, 0x09, 0x07, + 0xa7, 0x8d, 0x0e, 0x07, 0x37, 0x3e, 0x20, 0xfa, 0x07, 0x30, 0x65, 0xe1, 0x10, 0x63, 0xb3, 0x17, + 0x62, 0x85, 0x39, 0x35, 0x14, 0x88, 0xc2, 0xde, 0x64, 0xf1, 0x98, 0x51, 0x9c, 0xcc, 0xf8, 0x7d, + 0x0d, 0x74, 0x15, 0xf9, 0x49, 0xe7, 0xd3, 0x7b, 0xca, 0x7c, 0x1a, 0x51, 0xa1, 0xd1, 0x81, 0xfe, + 0x3e, 0x88, 0x03, 0xfd, 0xe5, 0xc7, 0x31, 0x60, 0x29, 0x72, 0x41, 0x0e, 0x82, 0xcd, 0xfe, 0x37, + 0x6e, 0xa5, 0x7b, 0x3b, 0x0e, 0x53, 0xc9, 0xdb, 0xce, 0x13, 0x44, 0x8f, 0xeb, 0x2a, 0x93, 0xa3, + 0x1a, 0xff, 0x75, 0x68, 0xec, 0x8e, 0x34, 0x9f, 0x0d, 0x98, 0x2a, 0xfb, 0xfb, 0x8e, 0x27, 0xa6, + 0x31, 0xd6, 0xb3, 0x83, 0x10, 0x93, 0x97, 0x1c, 0x2d, 0x18, 0xe0, 0x05, 0x98, 0xac, 0xfb, 0xbd, + 0x62, 0x99, 0xfb, 0xa1, 0x23, 0x9f, 0x9e, 0xdf, 0xb3, 0x9d, 0x8e, 0xc9, 0x0a, 0x48, 0x0d, 0xc0, + 0x6a, 0x07, 0xae, 0xdb, 0xb3, 0xbc, 0xaf, 0xb8, 0x29, 0x15, 0x94, 0x8e, 0x57, 0x77, 0x80, 0x62, + 0x96, 0xdd, 0xcb, 0x23, 0xa2, 0x1d, 0x7a, 0x5f, 0x91, 0xb7, 0x6c, 0x89, 0x1e, 0xa5, 0x0c, 0x0f, + 0x7a, 0x9b, 0x9a, 0x15, 0x57, 0xbf, 0x1f, 0x52, 0x26, 0xf3, 0x53, 0xd8, 0xc3, 0x57, 0x33, 0xa5, + 0xcc, 0xef, 0x6b, 0x70, 0x76, 0x0c, 0xcd, 0x53, 0x18, 0x95, 0xbf, 0xea, 0x0e, 0x77, 0x01, 0x12, + 0x22, 0x4c, 0xce, 0xef, 0x75, 0xf8, 0xbb, 0x94, 0x79, 0x9e, 0x9c, 0x9f, 0x02, 0x94, 0xe4, 0xfc, + 0x14, 0x40, 0xd5, 0xb1, 0x4d, 0xd7, 0xdb, 0xdd, 0x63, 0x0e, 0x80, 0xf3, 0x4c, 0x52, 0xee, 0x21, + 0x44, 0x56, 0xc7, 0x18, 0x8e, 0xf1, 0x6f, 0xa7, 0xe0, 0xb4, 0xe9, 0xee, 0x7a, 0xf4, 0xc0, 0xba, + 0x1d, 0x7a, 0xbd, 0x5d, 0x25, 0x16, 0x9d, 0x91, 0x12, 0x44, 0x3c, 0xcf, 0x19, 0x85, 0xc4, 0x33, + 0xf1, 0x45, 0x28, 0x50, 0x1d, 0x46, 0x92, 0x45, 0x78, 0xf9, 0xd9, 0xf3, 0x3b, 0x2e, 0xcf, 0xc9, + 0x21, 0x8a, 0xc9, 0x1a, 0xd7, 0x9f, 0xa5, 0x4c, 0x94, 0x54, 0x7f, 0xfe, 0x8b, 0xcf, 0x56, 0x81, + 0xa5, 0xe6, 0xc7, 0xe0, 0xaa, 0x4c, 0x87, 0x8e, 0x0f, 0xb9, 0x13, 0x23, 0x0e, 0xb9, 0x5b, 0xb0, + 0x5c, 0xec, 0x30, 0x5d, 0xc1, 0xe9, 0x36, 0x03, 0xaf, 0xd7, 0xf6, 0xfa, 0x4e, 0x57, 0x18, 0x6e, + 0xb0, 0x97, 0x9d, 0xb8, 0xdc, 0xee, 0xc7, 0x08, 0x66, 0x26, 0x19, 0x6d, 0x46, 0xb9, 0x6e, 0x61, + 0xa0, 0x33, 0x7e, 0xaf, 0x8d, 0xcd, 0xe8, 0xf4, 0x42, 0x6c, 0x45, 0x68, 0xc6, 0xc5, 0x78, 0xbc, + 0x46, 0x2f, 0x86, 0x56, 0xcd, 0xba, 0xe5, 0xb2, 0x08, 0x55, 0x22, 0x51, 0x16, 0xf3, 0x5a, 0x89, + 0xba, 0x21, 0x3a, 0xdb, 0x2a, 0x78, 0x09, 0x9d, 0x65, 0x6d, 0x52, 0xba, 0xc2, 0x10, 0x5d, 0x18, + 0xee, 0xc9, 0x74, 0x0c, 0x8f, 0xbc, 0x4a, 0xa7, 0xc2, 0xbe, 0x1f, 0xb9, 0x38, 0x85, 0x67, 0x92, + 0xc3, 0x78, 0x80, 0x50, 0x76, 0x18, 0x97, 0x50, 0xc8, 0xbb, 0x70, 0xbc, 0x52, 0xba, 0x26, 0x6e, + 0x23, 0xca, 0x7e, 0x7b, 0x80, 0x5e, 0x26, 0x80, 0xdf, 0xc3, 0x31, 0x74, 0xdb, 0xd7, 0xa8, 0x34, + 0xc9, 0x42, 0x23, 0x97, 0x60, 0xba, 0x5a, 0x96, 0xb5, 0x64, 0x9e, 0x0d, 0x96, 0xbb, 0xce, 0x89, + 0x42, 0xd2, 0x48, 0x4e, 0x8b, 0x73, 0x87, 0x1e, 0xeb, 0x4e, 0x1f, 0xe1, 0xa4, 0x78, 0x3d, 0xad, + 0xe5, 0x4b, 0x29, 0x51, 0x52, 0x5a, 0x7e, 0x5a, 0xb7, 0x7f, 0x1b, 0x49, 0x6f, 0xb8, 0x3d, 0x37, + 0x48, 0x52, 0xa1, 0x4c, 0xb2, 0xbe, 0xa5, 0xa4, 0xbb, 0x71, 0x89, 0xa9, 0x22, 0x12, 0x13, 0x4e, + 0x34, 0x03, 0xf7, 0x81, 0xe7, 0x0f, 0x42, 0xf5, 0xe3, 0x8b, 0xc9, 0x49, 0xa8, 0xcf, 0x11, 0xec, + 0x74, 0x2d, 0xb2, 0x49, 0x79, 0xf6, 0x5b, 0x16, 0x20, 0xb8, 0xe4, 0x77, 0xdc, 0x90, 0x49, 0xa0, + 0xbf, 0x46, 0xd9, 0x6f, 0xa5, 0xb6, 0x8d, 0x91, 0xca, 0xdf, 0xc4, 0xec, 0xb7, 0x43, 0xb8, 0xe4, + 0x6d, 0x98, 0xc4, 0x9f, 0xfc, 0xfc, 0x70, 0x3c, 0x83, 0x6d, 0x72, 0x76, 0x68, 0x53, 0x4c, 0x93, + 0x11, 0x90, 0x2a, 0x4c, 0xf3, 0x53, 0xfc, 0xe3, 0xe4, 0x70, 0xe4, 0xe6, 0x00, 0x36, 0xdb, 0x38, + 0xbd, 0xd1, 0x81, 0x39, 0xf9, 0x83, 0x74, 0x95, 0x6d, 0x3a, 0xe1, 0x9e, 0xdb, 0xa1, 0xbf, 0x78, + 0xfa, 0x65, 0x5c, 0x65, 0x7b, 0x08, 0xb5, 0x69, 0x3d, 0x4c, 0x09, 0x85, 0xee, 0xd3, 0xd5, 0x70, + 0x3b, 0xe4, 0x55, 0xe1, 0x76, 0x3d, 0x0f, 0x6d, 0xc4, 0x1d, 0x93, 0x17, 0x19, 0x5f, 0x84, 0xe5, + 0xfa, 0xa0, 0xdb, 0x75, 0xee, 0x75, 0x5d, 0x91, 0x9e, 0x2f, 0x72, 0x22, 0x97, 0xac, 0xc3, 0x24, + 0xfe, 0xc1, 0x75, 0x8e, 0xe3, 0x71, 0xfe, 0xc3, 0x04, 0x07, 0x5d, 0xa7, 0x35, 0x0c, 0x64, 0x47, + 0x7f, 0x2a, 0x81, 0xec, 0x28, 0xc0, 0xf8, 0x1d, 0x0d, 0x96, 0x85, 0x6f, 0x4a, 0xe0, 0xb4, 0xef, + 0xbb, 0x01, 0xd7, 0x1f, 0x2f, 0x29, 0x73, 0x0d, 0x17, 0x41, 0x6a, 0x1a, 0xb1, 0x59, 0x77, 0x53, + 0x54, 0x42, 0x55, 0xc9, 0xb2, 0x2a, 0x7c, 0x58, 0x65, 0xc8, 0xbb, 0x30, 0xcb, 0xb7, 0x5c, 0x29, + 0x42, 0x39, 0xc6, 0xd1, 0xe4, 0xc7, 0xe3, 0xb4, 0xa7, 0x94, 0x8c, 0x6e, 0xfc, 0xb4, 0x06, 0xba, + 0xda, 0x94, 0x27, 0xd5, 0x2b, 0xb2, 0xb5, 0x4d, 0xf5, 0x1b, 0x63, 0xa6, 0xee, 0xbf, 0x9a, 0x4d, + 0xf7, 0x2d, 0x9f, 0xbb, 0x6f, 0xca, 0xa1, 0x63, 0xb5, 0xc4, 0x8a, 0x90, 0x84, 0x8e, 0x95, 0xad, + 0x08, 0x31, 0x6a, 0x3c, 0x26, 0xb9, 0x43, 0xc6, 0xe4, 0x7d, 0x31, 0x26, 0xf9, 0xd1, 0x13, 0xe3, + 0xf8, 0x98, 0x71, 0xb0, 0x92, 0x15, 0x32, 0x71, 0x24, 0x1b, 0xde, 0x31, 0x4c, 0xe5, 0xc4, 0x48, + 0xd2, 0x92, 0x99, 0x73, 0x92, 0x0d, 0x83, 0x93, 0x47, 0x67, 0x7a, 0x88, 0xb8, 0x7f, 0x07, 0xe6, + 0x8a, 0x51, 0xe4, 0xb4, 0xf7, 0xdc, 0x4e, 0x99, 0x8a, 0x27, 0x29, 0x36, 0xa4, 0xc3, 0xe1, 0xb2, + 0x49, 0x45, 0xc6, 0x65, 0xe9, 0x00, 0x9c, 0x90, 0x7b, 0x5c, 0xc7, 0xe9, 0x00, 0x28, 0x44, 0x4d, + 0x07, 0x40, 0x21, 0xe4, 0x55, 0x98, 0xae, 0xf6, 0x1e, 0x78, 0xb4, 0x4f, 0x58, 0x78, 0x48, 0x34, + 0x84, 0x7a, 0x0c, 0x24, 0x0b, 0x57, 0x8e, 0x45, 0xae, 0x4b, 0xc7, 0xc6, 0x99, 0xc4, 0xd4, 0x24, + 0x62, 0x4c, 0xf3, 0x22, 0xf9, 0x48, 0x18, 0x9f, 0x23, 0xdf, 0x84, 0x69, 0x61, 0x36, 0x87, 0x64, + 0x07, 0xe1, 0x94, 0xc3, 0x41, 0x79, 0x04, 0x32, 0x5d, 0x3f, 0x72, 0x1a, 0xe9, 0xd9, 0x64, 0xfd, + 0xc8, 0x69, 0xa4, 0xe5, 0xf5, 0x23, 0x27, 0x94, 0x8e, 0x2d, 0x8e, 0x73, 0x87, 0x5a, 0x1c, 0x6f, + 0xc3, 0x5c, 0xd3, 0x09, 0x22, 0x8f, 0xea, 0x3d, 0xbd, 0x28, 0x5c, 0x99, 0x57, 0x8c, 0xf4, 0x52, + 0xd1, 0xfa, 0x79, 0x3e, 0x90, 0x27, 0xfb, 0x12, 0xbe, 0x9a, 0x92, 0x3f, 0x81, 0x67, 0xfb, 0x5b, + 0x2f, 0x3c, 0x89, 0xbf, 0x35, 0x76, 0x2a, 0x1a, 0x66, 0x17, 0x13, 0x43, 0x21, 0x1e, 0x84, 0x52, + 0xd6, 0xd9, 0x18, 0x91, 0x7c, 0x09, 0xe6, 0xe8, 0xdf, 0x4d, 0xbf, 0xeb, 0xb5, 0x3d, 0x37, 0x5c, + 0xd1, 0xb1, 0x71, 0xe7, 0x33, 0x57, 0x3f, 0x22, 0x1d, 0x58, 0x6e, 0xc4, 0x16, 0x30, 0x32, 0x4e, + 0xdf, 0xb8, 0x28, 0xdc, 0xc8, 0x07, 0x30, 0x47, 0x67, 0xdf, 0x3d, 0x27, 0x64, 0xea, 0xee, 0x52, + 0x62, 0x09, 0xec, 0x70, 0xf8, 0x50, 0x46, 0x0e, 0x99, 0x80, 0x6e, 0xf3, 0xc5, 0x3e, 0x13, 0x90, + 0x44, 0x9a, 0xed, 0xfd, 0x21, 0xe1, 0x28, 0xd0, 0xc8, 0x17, 0x60, 0xae, 0xd8, 0xef, 0x27, 0x12, + 0xe7, 0xb8, 0x64, 0x9f, 0xed, 0xf7, 0xed, 0x4c, 0xa9, 0xa3, 0x50, 0xa4, 0x05, 0xf3, 0xf2, 0x63, + 0x09, 0x66, 0xf2, 0x4a, 0x7c, 0x02, 0x38, 0x91, 0x5c, 0x21, 0xf0, 0xc3, 0xa8, 0x72, 0x9c, 0x60, + 0x87, 0x81, 0x12, 0xcc, 0x33, 0x03, 0xa5, 0xd0, 0x66, 0x4e, 0x0e, 0xad, 0x9e, 0x0c, 0xa5, 0x46, + 0xa5, 0x61, 0x11, 0x08, 0xbc, 0xc8, 0x73, 0xba, 0x3c, 0x55, 0xca, 0xca, 0x29, 0x5c, 0xb5, 0x3c, + 0x02, 0x01, 0x96, 0x60, 0x3e, 0x3e, 0x47, 0xe1, 0x92, 0x22, 0x32, 0xfe, 0x54, 0x83, 0x53, 0x23, + 0x46, 0x3c, 0x4e, 0xa4, 0xa1, 0x8d, 0x4f, 0xa4, 0x41, 0x25, 0x87, 0x6a, 0x77, 0xc2, 0xf6, 0x0f, + 0x3f, 0x58, 0x8c, 0xf5, 0x2d, 0x1f, 0x08, 0x4f, 0xbd, 0xc9, 0x3f, 0x7d, 0xd3, 0xc7, 0x7b, 0x80, + 0xfc, 0xf0, 0x26, 0xc4, 0xf1, 0x58, 0xa5, 0x58, 0x94, 0x68, 0x9e, 0xd9, 0x33, 0x1e, 0xd6, 0x8f, + 0x7d, 0x65, 0x05, 0x67, 0xb0, 0x36, 0x7e, 0x26, 0x07, 0xb3, 0xd2, 0x3a, 0x24, 0x17, 0xa4, 0xe7, + 0xf0, 0x3a, 0xcb, 0x0d, 0x2b, 0x71, 0xc8, 0xb1, 0x9d, 0x08, 0x17, 0x55, 0xee, 0xf0, 0xdb, 0x0e, + 0x8c, 0x00, 0x28, 0x25, 0x1b, 0x49, 0x85, 0xfc, 0xc3, 0x72, 0xf2, 0x65, 0x80, 0x9a, 0x13, 0x46, + 0xc5, 0x76, 0xe4, 0x3d, 0x70, 0x8f, 0xb0, 0xe9, 0x88, 0x28, 0xb9, 0x27, 0x30, 0x0b, 0x98, 0x83, + 0x64, 0xa9, 0x3d, 0x42, 0x62, 0x48, 0x87, 0x40, 0xce, 0x56, 0xc0, 0x87, 0x60, 0x58, 0x84, 0x08, + 0x2c, 0xe3, 0xc7, 0x35, 0x80, 0xed, 0x6a, 0x09, 0xd3, 0x0b, 0x3d, 0xa9, 0x16, 0x91, 0x1d, 0x59, + 0x5f, 0x70, 0x1f, 0xa3, 0x3f, 0xfc, 0xa1, 0x06, 0x0b, 0x2a, 0x1a, 0x79, 0x1f, 0x16, 0xad, 0x76, + 0xe0, 0x77, 0xbb, 0xf7, 0x9c, 0xf6, 0xfd, 0x9a, 0xd7, 0x73, 0x59, 0xb4, 0xf1, 0x49, 0xb6, 0x79, + 0x85, 0x71, 0x91, 0xdd, 0xa5, 0x65, 0x66, 0x1a, 0x99, 0xfc, 0x84, 0x06, 0xf3, 0xd6, 0x9e, 0xff, + 0x30, 0x0e, 0xde, 0xcd, 0x47, 0xf0, 0xcb, 0x54, 0x18, 0x84, 0x7b, 0xfe, 0xc3, 0x24, 0x13, 0xbd, + 0xe2, 0xc9, 0xfc, 0xde, 0xd1, 0x9c, 0x4c, 0xda, 0x3e, 0x1e, 0x60, 0xa2, 0xf0, 0x8a, 0xf2, 0x11, + 0x53, 0xfd, 0xa6, 0xf1, 0x97, 0x1a, 0xcc, 0xe2, 0x51, 0xa7, 0xdb, 0x45, 0x25, 0xed, 0xaf, 0x53, + 0x5a, 0xf3, 0xb8, 0x5d, 0x63, 0x06, 0xf6, 0x0d, 0x58, 0x4c, 0xa1, 0x11, 0x03, 0xa6, 0x2c, 0x0c, + 0x8d, 0x21, 0x5b, 0x49, 0x58, 0xb0, 0x0c, 0x93, 0x97, 0x18, 0x15, 0x89, 0xec, 0xf6, 0x55, 0x74, + 0x3a, 0xb8, 0x06, 0xe0, 0x09, 0x90, 0x38, 0x0a, 0x91, 0x74, 0x4d, 0x6e, 0x5f, 0x35, 0x25, 0x2c, + 0xa3, 0x0e, 0x53, 0x96, 0x1f, 0x44, 0xeb, 0x07, 0xec, 0xf4, 0x51, 0x76, 0xc3, 0xb6, 0xec, 0x55, + 0xe0, 0xe1, 0x9d, 0x5f, 0xdb, 0xe4, 0x45, 0x64, 0x15, 0x26, 0x37, 0x3c, 0xb7, 0xdb, 0x91, 0xbd, + 0xcd, 0x77, 0x28, 0xc0, 0x64, 0x70, 0x7a, 0x42, 0x3b, 0x99, 0xe4, 0xf4, 0x4b, 0xdc, 0xda, 0x9f, + 0x74, 0xdd, 0x94, 0x94, 0xfe, 0xbd, 0x18, 0x67, 0xbe, 0x1a, 0xfe, 0xd2, 0x98, 0xae, 0xfe, 0x75, + 0x0d, 0xce, 0x8c, 0x26, 0x91, 0x3d, 0xe5, 0xb5, 0x31, 0x9e, 0xf2, 0xcf, 0xa7, 0x6f, 0xc1, 0x11, + 0x8d, 0xdf, 0x82, 0x27, 0x77, 0xdf, 0x65, 0x7c, 0xa8, 0xd0, 0x76, 0x45, 0x22, 0xbf, 0x0b, 0x63, + 0xea, 0x8c, 0x88, 0x6c, 0x98, 0x23, 0xa4, 0x31, 0x39, 0xad, 0xf1, 0x2f, 0x26, 0xe0, 0xf4, 0x48, + 0x0a, 0xb2, 0x29, 0xa5, 0x07, 0x5d, 0x88, 0x13, 0x13, 0x8e, 0xc4, 0xbf, 0x82, 0xff, 0xa2, 0x2f, + 0x6a, 0xfa, 0x4d, 0x64, 0x23, 0x4e, 0x0b, 0x99, 0x43, 0x5e, 0x2f, 0x1d, 0xca, 0x8b, 0xa1, 0x23, + 0x33, 0x18, 0xce, 0x10, 0x89, 0xcf, 0x6f, 0xdd, 0xc8, 0xf1, 0xba, 0xa1, 0xbc, 0xec, 0x3a, 0x0c, + 0x64, 0x8a, 0xb2, 0xe4, 0xf9, 0xc2, 0x44, 0xf6, 0xf3, 0x05, 0xe3, 0x7f, 0x6a, 0x30, 0x13, 0x57, + 0x9b, 0x9c, 0x81, 0x93, 0x2d, 0xb3, 0x58, 0xaa, 0xd8, 0xad, 0xbb, 0xcd, 0x8a, 0xbd, 0x5d, 0xb7, + 0x9a, 0x95, 0x52, 0x75, 0xa3, 0x5a, 0x29, 0xeb, 0xc7, 0xc8, 0x12, 0xcc, 0x6f, 0xd7, 0x6f, 0xd5, + 0x1b, 0x77, 0xea, 0x76, 0xc5, 0x34, 0x1b, 0xa6, 0xae, 0x91, 0x79, 0x98, 0x31, 0xd7, 0x8b, 0x25, + 0xbb, 0xde, 0x28, 0x57, 0xf4, 0x1c, 0xd1, 0x61, 0xae, 0xd4, 0xa8, 0xd7, 0x2b, 0xa5, 0x56, 0xf5, + 0x76, 0xb5, 0x75, 0x57, 0xcf, 0x13, 0x02, 0x0b, 0x88, 0xd0, 0x34, 0xab, 0xf5, 0x52, 0xb5, 0x59, + 0xac, 0xe9, 0x13, 0x14, 0x46, 0xf1, 0x25, 0xd8, 0x64, 0xcc, 0xe8, 0xd6, 0xf6, 0x7a, 0x45, 0x9f, + 0xa2, 0x28, 0xf4, 0x2f, 0x09, 0x65, 0x9a, 0x7e, 0x1e, 0x51, 0xca, 0xc5, 0x56, 0x71, 0xbd, 0x68, + 0x55, 0xf4, 0x02, 0x39, 0x05, 0xc7, 0x15, 0x90, 0x5d, 0x6b, 0xdc, 0xa8, 0xd6, 0xf5, 0x19, 0xb2, + 0x0c, 0x7a, 0x0c, 0x2b, 0xaf, 0xdb, 0xdb, 0x56, 0xc5, 0xd4, 0x21, 0x0d, 0xad, 0x17, 0xb7, 0x2a, + 0xfa, 0xac, 0xf1, 0x1e, 0x7b, 0xad, 0xca, 0xba, 0x9a, 0x9c, 0x04, 0x62, 0xb5, 0x8a, 0xad, 0x6d, + 0x2b, 0xd5, 0xf8, 0x59, 0x98, 0xb6, 0xb6, 0x4b, 0xa5, 0x8a, 0x65, 0xe9, 0x1a, 0x01, 0x98, 0xda, + 0x28, 0x56, 0x6b, 0x95, 0xb2, 0x9e, 0x33, 0xbe, 0xa1, 0xc1, 0x92, 0x50, 0x19, 0xc5, 0x3d, 0xde, + 0x13, 0xae, 0xc5, 0xf7, 0x95, 0x93, 0xb0, 0x78, 0x7a, 0x98, 0xfa, 0xc8, 0x98, 0x65, 0xf8, 0x8b, + 0x1a, 0x9c, 0xc8, 0xc4, 0x26, 0x77, 0x41, 0x17, 0x35, 0x88, 0x9f, 0x11, 0x6b, 0x8a, 0xc6, 0x2d, + 0xe8, 0x52, 0x68, 0xcc, 0xb6, 0x1a, 0x6f, 0x57, 0xe6, 0x10, 0x9b, 0xa3, 0xe7, 0x4e, 0x32, 0x3e, + 0xd5, 0xe0, 0xd4, 0x88, 0xcf, 0x90, 0x12, 0x4c, 0xc5, 0xa9, 0x10, 0xc7, 0xb8, 0x63, 0x2e, 0x7f, + 0xfb, 0xb3, 0x55, 0x8e, 0x48, 0x97, 0x06, 0x4b, 0x76, 0x68, 0x4e, 0xc5, 0xb9, 0x0d, 0x31, 0xc1, + 0x20, 0xeb, 0xbe, 0xd3, 0xa9, 0x9e, 0xe7, 0x5f, 0x2a, 0xde, 0xb1, 0xd6, 0x67, 0x79, 0xdf, 0xe5, + 0x9d, 0x87, 0x21, 0x66, 0x18, 0x34, 0x7e, 0x41, 0xa3, 0xda, 0x60, 0x1a, 0x91, 0x2a, 0xc9, 0x49, + 0x2e, 0xb1, 0xa2, 0x59, 0xe7, 0xdb, 0x06, 0xaa, 0xb7, 0x2c, 0xff, 0x18, 0x9e, 0x43, 0x6c, 0x27, + 0x50, 0x62, 0x62, 0x28, 0x34, 0xe4, 0x3a, 0x40, 0xe5, 0x51, 0xe4, 0x06, 0x3d, 0xa7, 0x1b, 0x47, + 0x37, 0x62, 0x51, 0xfc, 0x38, 0x54, 0x55, 0xd0, 0x25, 0x64, 0xe3, 0x6b, 0x1a, 0xcc, 0x89, 0xc4, + 0x6a, 0x5d, 0x37, 0x88, 0x9e, 0x6c, 0x7a, 0x5d, 0x57, 0xa6, 0x57, 0xfc, 0xfa, 0x48, 0xe2, 0x4f, + 0x8b, 0x33, 0x67, 0xd6, 0xbf, 0xd6, 0x40, 0x4f, 0x23, 0x92, 0xf7, 0xa1, 0x60, 0xb9, 0x0f, 0xdc, + 0xc0, 0x8b, 0x0e, 0xb8, 0xa0, 0x14, 0x29, 0xa8, 0x19, 0x0e, 0x2f, 0x63, 0xf3, 0x21, 0xe4, 0xbf, + 0xcc, 0x98, 0xe6, 0xa8, 0xf2, 0x5e, 0xb2, 0x93, 0xe4, 0x9f, 0x96, 0x9d, 0xc4, 0xf8, 0xe3, 0x1c, + 0x9c, 0xba, 0xe1, 0x46, 0x72, 0x9b, 0x62, 0x37, 0x99, 0xd7, 0x8e, 0xd6, 0x2e, 0xa9, 0x25, 0x2b, + 0x30, 0x8d, 0x45, 0x62, 0x7c, 0x4d, 0xf1, 0x93, 0xac, 0xc7, 0xf3, 0x3a, 0xaf, 0xe4, 0xb8, 0x1d, + 0xf1, 0xed, 0x2b, 0x52, 0x9e, 0xca, 0x78, 0x5a, 0x5f, 0x82, 0x05, 0xcc, 0x64, 0x33, 0xa0, 0xcb, + 0xc1, 0xed, 0x70, 0x7b, 0x51, 0xc1, 0x4c, 0x41, 0xc9, 0x1a, 0xe8, 0x14, 0x52, 0x6c, 0xdf, 0xef, + 0xf9, 0x0f, 0xbb, 0x6e, 0x67, 0xd7, 0xed, 0xe0, 0xb6, 0x5e, 0x30, 0x87, 0xe0, 0x82, 0xe7, 0x76, + 0x8f, 0x9d, 0xf5, 0xdc, 0x0e, 0x1a, 0x75, 0x38, 0xcf, 0x04, 0x7a, 0xe6, 0x3a, 0xcc, 0x7e, 0x8f, + 0x19, 0x6c, 0x8d, 0xff, 0xa4, 0xc1, 0x32, 0x36, 0x4e, 0xfa, 0x30, 0x5e, 0x5b, 0xbc, 0x96, 0xf4, + 0x96, 0x94, 0x86, 0xd1, 0xa1, 0x20, 0x75, 0x29, 0xc4, 0xbd, 0x98, 0x18, 0x91, 0x72, 0x47, 0x30, + 0x22, 0x49, 0x36, 0xb0, 0x89, 0xa7, 0x65, 0x03, 0xbb, 0x39, 0x51, 0xc8, 0xeb, 0x13, 0xc9, 0x90, + 0x1b, 0x3f, 0x91, 0x83, 0x69, 0xd3, 0xed, 0xba, 0x4e, 0xe8, 0x92, 0x4b, 0x30, 0x5d, 0xf7, 0x23, + 0x37, 0xdc, 0x2a, 0xcb, 0x2e, 0xe8, 0x3d, 0x0a, 0xb2, 0xf7, 0x3b, 0xa6, 0x28, 0xa4, 0x13, 0xbe, + 0x19, 0xf8, 0x9d, 0x41, 0x3b, 0x92, 0x27, 0x7c, 0x9f, 0x81, 0x4c, 0x51, 0x46, 0x5e, 0x86, 0x19, + 0xce, 0x39, 0xbe, 0x59, 0x46, 0xcf, 0xfa, 0x80, 0x01, 0x31, 0xe7, 0x55, 0x8c, 0x80, 0x3a, 0x2d, + 0x53, 0x30, 0x26, 0x24, 0x9d, 0x76, 0x48, 0x67, 0x10, 0xaa, 0xfa, 0xe4, 0x18, 0x55, 0xfd, 0x35, + 0x98, 0x2a, 0x86, 0xa1, 0x1b, 0x89, 0x60, 0x1d, 0x73, 0x49, 0x3e, 0x45, 0x37, 0x62, 0x8c, 0x1d, + 0x2c, 0x37, 0x39, 0x9e, 0xf1, 0xe7, 0x39, 0x98, 0xc4, 0x3f, 0xf1, 0xde, 0x36, 0x68, 0xef, 0x29, + 0xf7, 0xb6, 0x41, 0x7b, 0xcf, 0x44, 0x28, 0xb9, 0x8a, 0xa6, 0x0d, 0x91, 0x31, 0x94, 0xb7, 0x1e, + 0x6d, 0xf6, 0x9d, 0x04, 0x6c, 0xca, 0x38, 0xb1, 0xd7, 0x44, 0x3e, 0xd3, 0x6b, 0xe2, 0x24, 0xe4, + 0x1a, 0x16, 0x6f, 0x31, 0xc6, 0x92, 0xf3, 0x43, 0x33, 0xd7, 0xb0, 0xb0, 0x37, 0x36, 0x8b, 0xd7, + 0xde, 0x78, 0x93, 0x37, 0x94, 0xf5, 0xc6, 0x9e, 0x73, 0xed, 0x8d, 0x37, 0x4d, 0x5e, 0x42, 0xfb, + 0x17, 0xeb, 0x8c, 0xb7, 0xbf, 0x2c, 0x92, 0x04, 0xf6, 0x2f, 0xb6, 0x0d, 0x6f, 0x7a, 0xcd, 0x04, + 0x81, 0x5c, 0x83, 0x59, 0x1e, 0xd2, 0x04, 0xf1, 0xa5, 0x90, 0x23, 0x3c, 0xe4, 0x09, 0xa3, 0x90, + 0x91, 0xd8, 0x3d, 0x20, 0x1f, 0x20, 0x9e, 0x1f, 0x4f, 0xdc, 0x03, 0x8a, 0x21, 0x0c, 0x4d, 0x09, + 0x85, 0x56, 0x89, 0x5d, 0x24, 0x26, 0x11, 0x22, 0xb0, 0x4a, 0xfc, 0xb6, 0x11, 0xb3, 0x06, 0xc5, + 0x08, 0xc6, 0xaf, 0xe5, 0xa0, 0xd0, 0xec, 0x0e, 0x76, 0xbd, 0xde, 0xed, 0xab, 0x84, 0x00, 0x1e, + 0xe3, 0x44, 0x5a, 0x29, 0xfa, 0x37, 0x39, 0x0d, 0x05, 0x71, 0x72, 0x13, 0x02, 0x29, 0xe4, 0xa7, + 0xb6, 0x15, 0x10, 0xe3, 0xce, 0x83, 0x06, 0x8a, 0x9f, 0xe4, 0x2a, 0xc4, 0xe7, 0xaf, 0x51, 0x07, + 0xb5, 0x09, 0xba, 0x58, 0xcc, 0x18, 0x8d, 0xbc, 0x02, 0xb8, 0x49, 0xf0, 0xc3, 0x83, 0xb0, 0x80, + 0xb3, 0xaa, 0x71, 0x3d, 0x85, 0x91, 0x20, 0x1a, 0xfa, 0x6f, 0xc8, 0x19, 0x20, 0x4f, 0xa8, 0x04, + 0x2c, 0x47, 0xa0, 0x20, 0x11, 0xd9, 0x1f, 0xdf, 0x55, 0xe3, 0x48, 0x4e, 0x2b, 0xf7, 0x5b, 0x8c, + 0x52, 0x0a, 0xe4, 0x78, 0xfb, 0xaa, 0x1a, 0x42, 0xf2, 0x77, 0x67, 0x60, 0x4e, 0xae, 0x0f, 0x31, + 0xe1, 0x78, 0xd8, 0xa5, 0x67, 0x77, 0xee, 0x34, 0xd9, 0xc7, 0x42, 0xbe, 0x9d, 0x5e, 0x50, 0x2b, + 0x44, 0xf1, 0x98, 0x07, 0xa5, 0x88, 0xc5, 0xb2, 0x79, 0xcc, 0x5c, 0x0a, 0x13, 0x30, 0xc3, 0x23, + 0x45, 0x28, 0xf8, 0xfd, 0x70, 0xd7, 0xed, 0x79, 0xe2, 0x82, 0xe6, 0x59, 0x85, 0x51, 0x83, 0x17, + 0x0e, 0xf1, 0x8a, 0xc9, 0xc8, 0x1b, 0x30, 0xe5, 0xf7, 0xdd, 0x9e, 0xe3, 0xa5, 0x7c, 0x66, 0x04, + 0x03, 0xb7, 0x57, 0xac, 0x4a, 0x84, 0x1c, 0x99, 0xbc, 0x0a, 0x13, 0xfe, 0xfd, 0x78, 0xbc, 0x4e, + 0xab, 0x44, 0xf7, 0x23, 0x47, 0x22, 0x41, 0x44, 0x4a, 0xf0, 0xb1, 0xb3, 0xbf, 0xc3, 0x47, 0x4c, + 0x25, 0xb8, 0xe9, 0xec, 0xef, 0xc8, 0x04, 0x14, 0x91, 0x7c, 0x00, 0xd0, 0x77, 0x76, 0xdd, 0xc0, + 0xee, 0x0c, 0xa2, 0x83, 0x54, 0x8a, 0x6c, 0x46, 0xd6, 0xa4, 0xc5, 0xe5, 0x41, 0x74, 0x20, 0xd1, + 0xce, 0xf4, 0x05, 0x90, 0x14, 0x01, 0xf6, 0x9d, 0x28, 0x72, 0x83, 0x7d, 0x9f, 0x7b, 0xad, 0x26, + 0x61, 0x40, 0x19, 0x83, 0xad, 0xb8, 0x58, 0xe2, 0x20, 0x11, 0x61, 0xa5, 0xbd, 0xc0, 0xc1, 0x6b, + 0xf8, 0xa1, 0x4a, 0x7b, 0x81, 0xd2, 0x4a, 0x8a, 0x48, 0xde, 0x86, 0x69, 0xcc, 0xfb, 0x1a, 0x74, + 0x78, 0x90, 0x9e, 0x73, 0x0a, 0x4d, 0x99, 0x95, 0x49, 0x64, 0x02, 0x9d, 0xd6, 0x96, 0x07, 0x7c, + 0xae, 0xfb, 0x0f, 0xf1, 0x5e, 0x20, 0x5d, 0x5b, 0x2b, 0x2e, 0x96, 0x6b, 0x9b, 0x10, 0xd1, 0xa1, + 0xdc, 0xf5, 0xa2, 0xae, 0x73, 0x8f, 0x5f, 0xb6, 0xab, 0x43, 0x79, 0x03, 0x8b, 0xe4, 0xa1, 0x64, + 0xc8, 0xe4, 0x3a, 0x14, 0x44, 0x18, 0x56, 0xfe, 0xe4, 0x57, 0xad, 0x34, 0x0f, 0xbe, 0x2a, 0x57, + 0x9a, 0x07, 0x5e, 0xa5, 0xfd, 0x13, 0xb6, 0xbd, 0x7d, 0xfe, 0x52, 0x57, 0xed, 0x1f, 0xab, 0x54, + 0xdd, 0x92, 0xfb, 0x87, 0x22, 0x92, 0xf7, 0x61, 0x9a, 0xae, 0xdf, 0x8e, 0xbf, 0xcb, 0xc3, 0x9c, + 0x18, 0x6a, 0xff, 0xb0, 0xb2, 0xa1, 0xe9, 0x2a, 0x88, 0xe8, 0x42, 0x76, 0x1e, 0x86, 0xb6, 0xd7, + 0xc6, 0x38, 0xc0, 0xe9, 0xe5, 0x58, 0xbc, 0x63, 0x55, 0x4b, 0x12, 0xd9, 0xa4, 0xf3, 0x30, 0xac, + 0xb6, 0xc9, 0x35, 0x98, 0xc4, 0x8c, 0x4b, 0x3c, 0xe8, 0xaf, 0x4a, 0x83, 0xb9, 0x96, 0x64, 0x1a, + 0x44, 0xa5, 0x03, 0xb9, 0x1f, 0xe2, 0xe3, 0x27, 0x9e, 0xf7, 0x48, 0xed, 0x93, 0x2d, 0x0b, 0x5f, + 0x44, 0xc9, 0x55, 0xe4, 0xe8, 0xb4, 0x8a, 0x3d, 0x37, 0xb2, 0xbd, 0x4f, 0x78, 0xe6, 0x22, 0xf5, + 0x73, 0x75, 0x37, 0xaa, 0x7e, 0x28, 0x7f, 0xae, 0xe7, 0x46, 0xd5, 0x4f, 0xf8, 0xd0, 0xed, 0x0d, + 0xee, 0xa1, 0xf1, 0x3d, 0x63, 0xe8, 0xf6, 0x06, 0xe9, 0xa1, 0xdb, 0x1b, 0xdc, 0xa3, 0x64, 0x5e, + 0x2f, 0x1a, 0xf4, 0x5c, 0xfe, 0xf4, 0x56, 0x25, 0xab, 0x62, 0x91, 0x4c, 0xc6, 0x90, 0xc9, 0x79, + 0x80, 0xc4, 0xdb, 0x81, 0xdd, 0x23, 0x99, 0x12, 0xe4, 0x9d, 0x89, 0xff, 0xfc, 0x4b, 0xab, 0xda, + 0x3a, 0x40, 0x41, 0xc4, 0x8a, 0xa2, 0x6a, 0xf8, 0x72, 0x56, 0x5d, 0xc8, 0x45, 0x98, 0x93, 0x23, + 0x59, 0xf1, 0xcd, 0x60, 0xd6, 0xe9, 0x7b, 0x22, 0x96, 0xd5, 0xf8, 0x9c, 0x36, 0x2f, 0xc1, 0x92, + 0xf2, 0x42, 0x2d, 0x71, 0xc3, 0x34, 0x75, 0xb9, 0x00, 0xf7, 0xde, 0x12, 0x40, 0x18, 0x39, 0x41, + 0x64, 0x77, 0x9c, 0xe8, 0x28, 0x66, 0xe4, 0x02, 0x95, 0xe7, 0xec, 0x41, 0x00, 0xd2, 0x95, 0x9d, + 0xc8, 0x65, 0x8d, 0x33, 0x6a, 0x70, 0x7a, 0xa4, 0xac, 0x25, 0x2f, 0x82, 0xbe, 0xe3, 0x70, 0x4b, + 0x6b, 0x7b, 0xcf, 0xe9, 0xf5, 0xdc, 0x2e, 0x6f, 0xd8, 0xa2, 0x80, 0x97, 0x18, 0x98, 0x73, 0xfb, + 0x40, 0xea, 0x1d, 0x69, 0x91, 0x1d, 0xa1, 0x77, 0x38, 0x83, 0x6f, 0x69, 0x70, 0x6e, 0x9c, 0xc8, + 0x26, 0x67, 0xa0, 0xd0, 0x0f, 0x3c, 0x1f, 0x8f, 0x06, 0xbc, 0x0f, 0xc5, 0x6f, 0x4c, 0xf9, 0x83, + 0x3a, 0x6c, 0xe4, 0xec, 0xf2, 0x27, 0x5f, 0xe6, 0x0c, 0x42, 0x5a, 0xce, 0x6e, 0x48, 0xbb, 0xb8, + 0xe3, 0xee, 0x38, 0x83, 0x6e, 0x64, 0x87, 0xed, 0x3d, 0xb7, 0x83, 0x8f, 0x32, 0xd1, 0x7f, 0xd5, + 0xd4, 0x79, 0x81, 0x25, 0xe0, 0x43, 0x35, 0x9e, 0x1c, 0x51, 0xe3, 0x9b, 0x13, 0x05, 0x4d, 0xcf, + 0x99, 0xe8, 0x22, 0x67, 0xfc, 0x58, 0x0e, 0x56, 0x46, 0xc9, 0x28, 0xf2, 0x5e, 0x56, 0x1f, 0xb0, + 0xdb, 0x25, 0x19, 0x2e, 0xdf, 0x2e, 0xc9, 0xb3, 0xe7, 0x1a, 0xc4, 0x4f, 0x2a, 0x0f, 0x0b, 0x8f, + 0x22, 0x60, 0x94, 0xa6, 0xef, 0x84, 0xe1, 0x43, 0x2a, 0x86, 0xf3, 0x52, 0xc0, 0x76, 0x0e, 0x93, + 0x69, 0x04, 0x8c, 0xbc, 0x05, 0xd0, 0xee, 0xfa, 0xa1, 0x8b, 0x4e, 0x1c, 0x5c, 0xbf, 0x63, 0x0f, + 0x45, 0x62, 0xa8, 0x7c, 0x6b, 0x8f, 0xd0, 0x92, 0xdf, 0x11, 0xf3, 0xc9, 0x81, 0x53, 0x23, 0x36, + 0x25, 0x3a, 0x3c, 0xf8, 0x46, 0x92, 0xc9, 0x20, 0x9e, 0x2e, 0x93, 0x42, 0x58, 0x9a, 0xb7, 0x74, + 0x8f, 0xe7, 0x46, 0xcd, 0x91, 0x03, 0x20, 0xc3, 0x3b, 0x0f, 0xe5, 0xce, 0x1f, 0x46, 0x0c, 0x82, + 0x98, 0x3b, 0x83, 0x6c, 0x07, 0x5d, 0xb2, 0x0a, 0xb3, 0x22, 0x63, 0x3c, 0x3d, 0x3f, 0x31, 0xe6, + 0xc0, 0x41, 0xb7, 0x5c, 0x9c, 0x3c, 0x18, 0x5f, 0x99, 0xa5, 0xef, 0x62, 0x2b, 0x6f, 0x06, 0x21, + 0xad, 0x83, 0xbe, 0x68, 0xdd, 0x39, 0x31, 0xbf, 0x55, 0x7d, 0x80, 0x97, 0xfe, 0x6d, 0x4d, 0x0c, + 0xff, 0xf0, 0x86, 0x7a, 0x58, 0xfd, 0x08, 0xe0, 0xbb, 0x45, 0x5e, 0x31, 0xfc, 0x9b, 0x6a, 0x8a, + 0x62, 0xd5, 0x71, 0x4d, 0x91, 0xff, 0x24, 0x97, 0x60, 0x31, 0x60, 0xee, 0xdc, 0x91, 0xcf, 0xfb, + 0x93, 0x65, 0xb8, 0x9a, 0x67, 0xe0, 0x96, 0x8f, 0x7d, 0xca, 0xeb, 0x75, 0x33, 0xee, 0x30, 0x49, + 0xbf, 0x20, 0x57, 0x60, 0x86, 0xea, 0x17, 0x18, 0x33, 0x2b, 0xf5, 0x60, 0x0a, 0xf1, 0x50, 0x5b, + 0x33, 0x0b, 0x1f, 0xf3, 0xbf, 0x39, 0xaf, 0x1f, 0x8f, 0x05, 0xa0, 0x2a, 0x55, 0xc9, 0x49, 0x98, + 0x8a, 0xdc, 0x9e, 0x13, 0x2f, 0x6e, 0xfe, 0x8b, 0x3c, 0x0f, 0x0b, 0xec, 0x95, 0x73, 0x6a, 0x60, + 0xe7, 0x11, 0x1a, 0x4f, 0xef, 0xa3, 0xa5, 0x47, 0xe3, 0x95, 0xf8, 0xcd, 0x9c, 0x68, 0x91, 0xac, + 0x62, 0x91, 0x53, 0x30, 0xed, 0x07, 0xbb, 0x52, 0xff, 0x4e, 0xf9, 0xc1, 0x2e, 0xed, 0xdc, 0xcb, + 0xa0, 0xb3, 0x47, 0x84, 0x2c, 0x98, 0x4b, 0x78, 0xd0, 0x6b, 0xf3, 0xb7, 0x1f, 0x0b, 0x0c, 0xbe, + 0x1d, 0xba, 0x81, 0x75, 0xd0, 0x6b, 0x53, 0xcc, 0x30, 0xf4, 0x6d, 0x39, 0xfe, 0x1e, 0xaf, 0xc8, + 0x42, 0x18, 0xfa, 0x49, 0x20, 0xbe, 0x0e, 0x59, 0x87, 0x79, 0xca, 0x27, 0x0e, 0x23, 0xc8, 0xc5, + 0xf0, 0x33, 0xc3, 0x1a, 0xe0, 0x41, 0xaf, 0x2d, 0xaa, 0x68, 0xce, 0x85, 0xd2, 0x2f, 0x72, 0x0b, + 0x74, 0x49, 0x55, 0xc6, 0x57, 0xe5, 0xa9, 0xf7, 0x0d, 0x09, 0x1b, 0x49, 0xc5, 0xae, 0xf6, 0x76, + 0x7c, 0x73, 0xb1, 0xad, 0x02, 0x62, 0x71, 0x34, 0xa5, 0x4f, 0x9b, 0x2b, 0xbc, 0xb9, 0x21, 0xba, + 0x8a, 0xda, 0x5d, 0x7f, 0xd7, 0x76, 0x1f, 0xd1, 0x89, 0x61, 0xfc, 0x7d, 0x4d, 0x08, 0xfc, 0x0c, + 0xa6, 0xc4, 0x80, 0xf9, 0x3d, 0x27, 0xb4, 0xc3, 0x70, 0x9f, 0x79, 0x30, 0xf2, 0x58, 0xe9, 0xb3, + 0x7b, 0x4e, 0x68, 0x85, 0xfb, 0x22, 0x6d, 0xd7, 0x09, 0x8a, 0x83, 0xf1, 0xdc, 0x6d, 0xf9, 0x60, + 0xc0, 0x7a, 0xf4, 0xf8, 0x9e, 0x13, 0x36, 0x68, 0x99, 0x1c, 0xdc, 0xfd, 0x39, 0x58, 0x40, 0xbe, + 0x6d, 0x4f, 0x30, 0xc6, 0x80, 0x3d, 0xe6, 0x1c, 0x65, 0xdc, 0xf6, 0x18, 0x67, 0x3e, 0xb8, 0xff, + 0x61, 0x0a, 0x4e, 0x66, 0xf7, 0x1e, 0xae, 0x21, 0xda, 0xe7, 0xf8, 0xb4, 0x98, 0xd7, 0x6d, 0x86, + 0x42, 0x58, 0xb0, 0xa5, 0xac, 0xc1, 0xcb, 0x65, 0x0e, 0xde, 0x1a, 0x2c, 0x21, 0x23, 0x7e, 0x04, + 0xe9, 0x7a, 0x61, 0xc4, 0x63, 0x08, 0x99, 0x8b, 0xb4, 0x80, 0x6d, 0x3a, 0x35, 0x0a, 0xa6, 0x33, + 0x53, 0x6c, 0x1b, 0xfe, 0xc3, 0x1e, 0xfd, 0x30, 0xdb, 0x33, 0xe6, 0x39, 0xb4, 0x81, 0x40, 0x72, + 0x02, 0xa6, 0x9c, 0x7e, 0x9f, 0x7e, 0x92, 0x6d, 0x15, 0x93, 0x4e, 0xbf, 0xcf, 0x22, 0xd7, 0xb3, + 0xac, 0x80, 0x3b, 0xe8, 0x6f, 0x26, 0x5e, 0x36, 0xcc, 0x21, 0x90, 0xf9, 0xa0, 0xe1, 0xdb, 0x08, + 0x4a, 0x2b, 0x50, 0xa6, 0x11, 0x05, 0x9c, 0x7e, 0x8c, 0x70, 0x1a, 0x0a, 0xc2, 0xf3, 0x81, 0xbd, + 0x1c, 0x33, 0xa7, 0x1d, 0xee, 0xf5, 0xf0, 0x06, 0x9c, 0xe2, 0x29, 0x08, 0x6d, 0xd6, 0xa4, 0x7e, + 0x9f, 0x3f, 0xdd, 0x66, 0x71, 0xca, 0xcd, 0x65, 0x5e, 0x4c, 0x7b, 0xb2, 0xd8, 0xef, 0xc7, 0x0f, + 0xb8, 0xcf, 0x08, 0xb2, 0x7b, 0x1e, 0x8b, 0x74, 0xc8, 0xbc, 0x7f, 0x71, 0x71, 0x00, 0x52, 0xae, + 0x70, 0x8c, 0x75, 0x19, 0x41, 0x2c, 0x93, 0x78, 0x25, 0xd9, 0xfc, 0x45, 0x00, 0x53, 0x9f, 0xf0, + 0x7e, 0x1c, 0x07, 0x0d, 0xa1, 0xe4, 0x2d, 0x18, 0x39, 0x17, 0x51, 0x3b, 0x2f, 0x98, 0x27, 0x58, + 0x39, 0xf3, 0x6a, 0xae, 0xf9, 0xbb, 0x15, 0x2c, 0x24, 0x1f, 0xc0, 0x39, 0x51, 0x41, 0x27, 0x0c, + 0xbd, 0xdd, 0x9e, 0x2d, 0x46, 0x01, 0x1d, 0x4f, 0x50, 0x43, 0x2f, 0x98, 0xa7, 0x39, 0x4e, 0x11, + 0x51, 0xca, 0x0c, 0x83, 0x3d, 0xfd, 0x7d, 0x0d, 0x96, 0x23, 0x6f, 0xdf, 0xb5, 0xef, 0xb9, 0xd1, + 0x43, 0xd7, 0xed, 0xd9, 0xde, 0x3e, 0xe5, 0xcb, 0xa2, 0xe9, 0xcc, 0x98, 0x84, 0x96, 0xad, 0xb3, + 0xa2, 0x2a, 0x2b, 0x21, 0x4d, 0x78, 0x5e, 0xa1, 0x60, 0xdf, 0xdd, 0xa7, 0x1a, 0x5b, 0x3f, 0xf0, + 0xd9, 0x4c, 0xf1, 0xfd, 0x3e, 0x4b, 0xfb, 0x31, 0x63, 0x5e, 0x94, 0x58, 0x14, 0x63, 0xd4, 0x26, + 0xc3, 0xac, 0x51, 0x44, 0x52, 0x82, 0xf3, 0xdc, 0x95, 0x83, 0x33, 0xf0, 0x7a, 0xbb, 0x36, 0x55, + 0x9b, 0xfc, 0x9d, 0x1d, 0x3b, 0x8c, 0xdc, 0x3e, 0x4b, 0xe3, 0x61, 0x9e, 0x65, 0x58, 0xcd, 0x18, + 0x69, 0x9d, 0xe1, 0x58, 0x91, 0xdb, 0x27, 0x45, 0x78, 0x66, 0x34, 0x93, 0x7d, 0xe7, 0x11, 0xf3, + 0xad, 0x31, 0xcf, 0x8c, 0xe0, 0xb1, 0xe5, 0x3c, 0xe2, 0x2b, 0xeb, 0x6d, 0x58, 0xe4, 0x07, 0x2f, + 0xae, 0xb5, 0xe1, 0xcc, 0xe3, 0x7b, 0x0a, 0x3e, 0xbb, 0x61, 0xb9, 0x20, 0x81, 0x83, 0xaa, 0x1d, + 0xb1, 0xb3, 0xfd, 0x91, 0x06, 0x27, 0x32, 0x4f, 0x6e, 0xe4, 0x87, 0x81, 0xbd, 0x4b, 0x8e, 0x7c, + 0x3b, 0x70, 0xdb, 0x5e, 0xdf, 0xc3, 0x40, 0x4f, 0xec, 0x66, 0xe3, 0xda, 0xb8, 0x33, 0x1f, 0xbe, + 0x71, 0x6e, 0xf9, 0x66, 0x4c, 0xc4, 0x4c, 0xae, 0x7a, 0x90, 0x02, 0x9f, 0xf9, 0x08, 0x4e, 0x64, + 0xa2, 0x66, 0x98, 0x42, 0x5f, 0x96, 0x4d, 0xa1, 0xc9, 0x5d, 0x75, 0xaa, 0xd1, 0x92, 0x89, 0x94, + 0x37, 0xef, 0xb7, 0xe3, 0xe6, 0xa5, 0xce, 0x78, 0xa4, 0x92, 0x96, 0xf2, 0x59, 0x66, 0x0a, 0x41, + 0x34, 0x5a, 0xd0, 0x7f, 0x04, 0x27, 0xb8, 0xa8, 0x61, 0x9b, 0x5c, 0xcc, 0x8e, 0x55, 0xf4, 0x85, + 0x2c, 0x76, 0x4c, 0x06, 0xdd, 0xa0, 0xf8, 0x31, 0xd7, 0xe3, 0xce, 0x30, 0x90, 0xb7, 0xe1, 0xbb, + 0x79, 0x21, 0xd8, 0x33, 0xaa, 0x93, 0x21, 0xc4, 0xb4, 0x2c, 0x21, 0x76, 0x74, 0x09, 0x5a, 0x07, + 0x22, 0x6f, 0x5d, 0xa1, 0xfc, 0x80, 0x6a, 0x55, 0xcd, 0x56, 0x22, 0x6d, 0x04, 0xfc, 0x65, 0xd0, + 0x52, 0x3b, 0x0d, 0xa2, 0x87, 0x23, 0xa6, 0x30, 0xd0, 0x4f, 0x32, 0x5d, 0xa6, 0xc0, 0x00, 0xd5, + 0x0e, 0xb9, 0x00, 0x73, 0xec, 0x64, 0xae, 0x48, 0x58, 0x40, 0x58, 0x11, 0xc5, 0xec, 0xbb, 0x59, + 0x62, 0x36, 0xb9, 0x62, 0xe1, 0x4a, 0xf9, 0x41, 0xaf, 0xcd, 0x24, 0x6a, 0x4a, 0xfe, 0xda, 0x70, + 0x5a, 0xda, 0x09, 0x78, 0x0f, 0x89, 0x36, 0x4d, 0x63, 0x9b, 0x9e, 0x53, 0xdb, 0x94, 0x6c, 0x10, + 0xac, 0xe7, 0x78, 0xc3, 0x4e, 0x3a, 0x99, 0x70, 0xb2, 0x09, 0x0b, 0x38, 0x8d, 0xbc, 0x5e, 0xe4, + 0x06, 0x0f, 0xe8, 0x66, 0x59, 0x50, 0x6e, 0xfb, 0x87, 0x06, 0xae, 0x2a, 0x10, 0x4d, 0x9c, 0x7f, + 0xf1, 0xcf, 0xf8, 0xd4, 0x76, 0x66, 0x34, 0x09, 0x59, 0x86, 0xc9, 0x8e, 0xdb, 0x8d, 0x1c, 0xbe, + 0x34, 0xd8, 0x0f, 0xaa, 0x61, 0xee, 0x0c, 0xba, 0x5d, 0xa1, 0x61, 0xd2, 0xbf, 0x39, 0xb7, 0x9f, + 0xca, 0x81, 0x9e, 0xee, 0x21, 0x62, 0x40, 0x4e, 0x24, 0x62, 0x19, 0x76, 0x8e, 0xda, 0x3c, 0x66, + 0xe6, 0xbc, 0x0e, 0xb9, 0x0e, 0x80, 0xc9, 0x97, 0x03, 0x77, 0xd7, 0x7d, 0xc4, 0x4f, 0x25, 0x78, + 0x56, 0x48, 0xa0, 0x0a, 0xcd, 0x0c, 0xda, 0x8f, 0x29, 0x98, 0x5c, 0x05, 0x70, 0x1f, 0xb1, 0x04, + 0x39, 0x42, 0xc5, 0xca, 0xf8, 0x8c, 0x66, 0xce, 0x70, 0xac, 0x6a, 0x87, 0x6c, 0x02, 0x11, 0x24, + 0xd2, 0x57, 0x27, 0x0e, 0xf9, 0xaa, 0x66, 0xea, 0x9c, 0xaa, 0x2e, 0x3e, 0xce, 0xcf, 0xf5, 0x33, + 0x30, 0xcd, 0x73, 0xf4, 0xd0, 0x3f, 0x39, 0x92, 0xf1, 0x55, 0x0d, 0x2e, 0x1c, 0xb6, 0x0e, 0xc9, + 0x1d, 0x38, 0x89, 0x2e, 0x85, 0xa1, 0x1f, 0x2f, 0x65, 0xbb, 0xed, 0xb4, 0xf7, 0x5c, 0x2e, 0xf9, + 0x8c, 0xcc, 0x05, 0xdd, 0xef, 0x5b, 0x56, 0x43, 0x5a, 0xcb, 0xfd, 0xbe, 0x15, 0xfa, 0xe2, 0x77, + 0x89, 0x92, 0xf3, 0x01, 0xe9, 0xc0, 0xd9, 0x31, 0x94, 0x92, 0xaa, 0xa1, 0xc9, 0xaa, 0xc6, 0x65, + 0xd0, 0x77, 0xdc, 0x8e, 0x1b, 0xe0, 0x73, 0x7e, 0x5a, 0xb5, 0x07, 0xd7, 0x70, 0x4c, 0xe6, 0xcc, + 0x85, 0x18, 0x6e, 0x85, 0xfe, 0xed, 0x6b, 0xfc, 0x2b, 0x5f, 0xcd, 0x0b, 0x2d, 0x5a, 0x36, 0x51, + 0x91, 0x6b, 0x70, 0x3c, 0x15, 0x89, 0x4d, 0x0a, 0xed, 0x93, 0x5b, 0xd1, 0xcc, 0x25, 0x5a, 0xac, + 0xc6, 0xee, 0x7c, 0x1e, 0xe6, 0xe4, 0xdd, 0x99, 0x4f, 0x05, 0x8a, 0x3c, 0xdb, 0x49, 0xf6, 0x64, + 0x72, 0x0f, 0x16, 0x24, 0xd1, 0x42, 0xb5, 0xdd, 0x7c, 0x86, 0xfc, 0x93, 0x6b, 0x73, 0x25, 0x91, + 0x39, 0xbd, 0x1d, 0x9f, 0x79, 0xd6, 0xa9, 0x2c, 0xcc, 0xf9, 0xb6, 0x8c, 0x42, 0x2c, 0x00, 0xda, + 0x52, 0xbb, 0xeb, 0xed, 0x7b, 0x49, 0x10, 0x85, 0x34, 0x7f, 0xd3, 0x89, 0xdc, 0x1a, 0xc5, 0x60, + 0x33, 0x27, 0xa1, 0x90, 0xcf, 0xb6, 0x81, 0x40, 0x3a, 0xf3, 0xa1, 0x14, 0xdf, 0x14, 0xbf, 0x72, + 0x0e, 0x26, 0x7a, 0x99, 0xe9, 0x0b, 0xf0, 0xdc, 0x7d, 0x0e, 0x26, 0xa2, 0xcc, 0x20, 0xdd, 0x51, + 0x7c, 0x94, 0xe4, 0x63, 0xf0, 0x3f, 0x34, 0x38, 0x9e, 0x51, 0x2b, 0xf2, 0x3c, 0x4c, 0x3b, 0x0f, + 0xdc, 0x40, 0x84, 0x72, 0xe5, 0xd1, 0x9a, 0x38, 0xc8, 0x14, 0x7f, 0x90, 0x55, 0x98, 0xbc, 0x37, + 0x08, 0xc2, 0x88, 0x87, 0x74, 0x42, 0x1f, 0x13, 0x04, 0x98, 0xec, 0x3f, 0x72, 0x1d, 0x16, 0xfa, + 0x6e, 0xe0, 0xf9, 0x1d, 0x3b, 0x74, 0xdb, 0x7e, 0xaf, 0xc3, 0xb4, 0xdc, 0x3c, 0x7f, 0x49, 0xa5, + 0x94, 0x98, 0xf3, 0xec, 0xb7, 0xc5, 0x7e, 0x92, 0xbb, 0x70, 0x7a, 0xdf, 0x79, 0x44, 0xa7, 0x41, + 0x7b, 0x10, 0x04, 0x54, 0x09, 0xf2, 0xfb, 0xdc, 0x30, 0xc6, 0x5c, 0xdb, 0x59, 0xf8, 0xef, 0xd1, + 0x48, 0xe6, 0xa9, 0x7d, 0xe7, 0x51, 0x29, 0x2e, 0x69, 0xc4, 0x05, 0xbc, 0xed, 0x03, 0x31, 0xcb, + 0x33, 0xad, 0x9d, 0x47, 0xb1, 0xa8, 0xbd, 0x02, 0x24, 0xb6, 0x4f, 0xc5, 0xda, 0x07, 0x17, 0x70, + 0x4b, 0xa2, 0x24, 0x56, 0x1b, 0xf8, 0x67, 0x7f, 0x63, 0x4a, 0x74, 0xb9, 0x62, 0x26, 0x25, 0xaf, + 0x80, 0x4e, 0xc5, 0xf3, 0x6e, 0x20, 0x19, 0xe0, 0x92, 0x49, 0xbf, 0x28, 0x95, 0xf1, 0xfb, 0xaf, + 0xa9, 0xc0, 0xdd, 0x8d, 0xef, 0xd2, 0x4c, 0xfe, 0x8b, 0x6a, 0x25, 0x4e, 0x20, 0xae, 0x76, 0xe8, + 0x9f, 0xa4, 0x0a, 0x4b, 0x98, 0xce, 0x2b, 0xf4, 0x7c, 0xcc, 0xb4, 0x87, 0x07, 0xee, 0x09, 0xc5, + 0x98, 0x8a, 0x35, 0x69, 0x4a, 0x48, 0xf4, 0xc4, 0x6d, 0xea, 0xfd, 0x14, 0x84, 0x7c, 0x1e, 0xce, + 0xc8, 0x1b, 0x55, 0x6a, 0x4b, 0xc7, 0xe7, 0x7c, 0xe6, 0xa9, 0x64, 0x0f, 0x2a, 0x2b, 0x9b, 0xfb, + 0x3a, 0x9c, 0xc7, 0x85, 0xed, 0x75, 0xfa, 0xf6, 0x50, 0x4e, 0x45, 0x6c, 0x2e, 0x4b, 0x29, 0x75, + 0x86, 0x62, 0x55, 0x3b, 0xfd, 0x54, 0x7a, 0x45, 0x6c, 0x75, 0x33, 0x73, 0xdb, 0x67, 0x5b, 0xe4, + 0x33, 0x72, 0x63, 0x86, 0x36, 0x7d, 0x26, 0x3a, 0x86, 0x37, 0xfe, 0x5d, 0x58, 0x4a, 0x8e, 0x12, + 0x62, 0xf7, 0x2e, 0xa0, 0x14, 0x3d, 0x23, 0x33, 0x14, 0x47, 0x74, 0xb6, 0x3d, 0xb1, 0x68, 0x55, + 0x43, 0x84, 0x72, 0x4c, 0xb6, 0x81, 0x42, 0x10, 0x92, 0x1a, 0x2c, 0x3b, 0x0f, 0x43, 0x7a, 0xe4, + 0xf3, 0x07, 0xbd, 0x28, 0x8c, 0xbf, 0x35, 0x33, 0xfc, 0x2d, 0xe1, 0xcc, 0xc1, 0x95, 0x05, 0xe2, + 0x3c, 0x0c, 0x8b, 0x9c, 0x4c, 0x70, 0xfb, 0x18, 0x08, 0x53, 0x38, 0x94, 0x7a, 0xc3, 0x61, 0xbc, + 0x98, 0x7f, 0xff, 0x30, 0xa5, 0x1c, 0x59, 0x16, 0x4b, 0xe5, 0x9a, 0xb7, 0xd4, 0x0b, 0xb8, 0x59, + 0xc5, 0x7b, 0x24, 0xdd, 0xdb, 0xcc, 0xb3, 0x45, 0xc2, 0x97, 0x0d, 0x8a, 0x12, 0x18, 0x6d, 0x48, + 0x18, 0x79, 0x12, 0xeb, 0xb1, 0xef, 0x77, 0x5c, 0xf6, 0x22, 0xc2, 0x9c, 0x47, 0x30, 0xad, 0xc0, + 0x56, 0x62, 0xd7, 0xfb, 0x54, 0x03, 0x3d, 0xfd, 0x29, 0xf2, 0x2e, 0x4c, 0xb1, 0x53, 0x1d, 0x57, + 0x8b, 0x8d, 0xec, 0x3a, 0xb1, 0x91, 0x66, 0x07, 0xbc, 0xcd, 0x63, 0x26, 0xa7, 0x21, 0x6f, 0xc2, + 0x84, 0xef, 0x75, 0x84, 0x37, 0xcc, 0x85, 0x71, 0xb4, 0x8d, 0x6a, 0xb9, 0x84, 0x37, 0x68, 0x5e, + 0x87, 0x1b, 0xa2, 0xd6, 0x0b, 0xe2, 0x29, 0xba, 0xf1, 0x31, 0x9c, 0x1d, 0xf3, 0x41, 0x52, 0x81, + 0xc5, 0x94, 0xa7, 0xd0, 0x11, 0x9d, 0x88, 0x9c, 0xc4, 0x89, 0x28, 0x10, 0xc6, 0x89, 0x2e, 0x9c, + 0x1e, 0x59, 0x41, 0x52, 0x1d, 0x29, 0x41, 0x30, 0x36, 0x5a, 0xba, 0x4c, 0x9e, 0xac, 0x29, 0xe9, + 0xc2, 0xbf, 0xf6, 0xf5, 0x1c, 0x1c, 0xcf, 0x98, 0x44, 0xff, 0xd7, 0xea, 0x66, 0xff, 0x58, 0xe3, + 0xfd, 0xa1, 0x0a, 0x03, 0xd2, 0x02, 0xee, 0x88, 0xc6, 0x05, 0xc7, 0xa5, 0xd1, 0x82, 0x43, 0xf6, + 0xef, 0xe1, 0x41, 0xf2, 0x10, 0x20, 0x7b, 0xb1, 0x30, 0xc8, 0x13, 0x78, 0xde, 0xf0, 0xe1, 0xfb, + 0x08, 0x4e, 0x64, 0x0a, 0x76, 0x72, 0x1a, 0x0a, 0xf8, 0x00, 0x26, 0xb1, 0x54, 0x4e, 0xd3, 0xdf, + 0xdb, 0x01, 0x5a, 0xc1, 0xef, 0xb9, 0x4e, 0xe0, 0x06, 0xdc, 0x4e, 0xc6, 0xad, 0xe0, 0x0c, 0x26, + 0x9b, 0xc9, 0x3a, 0xea, 0x2e, 0xc6, 0xaf, 0xee, 0xc9, 0x16, 0x1c, 0x67, 0xd2, 0x85, 0x99, 0x3b, + 0x6c, 0x7e, 0xdd, 0xaf, 0xe6, 0x68, 0x44, 0x12, 0x34, 0x04, 0x31, 0xd3, 0x07, 0xa3, 0x36, 0x97, + 0x76, 0xd3, 0x20, 0xaa, 0x0d, 0x9f, 0xcc, 0xc6, 0x26, 0xeb, 0x30, 0xcb, 0x98, 0xb3, 0x9b, 0x02, + 0xe6, 0xa7, 0x75, 0x71, 0xec, 0x17, 0x4a, 0xf8, 0x2e, 0x34, 0x8c, 0xff, 0x26, 0xcf, 0xc2, 0x3c, + 0xba, 0xc4, 0xda, 0xfb, 0xb2, 0x1b, 0x9a, 0x39, 0x87, 0x40, 0xee, 0x7e, 0x66, 0xfc, 0xbb, 0x58, + 0x47, 0x52, 0xee, 0x28, 0xe9, 0x0e, 0x1c, 0xba, 0x3d, 0xe1, 0x8a, 0x37, 0x63, 0xf2, 0x5f, 0x8f, + 0xa9, 0x15, 0x90, 0xb7, 0x60, 0x8e, 0xb2, 0xdd, 0x1d, 0xf4, 0xd8, 0xce, 0x9c, 0x57, 0x82, 0xf7, + 0x6e, 0xb1, 0x22, 0x3a, 0x6c, 0x9b, 0xc7, 0xcc, 0xd9, 0xfd, 0xe4, 0x27, 0xb9, 0x02, 0x33, 0xe1, + 0x7e, 0xd4, 0x97, 0xf7, 0x73, 0xe1, 0xaf, 0x61, 0x6d, 0xb5, 0x9a, 0x9c, 0xa4, 0x40, 0x71, 0x12, + 0x03, 0xfa, 0xfa, 0x14, 0xf3, 0xd8, 0x30, 0x5e, 0x82, 0x59, 0x89, 0x37, 0x6d, 0x0c, 0x8b, 0xa2, + 0x20, 0x1a, 0xc3, 0x7e, 0xf1, 0xc1, 0xbe, 0x07, 0x05, 0xc1, 0x92, 0x9e, 0xe3, 0xf6, 0xfc, 0x50, + 0xe8, 0x43, 0xf8, 0x37, 0x85, 0xa1, 0x45, 0x8d, 0x36, 0x72, 0xd2, 0xc4, 0xbf, 0xf1, 0x2c, 0x8f, + 0x97, 0x84, 0x98, 0x08, 0x02, 0x5f, 0xce, 0xc4, 0xa6, 0x6c, 0x0a, 0x6f, 0x75, 0x43, 0xf6, 0x9e, + 0x46, 0x18, 0xd5, 0x63, 0x23, 0x48, 0xea, 0x52, 0x77, 0xd4, 0x79, 0x43, 0x39, 0xb2, 0xe7, 0x86, + 0x8f, 0xec, 0x2c, 0x28, 0x2b, 0xa7, 0x64, 0x5f, 0x06, 0x84, 0xb1, 0x23, 0x7b, 0xa2, 0x40, 0x4d, + 0x28, 0x0a, 0x94, 0x74, 0x4d, 0x97, 0x8c, 0x1e, 0x3b, 0xf1, 0x8b, 0x6b, 0xba, 0xb4, 0x4a, 0xf7, + 0x2b, 0xf1, 0x0c, 0x51, 0xae, 0x95, 0xc9, 0x35, 0x38, 0xc1, 0xcc, 0xd4, 0x2c, 0x73, 0x66, 0x5a, + 0x97, 0x3c, 0x8e, 0x85, 0x2c, 0x6d, 0x74, 0xac, 0x53, 0x1e, 0x7e, 0x0d, 0x45, 0x5e, 0x83, 0x65, + 0xaf, 0x17, 0xba, 0xed, 0x41, 0xe0, 0xda, 0xe1, 0x7d, 0xaf, 0xcf, 0x72, 0xb8, 0x1f, 0x70, 0x03, + 0x32, 0x11, 0x65, 0xd6, 0x7d, 0xaf, 0x8f, 0xf9, 0xdc, 0x45, 0x0f, 0xff, 0x6a, 0x4e, 0x5c, 0x6e, + 0xae, 0xfb, 0x7e, 0x14, 0x46, 0x81, 0xd3, 0x57, 0x1c, 0x67, 0xc8, 0x3e, 0x9c, 0xc6, 0x2a, 0x5d, + 0xc3, 0x54, 0xa1, 0x7e, 0x20, 0x2e, 0x83, 0xe3, 0x05, 0x36, 0x7b, 0xed, 0x55, 0xf5, 0x62, 0xa0, + 0x48, 0xb1, 0x8b, 0x32, 0x32, 0x5d, 0x57, 0x12, 0xd7, 0xcd, 0x63, 0xe6, 0x29, 0xc6, 0x73, 0x08, + 0x8b, 0x6c, 0x66, 0xc8, 0x9a, 0xb4, 0xe7, 0xcc, 0x7a, 0x22, 0x78, 0x54, 0xae, 0xb2, 0x48, 0x22, + 0xef, 0xc3, 0x8c, 0xd7, 0x11, 0xb9, 0x59, 0xf3, 0x19, 0x3e, 0x1b, 0xd5, 0x0e, 0x4b, 0xa1, 0x95, + 0xf0, 0xa0, 0x4b, 0xc3, 0xe3, 0xd0, 0xf5, 0x79, 0x45, 0xc3, 0x31, 0x36, 0xc5, 0x3d, 0xda, 0x30, + 0x19, 0x59, 0x48, 0xf6, 0x3e, 0xdc, 0xe7, 0x50, 0x0a, 0x48, 0x39, 0x61, 0xf9, 0x2f, 0xde, 0xe5, + 0x3f, 0x02, 0x97, 0x8f, 0xda, 0x53, 0x54, 0x6e, 0x8c, 0xe8, 0xf6, 0x19, 0x96, 0xc0, 0x43, 0xed, + 0xbd, 0x8b, 0x20, 0x27, 0x1e, 0xf2, 0xc4, 0x44, 0x11, 0xb0, 0xed, 0xc0, 0x33, 0xfe, 0x2c, 0x0f, + 0x0b, 0xaa, 0x6b, 0x15, 0x79, 0x09, 0x26, 0x24, 0x71, 0x79, 0x2a, 0xc3, 0xff, 0x0a, 0x85, 0x24, + 0x22, 0x1d, 0x49, 0x3c, 0x92, 0x9b, 0xb0, 0x80, 0xaf, 0xc3, 0x50, 0x8d, 0x8b, 0x3c, 0xee, 0x36, + 0x70, 0x54, 0x87, 0x80, 0x39, 0x4a, 0x4b, 0xb7, 0x47, 0x5a, 0x28, 0x79, 0xce, 0x4c, 0x8c, 0xf6, + 0x9c, 0xe1, 0x4d, 0x19, 0xe1, 0x39, 0x33, 0x39, 0xc6, 0x73, 0x26, 0xa1, 0x94, 0x3d, 0x67, 0xd0, + 0x7f, 0x6a, 0x7a, 0x94, 0xff, 0x54, 0x42, 0xc3, 0xfc, 0xa7, 0x12, 0xcf, 0x97, 0xc2, 0x48, 0xcf, + 0x97, 0x84, 0x86, 0x7b, 0xbe, 0x24, 0xbe, 0x28, 0x33, 0x23, 0x7d, 0x51, 0x24, 0x22, 0xe6, 0x8b, + 0xf2, 0x1c, 0xef, 0xd8, 0xc0, 0x79, 0x68, 0x63, 0x8f, 0xf3, 0xf3, 0x11, 0x76, 0x99, 0xe9, 0x3c, + 0xc4, 0x57, 0x1c, 0x54, 0x3d, 0xe1, 0x4f, 0x3f, 0x8c, 0x6f, 0xa5, 0xc4, 0x90, 0x18, 0xf3, 0xe7, + 0x61, 0x81, 0xed, 0xc6, 0x3c, 0x31, 0x0c, 0xdb, 0x8e, 0xe7, 0xcd, 0x79, 0x01, 0x65, 0xd7, 0x57, + 0x2f, 0xc0, 0x62, 0x8c, 0xc6, 0x6f, 0x70, 0x30, 0x2c, 0x8d, 0x19, 0x53, 0xf3, 0xbb, 0x1b, 0x99, + 0x5f, 0xc0, 0xc3, 0xdb, 0x2a, 0xfc, 0xd8, 0x05, 0xc8, 0x2b, 0x40, 0x12, 0xb4, 0xf8, 0x21, 0xdc, + 0x04, 0xa2, 0x2e, 0xc5, 0xa8, 0xf1, 0x6b, 0xb5, 0x9f, 0xd7, 0x52, 0xce, 0x1b, 0xdf, 0xaf, 0xea, + 0xbf, 0x04, 0xf1, 0xd7, 0x6d, 0x7e, 0x01, 0x2f, 0x5a, 0xa0, 0x8b, 0x82, 0x26, 0x87, 0x1b, 0xff, + 0xdf, 0x90, 0x69, 0xfe, 0xfb, 0x55, 0xad, 0xb3, 0x30, 0x93, 0x9c, 0x84, 0xd8, 0x76, 0x55, 0x08, + 0xf9, 0x21, 0xc8, 0xf8, 0xd5, 0x09, 0xe5, 0xc6, 0x59, 0xd4, 0x81, 0xea, 0x40, 0xa1, 0x6f, 0xf3, + 0x09, 0xc0, 0x45, 0xf4, 0xc5, 0x11, 0x93, 0x98, 0x3f, 0x0c, 0xb2, 0xac, 0x86, 0x09, 0x61, 0xe8, + 0x8b, 0x77, 0x42, 0x36, 0xb3, 0x25, 0x4a, 0x67, 0x42, 0xc1, 0x8e, 0xc9, 0xe3, 0xb5, 0xf1, 0xec, + 0xc4, 0x95, 0x1e, 0x5d, 0xc3, 0x68, 0x53, 0x8c, 0x7f, 0x89, 0x0f, 0x6c, 0x03, 0x7a, 0x89, 0x84, + 0x2a, 0xf3, 0x2c, 0xcb, 0xdb, 0x10, 0x73, 0xec, 0x42, 0xe4, 0x8c, 0xf7, 0x7d, 0xa1, 0xcc, 0xb6, + 0x02, 0x73, 0x78, 0x9f, 0x2b, 0x18, 0x4e, 0x64, 0xf8, 0xb1, 0x0d, 0x37, 0xbe, 0x54, 0xdd, 0x32, + 0x67, 0x29, 0x9d, 0x60, 0xb3, 0xa7, 0xd8, 0xde, 0x53, 0x95, 0x9c, 0x14, 0xc9, 0x9e, 0xc6, 0xf6, + 0x40, 0x72, 0x59, 0x8b, 0x55, 0x95, 0x8c, 0xf0, 0x4a, 0x85, 0xdb, 0x70, 0x7a, 0xe8, 0x0e, 0x32, + 0xfe, 0x12, 0xf3, 0xab, 0xbc, 0x3c, 0xe2, 0x4b, 0xa9, 0xcb, 0x49, 0x37, 0x30, 0x4f, 0x86, 0x2a, + 0x88, 0x7f, 0x04, 0x1f, 0xe2, 0x8d, 0x1e, 0xf8, 0x31, 0x69, 0xc8, 0x13, 0x25, 0x2b, 0x27, 0x2b, + 0x59, 0xf2, 0xcd, 0x6f, 0x5e, 0xbd, 0xf9, 0xdd, 0x80, 0x0b, 0x54, 0x22, 0xf2, 0x99, 0xe3, 0x3e, + 0x70, 0x83, 0x03, 0xbf, 0x87, 0x11, 0x82, 0xfb, 0xb1, 0x60, 0x60, 0x57, 0xd5, 0xe7, 0x28, 0x1e, + 0xce, 0x8b, 0x0a, 0xc7, 0xda, 0x42, 0x24, 0x16, 0xf9, 0xfa, 0x1f, 0xe6, 0xe1, 0xd9, 0x23, 0x4c, + 0xae, 0x31, 0x75, 0xff, 0x82, 0x7a, 0x14, 0xc8, 0x29, 0xb7, 0x40, 0x94, 0x69, 0x72, 0xbb, 0x30, + 0xe2, 0x20, 0xf0, 0xc3, 0xb0, 0xc8, 0x36, 0x31, 0xf6, 0x12, 0x71, 0x67, 0xd0, 0x3d, 0xc2, 0x2e, + 0x76, 0x56, 0xc4, 0x59, 0x49, 0x91, 0xe2, 0xc6, 0x86, 0xb2, 0xdb, 0x8a, 0x61, 0xa4, 0x05, 0xb3, + 0x88, 0xb6, 0xe3, 0x78, 0xdd, 0x23, 0x05, 0xfc, 0x10, 0x51, 0x5c, 0x64, 0x32, 0xf6, 0xe2, 0x9a, + 0x02, 0x36, 0xf0, 0x37, 0xb9, 0x04, 0x8b, 0xbd, 0xc1, 0x3e, 0x55, 0x72, 0xd9, 0xcc, 0xe5, 0x0f, + 0x3e, 0x26, 0xcd, 0xf9, 0xde, 0x60, 0xbf, 0xd8, 0xef, 0xe3, 0x04, 0xc4, 0x97, 0x21, 0x4b, 0x14, + 0x8f, 0x67, 0xf7, 0xe7, 0x98, 0x53, 0x88, 0x49, 0x19, 0x30, 0x11, 0xc4, 0x71, 0x97, 0x81, 0xbd, + 0x13, 0xe4, 0xe9, 0xdc, 0xd9, 0x0f, 0xe3, 0xbf, 0xe7, 0xc4, 0xbd, 0xc4, 0xe8, 0x55, 0xfa, 0x37, + 0x43, 0x94, 0x31, 0x44, 0x97, 0x41, 0xa7, 0x5d, 0x9f, 0x88, 0xc0, 0x78, 0x8c, 0x16, 0x7a, 0x83, + 0xfd, 0xb8, 0xef, 0xe4, 0x8e, 0x9f, 0x92, 0x3b, 0xfe, 0x2d, 0x61, 0xa6, 0xce, 0x14, 0x66, 0xa3, + 0xbb, 0x9c, 0x6a, 0x7f, 0x97, 0x8e, 0x26, 0xb2, 0xfe, 0x66, 0xdc, 0x32, 0xc6, 0x2d, 0xe5, 0x14, + 0x33, 0x39, 0xe4, 0x14, 0x93, 0xb1, 0xf6, 0xa6, 0xb2, 0xd6, 0xde, 0x90, 0x0b, 0xce, 0x74, 0x86, + 0x0b, 0x4e, 0xe6, 0x02, 0x2d, 0x1c, 0xb2, 0x40, 0x67, 0xe4, 0x79, 0xf2, 0x7b, 0x39, 0xb8, 0x78, + 0xe8, 0xbe, 0xf1, 0x37, 0x23, 0x9d, 0x31, 0xd2, 0xd9, 0xfd, 0xf9, 0x67, 0x39, 0xa1, 0x4d, 0xab, + 0x87, 0xe4, 0x8f, 0xe0, 0xb8, 0x38, 0x24, 0x33, 0xbd, 0x21, 0xf1, 0x54, 0x9b, 0xbd, 0xf6, 0x62, + 0xd6, 0xf1, 0x18, 0xd1, 0x32, 0x8e, 0xb0, 0x4b, 0xfc, 0x60, 0x9c, 0x94, 0xff, 0x9f, 0x73, 0x24, + 0x26, 0x77, 0xe1, 0x24, 0x26, 0x7c, 0x6c, 0xcb, 0x3e, 0x76, 0x76, 0xe0, 0xee, 0xf0, 0x5e, 0xbf, + 0x38, 0x74, 0x74, 0xf4, 0xda, 0x52, 0x75, 0x4c, 0x77, 0x67, 0xf3, 0x98, 0xb9, 0x1c, 0x66, 0xc0, + 0xb9, 0x21, 0x2a, 0x75, 0xe6, 0xfe, 0x67, 0x1a, 0x18, 0x87, 0xf7, 0x1a, 0x9a, 0x47, 0xd2, 0xdd, + 0x3e, 0x63, 0xce, 0x3a, 0x52, 0x1f, 0x3e, 0x0b, 0xf3, 0x81, 0xbb, 0x13, 0xb8, 0xe1, 0x9e, 0x62, + 0xc3, 0x9c, 0xe3, 0x40, 0xd1, 0x3d, 0x22, 0xf7, 0xcc, 0x63, 0x1d, 0x57, 0x05, 0x51, 0xec, 0x07, + 0x71, 0x6e, 0xdc, 0x98, 0xd0, 0x99, 0x25, 0x57, 0x93, 0xfd, 0x88, 0x3d, 0x25, 0x73, 0x7a, 0xde, + 0xe4, 0xd9, 0x72, 0x76, 0xbc, 0xae, 0x6b, 0xfc, 0x56, 0xac, 0xbb, 0x65, 0x75, 0x27, 0xf9, 0x48, + 0x7a, 0x21, 0x9d, 0x1f, 0x52, 0x4b, 0xb3, 0x48, 0x8e, 0x62, 0x6c, 0xae, 0x3d, 0x25, 0x63, 0xf3, + 0x75, 0xf1, 0xcc, 0x8a, 0xee, 0x2a, 0xb7, 0xaf, 0x92, 0x17, 0x61, 0x9a, 0xbd, 0xac, 0x12, 0xd5, + 0x5d, 0x54, 0xaa, 0x7b, 0xfb, 0xaa, 0x29, 0xca, 0x8d, 0x4f, 0x63, 0x9f, 0xd0, 0xa1, 0x46, 0xdc, + 0xbe, 0x4a, 0xde, 0x3a, 0xda, 0x8b, 0xe7, 0x82, 0x78, 0xf1, 0x1c, 0xbf, 0x76, 0x7e, 0x5b, 0x79, + 0xed, 0xfc, 0xdc, 0xf8, 0xde, 0xe2, 0xee, 0xc6, 0x2c, 0x03, 0x49, 0xe1, 0xd3, 0x5f, 0x62, 0x91, + 0xdf, 0x8d, 0x9f, 0xc8, 0xc3, 0x33, 0x63, 0x29, 0xc8, 0x39, 0x28, 0x14, 0x9b, 0xd5, 0x56, 0x32, + 0xca, 0x74, 0x15, 0x09, 0x08, 0xb9, 0x01, 0x33, 0xeb, 0x4e, 0xe8, 0xb5, 0xe9, 0x94, 0xce, 0x74, + 0xb6, 0x1a, 0x62, 0x1b, 0xa3, 0x6f, 0x1e, 0x33, 0x13, 0x5a, 0x62, 0xc3, 0x12, 0xae, 0x0b, 0x25, + 0x6d, 0x7c, 0x3e, 0xc3, 0x24, 0x37, 0xc4, 0x70, 0x88, 0x8c, 0x4a, 0x9e, 0x21, 0x20, 0xb9, 0x07, + 0xc4, 0xb2, 0x36, 0x4b, 0x6e, 0x10, 0x71, 0x23, 0x55, 0xe4, 0xc5, 0xcf, 0x67, 0x5f, 0x3b, 0xa4, + 0xef, 0x86, 0xe8, 0x36, 0x8f, 0x99, 0x19, 0xdc, 0xc8, 0x45, 0x98, 0xed, 0x07, 0xde, 0x03, 0x27, + 0x72, 0xd1, 0x09, 0x9e, 0x6a, 0x41, 0x73, 0x9b, 0xc7, 0x4c, 0xe0, 0xc0, 0x5b, 0xee, 0x41, 0xb6, + 0x6c, 0xf8, 0x11, 0xa1, 0x7a, 0x8e, 0xee, 0xad, 0xc7, 0x48, 0x03, 0x75, 0x19, 0x0a, 0x4d, 0xf1, + 0x2a, 0x41, 0x8a, 0x57, 0x20, 0x5e, 0x20, 0x98, 0x71, 0x29, 0x9f, 0xd9, 0x3f, 0xa7, 0x09, 0x23, + 0xde, 0xe1, 0x7d, 0x4b, 0x5e, 0x04, 0x91, 0xe5, 0x5f, 0x5c, 0x90, 0x61, 0xfc, 0x84, 0xf8, 0xe1, + 0x8d, 0x19, 0x17, 0x93, 0x37, 0x40, 0xc9, 0xee, 0xcf, 0xeb, 0x82, 0x69, 0xaa, 0x39, 0x3a, 0x93, + 0xde, 0xa6, 0x82, 0xc6, 0x2b, 0xe5, 0xc3, 0x8b, 0x47, 0x1e, 0x0d, 0xf2, 0x2e, 0xe8, 0x98, 0x24, + 0xdd, 0x91, 0x46, 0x56, 0x4d, 0x57, 0xc0, 0xc2, 0xd7, 0x36, 0x1d, 0x2f, 0x30, 0x17, 0xdb, 0x2a, + 0x35, 0xff, 0xe0, 0xaf, 0xf1, 0x0c, 0x91, 0xd5, 0x4e, 0x33, 0x75, 0x31, 0xff, 0xa4, 0x41, 0x2f, + 0x2a, 0xca, 0x3a, 0x15, 0x7b, 0x5d, 0xf6, 0xb7, 0x46, 0xc7, 0xbe, 0x90, 0x16, 0xed, 0xdf, 0xc9, + 0xc3, 0xb9, 0x71, 0xe4, 0xa4, 0x08, 0x7a, 0x85, 0x05, 0xd2, 0xe5, 0xcf, 0x8d, 0xfd, 0x40, 0xce, + 0x58, 0xcc, 0x82, 0xec, 0xda, 0x9d, 0xb8, 0xd0, 0x1c, 0x42, 0xa7, 0xe3, 0xcc, 0x60, 0x71, 0x44, + 0x07, 0x1c, 0x67, 0x4e, 0x4a, 0xc7, 0x59, 0x14, 0x93, 0x67, 0x61, 0xaa, 0x58, 0xb2, 0xb6, 0xcd, + 0x9a, 0x1c, 0xae, 0xc5, 0x69, 0x87, 0xf8, 0xa8, 0x97, 0x17, 0x91, 0x1f, 0x02, 0x3d, 0x9d, 0x99, + 0x15, 0x0f, 0xcf, 0x52, 0x28, 0x32, 0x39, 0x65, 0xa8, 0x48, 0xce, 0x83, 0xf5, 0x4d, 0x92, 0x8d, + 0xf2, 0xfc, 0x7d, 0xe6, 0x10, 0x2f, 0x62, 0xc0, 0x54, 0x33, 0x70, 0x43, 0x37, 0x92, 0x9f, 0x45, + 0xf7, 0x11, 0x62, 0xf2, 0x12, 0xfe, 0x68, 0xd9, 0x39, 0x60, 0x41, 0x2d, 0xa7, 0xe4, 0xe0, 0xc5, + 0xf8, 0xca, 0x99, 0x82, 0x4d, 0x09, 0x85, 0x12, 0xd4, 0x9c, 0x41, 0xaf, 0xbd, 0xb7, 0x6d, 0xd6, + 0xb8, 0x5a, 0xcb, 0x08, 0xba, 0x08, 0xa5, 0x0d, 0x0c, 0x4d, 0x09, 0xc5, 0xf8, 0x49, 0x0d, 0x96, + 0xb3, 0xda, 0x71, 0x88, 0x3f, 0xd3, 0x6b, 0x30, 0x8b, 0xf7, 0xb7, 0x3b, 0x7e, 0xb0, 0xef, 0x44, + 0xf2, 0xe3, 0x71, 0x09, 0x6c, 0xe2, 0x7d, 0xf3, 0x06, 0xfe, 0x4d, 0x56, 0xc5, 0x6e, 0x25, 0xe5, + 0x5d, 0x42, 0x00, 0xdf, 0xb8, 0x8c, 0x22, 0x40, 0xb5, 0xd3, 0x6c, 0xf4, 0x59, 0xf2, 0xd0, 0xd7, + 0x61, 0x82, 0x56, 0x2b, 0x35, 0x7b, 0xe9, 0xfc, 0x29, 0x6e, 0xd5, 0x38, 0x12, 0xab, 0x55, 0xe8, + 0xec, 0x77, 0x4d, 0x44, 0x36, 0xee, 0xc0, 0x82, 0x8a, 0x41, 0x2a, 0x6a, 0xfe, 0xa8, 0xd9, 0x6b, + 0x3a, 0xe7, 0xb4, 0xee, 0xfb, 0x2c, 0x80, 0xc9, 0xfa, 0xe9, 0x6f, 0x7f, 0xb6, 0x0a, 0xf4, 0x27, + 0xa3, 0xc9, 0xca, 0x2f, 0x65, 0xfc, 0x4c, 0x0e, 0x96, 0x93, 0x20, 0x8b, 0x62, 0x0d, 0xfd, 0xc0, + 0x06, 0xf0, 0x2a, 0x2a, 0x01, 0xa6, 0x84, 0x12, 0x3a, 0xdc, 0xc0, 0x31, 0x71, 0x6d, 0x6e, 0xc0, + 0xca, 0x28, 0x7c, 0xf2, 0x12, 0xcc, 0x60, 0xac, 0xef, 0xbe, 0xd3, 0x76, 0x65, 0x91, 0xdb, 0x13, + 0x40, 0x33, 0x29, 0x37, 0x7e, 0x4f, 0x83, 0x33, 0x3c, 0xec, 0xc6, 0x96, 0xe3, 0xf5, 0xf0, 0xba, + 0xb0, 0xed, 0x3e, 0x9d, 0x00, 0x74, 0x37, 0x14, 0x39, 0xf6, 0xbc, 0x1a, 0x5d, 0x65, 0xe8, 0x6b, + 0xa3, 0x5b, 0x4b, 0x5e, 0xc4, 0xf8, 0xf5, 0xdc, 0x9d, 0x79, 0x82, 0x45, 0x08, 0xed, 0x51, 0x80, + 0x1c, 0x21, 0x14, 0x31, 0x8c, 0x1f, 0x85, 0xf3, 0xe3, 0x3f, 0x40, 0xbe, 0x0c, 0xf3, 0x98, 0xed, + 0x7f, 0xbb, 0xbf, 0x1b, 0x38, 0x1d, 0x57, 0x18, 0x89, 0xc5, 0x4d, 0x87, 0x5c, 0xc6, 0xc2, 0xf1, + 0xf3, 0x88, 0x95, 0x14, 0x6e, 0x0f, 0x38, 0x91, 0x12, 0xdb, 0x46, 0xe6, 0x66, 0xfc, 0x98, 0x06, + 0x64, 0x98, 0x07, 0x79, 0x13, 0xe6, 0xb6, 0x5b, 0x25, 0x2b, 0x72, 0x82, 0x68, 0xd3, 0x1f, 0x04, + 0x3c, 0x16, 0x3e, 0x0b, 0x60, 0x18, 0xb5, 0x6d, 0x76, 0x31, 0xbc, 0xe7, 0x0f, 0x02, 0x53, 0xc1, + 0xc3, 0x8c, 0xf0, 0xae, 0x7b, 0xbf, 0xe3, 0x1c, 0xa8, 0x19, 0xe1, 0x39, 0x4c, 0xc9, 0x08, 0xcf, + 0x61, 0xc6, 0xaf, 0x68, 0x70, 0x56, 0x3c, 0x9c, 0xec, 0x64, 0xd4, 0xa5, 0x84, 0x61, 0x7a, 0x03, + 0x91, 0x95, 0x6b, 0x9c, 0xa2, 0xbf, 0x24, 0x22, 0x59, 0x63, 0x05, 0x51, 0xe3, 0x67, 0xb4, 0xe4, + 0x0b, 0x30, 0x61, 0x45, 0x7e, 0xff, 0x08, 0xa1, 0xac, 0xf5, 0x78, 0x44, 0x23, 0xbf, 0x8f, 0x2c, + 0x90, 0xd2, 0x70, 0x61, 0x59, 0xae, 0x9c, 0xa8, 0x31, 0xd9, 0x82, 0x69, 0x9e, 0x07, 0x21, 0xe5, + 0xbc, 0x3b, 0xa6, 0x4d, 0xeb, 0x8b, 0x22, 0x5e, 0x36, 0xcf, 0x1f, 0x65, 0x0a, 0x1e, 0xc6, 0x4f, + 0x6b, 0x30, 0x4b, 0x55, 0x1d, 0xb4, 0x18, 0x3c, 0xe9, 0x94, 0x56, 0x55, 0x68, 0xf1, 0x9e, 0x21, + 0x66, 0x7f, 0xa4, 0xdd, 0xf8, 0x0d, 0x58, 0x4c, 0x11, 0x10, 0x03, 0x23, 0xa5, 0x76, 0xbd, 0x36, + 0xf7, 0xe6, 0x64, 0x6f, 0x01, 0x14, 0x98, 0xf1, 0x53, 0x1a, 0x2c, 0x37, 0xee, 0x47, 0x0e, 0xf3, + 0xdf, 0x30, 0x07, 0x5d, 0xb1, 0xde, 0xa9, 0xfa, 0x26, 0x5e, 0xe0, 0xb2, 0xa0, 0x8c, 0x4c, 0x7d, + 0xe3, 0x30, 0x33, 0x2e, 0x25, 0x9b, 0x50, 0xe0, 0xfb, 0x4b, 0xc8, 0x33, 0x18, 0x9d, 0x97, 0xcc, + 0x19, 0x09, 0x63, 0x8e, 0x44, 0x5b, 0x82, 0x22, 0x8c, 0xd3, 0x98, 0x31, 0xb5, 0xf1, 0xe7, 0x1a, + 0x9c, 0x1a, 0x41, 0x43, 0xde, 0x83, 0x49, 0x0c, 0x18, 0xc5, 0x47, 0xef, 0xdc, 0x88, 0x4f, 0x44, + 0xed, 0xbd, 0xdb, 0x57, 0xd9, 0x46, 0xb4, 0x4f, 0x7f, 0x98, 0x8c, 0x8a, 0x7c, 0x04, 0x33, 0xc5, + 0x4e, 0x87, 0x1f, 0xec, 0x72, 0xca, 0xc1, 0x6e, 0xc4, 0x17, 0xaf, 0xc4, 0xf8, 0xec, 0x60, 0xc7, + 0x42, 0x97, 0x74, 0x3a, 0x36, 0x0f, 0x86, 0x95, 0xf0, 0x3b, 0xf3, 0x2e, 0x2c, 0xa8, 0xc8, 0x8f, + 0x15, 0xbf, 0xe7, 0x53, 0x0d, 0x74, 0xb5, 0x0e, 0xdf, 0x9f, 0x48, 0xdf, 0x59, 0xc3, 0x7c, 0xc8, + 0xa4, 0xfa, 0xd9, 0x1c, 0x9c, 0xc8, 0xec, 0x61, 0xf2, 0x0a, 0x4c, 0x15, 0xfb, 0xfd, 0x6a, 0x99, + 0xcf, 0x2a, 0xae, 0x21, 0xe1, 0xcd, 0x86, 0x72, 0xee, 0x65, 0x48, 0xe4, 0x75, 0x28, 0x30, 0x37, + 0xa1, 0xb2, 0x10, 0x38, 0x18, 0xba, 0x98, 0xfb, 0x30, 0xa9, 0xb9, 0x84, 0x04, 0x22, 0xd9, 0x80, + 0x05, 0x1e, 0xf4, 0x17, 0x7d, 0xc6, 0xe2, 0xfc, 0x9e, 0xe8, 0x66, 0x27, 0xae, 0x4b, 0x98, 0xb7, + 0x99, 0x22, 0x3b, 0x53, 0x54, 0xa4, 0x06, 0x3a, 0xf2, 0x94, 0x39, 0xb1, 0x84, 0x46, 0x92, 0x9b, + 0xe6, 0x08, 0x5e, 0x43, 0x94, 0xf1, 0x70, 0x25, 0x6f, 0xc1, 0xbe, 0x7f, 0xc3, 0x95, 0x7c, 0xe3, + 0x48, 0xc3, 0xf5, 0x0b, 0x13, 0x6c, 0x31, 0xa7, 0xc9, 0xa8, 0x46, 0x23, 0xa5, 0xf3, 0x43, 0x8d, + 0x86, 0x9e, 0xd8, 0x78, 0x58, 0xdb, 0x32, 0x4c, 0xb7, 0x78, 0x76, 0x35, 0xb6, 0x32, 0x9e, 0xc9, + 0xac, 0x02, 0xc3, 0xb9, 0x7d, 0x95, 0xa9, 0x2f, 0x3c, 0xdb, 0x9a, 0x29, 0x48, 0xc9, 0x6d, 0x98, + 0x2d, 0x75, 0x5d, 0xa7, 0x37, 0xe8, 0xb7, 0x8e, 0xe6, 0x9d, 0xb0, 0xc2, 0xdb, 0x32, 0xd7, 0x66, + 0x64, 0xe8, 0xd5, 0x80, 0x92, 0x5c, 0x66, 0x44, 0x5a, 0x71, 0x30, 0x9b, 0x09, 0xb4, 0x95, 0xbe, + 0x36, 0xa6, 0x7f, 0xd2, 0xc0, 0x91, 0xa9, 0x8f, 0x88, 0x0d, 0x0b, 0x35, 0x27, 0x8c, 0x5a, 0x81, + 0xd3, 0x0b, 0x31, 0xf5, 0xc9, 0x11, 0xc2, 0xb8, 0x9f, 0xe5, 0x15, 0x66, 0x36, 0xd4, 0x28, 0x26, + 0x65, 0x36, 0x54, 0x95, 0x1d, 0xd5, 0x97, 0x36, 0xbc, 0x9e, 0xd3, 0xf5, 0xbe, 0x22, 0x62, 0x7e, + 0x31, 0x7d, 0x69, 0x47, 0x00, 0xcd, 0xa4, 0xdc, 0xf8, 0xd2, 0xd0, 0xb8, 0xb1, 0x5a, 0xce, 0xc2, + 0x34, 0x8f, 0x08, 0xc9, 0x22, 0x24, 0x36, 0x2b, 0xf5, 0x72, 0xb5, 0x7e, 0x43, 0xd7, 0xc8, 0x02, + 0x40, 0xd3, 0x6c, 0x94, 0x2a, 0x96, 0x45, 0x7f, 0xe7, 0xe8, 0x6f, 0x1e, 0x3e, 0x71, 0x63, 0xbb, + 0xa6, 0xe7, 0xa5, 0x08, 0x8a, 0x13, 0xc6, 0xd7, 0x72, 0x70, 0x32, 0x7b, 0x28, 0x49, 0x8b, 0x3f, + 0x61, 0x60, 0x7e, 0x2a, 0x6f, 0x8e, 0x1d, 0xf7, 0x4c, 0x70, 0x3a, 0x16, 0x67, 0xc4, 0x62, 0x3c, + 0xe6, 0xc4, 0x05, 0x27, 0x0b, 0x1a, 0xe5, 0x75, 0xb8, 0xb3, 0xaa, 0x9a, 0xb0, 0xea, 0xe2, 0x98, + 0xef, 0x59, 0x6a, 0xaa, 0xaa, 0x12, 0xac, 0x8c, 0xfa, 0xbc, 0xda, 0x4b, 0x8b, 0x30, 0x5b, 0x6c, + 0x36, 0x6b, 0xd5, 0x52, 0xb1, 0x55, 0x6d, 0xd4, 0x75, 0x8d, 0xcc, 0xc0, 0xe4, 0x0d, 0xb3, 0xb1, + 0xdd, 0xd4, 0x73, 0xc6, 0xbf, 0xd7, 0xe0, 0xcc, 0xe8, 0x6f, 0x91, 0x15, 0x98, 0xf6, 0x07, 0x51, + 0xdb, 0x17, 0x07, 0x24, 0x53, 0xfc, 0x24, 0x0b, 0x90, 0xe3, 0x4a, 0xc9, 0x8c, 0x99, 0xf3, 0xfb, + 0x64, 0x97, 0xfb, 0x8c, 0xf0, 0x97, 0x9a, 0x47, 0x8a, 0x98, 0xf7, 0x1c, 0x9f, 0x3d, 0x2b, 0x2a, + 0x65, 0x2a, 0x0e, 0xda, 0x3c, 0x2d, 0x6d, 0x8a, 0x42, 0xf2, 0x2c, 0xcc, 0xef, 0x38, 0x5e, 0x77, + 0x10, 0xb8, 0x36, 0x3a, 0xa6, 0xe3, 0x1a, 0x98, 0x34, 0xe7, 0x38, 0xb0, 0x44, 0x61, 0xc6, 0x1f, + 0x6a, 0x30, 0x5f, 0x4d, 0x3c, 0x8b, 0x9f, 0x54, 0x1c, 0xbd, 0xa3, 0x88, 0xa3, 0x95, 0x38, 0xfe, + 0x6e, 0xfc, 0x81, 0x31, 0x3a, 0xf5, 0x7a, 0x1c, 0x24, 0x2d, 0xaf, 0x78, 0xe0, 0xc8, 0xd4, 0x22, + 0xfc, 0x54, 0x9c, 0xa4, 0x55, 0x0d, 0xa2, 0x26, 0xc9, 0xb3, 0x7f, 0x9a, 0x87, 0xa5, 0xa1, 0xef, + 0x12, 0x0b, 0xa6, 0x8b, 0x77, 0xac, 0x46, 0xb5, 0x5c, 0xe2, 0xad, 0x5b, 0x4d, 0xbc, 0x51, 0x29, + 0x74, 0xb8, 0xa6, 0x2c, 0xee, 0xdd, 0xc3, 0xd0, 0xf6, 0xbd, 0x4e, 0x5b, 0x71, 0x87, 0x16, 0x9c, + 0x50, 0x3f, 0xf8, 0xca, 0x20, 0x40, 0x0f, 0x6f, 0xde, 0xf2, 0xd8, 0xc9, 0x55, 0xc0, 0x87, 0x19, + 0xa3, 0xcf, 0xb3, 0x43, 0xcb, 0x87, 0x59, 0x27, 0xfc, 0x48, 0x1d, 0xa6, 0x6e, 0x78, 0xd1, 0xe6, + 0xe0, 0x1e, 0xef, 0x95, 0xf3, 0x49, 0x06, 0xfa, 0xcd, 0xc1, 0xbd, 0x61, 0xb6, 0x68, 0x43, 0x66, + 0x31, 0x6f, 0x14, 0x96, 0x9c, 0x0b, 0xb9, 0x05, 0x93, 0xc5, 0x3b, 0x96, 0x59, 0xe4, 0x32, 0x4b, + 0xf2, 0xf7, 0x35, 0x8b, 0x23, 0xb8, 0xd1, 0xd6, 0x07, 0x8e, 0xc2, 0x8d, 0xf1, 0x48, 0xc7, 0xfd, + 0x9a, 0x78, 0xac, 0xb8, 0x5f, 0xeb, 0xf3, 0x30, 0xcb, 0x8f, 0xb9, 0x78, 0x82, 0x7c, 0x04, 0xc7, + 0x33, 0x86, 0x9a, 0x38, 0xf8, 0xaa, 0x00, 0xfd, 0x10, 0x8a, 0xbd, 0x83, 0x87, 0x7b, 0x6e, 0xe0, + 0x0e, 0x8f, 0x9d, 0x5a, 0x77, 0x31, 0x4b, 0x32, 0x6b, 0x6f, 0x0e, 0xb1, 0x33, 0x7e, 0x5b, 0x83, + 0x95, 0x51, 0x13, 0x80, 0x9e, 0xd9, 0xd5, 0xc8, 0xa2, 0x27, 0xe3, 0x4c, 0xcb, 0xea, 0x6b, 0x00, + 0x81, 0x46, 0x3e, 0x80, 0x59, 0xe6, 0x31, 0x6a, 0xbd, 0xbe, 0x6d, 0x56, 0xb9, 0x2c, 0x7b, 0xe6, + 0xbb, 0x9f, 0xad, 0x9e, 0xe2, 0x4e, 0xa6, 0xe1, 0xeb, 0xf6, 0x20, 0xf0, 0x12, 0xd2, 0x15, 0xcd, + 0x94, 0x29, 0xe8, 0x11, 0xcb, 0x19, 0x74, 0x3c, 0x57, 0x1c, 0x30, 0x45, 0xf4, 0x45, 0x0e, 0x93, + 0x15, 0x1e, 0x01, 0x33, 0xbe, 0xa6, 0xc1, 0x99, 0xd1, 0xb3, 0x8d, 0x2a, 0x51, 0x2d, 0xe6, 0x78, + 0x2b, 0xe2, 0x1f, 0xa2, 0x12, 0x15, 0x7b, 0xe7, 0xca, 0x3c, 0x05, 0x22, 0x25, 0xe2, 0xa6, 0x50, + 0x61, 0x41, 0x43, 0xa2, 0xd8, 0x52, 0x2a, 0x13, 0x09, 0x44, 0xe3, 0x2e, 0x9c, 0x1a, 0x31, 0x37, + 0xc9, 0xfb, 0x99, 0xf9, 0xdb, 0x31, 0xbe, 0x8d, 0x1c, 0xc0, 0x48, 0x4e, 0xbe, 0x20, 0xe3, 0x1b, + 0xff, 0x86, 0xb9, 0x9a, 0x67, 0x4c, 0x54, 0xaa, 0xef, 0x61, 0xbe, 0xf0, 0x62, 0xaf, 0xbd, 0xe7, + 0x07, 0xc9, 0x60, 0xa1, 0xbe, 0x17, 0xd1, 0x12, 0xdb, 0xc1, 0xa2, 0xd4, 0xa0, 0xa5, 0xa8, 0x88, + 0x0f, 0x4b, 0xcd, 0xc0, 0xdf, 0xf1, 0x58, 0x30, 0x04, 0x76, 0x4c, 0xe7, 0x6b, 0xfa, 0xb2, 0x34, + 0xdd, 0xe4, 0xe9, 0x33, 0x84, 0x1f, 0xe7, 0xba, 0xa4, 0x60, 0xe6, 0xad, 0xd4, 0xc6, 0x02, 0x73, + 0x98, 0xb7, 0xf1, 0xed, 0x1c, 0x5c, 0x3c, 0x94, 0xe3, 0x51, 0xd3, 0x9e, 0xbf, 0x0a, 0xc0, 0x69, + 0x69, 0x0f, 0x48, 0x46, 0x38, 0x51, 0x19, 0x27, 0xe8, 0x99, 0x12, 0x0a, 0xb9, 0x0f, 0xcf, 0x88, + 0x5f, 0xed, 0xb6, 0xdb, 0x8f, 0x42, 0x5a, 0x0f, 0x9e, 0x10, 0x21, 0x8e, 0xec, 0x58, 0x58, 0x7f, + 0xfe, 0xbb, 0x9f, 0xad, 0x5e, 0x8c, 0x79, 0x30, 0x4c, 0xf6, 0x20, 0x46, 0xe4, 0x56, 0x40, 0x53, + 0xe0, 0x78, 0x5e, 0xe4, 0x52, 0xb2, 0x92, 0x26, 0x12, 0xa3, 0xbe, 0x58, 0x49, 0xc9, 0xfa, 0xd9, + 0x04, 0xc2, 0x19, 0x51, 0xb2, 0x0d, 0xf9, 0x76, 0x9f, 0x49, 0x4d, 0x51, 0x13, 0x66, 0x5a, 0x64, + 0xe5, 0x66, 0x06, 0x8d, 0xf1, 0xf3, 0x6c, 0x61, 0x67, 0x4a, 0x07, 0xf2, 0x10, 0x16, 0x6b, 0x6c, + 0xc3, 0x14, 0x9d, 0xcd, 0xe5, 0xca, 0xb5, 0xc3, 0x07, 0xba, 0x1a, 0xf1, 0x97, 0x8a, 0xd6, 0x60, + 0x7f, 0xdf, 0x09, 0x0e, 0xd6, 0x4f, 0x8b, 0xfc, 0xdd, 0x62, 0x87, 0x8e, 0xc7, 0xde, 0x4c, 0x7f, + 0xc5, 0xf8, 0xd3, 0x1c, 0xbc, 0xf4, 0x18, 0xbc, 0x49, 0x13, 0x66, 0xd0, 0xc2, 0x81, 0xba, 0xf1, + 0xe1, 0x16, 0x92, 0x93, 0x7c, 0x6f, 0xe4, 0x01, 0xc0, 0x62, 0xcd, 0x38, 0x61, 0x42, 0x6e, 0xd2, + 0xe9, 0xd4, 0x41, 0x7e, 0x87, 0x5b, 0x4b, 0x96, 0x85, 0xe1, 0xd0, 0xed, 0x75, 0x12, 0x6e, 0x82, + 0x81, 0x14, 0xdb, 0x34, 0x3f, 0x32, 0xb6, 0xe9, 0x1b, 0x30, 0x57, 0x91, 0x1c, 0x92, 0xf9, 0xf0, + 0xe3, 0x3d, 0x8a, 0xe2, 0xbd, 0x6c, 0x2a, 0x68, 0xe4, 0xf3, 0xb0, 0xc0, 0xfc, 0x29, 0x78, 0xef, + 0x30, 0x6f, 0xbf, 0x49, 0x9e, 0xb1, 0x09, 0x4b, 0x44, 0x57, 0x87, 0x66, 0x0a, 0x95, 0x2e, 0xac, + 0x93, 0x54, 0x2b, 0xe9, 0xba, 0x61, 0x58, 0x1c, 0x44, 0x7b, 0x74, 0xd7, 0x61, 0x96, 0x0b, 0xf2, + 0x16, 0x4c, 0xed, 0x3d, 0xde, 0x7d, 0x25, 0x43, 0x27, 0x04, 0xf0, 0xec, 0x23, 0x1e, 0xc8, 0xd3, + 0xbf, 0xc9, 0xdb, 0x30, 0x89, 0x66, 0x77, 0x7e, 0xc4, 0x10, 0xa6, 0xa1, 0xec, 0x4f, 0xa3, 0x51, + 0xde, 0x64, 0x04, 0x74, 0xb5, 0x26, 0x89, 0xc9, 0xf9, 0x7e, 0x2c, 0xcc, 0xd1, 0x71, 0x6e, 0x72, + 0x73, 0x66, 0x7f, 0xc7, 0xe1, 0xd9, 0xbe, 0xd7, 0x60, 0x49, 0xc8, 0xde, 0xbe, 0xc8, 0x7e, 0xc4, + 0x3d, 0x91, 0x16, 0x79, 0x98, 0xb8, 0xbe, 0xc8, 0x80, 0xf4, 0x1c, 0x2c, 0x84, 0xe1, 0x9e, 0xcd, + 0x83, 0x93, 0xde, 0x17, 0x89, 0x15, 0xcd, 0xb9, 0x30, 0xdc, 0x63, 0x51, 0x4a, 0x6f, 0xb9, 0x07, + 0x14, 0x0b, 0xdf, 0x7e, 0x24, 0x58, 0x05, 0x86, 0x15, 0x75, 0xc3, 0x18, 0x8b, 0xc7, 0xd5, 0x85, + 0x04, 0xcb, 0xf8, 0x10, 0x66, 0xad, 0xb6, 0xdf, 0x17, 0xaf, 0xcd, 0x96, 0x61, 0x32, 0xa4, 0x3f, + 0xc5, 0x25, 0x3a, 0xfe, 0x20, 0x2f, 0xc3, 0xc4, 0x7e, 0xe2, 0xa7, 0xb2, 0x72, 0x45, 0x24, 0xc8, + 0xb8, 0x82, 0xc5, 0xe1, 0x95, 0x07, 0x57, 0xaf, 0x6c, 0xa1, 0x27, 0x3b, 0xc5, 0x32, 0xfe, 0x79, + 0x1e, 0x66, 0xee, 0x38, 0x51, 0x7b, 0x0f, 0xed, 0xde, 0xe3, 0xed, 0xe8, 0xd7, 0x60, 0xb6, 0xe6, + 0x3b, 0xdc, 0x0f, 0x82, 0x87, 0x15, 0x62, 0xef, 0xcf, 0xba, 0xbe, 0x23, 0x5c, 0x2a, 0x42, 0x53, + 0x46, 0x3a, 0x24, 0x56, 0xed, 0x4d, 0x98, 0x62, 0x6d, 0xe1, 0x57, 0x3a, 0xc2, 0x46, 0x14, 0xd7, + 0xe8, 0x0a, 0x2b, 0x96, 0x2e, 0xea, 0x99, 0xe0, 0x91, 0x0d, 0x16, 0xbc, 0x37, 0x24, 0x2b, 0xff, + 0xe4, 0xd1, 0xac, 0xfc, 0x52, 0x62, 0x9c, 0xa9, 0x23, 0x25, 0xc6, 0x69, 0x2a, 0xfd, 0xcf, 0x5d, + 0xe3, 0x49, 0x6c, 0x95, 0x8c, 0x4b, 0xd8, 0xe6, 0x8a, 0xfd, 0x6d, 0x0f, 0xd5, 0x57, 0x66, 0x71, + 0xe6, 0x3a, 0xcc, 0x4a, 0x2d, 0x7c, 0x2c, 0x23, 0xd4, 0x57, 0x73, 0x30, 0x8f, 0xfd, 0x14, 0x8b, + 0xd6, 0x1f, 0xcc, 0x5b, 0x90, 0x77, 0x94, 0x5b, 0x90, 0x15, 0x79, 0x06, 0x70, 0x5f, 0xad, 0xd1, + 0xd7, 0x1f, 0x37, 0x61, 0x69, 0x08, 0x91, 0xbc, 0x01, 0x93, 0xb4, 0xfa, 0xc2, 0x6a, 0xac, 0xa7, + 0xe7, 0x54, 0x92, 0x96, 0x91, 0x36, 0x3c, 0x34, 0x19, 0xb6, 0xf1, 0xdf, 0x34, 0x98, 0xe3, 0xc9, + 0xfa, 0x7b, 0x3b, 0xfe, 0xa1, 0xdd, 0x79, 0x29, 0xdd, 0x9d, 0x2c, 0x96, 0x3b, 0xef, 0xce, 0xbf, + 0xea, 0x4e, 0xbc, 0xae, 0x74, 0xe2, 0xa9, 0x38, 0x49, 0x93, 0x68, 0xce, 0x98, 0x3e, 0xfc, 0x16, + 0xa6, 0x2d, 0x54, 0x11, 0xc9, 0x0f, 0xc1, 0x4c, 0xdd, 0x7d, 0xa8, 0x18, 0x5f, 0x2f, 0x8d, 0x60, + 0x7a, 0x25, 0x46, 0x64, 0xab, 0x94, 0xbd, 0x2a, 0x75, 0x1f, 0xda, 0x43, 0x2e, 0x35, 0x09, 0xcb, + 0x33, 0xef, 0xc2, 0x82, 0x4a, 0xf6, 0x38, 0x53, 0x9f, 0x87, 0x76, 0xc4, 0xf4, 0x04, 0xdf, 0xcc, + 0x03, 0x24, 0x51, 0xf1, 0xe8, 0x92, 0x56, 0xbc, 0xf8, 0xc4, 0xbd, 0x35, 0x82, 0xe4, 0x39, 0x2e, + 0x9c, 0xfb, 0x2e, 0xf1, 0xfb, 0xd5, 0xdc, 0xe8, 0x24, 0x5a, 0x78, 0xd3, 0x7a, 0x9b, 0x47, 0x38, + 0xeb, 0xb8, 0x5d, 0xe7, 0x80, 0xc7, 0x6a, 0x78, 0x0b, 0x73, 0x26, 0xc6, 0x50, 0x25, 0x61, 0xd1, + 0xd9, 0xf2, 0x40, 0x28, 0x3e, 0x81, 0xd7, 0xdb, 0xdd, 0xf0, 0x03, 0x29, 0x76, 0x1f, 0xbe, 0x67, + 0x28, 0x53, 0x9a, 0xa1, 0xe0, 0x93, 0x13, 0x8f, 0x17, 0x7c, 0xb2, 0x09, 0x33, 0x5e, 0xef, 0x81, + 0xdb, 0x8b, 0xfc, 0xe0, 0x00, 0xef, 0x99, 0x93, 0x0b, 0x2c, 0xfa, 0xbd, 0xaa, 0x28, 0x63, 0x43, + 0x83, 0x1a, 0x72, 0x8c, 0x2f, 0x8f, 0x4c, 0x0c, 0x8c, 0x7d, 0xb0, 0x26, 0xf5, 0x29, 0x16, 0xb3, + 0xee, 0xe6, 0x44, 0xa1, 0xa0, 0xcf, 0xdc, 0x9c, 0x28, 0xcc, 0xe8, 0x60, 0x4a, 0x6e, 0x23, 0xb1, + 0x5b, 0x88, 0xe4, 0xc3, 0xa1, 0xfa, 0x67, 0x18, 0x7f, 0x94, 0x07, 0x32, 0x5c, 0x0d, 0xf2, 0x0e, + 0xcc, 0x32, 0xa9, 0x68, 0x07, 0xe1, 0x27, 0xfc, 0xa1, 0x2d, 0x7b, 0x1d, 0x2f, 0x81, 0xe5, 0xbc, + 0x0f, 0x0c, 0x6c, 0x86, 0x9f, 0x74, 0x49, 0x1f, 0x8e, 0x63, 0x8f, 0xf3, 0x10, 0x19, 0x98, 0xd5, + 0xcf, 0xe9, 0xf2, 0x30, 0x1b, 0x5f, 0xf8, 0xee, 0x67, 0xab, 0xcf, 0x64, 0x14, 0x1f, 0x7d, 0x64, + 0x30, 0xfa, 0x5c, 0x13, 0x89, 0x9b, 0x8c, 0x96, 0xec, 0x82, 0x2e, 0xb3, 0xc4, 0x50, 0x3d, 0x6c, + 0xfc, 0xdf, 0xa3, 0xa7, 0x9c, 0x74, 0xd9, 0xd1, 0xbf, 0xb5, 0x90, 0x7c, 0x6b, 0x63, 0xd0, 0xed, + 0x92, 0xb7, 0x00, 0xfc, 0x9e, 0xbd, 0xef, 0x61, 0x8c, 0x30, 0xf9, 0x89, 0x76, 0x02, 0x95, 0x87, + 0xcc, 0xef, 0x6d, 0x31, 0x20, 0xf9, 0x1c, 0x60, 0x50, 0x69, 0x8c, 0xb6, 0xce, 0x75, 0x35, 0x76, + 0x16, 0x12, 0x40, 0x35, 0x9e, 0xe8, 0xae, 0x8b, 0x91, 0xd8, 0xdf, 0x81, 0x59, 0xa6, 0x03, 0xb1, + 0xc0, 0x9a, 0x53, 0xc9, 0x28, 0x48, 0x60, 0x79, 0x14, 0x18, 0x58, 0x0a, 0xba, 0xf9, 0x45, 0x58, + 0xe2, 0x3a, 0xe3, 0x1d, 0x2f, 0xda, 0xe3, 0xb6, 0xa8, 0x27, 0x31, 0x64, 0x49, 0x86, 0xa4, 0xdf, + 0x9a, 0x04, 0x28, 0xde, 0xb1, 0x44, 0x12, 0x94, 0x17, 0x61, 0x92, 0x7e, 0x58, 0xdc, 0x5d, 0xa0, + 0x2a, 0x8a, 0x7c, 0xe5, 0x9b, 0x5f, 0xc4, 0xa0, 0x22, 0xc0, 0xc4, 0x07, 0xab, 0xe2, 0xde, 0x02, + 0x45, 0x00, 0x7b, 0xc3, 0xaa, 0x64, 0xad, 0xe4, 0x58, 0xa4, 0x06, 0x90, 0xa4, 0x25, 0xe1, 0xf6, + 0x9e, 0xa5, 0x24, 0xbe, 0x3f, 0x2f, 0xe0, 0xd9, 0xb8, 0x93, 0xa8, 0x04, 0x72, 0xd7, 0x24, 0x68, + 0xe4, 0x16, 0x4c, 0xb4, 0x9c, 0x38, 0xea, 0xe4, 0x88, 0x64, 0x2d, 0x17, 0x68, 0xeb, 0x95, 0x84, + 0x2d, 0x0b, 0x91, 0xb3, 0x2b, 0xd7, 0x0e, 0x99, 0x90, 0x0a, 0x4c, 0x35, 0x9d, 0xc0, 0xd9, 0x0f, + 0x47, 0x25, 0xf9, 0x62, 0xa5, 0x22, 0x17, 0x28, 0x02, 0x65, 0xd5, 0x88, 0x15, 0x93, 0x6b, 0x90, + 0xb7, 0xac, 0x2d, 0xfe, 0x94, 0x66, 0x3e, 0x39, 0x67, 0x59, 0xd6, 0x16, 0x3b, 0x0e, 0x84, 0xe1, + 0xbe, 0x44, 0x46, 0x91, 0xc9, 0xe7, 0x61, 0x56, 0x3a, 0xbe, 0xf1, 0xe0, 0xfe, 0xd8, 0x07, 0x52, + 0x48, 0x04, 0x59, 0x2c, 0x49, 0xd8, 0xa4, 0x06, 0xfa, 0xad, 0xc1, 0x3d, 0xb7, 0xd8, 0xef, 0x63, + 0x88, 0xb7, 0x07, 0x6e, 0xc0, 0x54, 0xdc, 0x42, 0x92, 0x46, 0x13, 0xdf, 0x1f, 0x77, 0x44, 0xa9, + 0x6c, 0x28, 0x4a, 0x53, 0x92, 0x26, 0x2c, 0x59, 0x6e, 0x34, 0xe8, 0x33, 0xcf, 0xd7, 0x0d, 0x66, + 0x68, 0x60, 0xa9, 0x00, 0x30, 0xe3, 0x60, 0x48, 0x0b, 0x85, 0xd3, 0xf1, 0xce, 0x90, 0xb1, 0x61, + 0x98, 0x98, 0x7c, 0x39, 0x65, 0x12, 0x81, 0xb4, 0x55, 0x4e, 0x2e, 0x15, 0xf9, 0x7e, 0x8e, 0x6e, + 0x31, 0xf9, 0x8f, 0xcc, 0x62, 0x92, 0xc1, 0x84, 0x54, 0x60, 0x41, 0x06, 0xc7, 0x76, 0x21, 0x8c, + 0x79, 0xa1, 0x04, 0x9a, 0x56, 0x0c, 0x3d, 0x29, 0x22, 0xf2, 0x08, 0x8e, 0xcb, 0x10, 0xa7, 0xbb, + 0xdd, 0xf3, 0x22, 0xf1, 0x30, 0xcd, 0xc8, 0x6e, 0x07, 0xa2, 0x88, 0xc6, 0x60, 0xc7, 0xf9, 0x0a, + 0x0b, 0x7b, 0x40, 0x11, 0xa4, 0x8f, 0x66, 0x7d, 0xc2, 0xf8, 0x51, 0x0c, 0xea, 0x31, 0x8a, 0x2f, + 0x4b, 0x53, 0x8b, 0x41, 0x21, 0xe4, 0x6b, 0x46, 0x1e, 0x32, 0x42, 0x4d, 0x53, 0x8b, 0x20, 0x4a, + 0x50, 0x61, 0x51, 0x24, 0xe4, 0xe5, 0xca, 0x03, 0x4b, 0xc8, 0x04, 0x1c, 0xcb, 0xf8, 0x86, 0x26, + 0xaf, 0x57, 0xd9, 0xbc, 0xa1, 0x8d, 0x33, 0x6f, 0xbc, 0x9a, 0x91, 0x6b, 0x08, 0x8d, 0x34, 0x52, + 0xae, 0x21, 0x39, 0xc3, 0x10, 0x79, 0x11, 0x0a, 0x94, 0x56, 0x3a, 0xbd, 0xb0, 0x84, 0x4f, 0x3e, + 0x37, 0x81, 0x98, 0x71, 0xb1, 0xf1, 0x97, 0x93, 0x52, 0x66, 0x3c, 0xbe, 0xe6, 0xde, 0x03, 0xb8, + 0xe9, 0x7b, 0xbd, 0x2d, 0x37, 0xda, 0xf3, 0x3b, 0xd2, 0x20, 0xcf, 0x7e, 0xec, 0x7b, 0x3d, 0x7b, + 0x1f, 0xc1, 0x7f, 0xf1, 0xd9, 0xaa, 0x84, 0x64, 0x4a, 0x7f, 0x93, 0x97, 0x61, 0x86, 0xfe, 0x6a, + 0x25, 0x7e, 0xda, 0xec, 0x3a, 0x1b, 0xa9, 0x59, 0x12, 0xfb, 0x04, 0x81, 0x5c, 0x07, 0x60, 0x39, + 0xfa, 0xa4, 0xda, 0xe2, 0x5a, 0x65, 0x6e, 0x79, 0xe9, 0xa8, 0x26, 0x12, 0x32, 0xd9, 0x8c, 0xab, + 0xde, 0xe2, 0x07, 0x45, 0x96, 0x43, 0x47, 0x84, 0x46, 0xc1, 0x22, 0x5b, 0x1c, 0x22, 0xe5, 0xd0, + 0x28, 0x29, 0x32, 0xac, 0x84, 0xb5, 0x59, 0xe6, 0xd6, 0xbb, 0x49, 0xa9, 0x12, 0xe1, 0x5e, 0x87, + 0xdb, 0xe2, 0x94, 0x4a, 0xc4, 0xc8, 0x64, 0x1d, 0x16, 0xd9, 0xb9, 0xb7, 0x19, 0xf8, 0x8f, 0x0e, + 0xe8, 0x61, 0x9a, 0xef, 0x47, 0xc2, 0xf0, 0xf4, 0xe8, 0x00, 0x4f, 0xde, 0xf2, 0xe7, 0x53, 0x04, + 0x64, 0x03, 0x26, 0xd1, 0x12, 0xcb, 0x8f, 0x65, 0x67, 0xe5, 0xbb, 0x80, 0xb4, 0xbc, 0xc4, 0xfd, + 0x03, 0x6f, 0x01, 0xe4, 0xfd, 0x03, 0x51, 0xc9, 0x17, 0x01, 0x2a, 0xbd, 0xc0, 0xef, 0x76, 0x31, + 0x71, 0x68, 0x41, 0x89, 0xa0, 0xc4, 0xf9, 0x20, 0x97, 0x04, 0x09, 0xb3, 0xe7, 0x9e, 0x70, 0xf1, + 0xb7, 0xad, 0xa6, 0x17, 0x5d, 0xd1, 0x4c, 0x89, 0x1b, 0x79, 0x19, 0xa6, 0xac, 0xc1, 0xce, 0x8e, + 0xf7, 0x88, 0x8b, 0x2f, 0x96, 0x35, 0x13, 0x21, 0xb2, 0xd8, 0x66, 0x38, 0xe4, 0x5d, 0x98, 0xdd, + 0xee, 0x77, 0x9c, 0xc8, 0xc5, 0x1b, 0x6d, 0x9e, 0x8a, 0x19, 0xa5, 0xd0, 0x00, 0xc1, 0xec, 0x51, + 0x8c, 0x2c, 0x83, 0x25, 0x74, 0xe2, 0xc1, 0xd2, 0x66, 0xab, 0xd5, 0xc4, 0x0e, 0x12, 0x81, 0x17, + 0x78, 0x88, 0x22, 0x71, 0xc8, 0x1a, 0x2a, 0x5f, 0xbf, 0x48, 0x35, 0xa8, 0xbd, 0x28, 0xea, 0xdb, + 0xac, 0xd3, 0x45, 0x10, 0x3d, 0x59, 0x9c, 0x0e, 0x51, 0x19, 0xbf, 0xa9, 0x65, 0x7c, 0x8b, 0xbc, + 0x09, 0x33, 0x31, 0x50, 0xce, 0x32, 0x9e, 0xb0, 0x97, 0xd5, 0x99, 0x18, 0x95, 0xce, 0x23, 0xfa, + 0xc3, 0x62, 0x84, 0x52, 0x56, 0x30, 0x4a, 0x18, 0x0e, 0x51, 0x4a, 0xc8, 0xf4, 0x8c, 0x5b, 0xf7, + 0x19, 0x9d, 0x64, 0xc1, 0xef, 0xf9, 0x43, 0x44, 0x02, 0xcd, 0xa8, 0xc2, 0x14, 0xdb, 0x08, 0x31, + 0x31, 0xb2, 0xdf, 0x1e, 0xec, 0xbb, 0xbd, 0x48, 0x4a, 0xab, 0xcb, 0x12, 0x23, 0x73, 0xf8, 0x70, + 0x62, 0x64, 0x89, 0xc0, 0xb8, 0x05, 0xcb, 0x59, 0x93, 0x4d, 0xb1, 0xe7, 0x6b, 0x47, 0xb5, 0xe7, + 0xff, 0x51, 0x1e, 0xe6, 0x90, 0x9b, 0x10, 0xac, 0x45, 0x98, 0xb7, 0x06, 0xf7, 0xe2, 0x24, 0x40, + 0x42, 0x13, 0xc2, 0xfa, 0x85, 0x72, 0x81, 0xec, 0x92, 0xa6, 0x50, 0xd0, 0xbd, 0x47, 0x68, 0x61, + 0x37, 0xc4, 0x83, 0xeb, 0x38, 0x27, 0xb1, 0x78, 0x88, 0xce, 0x5f, 0x5a, 0xc9, 0x7b, 0x8f, 0x4a, + 0x94, 0xe8, 0x62, 0xf9, 0xc7, 0xd1, 0xc5, 0x26, 0x8e, 0xa4, 0x8b, 0x7d, 0x04, 0x73, 0xe2, 0x6b, + 0xa8, 0x45, 0x4d, 0x3e, 0x99, 0x16, 0xa5, 0x30, 0x23, 0xb5, 0x58, 0x9b, 0x9a, 0x1a, 0xab, 0x4d, + 0xa1, 0x9f, 0x9f, 0x90, 0x7c, 0x7d, 0x84, 0x65, 0x28, 0x55, 0x4f, 0xa2, 0x20, 0x19, 0x7f, 0x92, + 0x07, 0xb8, 0x51, 0x6a, 0x7e, 0x0f, 0xea, 0xed, 0x1b, 0x30, 0x53, 0xf3, 0x85, 0x7f, 0x98, 0xe4, + 0x98, 0xd3, 0x15, 0x40, 0x79, 0x51, 0xc5, 0x98, 0xb1, 0x5a, 0x9a, 0x7f, 0x1a, 0x6a, 0xe9, 0x75, + 0xbc, 0xf0, 0xf8, 0xd8, 0x6d, 0x47, 0xd5, 0xb2, 0x18, 0x59, 0x6c, 0xb9, 0x88, 0xe4, 0xaf, 0xfa, + 0x07, 0x49, 0xc8, 0x74, 0xbb, 0xe1, 0xae, 0xe7, 0x22, 0x70, 0x1b, 0xbf, 0x62, 0xc0, 0xed, 0x46, + 0x44, 0xbf, 0x13, 0xb1, 0xe0, 0x64, 0x79, 0x9f, 0x22, 0x7b, 0xca, 0xa3, 0xf9, 0x61, 0xfc, 0x86, + 0x68, 0x7a, 0x5c, 0x0f, 0x19, 0x43, 0x3d, 0x34, 0xf2, 0xe5, 0x90, 0xf1, 0xbb, 0x9a, 0x9c, 0x4d, + 0xfe, 0x7b, 0x18, 0xea, 0xb7, 0x01, 0x62, 0x07, 0x5d, 0x31, 0xd6, 0x71, 0xcc, 0x2e, 0x06, 0x95, + 0x7b, 0x39, 0xc1, 0x95, 0x5a, 0x93, 0x7f, 0x5a, 0xad, 0x69, 0xc1, 0x6c, 0xe3, 0x7e, 0xe4, 0x24, + 0x1e, 0xdd, 0x60, 0xc5, 0xc7, 0x57, 0x1e, 0x0a, 0xf3, 0x79, 0xdc, 0xec, 0x93, 0xf3, 0xb0, 0x72, + 0x14, 0x2e, 0x88, 0xa3, 0xb0, 0x29, 0x11, 0x1a, 0x7f, 0xa9, 0xc1, 0xa2, 0x1c, 0xc7, 0xf5, 0xa0, + 0xd7, 0x26, 0xef, 0xb3, 0x5c, 0x95, 0x9a, 0x62, 0xcd, 0x90, 0x90, 0xa8, 0xbc, 0x3e, 0xe8, 0xb5, + 0xd9, 0xc9, 0xc5, 0x79, 0x28, 0x57, 0x96, 0x12, 0x92, 0x7b, 0x30, 0xd7, 0xf4, 0xbb, 0x5d, 0x11, + 0x76, 0x37, 0x4e, 0x7a, 0x99, 0xbe, 0x6b, 0x11, 0x15, 0x5a, 0x7f, 0x96, 0x5b, 0xc5, 0x4e, 0xf5, + 0xe9, 0x06, 0x2e, 0x42, 0x01, 0x27, 0x6c, 0x3f, 0xc5, 0x78, 0x2c, 0x32, 0xcf, 0x44, 0xd9, 0x50, + 0xb3, 0xa2, 0xcb, 0xb5, 0xa4, 0xc5, 0x58, 0xcf, 0x31, 0xca, 0x86, 0xf1, 0xff, 0x6b, 0x70, 0x61, + 0xb8, 0x69, 0xa5, 0xae, 0x3f, 0xe8, 0xb4, 0x02, 0xc7, 0xeb, 0xd6, 0xfc, 0xdd, 0x90, 0xe5, 0xf8, + 0xdb, 0x4d, 0xae, 0x6e, 0x79, 0x8e, 0xbf, 0x5d, 0x2f, 0x9d, 0xe3, 0x0f, 0xc3, 0x34, 0xbd, 0x0e, + 0x05, 0xeb, 0x43, 0xeb, 0xc3, 0x81, 0x2b, 0x2c, 0x67, 0x4c, 0x3e, 0x84, 0x9f, 0x84, 0xf6, 0x27, + 0x14, 0x28, 0x6f, 0x37, 0x02, 0xd1, 0xd8, 0x87, 0xf3, 0xc3, 0xd5, 0xa8, 0xdc, 0xb2, 0x8a, 0x83, + 0x8e, 0x17, 0x61, 0x25, 0x84, 0x00, 0xd1, 0x9e, 0x82, 0x00, 0x31, 0xfe, 0x49, 0x1e, 0xc8, 0xf0, + 0xf7, 0xe4, 0xed, 0x42, 0xfb, 0xdf, 0x70, 0x74, 0x4f, 0x49, 0xf4, 0x89, 0xc7, 0x3a, 0xf2, 0x7e, + 0x02, 0x7a, 0x9b, 0x0e, 0x9b, 0x1d, 0xd1, 0x71, 0xb3, 0xbb, 0x7e, 0xbc, 0x7b, 0xbd, 0x30, 0x72, + 0x0a, 0xab, 0xe3, 0xcc, 0x44, 0x60, 0x9a, 0x89, 0xbc, 0x11, 0xb7, 0xd5, 0x79, 0xe1, 0xc1, 0x82, + 0x7b, 0x3f, 0xb4, 0x1d, 0x3a, 0x46, 0xec, 0x83, 0x53, 0x8a, 0x87, 0xfd, 0xf8, 0x11, 0x65, 0x82, + 0x51, 0x65, 0x20, 0x6f, 0x9d, 0xee, 0xfd, 0x30, 0xc6, 0x35, 0xbe, 0xa9, 0xc1, 0x72, 0xd6, 0xe4, + 0xa6, 0x3a, 0x85, 0xac, 0x64, 0xa8, 0xe7, 0x59, 0x59, 0x2f, 0x49, 0x9d, 0x67, 0x55, 0xa2, 0x74, + 0xd7, 0xe7, 0x1e, 0x6b, 0x33, 0xfd, 0xd3, 0x3c, 0xcc, 0x31, 0xb7, 0xb0, 0x4d, 0xd7, 0xe9, 0x46, + 0x7b, 0x74, 0x1e, 0xf1, 0x0b, 0x39, 0xf9, 0xf5, 0x12, 0xbf, 0xba, 0x53, 0xb2, 0x68, 0xf2, 0x6b, + 0xbb, 0x6b, 0x50, 0x68, 0x52, 0xb1, 0xd0, 0xf6, 0xbb, 0xf2, 0xe5, 0x48, 0x9f, 0xc3, 0xe4, 0x25, + 0x23, 0xf0, 0x50, 0x97, 0x97, 0x6f, 0x60, 0x99, 0x2e, 0x8f, 0x10, 0x45, 0x97, 0x67, 0x77, 0xb1, + 0x8f, 0xe0, 0x78, 0xe2, 0x6a, 0x18, 0xdf, 0xf2, 0x1e, 0xe1, 0xa1, 0xf6, 0x1a, 0xbf, 0xe2, 0x3e, + 0x9f, 0x78, 0x2f, 0xe2, 0x75, 0x30, 0x96, 0xa6, 0x5c, 0xd1, 0xb2, 0x3e, 0x41, 0x6e, 0x81, 0x9e, + 0x80, 0x79, 0xae, 0x50, 0x76, 0x38, 0xc3, 0x50, 0xad, 0x12, 0xdb, 0xa1, 0xb4, 0xa1, 0x43, 0x84, + 0x74, 0xfb, 0x4e, 0x60, 0x95, 0x24, 0x26, 0x83, 0xf0, 0xf8, 0x88, 0x79, 0xe1, 0x05, 0xb3, 0xbc, + 0x7d, 0xa7, 0xc8, 0xe8, 0x18, 0x89, 0x7b, 0xe9, 0xe9, 0x64, 0x8c, 0xf8, 0x8d, 0xb4, 0x3c, 0x46, + 0x1c, 0x6b, 0xed, 0x1b, 0x1a, 0x2c, 0x56, 0x8b, 0x5b, 0x2c, 0x34, 0x1e, 0xef, 0xd5, 0x8b, 0xf0, + 0x4c, 0xb5, 0xb8, 0x65, 0x37, 0x1b, 0xb5, 0x6a, 0xe9, 0xae, 0x9d, 0x99, 0xa5, 0xfa, 0x19, 0x38, + 0x3d, 0x8c, 0x92, 0x78, 0x65, 0x9e, 0x83, 0x95, 0xe1, 0x62, 0x91, 0xc9, 0x3a, 0x9b, 0x58, 0x24, + 0xbd, 0xce, 0xaf, 0x7d, 0x00, 0x8b, 0x22, 0x6b, 0x73, 0xab, 0x66, 0xe1, 0x09, 0x6f, 0x11, 0x66, + 0x6f, 0x57, 0xcc, 0xea, 0xc6, 0x5d, 0x7b, 0x63, 0xbb, 0x56, 0xd3, 0x8f, 0x91, 0x79, 0x98, 0xe1, + 0x80, 0x52, 0x51, 0xd7, 0xc8, 0x1c, 0x14, 0xaa, 0x75, 0xab, 0x52, 0xda, 0x36, 0x2b, 0x7a, 0x6e, + 0xed, 0xef, 0x6a, 0x30, 0xcf, 0xce, 0x6c, 0x01, 0x6f, 0xd1, 0x79, 0x38, 0xb3, 0xdd, 0x2c, 0x17, + 0x5b, 0x15, 0x33, 0xbb, 0x39, 0x27, 0x60, 0x29, 0x55, 0xde, 0xb8, 0xa5, 0x6b, 0xe4, 0x2c, 0x9c, + 0x4a, 0x81, 0xcb, 0x55, 0xab, 0xb8, 0xce, 0x5a, 0x71, 0x1a, 0x4e, 0xa4, 0x0a, 0x9b, 0xd5, 0x7a, + 0xbd, 0x52, 0xd6, 0xf3, 0xb4, 0x81, 0x43, 0x9f, 0x33, 0x2b, 0xc5, 0x32, 0x25, 0xd5, 0x27, 0xd6, + 0x3e, 0x80, 0x85, 0x66, 0xfc, 0x04, 0x15, 0x3d, 0x37, 0xa7, 0x21, 0x6f, 0x16, 0xef, 0xe8, 0xc7, + 0x08, 0xc0, 0x54, 0xf3, 0x56, 0xc9, 0xba, 0x7a, 0x55, 0xd7, 0xc8, 0x2c, 0x4c, 0xdf, 0x28, 0x35, + 0xed, 0x5b, 0x5b, 0x96, 0x9e, 0xa3, 0x3f, 0x8a, 0x77, 0x2c, 0xfc, 0x91, 0x5f, 0x7b, 0x0d, 0x1d, + 0x7b, 0x1e, 0x1d, 0xd4, 0xbc, 0x30, 0x72, 0x7b, 0x6e, 0x80, 0x7d, 0x34, 0x07, 0x05, 0xcb, 0xa5, + 0x9a, 0x58, 0xe4, 0xb2, 0x0e, 0xda, 0x1a, 0x74, 0x23, 0xaf, 0xdf, 0x75, 0x1f, 0xe9, 0xda, 0xda, + 0x75, 0x58, 0x34, 0xfd, 0x01, 0x3d, 0x41, 0x5a, 0x11, 0xc5, 0xd8, 0x3d, 0xc0, 0x36, 0xd7, 0x8b, + 0x5b, 0xeb, 0xd5, 0x1b, 0xdb, 0x8d, 0x6d, 0xcb, 0xde, 0x2a, 0xb6, 0x4a, 0x9b, 0xcc, 0x6f, 0x74, + 0xab, 0x61, 0xb5, 0x6c, 0xb3, 0x52, 0xaa, 0xd4, 0x5b, 0xba, 0xb6, 0xf6, 0x33, 0x78, 0x93, 0xd5, + 0xf6, 0x7b, 0x9d, 0x0d, 0xa7, 0x1d, 0xf9, 0x01, 0x56, 0xd8, 0x80, 0xf3, 0x56, 0xa5, 0xd4, 0xa8, + 0x97, 0xed, 0x8d, 0x62, 0xa9, 0xd5, 0x30, 0xb3, 0xd2, 0xb8, 0x9f, 0x81, 0x93, 0x19, 0x38, 0x8d, + 0x56, 0x53, 0xd7, 0xc8, 0x2a, 0x9c, 0xcd, 0x28, 0xbb, 0x53, 0x59, 0x2f, 0x6e, 0xb7, 0x36, 0xeb, + 0x7a, 0x6e, 0x04, 0xb1, 0x65, 0x35, 0xf4, 0xfc, 0xda, 0xdf, 0xd2, 0x60, 0x61, 0x3b, 0xe4, 0xef, + 0xdf, 0xb7, 0xd1, 0xbb, 0xe2, 0x02, 0x9c, 0xdb, 0xb6, 0x2a, 0xa6, 0xdd, 0x6a, 0xdc, 0xaa, 0xd4, + 0xed, 0x6d, 0xab, 0x78, 0x23, 0x5d, 0x9b, 0x55, 0x38, 0x2b, 0x61, 0x98, 0x95, 0x52, 0xe3, 0x76, + 0xc5, 0xb4, 0x9b, 0x45, 0xcb, 0xba, 0xd3, 0x30, 0xcb, 0xba, 0x46, 0xbf, 0x98, 0x81, 0xb0, 0xb5, + 0x51, 0x64, 0xb5, 0x51, 0xca, 0xea, 0x95, 0x3b, 0xc5, 0x9a, 0xbd, 0xde, 0x68, 0xe9, 0xf9, 0xb5, + 0x2d, 0x7a, 0xb8, 0xc2, 0x64, 0xca, 0xec, 0x95, 0x62, 0x01, 0x26, 0xea, 0x8d, 0x7a, 0x25, 0xed, + 0xa8, 0x3c, 0x07, 0x85, 0x62, 0xb3, 0x69, 0x36, 0x6e, 0xe3, 0xe4, 0x01, 0x98, 0x2a, 0x57, 0xea, + 0x55, 0x9c, 0x2d, 0x73, 0x50, 0x68, 0x9a, 0x8d, 0xad, 0x46, 0xab, 0x52, 0xd6, 0x27, 0xd6, 0x8a, + 0xb0, 0xc4, 0xb6, 0x04, 0xce, 0x14, 0xaf, 0x3b, 0xe7, 0x61, 0x66, 0xbb, 0x5e, 0xae, 0x6c, 0x54, + 0xeb, 0xd8, 0x96, 0x05, 0x00, 0x6b, 0xb3, 0x61, 0xb6, 0xec, 0x56, 0xc5, 0xdc, 0x62, 0xd9, 0xf1, + 0x6b, 0x8d, 0xfa, 0x0d, 0xf6, 0x33, 0xb7, 0x66, 0x0a, 0x35, 0x40, 0xd4, 0x0b, 0x3d, 0x18, 0x66, + 0x61, 0xba, 0x5c, 0xd9, 0x28, 0x6e, 0xd7, 0x5a, 0x7c, 0x94, 0xef, 0xda, 0x66, 0xe5, 0xc3, 0xed, + 0x8a, 0xd5, 0xb2, 0x74, 0x8d, 0xe8, 0x30, 0x57, 0xaf, 0x54, 0xca, 0x96, 0x6d, 0x56, 0x6e, 0x57, + 0x2b, 0x77, 0xf4, 0x1c, 0xad, 0x16, 0xfb, 0x9b, 0x56, 0x72, 0xed, 0x57, 0x34, 0x20, 0x2c, 0x97, + 0xf5, 0xa6, 0x1f, 0x46, 0xb4, 0xf7, 0x71, 0xd2, 0x9d, 0x87, 0x33, 0x9b, 0x74, 0xb6, 0x60, 0xef, + 0x6c, 0x35, 0xca, 0xe9, 0x5e, 0x3f, 0x09, 0x24, 0x55, 0xde, 0xd8, 0xd8, 0xc0, 0x95, 0x75, 0x3c, + 0x05, 0x2f, 0x9b, 0x8d, 0xa6, 0x9e, 0x3b, 0x93, 0x2b, 0x68, 0xe4, 0xd4, 0x50, 0xe1, 0xad, 0x4a, + 0xa5, 0xa9, 0xe7, 0xe9, 0x28, 0xa7, 0x0a, 0xc4, 0xaa, 0x67, 0xe4, 0x13, 0x6b, 0x5f, 0xd3, 0xe0, + 0x24, 0xab, 0xa6, 0x10, 0x21, 0x71, 0x55, 0xcf, 0xc1, 0x0a, 0xcf, 0xd0, 0x9f, 0x55, 0xd1, 0x65, + 0xd0, 0x95, 0x52, 0x56, 0xcd, 0x13, 0xb0, 0xa4, 0x40, 0xb1, 0x1e, 0x39, 0x2a, 0x20, 0x15, 0xf0, + 0x7a, 0xc5, 0x6a, 0xd9, 0x95, 0x8d, 0x0d, 0x3a, 0x24, 0x58, 0x91, 0xfc, 0xda, 0xdf, 0xd3, 0x60, + 0xe5, 0x8e, 0x7b, 0xaf, 0xe5, 0x06, 0xfb, 0x5e, 0xcf, 0xe9, 0x96, 0xba, 0x5e, 0xff, 0x9e, 0xef, + 0x04, 0x1d, 0xac, 0xca, 0x4b, 0xf0, 0xc2, 0x9d, 0xca, 0x3a, 0x0e, 0x57, 0xb5, 0x5e, 0xac, 0xd9, + 0xa5, 0x5a, 0xb5, 0xb9, 0xde, 0x28, 0x9a, 0xe5, 0xac, 0x9a, 0xbd, 0x0c, 0x97, 0xc7, 0x23, 0x9b, + 0x15, 0xab, 0x65, 0x56, 0x4b, 0x74, 0xfa, 0x68, 0xe4, 0x05, 0x78, 0x76, 0x1c, 0x76, 0xbd, 0x61, + 0x97, 0x1a, 0xcd, 0xbb, 0x7a, 0x6e, 0xcd, 0x80, 0xa5, 0x92, 0x1b, 0x44, 0x95, 0x47, 0x91, 0xdb, + 0x0b, 0x3d, 0xbf, 0x87, 0x15, 0x9b, 0x87, 0x99, 0xca, 0xff, 0xd3, 0xaa, 0xd4, 0xad, 0x6a, 0xa3, + 0xae, 0x1f, 0x5b, 0x3b, 0x97, 0xc2, 0x11, 0xb2, 0xca, 0xb2, 0x36, 0xf5, 0x63, 0x6b, 0x0e, 0xcc, + 0x8b, 0xa7, 0xeb, 0x6c, 0xe6, 0x9f, 0x87, 0x33, 0x62, 0x3d, 0xa1, 0xdc, 0x4b, 0xb7, 0x64, 0x05, + 0x96, 0x87, 0xcb, 0x2b, 0x2d, 0x5d, 0xa3, 0xd3, 0x24, 0x55, 0x42, 0xe1, 0xb9, 0xb5, 0x5f, 0xd4, + 0x60, 0x3e, 0xf6, 0x1e, 0xc2, 0x95, 0xb0, 0x0a, 0x67, 0xb7, 0x36, 0x8a, 0x76, 0xb9, 0x72, 0xbb, + 0x5a, 0xaa, 0xd8, 0xb7, 0xaa, 0xf5, 0x72, 0xea, 0x23, 0xa7, 0xe1, 0x44, 0x06, 0x02, 0x7e, 0x65, + 0x05, 0x96, 0xd3, 0x45, 0x2d, 0x2a, 0x8e, 0x72, 0x74, 0x6e, 0xa4, 0x4b, 0x62, 0x59, 0x94, 0xa7, + 0xf3, 0x31, 0x5d, 0x4a, 0x05, 0xd1, 0xc4, 0xda, 0x97, 0x60, 0xf1, 0x8e, 0x7b, 0x8f, 0xbb, 0x05, + 0x32, 0x41, 0x74, 0x11, 0x9e, 0xa1, 0xfd, 0x6f, 0x55, 0x2c, 0xda, 0x87, 0x99, 0x92, 0xe8, 0x12, + 0x18, 0xc3, 0x28, 0x45, 0xdc, 0xfd, 0xec, 0x1b, 0x66, 0xb1, 0xb9, 0x69, 0x17, 0x9b, 0x55, 0x5d, + 0x5b, 0xbb, 0x0d, 0x0b, 0x56, 0x71, 0xab, 0xb6, 0xe1, 0x07, 0x6d, 0xb7, 0x38, 0x88, 0xf6, 0x7a, + 0x74, 0x3f, 0xda, 0x68, 0x98, 0xa5, 0x8a, 0x8d, 0x35, 0x4b, 0xb1, 0x3d, 0x0e, 0x8b, 0x72, 0xe1, + 0xdd, 0x0a, 0x5d, 0xd6, 0x04, 0x16, 0x64, 0x60, 0xbd, 0xa1, 0xe7, 0xd6, 0x6c, 0x58, 0xe6, 0xaf, + 0xf7, 0xca, 0x6e, 0x78, 0x3f, 0xf2, 0xfb, 0x3c, 0x34, 0x3d, 0xad, 0x57, 0xb5, 0x5e, 0x6e, 0xdc, + 0xb1, 0xec, 0x72, 0xc5, 0xba, 0xd5, 0x6a, 0x34, 0x6d, 0xab, 0xb1, 0x4d, 0x89, 0x87, 0x24, 0xe9, + 0x08, 0xbc, 0x5a, 0xb9, 0xd8, 0xd4, 0xb5, 0xb5, 0x2f, 0xc1, 0x1c, 0xef, 0x13, 0x36, 0x2f, 0x4e, + 0xc1, 0x71, 0xf9, 0x77, 0xd3, 0xed, 0x75, 0xbc, 0xde, 0xae, 0x7e, 0x2c, 0x5d, 0x60, 0x0e, 0x7a, + 0x3d, 0x5a, 0x80, 0xb2, 0x58, 0x2e, 0xe0, 0xab, 0x28, 0x72, 0x3b, 0x7a, 0x6e, 0xed, 0x0a, 0xcc, + 0x2b, 0x69, 0xf1, 0xe9, 0x84, 0xac, 0x35, 0xf8, 0xe6, 0xb9, 0x55, 0x29, 0x57, 0xb7, 0xb7, 0xf4, + 0x49, 0x2a, 0x85, 0x37, 0xab, 0x37, 0x36, 0x75, 0x58, 0xfb, 0x39, 0x0d, 0x16, 0xa8, 0x20, 0xf4, + 0x02, 0x77, 0x6b, 0xa3, 0x28, 0xa6, 0x30, 0x5d, 0xdf, 0x28, 0xa1, 0xf9, 0x30, 0x30, 0xa5, 0x45, + 0x8c, 0x49, 0xb1, 0x5e, 0xb6, 0x37, 0x8b, 0x66, 0xf9, 0x4e, 0xd1, 0xa4, 0x8b, 0xfe, 0xae, 0x9e, + 0x43, 0x49, 0x26, 0x41, 0xec, 0x56, 0x63, 0xbb, 0xb4, 0xa9, 0xe7, 0xa9, 0xe0, 0x50, 0xe0, 0xcd, + 0x6a, 0x5d, 0x9f, 0x40, 0xb9, 0x38, 0x84, 0x8d, 0x6c, 0x69, 0xf9, 0xe4, 0xda, 0x77, 0x34, 0x38, + 0x65, 0x79, 0xbb, 0x3d, 0x27, 0x1a, 0x04, 0x6e, 0xb1, 0xbb, 0xeb, 0x07, 0x5e, 0xb4, 0xb7, 0x6f, + 0x0d, 0xbc, 0xc8, 0x25, 0x2f, 0xc2, 0xf3, 0x56, 0xf5, 0x46, 0xbd, 0xd8, 0xa2, 0x72, 0xad, 0x58, + 0xbb, 0xd1, 0x30, 0xab, 0xad, 0xcd, 0x2d, 0xdb, 0xda, 0xae, 0x0e, 0xad, 0xa8, 0xe7, 0xe0, 0xc2, + 0x68, 0xd4, 0x5a, 0xe5, 0x46, 0xb1, 0x74, 0x57, 0xd7, 0xc6, 0x33, 0x5c, 0x2f, 0xd6, 0x8a, 0xf5, + 0x52, 0xa5, 0x6c, 0xdf, 0xbe, 0xaa, 0xe7, 0xc8, 0xf3, 0x70, 0x71, 0x34, 0xea, 0x46, 0xb5, 0x69, + 0x51, 0xb4, 0xfc, 0xf8, 0xef, 0x6e, 0x5a, 0x5b, 0x14, 0x6b, 0x62, 0xed, 0x9b, 0x1a, 0xac, 0x8c, + 0x4a, 0x8a, 0x44, 0x67, 0x5b, 0xa5, 0xde, 0x32, 0x8b, 0xd5, 0xb2, 0x5d, 0x32, 0x2b, 0xe5, 0x4a, + 0xbd, 0x55, 0x2d, 0xd6, 0xac, 0x64, 0xc6, 0x89, 0x77, 0x2c, 0xcf, 0xc2, 0xea, 0x18, 0xbc, 0x46, + 0xb5, 0x5c, 0xd2, 0x35, 0x72, 0x15, 0x5e, 0x19, 0x83, 0x64, 0xdd, 0xb5, 0x5a, 0x95, 0x2d, 0xb9, + 0x44, 0xcf, 0xad, 0xfd, 0x17, 0x0d, 0x9e, 0x19, 0x9b, 0xdd, 0x48, 0x66, 0xca, 0x57, 0x67, 0xad, + 0x6a, 0xb5, 0xec, 0xc6, 0x9d, 0x7a, 0xc5, 0xb4, 0xb2, 0x97, 0xc6, 0xcb, 0x70, 0xf9, 0x70, 0x92, + 0x66, 0x6d, 0xfb, 0x46, 0x95, 0x4e, 0xb8, 0x57, 0xe0, 0xc5, 0xc3, 0xb1, 0x39, 0x86, 0x9e, 0x23, + 0x6f, 0xc3, 0xe7, 0x8e, 0xca, 0x1c, 0x67, 0x9a, 0xa0, 0xcc, 0xe3, 0xae, 0x98, 0x9d, 0xa6, 0x82, + 0x8e, 0x24, 0x96, 0x8c, 0x1f, 0x84, 0x8b, 0xf0, 0xcc, 0x48, 0x2c, 0x3e, 0x04, 0xcf, 0xc2, 0xea, + 0x48, 0x14, 0x36, 0x00, 0x7a, 0x6e, 0xed, 0x23, 0x38, 0x33, 0x3a, 0x54, 0x3a, 0x55, 0x4a, 0xd4, + 0xee, 0x2c, 0xc0, 0x44, 0x99, 0xaa, 0x52, 0x9a, 0xf4, 0x8c, 0x0b, 0x57, 0xa2, 0x59, 0xa9, 0x6e, + 0x35, 0xe9, 0x6e, 0xcb, 0x35, 0x18, 0x54, 0x51, 0x7e, 0x4c, 0x8b, 0xf3, 0x3a, 0x25, 0x3c, 0xd3, + 0x2f, 0xc7, 0xcc, 0xed, 0x7a, 0x9d, 0x29, 0x64, 0x8b, 0x30, 0xdb, 0x68, 0x6d, 0x56, 0x4c, 0xbb, + 0x62, 0x9a, 0x0d, 0x53, 0xcf, 0x51, 0x15, 0x68, 0xbb, 0x4e, 0xe5, 0x64, 0xc3, 0xac, 0x7e, 0x11, + 0x35, 0xb3, 0x15, 0x58, 0xb6, 0x6a, 0xc5, 0xd2, 0x2d, 0xbb, 0xde, 0x68, 0xd9, 0xd5, 0xba, 0x5d, + 0xda, 0x2c, 0xd6, 0xeb, 0x95, 0x9a, 0x0e, 0x54, 0x31, 0x68, 0xdc, 0x6a, 0x15, 0xed, 0x52, 0xa3, + 0xbe, 0x51, 0xbd, 0xc1, 0x59, 0x2c, 0xe3, 0x8c, 0x1f, 0x15, 0xbb, 0x8c, 0x4e, 0x0e, 0xa4, 0xe1, + 0x43, 0x64, 0xdd, 0xad, 0x97, 0xc4, 0xf1, 0xa0, 0x34, 0xbc, 0xed, 0x5f, 0x86, 0xe7, 0xc6, 0x62, + 0x8b, 0xf3, 0x92, 0x46, 0x57, 0xd2, 0x58, 0x4c, 0xde, 0xbe, 0xb5, 0xdf, 0xd3, 0xe0, 0xec, 0x18, + 0x4f, 0x5e, 0x3a, 0x1d, 0x37, 0x2b, 0xc5, 0x72, 0x8d, 0xce, 0x2c, 0xda, 0x0d, 0x74, 0x10, 0xd9, + 0xeb, 0xb1, 0xcc, 0xdd, 0xfc, 0x45, 0x78, 0x7e, 0x3c, 0x7a, 0xa2, 0xfb, 0x5e, 0x86, 0xe7, 0xc6, + 0xa3, 0x72, 0x5d, 0x38, 0x47, 0xd6, 0xe0, 0xd2, 0x78, 0xcc, 0x58, 0x87, 0xce, 0xaf, 0x7d, 0x5d, + 0x83, 0x93, 0xd9, 0x57, 0x87, 0xb4, 0x6e, 0xd5, 0xba, 0xd5, 0x2a, 0xd6, 0x6a, 0x76, 0xb3, 0x68, + 0x16, 0xb7, 0xec, 0x4a, 0xdd, 0x6c, 0xd4, 0x6a, 0x59, 0xea, 0xd5, 0x73, 0x70, 0x61, 0x34, 0xaa, + 0x55, 0x32, 0xab, 0x4d, 0xaa, 0x3a, 0x18, 0x70, 0x7e, 0x34, 0x56, 0xa5, 0x5a, 0xaa, 0xe8, 0xb9, + 0xf5, 0xf7, 0x7e, 0xe7, 0x4f, 0xce, 0x1f, 0xfb, 0x9d, 0xef, 0x9c, 0xd7, 0xfe, 0xe0, 0x3b, 0xe7, + 0xb5, 0x3f, 0xfe, 0xce, 0x79, 0xed, 0x8b, 0x2f, 0xb1, 0xb7, 0x4b, 0x57, 0xda, 0xfe, 0xfe, 0xab, + 0xbb, 0x81, 0xf3, 0xc0, 0x8b, 0xf8, 0xa5, 0xff, 0xab, 0xe2, 0xe6, 0xf7, 0x55, 0xa7, 0xef, 0xbd, + 0x8a, 0x76, 0xa5, 0x7b, 0x53, 0x68, 0xa6, 0x78, 0xfd, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x02, + 0x11, 0xb6, 0x75, 0x92, 0x1f, 0x02, 0x00, } func (this *SSHKeyPair) Equal(that interface{}) bool { @@ -31607,6 +31609,13 @@ func (m *KeepAlive) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.Scope) > 0 { + i -= len(m.Scope) + copy(dAtA[i:], m.Scope) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Scope))) + i-- + dAtA[i] = 0x5a + } if len(m.HostID) > 0 { i -= len(m.HostID) copy(dAtA[i:], m.HostID) @@ -61633,6 +61642,10 @@ func (m *KeepAlive) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = len(m.Scope) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -75401,6 +75414,38 @@ func (m *KeepAlive) Unmarshal(dAtA []byte) error { } m.HostID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + 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 ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Scope = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/lib/inventory/controller.go b/lib/inventory/controller.go index 6bebc9edcac6..363f02d89cfe 100644 --- a/lib/inventory/controller.go +++ b/lib/inventory/controller.go @@ -1136,7 +1136,7 @@ func (c *Controller) handleAppServerHB(handle *upstreamHandle, appServer *types. handle.appServers = make(map[resourceKey]*heartBeatInfo[*types.AppServerV3]) } - appKey := resourceKey{hostID: appServer.GetHostID(), name: appServer.GetApp().GetName()} + appKey := resourceKey{hostID: appServer.GetHostID(), name: appServer.GetApp().GetName(), scope: appServer.GetScope()} srv := handle.appServers[appKey] if srv == nil { @@ -1214,7 +1214,7 @@ func (c *Controller) handleDatabaseServerHB(handle *upstreamHandle, databaseServ handle.databaseServers = make(map[resourceKey]*heartBeatInfo[*types.DatabaseServerV3]) } - dbKey := resourceKey{hostID: databaseServer.GetHostID(), name: databaseServer.GetDatabase().GetName()} + dbKey := resourceKey{hostID: databaseServer.GetHostID(), name: databaseServer.GetDatabase().GetName(), scope: databaseServer.GetScope()} if _, ok := handle.databaseServers[dbKey]; !ok { c.onConnectFunc(constants.KeepAliveDatabase) @@ -1283,8 +1283,7 @@ func (c *Controller) handleKubernetesServerHB(handle *upstreamHandle, kubernetes handle.kubernetesServers = make(map[resourceKey]*heartBeatInfo[*types.KubernetesServerV3]) } - kubeKey := resourceKey{hostID: kubernetesServer.GetHostID(), name: kubernetesServer.GetCluster().GetName()} - + kubeKey := resourceKey{hostID: kubernetesServer.GetHostID(), name: kubernetesServer.GetCluster().GetName(), scope: kubernetesServer.GetScope()} if _, ok := handle.kubernetesServers[kubeKey]; !ok { c.onConnectFunc(constants.KeepAliveKube) if c.kubeHBVariableDuration != nil { diff --git a/lib/inventory/controller_test.go b/lib/inventory/controller_test.go index 8562f61e6a4e..07017784c322 100644 --- a/lib/inventory/controller_test.go +++ b/lib/inventory/controller_test.go @@ -77,6 +77,7 @@ type fakeAuth struct { lastRawInstance []byte lastServerExpiry time.Time + expectScope string } func (a *fakeAuth) getLastServerExpiry() time.Time { @@ -167,7 +168,18 @@ func (a *fakeAuth) UpsertKubernetesServer(_ context.Context, server types.KubeSe return nil, trace.Errorf("upsert failed as test condition") } a.lastServerExpiry = server.Expiry() - return &types.KeepAlive{}, a.err + if a.lastServerExpiry.Equal(time.Time{}) { + return &types.KeepAlive{}, a.err + } + + return &types.KeepAlive{ + Type: types.KeepAlive_KUBERNETES, + Name: server.GetName(), + Namespace: server.GetNamespace(), + HostID: server.GetHostID(), + Expires: server.Expiry(), + Scope: server.GetScope(), + }, a.err } func (a *fakeAuth) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { @@ -182,6 +194,9 @@ func (a *fakeAuth) KeepAliveServer(_ context.Context, ka types.KeepAlive) error a.failKeepAlives-- return trace.Errorf("keepalive failed as test condition") } + if ka.Scope != a.expectScope { + return trace.Errorf("keepalive has mismatched scope") + } a.lastServerExpiry = ka.Expires return a.err } @@ -2034,7 +2049,9 @@ func testKubernetesServerScoped(initialScope, serverScope, clusterScope string) events := make(chan testEvent, 1024) - auth := &fakeAuth{} + auth := &fakeAuth{ + expectScope: initialScope, + } rc := &resourceCounter{} controller := NewController( diff --git a/lib/inventory/inventory.go b/lib/inventory/inventory.go index 06604d07a553..e7ac047b30ad 100644 --- a/lib/inventory/inventory.go +++ b/lib/inventory/inventory.go @@ -736,7 +736,7 @@ type upstreamHandle struct { } type resourceKey struct { - hostID, name string + hostID, name, scope string } type heartBeatInfo[T any] struct { diff --git a/lib/services/local/presence.go b/lib/services/local/presence.go index 2d605f679250..223e412a199d 100644 --- a/lib/services/local/presence.go +++ b/lib/services/local/presence.go @@ -365,8 +365,9 @@ func (s *PresenceService) UpsertNode(ctx context.Context, server types.Server) ( return &types.KeepAlive{}, nil } return &types.KeepAlive{ - Type: types.KeepAlive_NODE, - Name: server.GetName(), + Type: types.KeepAlive_NODE, + Name: server.GetName(), + Scope: server.GetScope(), }, nil } @@ -1037,6 +1038,7 @@ func (s *PresenceService) UpsertKubernetesServer(ctx context.Context, server typ Namespace: server.GetNamespace(), HostID: server.GetHostID(), Expires: server.Expiry(), + Scope: server.GetScope(), }, nil } @@ -1283,6 +1285,7 @@ func (s *PresenceService) UpsertApplicationServer(ctx context.Context, server ty Namespace: server.GetNamespace(), HostID: server.GetHostID(), Expires: upserted.Expiry(), + Scope: server.GetScope(), }, nil } diff --git a/lib/services/local/presence_test.go b/lib/services/local/presence_test.go index 7b169fbecf67..71a64cc64af4 100644 --- a/lib/services/local/presence_test.go +++ b/lib/services/local/presence_test.go @@ -2218,5 +2218,125 @@ func TestReverseTunnels_SkipsUnmarshalErrorsHittingPageBoundary(t *testing.T) { assert.NotEqual(t, slices[i], slices[j], "slices %d and %d should differ", i, j) } } +} + +func TestScopedLease(t *testing.T) { + t.Parallel() + backend, err := memory.New(memory.Config{}) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Close() }) + + presence := NewPresenceService(backend) + + const scope = "/aa" + expires := time.Now().Add(24 * time.Hour) + t.Run("kube servers", func(t *testing.T) { + ctx := t.Context() + unscoped := &types.KubernetesServerV3{ + Metadata: types.Metadata{ + Name: "unscoped", + Expires: &expires, + }, + Spec: types.KubernetesServerSpecV3{ + HostID: "unscoped", + Cluster: &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "unscoped", + }, + }, + }, + } + + scoped := &types.KubernetesServerV3{ + Metadata: types.Metadata{ + Name: "scoped", + Expires: &expires, + }, + Spec: types.KubernetesServerSpecV3{ + HostID: "scoped", + Cluster: &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "scoped", + }, + }, + }, + Scope: scope, + } + + // Create servers and check lease scopes + lease, err := presence.UpsertKubernetesServer(ctx, unscoped) + require.NoError(t, err) + require.Empty(t, lease.Scope) + + lease, err = presence.UpsertKubernetesServer(ctx, scoped) + require.NoError(t, err) + require.Equal(t, scope, lease.Scope) + }) + + t.Run("nodes", func(t *testing.T) { + ctx := t.Context() + unscopedNode, err := types.NewServerWithLabels("node1", types.KindNode, types.ServerSpecV2{}, nil) + require.NoError(t, err) + unscopedNode.SetExpiry(expires) + const scope = "/aa" + scopedNode, err := types.NewServerWithLabels("scoped-node", types.KindNode, types.ServerSpecV2{}, nil) + require.NoError(t, err) + scopedServer, ok := scopedNode.(*types.ServerV2) + require.True(t, ok, "expected types.ServerV2") + scopedServer.Scope = scope + scopedServer.SetExpiry(expires) + + // Create servers and check lease scopes + lease, err := presence.UpsertNode(ctx, unscopedNode) + require.NoError(t, err) + require.Empty(t, lease.Scope) + + lease, err = presence.UpsertNode(ctx, scopedServer) + require.NoError(t, err) + require.Equal(t, scope, lease.Scope) + }) + + t.Run("apps", func(t *testing.T) { + ctx := t.Context() + expires := time.Now().Add(24 * time.Hour) + unscopedApp, err := types.NewAppV3(types.Metadata{Name: "a"}, + types.AppSpecV3{URI: "http://localhost:8080"}) + require.NoError(t, err) + unscopedServer, err := types.NewAppServerV3(types.Metadata{ + Name: unscopedApp.GetName(), + Expires: &expires, + }, types.AppServerSpecV3{ + Hostname: "localhost", + HostID: uuid.New().String(), + App: unscopedApp, + }) + require.NoError(t, err) + + const scope = "/aa" + scopedApp, err := types.NewAppV3(types.Metadata{Name: "graf"}, + types.AppSpecV3{URI: "http://localhost:8080"}) + require.NoError(t, err) + scopedApp.Scope = scope + scopedServer, err := types.NewAppServerV3(types.Metadata{ + Name: scopedApp.GetName(), + Expires: &expires, + }, types.AppServerSpecV3{ + Hostname: "localhost", + HostID: uuid.New().String(), + App: scopedApp, + }) + require.NoError(t, err) + scopedServer.Scope = scope + require.NoError(t, err) + + // Create servers and check lease scopes + lease, err := presence.UpsertApplicationServer(ctx, unscopedServer) + require.NoError(t, err) + require.Empty(t, lease.Scope) + + lease, err = presence.UpsertApplicationServer(ctx, scopedServer) + require.NoError(t, err) + require.Equal(t, scope, lease.Scope) + }) } From c97d53fd4830bfd15e3fd560b1e6c70ddf015080 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:37:20 -0400 Subject: [PATCH 014/347] Terraform: convert scoped resources to generic provider (#68866) The currently supported set of scoped resources: ScopedRole, ScopedToken, ScopedRoleAssignment, KubernetesCluster, and App have all been migrated to the new generic terraform provider mechanism. This is required for those resources to properly provide the name and scope pair which uniquely identifies a scoped resource. Note: not all scoped resources currently support being identifier by a the (name, scope) pair. However, this conversion is a prereq for when they do. At such a time, those resources should only require changing the identifier type and plumbing the name and scope into the Get and Delete requests. --- api/client/accesslist/accesslist.go | 20 +- .../data-sources/access_list.mdx | 1 - .../data-sources/access_list_member.mdx | 1 - .../resources/access_list.mdx | 1 - .../resources/access_list_member.mdx | 1 - integrations/terraform/gen/main.go | 155 -------- .../protoc-gen-terraform-accesslist.yaml | 10 +- .../protoc-gen-terraform-teleport.yaml | 5 + .../data_source_teleport_access_list.go | 97 ----- ...data_source_teleport_access_list_member.go | 103 ----- .../data_source_teleport_kube_cluster.go | 97 ----- .../terraform/provider/internal/legacy/id.go | 45 --- .../provider/internal/legacy/registry.go | 10 - .../legacy/resource_teleport_access_list.go | 343 ----------------- .../resource_teleport_access_list_member.go | 364 ------------------ .../legacy/resource_teleport_kube_cluster.go | 346 ----------------- .../internal/resources/access_list.go | 100 +++++ .../internal/resources/access_list_member.go | 99 +++++ .../provider/internal/resources/kubernetes.go | 71 ++++ .../internal/resources/scoped_role.go | 69 ++++ .../resources/scoped_role_assignment.go | 69 ++++ .../provider/internal/teleport/access_list.go | 84 ++++ .../internal/teleport/access_list_member.go | 82 ++++ .../provider/internal/teleport/kubernetes.go | 83 ++++ .../provider/internal/teleport/scoped_role.go | 84 ++++ .../teleport/scoped_role_assignment.go | 88 +++++ .../provider/internal/tfdriver/identifier.go | 189 +++++++++ .../internal/tfdriver/identifier_test.go | 219 +++++++++++ integrations/terraform/provider/provider.go | 18 +- .../testlib/access_list_member_test.go | 221 +++++++++++ .../terraform/testlib/access_list_test.go | 136 ++++++- .../fixtures/access_list_member_0_create.tf | 39 ++ .../fixtures/access_list_member_1_update.tf | 39 ++ .../access_list_member_scoped_and_unscoped.tf | 118 ++++++ .../access_list_next_audit_date_0_create.tf | 35 ++ ...s_list_next_audit_date_1_attempt_update.tf | 38 ++ .../access_list_scoped_and_unscoped.tf | 59 +++ .../fixtures/classifier_data_source.tf | 1 + integrations/terraform/testlib/main_test.go | 3 + .../testlib/terraform_enterprise_test.go | 33 +- .../accesslist/v1/accesslist_terraform.go | 106 +---- .../terraform/tfschema/types_terraform.go | 14 +- lib/auth/authtest/authtest.go | 6 +- 43 files changed, 2010 insertions(+), 1692 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_access_list.go delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_access_list_member.go delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_kube_cluster.go delete mode 100644 integrations/terraform/provider/internal/legacy/id.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_access_list.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_access_list_member.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_kube_cluster.go create mode 100644 integrations/terraform/provider/internal/resources/access_list.go create mode 100644 integrations/terraform/provider/internal/resources/access_list_member.go create mode 100644 integrations/terraform/provider/internal/resources/kubernetes.go create mode 100644 integrations/terraform/provider/internal/resources/scoped_role.go create mode 100644 integrations/terraform/provider/internal/resources/scoped_role_assignment.go create mode 100644 integrations/terraform/provider/internal/teleport/access_list.go create mode 100644 integrations/terraform/provider/internal/teleport/access_list_member.go create mode 100644 integrations/terraform/provider/internal/teleport/kubernetes.go create mode 100644 integrations/terraform/provider/internal/teleport/scoped_role.go create mode 100644 integrations/terraform/provider/internal/teleport/scoped_role_assignment.go create mode 100644 integrations/terraform/testlib/access_list_member_test.go create mode 100644 integrations/terraform/testlib/fixtures/access_list_member_0_create.tf create mode 100644 integrations/terraform/testlib/fixtures/access_list_member_1_update.tf create mode 100644 integrations/terraform/testlib/fixtures/access_list_member_scoped_and_unscoped.tf create mode 100644 integrations/terraform/testlib/fixtures/access_list_next_audit_date_0_create.tf create mode 100644 integrations/terraform/testlib/fixtures/access_list_next_audit_date_1_attempt_update.tf create mode 100644 integrations/terraform/testlib/fixtures/access_list_scoped_and_unscoped.tf diff --git a/api/client/accesslist/accesslist.go b/api/client/accesslist/accesslist.go index a5ae3970032f..002f94d166f4 100644 --- a/api/client/accesslist/accesslist.go +++ b/api/client/accesslist/accesslist.go @@ -299,10 +299,16 @@ func (c *Client) GetAccessListMemberV2(ctx context.Context, req *accesslistv1.Ge // GetStaticAccessListMember returns the specified access_list_member resource. If returns error if // the target access_list is not of type static. func (c *Client) GetStaticAccessListMember(ctx context.Context, accessList, memberName string) (*accesslist.AccessListMember, error) { - resp, err := c.grpcClient.GetStaticAccessListMember(ctx, &accesslistv1.GetStaticAccessListMemberRequest{ + return c.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{ AccessList: accessList, MemberName: memberName, - }) + }.Build()) +} + +// GetStaticAccessListMemberV2 returns the specified access_list_member resource. If returns error if +// the target access_list is not of type static. +func (c *Client) GetStaticAccessListMemberV2(ctx context.Context, req *accesslistv1.GetStaticAccessListMemberRequest) (*accesslist.AccessListMember, error) { + resp, err := c.grpcClient.GetStaticAccessListMember(ctx, req) if err != nil { return nil, trace.Wrap(err) } @@ -378,10 +384,16 @@ func (c *Client) UpdateAccessListMember(ctx context.Context, member *accesslist. // DeleteStaticAccessListMember hard deletes the specified access_list_member. It returns error and // does nothing if the target access_list is not of static type. func (c *Client) DeleteStaticAccessListMember(ctx context.Context, accessList, memberName string) error { - _, err := c.grpcClient.DeleteStaticAccessListMember(ctx, &accesslistv1.DeleteStaticAccessListMemberRequest{ + return c.DeleteStaticAccessListMemberV2(ctx, accesslistv1.DeleteStaticAccessListMemberRequest_builder{ AccessList: accessList, MemberName: memberName, - }) + }.Build()) +} + +// DeleteStaticAccessListMemberV2 hard deletes the specified access_list_member. It returns error and +// does nothing if the target access_list is not of static type. +func (c *Client) DeleteStaticAccessListMemberV2(ctx context.Context, req *accesslistv1.DeleteStaticAccessListMemberRequest) error { + _, err := c.grpcClient.DeleteStaticAccessListMember(ctx, req) return trace.Wrap(err) } diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list.mdx index 35a19d437c31..21a70298859c 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list.mdx @@ -52,7 +52,6 @@ Optional: - `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. - `namespace` (String) namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. -- `revision` (String) 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. diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list_member.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list_member.mdx index 29a90d75a08a..73a49f0bcd59 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list_member.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/access_list_member.mdx @@ -52,7 +52,6 @@ Optional: - `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. - `namespace` (String) namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. -- `revision` (String) 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. diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list.mdx index 253fee5480d3..03677a998f78 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list.mdx @@ -95,7 +95,6 @@ Optional: - `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. - `namespace` (String) namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. -- `revision` (String) 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. diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list_member.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list_member.mdx index 85b1864adf94..b9cdd51e6e0e 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list_member.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/access_list_member.mdx @@ -127,7 +127,6 @@ Optional: - `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. - `namespace` (String) namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. -- `revision` (String) 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. diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index f956c467ccc9..6f9d02db55fe 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -356,21 +356,6 @@ var ( HasCheckAndSetDefaults: true, } - kubernetesCluster = payload{ - Name: "KubeCluster", - TypeName: "KubernetesClusterV3", - VarName: "kubeCluster", - GetMethod: "GetKubernetesCluster", - CreateMethod: "CreateKubernetesCluster", - UpdateMethod: "UpdateKubernetesCluster", - DeleteMethod: "DeleteKubernetesCluster", - ID: `kubeCluster.Metadata.Name`, - Kind: "kube_cluster", - HasStaticID: false, - TerraformResourceType: "teleport_kube_cluster", - HasCheckAndSetDefaults: true, - } - lock = payload{ Name: "Lock", TypeName: "LockV2", @@ -591,52 +576,6 @@ var ( HasCheckAndSetDefaults: true, } - accessList = payload{ - Name: "AccessList", - TypeName: "AccessList", - VarName: "accessList", - GetMethod: "AccessListClient().GetAccessList", - CreateMethod: "AccessListClient().UpsertAccessList", - UpsertMethodArity: 2, - UpdateMethod: "AccessListClient().UpsertAccessList", - DeleteMethod: "AccessListClient().DeleteAccessList", - ID: "accessList.Header.Metadata.Name", - Kind: "access_list", - HasStaticID: false, - SchemaPackage: "schemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1", - ProtoPackage: "accesslist", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1", - TerraformResourceType: "teleport_access_list", - ConvertPackagePath: "github.com/gravitational/teleport/api/types/accesslist/convert/v1", - HasCheckAndSetDefaults: true, - PropagatedFields: []string{"Spec.Audit.NextAuditDate"}, - } - - accessListMember = payload{ - Name: "Member", - TypeName: "Member", - VarName: "accessListMember", - GetMethod: "AccessListClient().GetStaticAccessListMember", - CreateMethod: "AccessListClient().UpsertStaticAccessListMember", - UpsertMethodArity: 2, - UpdateMethod: "AccessListClient().UpsertStaticAccessListMember", - DeleteMethod: "AccessListClient().DeleteStaticAccessListMember", - IDPrefix: "accessListMember.Spec.AccessList", - ID: "accessListMember.Header.Metadata.Name", - Kind: "access_list_member", - HasStaticID: false, - SchemaPackage: "schemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1", - ProtoPackage: "accesslist", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1", - TerraformResourceType: "teleport_access_list_member", - ConvertPackagePath: "github.com/gravitational/teleport/api/types/accesslist/convert/v1", - ConvertToProtoFunc: "ToMemberProto", - ConvertFromProtoFunc: "FromMemberProto", - HasCheckAndSetDefaults: true, - } - server = payload{ Name: "Server", TypeName: "ServerV2", @@ -1055,88 +994,6 @@ var ( ForceSetKind: "apitypes.KindClassifier", } - scopedRole = payload{ - Name: "ScopedRole", - TypeName: "ScopedRole", - VarName: "scopedRole", - GetMethod: "ScopedAccessServiceClient().GetScopedRole", - CreateMethod: "ScopedAccessServiceClient().CreateScopedRole", - UpdateMethod: "ScopedAccessServiceClient().UpsertScopedRole", - UpsertMethodArity: 2, - DeleteMethod: "ScopedAccessServiceClient().DeleteScopedRole", - ID: "scopedRole.Metadata.Name", - Kind: "scoped_role", - HasStaticID: false, - ProtoPackage: "accessv1", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1", - SchemaPackage: "schemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/v1", - TerraformResourceType: "teleport_scoped_role", - IsPlainStruct: true, - ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/lib/scopes/access\""}, - ForceSetKind: "apitypes.KindScopedRole", - RequestWrapper: &RequestWrapper{ - RequestResourceField: "Role", - GetRequest: "GetScopedRoleRequest", - CreateRequest: "CreateScopedRoleRequest", - UpdateRequest: "UpsertScopedRoleRequest", - DeleteRequest: "DeleteScopedRoleRequest", - }, - } - - scopedRoleAssignment = payload{ - Name: "ScopedRoleAssignment", - TypeName: "ScopedRoleAssignment", - VarName: "scopedRoleAssignment", - GetMethod: "ScopedAccessServiceClient().GetScopedRoleAssignment", - CreateMethod: "ScopedAccessServiceClient().CreateScopedRoleAssignment", - UpdateMethod: "ScopedAccessServiceClient().UpsertScopedRoleAssignment", - UpsertMethodArity: 2, - DeleteMethod: "ScopedAccessServiceClient().DeleteScopedRoleAssignment", - ID: "scopedRoleAssignment.Metadata.Name", - Kind: "scoped_role_assignment", - HasStaticID: false, - ProtoPackage: "accessv1", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1", - SchemaPackage: "assignmentschemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1", - TerraformResourceType: "teleport_scoped_role_assignment", - IsPlainStruct: true, - ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/lib/scopes/access\""}, - ForceSetKind: "apitypes.KindScopedRoleAssignment", - RequestWrapper: &RequestWrapper{ - RequestResourceField: "Assignment", - GetRequest: "GetScopedRoleAssignmentRequest", - CreateRequest: "CreateScopedRoleAssignmentRequest", - UpdateRequest: "UpsertScopedRoleAssignmentRequest", - DeleteRequest: "DeleteScopedRoleAssignmentRequest", - }, - DefaultSubKind: "\"dynamic\"", - } - - scopedToken = payload{ - Name: "ScopedToken", - TypeName: "ScopedToken", - VarName: "scopedToken", - GetMethod: "GetScopedToken", - CreateMethod: "CreateScopedToken", - UpdateMethod: "UpsertScopedToken", - UpsertMethodArity: 2, - DeleteMethod: "DeleteScopedToken", - ID: "scopedToken.Metadata.Name", - Kind: "scoped_token", - WithSecrets: "true", - HasStaticID: false, - ProtoPackage: "joiningv1", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1", - SchemaPackage: "schemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/joining/v1", - TerraformResourceType: "teleport_scoped_token", - IsPlainStruct: true, - ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""}, - ForceSetKind: "apitypes.KindScopedToken", - } - workloadCluster = payload{ Name: "WorkloadCluster", TypeName: "WorkloadCluster", @@ -1281,8 +1138,6 @@ func genTFSchema() { generateDataSource(dynamicWindowsDesktop, pluralDataSource) generateResource(githubConnector, pluralResource) generateDataSource(githubConnector, pluralDataSource) - generateResource(kubernetesCluster, pluralResource) - generateDataSource(kubernetesCluster, pluralDataSource) generateResource(lock, pluralResource) generateDataSource(lock, pluralDataSource) generateResource(oidcConnector, pluralResource) @@ -1309,10 +1164,6 @@ func genTFSchema() { generateDataSource(deviceTrust, pluralDataSource) generateResource(oktaImportRule, pluralResource) generateDataSource(oktaImportRule, pluralDataSource) - generateResource(accessList, pluralResource) - generateDataSource(accessList, pluralDataSource) - generateResource(accessListMember, pluralResource) - generateDataSource(accessListMember, pluralDataSource) generateResource(server, pluralResource) generateDataSource(server, pluralDataSource) generateResource(installer, pluralResource) @@ -1347,12 +1198,6 @@ func genTFSchema() { generateDataSource(classifier, pluralDataSource) generateResource(retrievalModel, singularResource) generateDataSource(retrievalModel, singularDataSource) - generateResource(scopedRole, pluralResource) - generateDataSource(scopedRole, pluralDataSource) - generateResource(scopedRoleAssignment, pluralResource) - generateDataSource(scopedRoleAssignment, pluralDataSource) - generateResource(scopedToken, pluralResource) - generateDataSource(scopedToken, pluralDataSource) generateResource(workloadCluster, pluralResource) generateDataSource(workloadCluster, pluralDataSource) generateResource(databaseObjectImportRule, pluralResource) diff --git a/integrations/terraform/protoc-gen-terraform-accesslist.yaml b/integrations/terraform/protoc-gen-terraform-accesslist.yaml index e277769d17e2..426f7fb1dc1a 100644 --- a/integrations/terraform/protoc-gen-terraform-accesslist.yaml +++ b/integrations/terraform/protoc-gen-terraform-accesslist.yaml @@ -42,11 +42,13 @@ injected_fields: exclude_fields: # Metadata (we id resources by name on our side) - "AccessList.header.metadata.id" + - "AccessList.header.metadata.revision" # Read only field - "AccessList.spec.owners.ineligible_status" - "AccessList.status" # Metadata (we id resources by name on our side) - "Member.header.metadata.id" + - "Member.header.metadata.revision" # Read only field - "Member.spec.ineligible_status" - "Member.status" @@ -56,12 +58,10 @@ computed_fields: - "AccessList.header.kind" - "AccessList.header.metadata.expires" - "AccessList.header.metadata.namespace" - - "AccessList.header.metadata.revision" - "AccessList.spec.audit.next_audit_date" - "Member.header.kind" - "Member.header.metadata.expires" - "Member.header.metadata.namespace" - - "Member.header.metadata.revision" - "Member.spec.name" - "Member.spec.joined" - "Member.spec.added_by" @@ -79,6 +79,12 @@ plan_modifiers: # Force to recreate resource if it's ID changes Metadata.name: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + # Scope is part of the access_list ID. + AccessList.scope: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + # Scope is part of the access_list_member ID. + Member.scope: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" # spec.access_list is a part of access_list_member ID. In the backend member's ID is # constructed like: # "{access_list_member.spec.access_list}/{access_list_member.header.metadata.name}" diff --git a/integrations/terraform/protoc-gen-terraform-teleport.yaml b/integrations/terraform/protoc-gen-terraform-teleport.yaml index 23b53408370d..88d6e4000f31 100644 --- a/integrations/terraform/protoc-gen-terraform-teleport.yaml +++ b/integrations/terraform/protoc-gen-terraform-teleport.yaml @@ -513,6 +513,11 @@ plan_modifiers: # Force to recreate resource if it's name changes Metadata.Name: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + # Scope is part of the scoped resource identity and must not be updated in-place. + AppV3.scope: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + KubernetesClusterV3.scope: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" ServerV2.Metadata.Name: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_access_list.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_access_list.go deleted file mode 100755 index 98e97eed42ab..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_access_list.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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 legacy - -import ( - "context" - - convert "github.com/gravitational/teleport/api/types/accesslist/convert/v1" - "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/accesslist/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportAccessListType is the data source metadata type -type dataSourceTeleportAccessListType struct{} - -// dataSourceTeleportAccessList is the resource -type dataSourceTeleportAccessList struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportAccessListType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaAccessList(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportAccessListType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportAccessList{ - p: p.(Provider), - }, nil -} - -// Read reads teleport AccessList -func (r dataSourceTeleportAccessList) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { - var id types.String - diags := req.Config.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - accessListI, err := r.p.Client().AccessListClient().GetAccessList(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = id - } - - accessList := convert.ToProto(accessListI) - - diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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/internal/legacy/data_source_teleport_access_list_member.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_access_list_member.go deleted file mode 100755 index 086ecbc66b70..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_access_list_member.go +++ /dev/null @@ -1,103 +0,0 @@ -// 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 legacy - -import ( - "context" - - convert "github.com/gravitational/teleport/api/types/accesslist/convert/v1" - "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/accesslist/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportMemberType is the data source metadata type -type dataSourceTeleportMemberType struct{} - -// dataSourceTeleportMember is the resource -type dataSourceTeleportMember struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportMemberType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaMember(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportMemberType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportMember{ - p: p.(Provider), - }, nil -} - -// Read reads teleport Member -func (r dataSourceTeleportMember) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { - var idPrefix types.String - diags := req.Config.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - var id types.String - diags = req.Config.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - accessListMemberI, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix.Value, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = types.String{Value: formatID(idPrefix.Value, id.Value)} - } - - accessListMember := convert.ToMemberProto(accessListMemberI) - - diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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/internal/legacy/data_source_teleport_kube_cluster.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_kube_cluster.go deleted file mode 100755 index df839c6f010d..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_kube_cluster.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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 legacy - -import ( - "context" - - apitypes "github.com/gravitational/teleport/api/types" - "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/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportKubeClusterType is the data source metadata type -type dataSourceTeleportKubeClusterType struct{} - -// dataSourceTeleportKubeCluster is the resource -type dataSourceTeleportKubeCluster struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportKubeClusterType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaKubernetesClusterV3(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportKubeClusterType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportKubeCluster{ - p: p.(Provider), - }, nil -} - -// Read reads teleport KubeCluster -func (r dataSourceTeleportKubeCluster) 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 - } - - kubeClusterI, err := r.p.Client().GetKubernetesCluster(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = id - } - - - kubeCluster := kubeClusterI.(*apitypes.KubernetesClusterV3) - diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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/internal/legacy/id.go b/integrations/terraform/provider/internal/legacy/id.go deleted file mode 100644 index 25c79768adf5..000000000000 --- a/integrations/terraform/provider/internal/legacy/id.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2025 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 legacy - -import ( - "strings" - - "github.com/gravitational/trace" -) - -// formatID takes the prefix and the name of the resource and converts it to a string ID. -func formatID(prefix, name string) string { - return prefix + "/" + name -} - -// parseID takes the ID generated by [formatID] and splits it back to the prefix and resource name. -func parseID(id string) (prefix, name string, err error) { - split := strings.Split(id, "/") - if len(split) != 2 { - return "", "", trace.BadParameter("expected id %q to have a single %q separator", id, "/") - } - prefix = split[0] - name = split[1] - if prefix == "" { - return "", "", trace.BadParameter("expected id %q prefix to be non-empty", id) - } - if name == "" { - return "", "", trace.BadParameter("expected id %q name to be non-empty", id) - } - return prefix, name, nil -} diff --git a/integrations/terraform/provider/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index aa531450dbc6..315d9472052a 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -29,7 +29,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_discovery_config": dataSourceTeleportDiscoveryConfigType{}, "teleport_dynamic_windows_desktop": dataSourceTeleportDynamicWindowsDesktopType{}, "teleport_github_connector": dataSourceTeleportGithubConnectorType{}, - "teleport_kube_cluster": dataSourceTeleportKubeClusterType{}, "teleport_lock": dataSourceTeleportLockType{}, "teleport_provision_token": dataSourceTeleportProvisionTokenType{}, "teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{}, @@ -43,8 +42,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_login_rule": dataSourceTeleportLoginRuleType{}, "teleport_trusted_device": dataSourceTeleportDeviceV1Type{}, "teleport_okta_import_rule": dataSourceTeleportOktaImportRuleType{}, - "teleport_access_list": dataSourceTeleportAccessListType{}, - "teleport_access_list_member": dataSourceTeleportMemberType{}, "teleport_installer": dataSourceTeleportInstallerType{}, "teleport_access_monitoring_rule": dataSourceTeleportAccessMonitoringRuleType{}, "teleport_static_host_user": dataSourceTeleportStaticHostUserType{}, @@ -54,8 +51,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_health_check_config": dataSourceTeleportHealthCheckConfigType{}, "teleport_vnet_config": dataSourceTeleportVnetConfigType{}, "teleport_integration": dataSourceTeleportIntegrationType{}, - "teleport_scoped_role": dataSourceTeleportScopedRoleType{}, - "teleport_scoped_role_assignment": dataSourceTeleportScopedRoleAssignmentType{}, "teleport_db_object_import_rule": dataSourceTeleportDatabaseObjectImportRuleType{}, "teleport_classifier": dataSourceTeleportClassifierType{}, // TODO(bl-nero): Add teleport_inference_* data sources after data sources @@ -81,7 +76,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_discovery_config": resourceTeleportDiscoveryConfigType{}, "teleport_dynamic_windows_desktop": resourceTeleportDynamicWindowsDesktopType{}, "teleport_github_connector": resourceTeleportGithubConnectorType{}, - "teleport_kube_cluster": resourceTeleportKubeClusterType{}, "teleport_lock": resourceTeleportLockType{}, "teleport_provision_token": resourceTeleportProvisionTokenType{}, "teleport_oidc_connector": resourceTeleportOIDCConnectorType{}, @@ -96,8 +90,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_login_rule": resourceTeleportLoginRuleType{}, "teleport_trusted_device": resourceTeleportDeviceV1Type{}, "teleport_okta_import_rule": resourceTeleportOktaImportRuleType{}, - "teleport_access_list": resourceTeleportAccessListType{}, - "teleport_access_list_member": resourceTeleportMemberType{}, "teleport_server": resourceTeleportServerType{}, "teleport_installer": resourceTeleportInstallerType{}, "teleport_access_monitoring_rule": resourceTeleportAccessMonitoringRuleType{}, @@ -114,8 +106,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_classifier": resourceTeleportClassifierType{}, "teleport_retrieval_model": resourceTeleportRetrievalModelType{}, "teleport_workload_cluster": resourceTeleportWorkloadClusterType{}, - "teleport_scoped_role": resourceTeleportScopedRoleType{}, - "teleport_scoped_role_assignment": resourceTeleportScopedRoleAssignmentType{}, "teleport_db_object_import_rule": resourceTeleportDatabaseObjectImportRuleType{}, "teleport_client_ip_restriction": resourceTeleportClientIPRestrictionType{}, } diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_access_list.go b/integrations/terraform/provider/internal/legacy/resource_teleport_access_list.go deleted file mode 100755 index 6e907af0809e..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_access_list.go +++ /dev/null @@ -1,343 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - accesslist "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" - convert "github.com/gravitational/teleport/api/types/accesslist/convert/v1" - "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/accesslist/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportAccessListType is the resource metadata type -type resourceTeleportAccessListType struct{} - -// resourceTeleportAccessList is the resource -type resourceTeleportAccessList struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportAccessListType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaAccessList(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportAccessListType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportAccessList{ - p: p.(Provider), - }, nil -} - -// Create creates the AccessList -func (r resourceTeleportAccessList) 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 - } - - accessList := &accesslist.AccessList{} - diags = schemav1.CopyAccessListFromTerraform(ctx, plan, accessList) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - accessListResource, err := convert.FromProto(accessList) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Errorf("Can not convert %T to AccessList: %s", accessListResource, err), "access_list")) - return - } - err = accessListResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list")) - return - } - - id := accessListResource.Metadata.Name - - _, err = r.p.Client().AccessListClient().GetAccessList(ctx, id) - if !trace.IsNotFound(err) { - if err == nil { - existErr := fmt.Sprintf("AccessList exists in Teleport. Either remove it (tctl rm access_list/%v)"+ - " or import it to the existing state (terraform import teleport_access_list.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("AccessList exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list")) - return - } - - _, err = r.p.Client().AccessListClient().UpsertAccessList(ctx, accessListResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating AccessList", trace.Wrap(err), "access_list")) - return - } - var accessListI = accessListResource - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - accessListI, err = r.p.Client().AccessListClient().GetAccessList(ctx, id) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(ctx.Err()), "access_list")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading AccessList (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "access_list") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list")) - return - } - - accessListResource = accessListI - - accessList = convert.ToProto(accessListResource) - - - diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: accessList.Header.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport AccessList -func (r resourceTeleportAccessList) 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("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - accessListI, err := r.p.Client().AccessListClient().GetAccessList(ctx, id.Value) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list")) - return - } - accessList := convert.ToProto(accessListI) - diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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 AccessList -func (r resourceTeleportAccessList) 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 - } - - accessList := &accesslist.AccessList{} - diags = schemav1.CopyAccessListFromTerraform(ctx, plan, accessList) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - accessListResource, err := convert.FromProto(accessList) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Errorf("Can not convert %T to AccessList: %s", accessListResource, err), "access_list")) - return - } - - if err := accessListResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AccessList", err, "access_list")) - return - } - name := accessListResource.Metadata.Name - - accessListBefore, err := r.p.Client().AccessListClient().GetAccessList(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", err, "access_list")) - return - } - accessListResource.Spec.Audit.NextAuditDate = accessListBefore.Spec.Audit.NextAuditDate - - _, err = r.p.Client().AccessListClient().UpsertAccessList(ctx, accessListResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AccessList", err, "access_list")) - return - } - var accessListI = accessListResource - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - accessListI, err = r.p.Client().AccessListClient().GetAccessList(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", err, "access_list")) - return - } - if accessListBefore.GetMetadata().Revision != accessListI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(ctx.Err()), "access_list")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading AccessList (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "access_list") - return - } - } - - accessListResource = accessListI - - diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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 AccessList -func (r resourceTeleportAccessList) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - var id types.String - diags := req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := r.p.Client().AccessListClient().DeleteAccessList(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting AccessList", trace.Wrap(err), "access_list")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports AccessList state -func (r resourceTeleportAccessList) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - accessList, err := r.p.Client().AccessListClient().GetAccessList(ctx, req.ID) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list")) - return - } - - accessListResource := convert.ToProto(accessList) - - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = schemav1.CopyAccessListToTerraform(ctx, accessListResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := accessList.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/provider/internal/legacy/resource_teleport_access_list_member.go b/integrations/terraform/provider/internal/legacy/resource_teleport_access_list_member.go deleted file mode 100755 index ae42bcf2b16b..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_access_list_member.go +++ /dev/null @@ -1,364 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - accesslist "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" - convert "github.com/gravitational/teleport/api/types/accesslist/convert/v1" - "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/accesslist/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportMemberType is the resource metadata type -type resourceTeleportMemberType struct{} - -// resourceTeleportMember is the resource -type resourceTeleportMember struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportMemberType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaMember(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportMemberType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportMember{ - p: p.(Provider), - }, nil -} - -// Create creates the Member -func (r resourceTeleportMember) 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 - } - - accessListMember := &accesslist.Member{} - diags = schemav1.CopyMemberFromTerraform(ctx, plan, accessListMember) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - accessListMemberResource, err := convert.FromMemberProto(accessListMember) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Errorf("Can not convert %T to Member: %s", accessListMemberResource, err), "access_list_member")) - return - } - err = accessListMemberResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Member defaults", trace.Wrap(err), "access_list_member")) - return - } - - idPrefix := accessListMemberResource.Spec.AccessList - id := accessListMemberResource.Metadata.Name - - _, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, id) - if !trace.IsNotFound(err) { - if err == nil { - id := formatID(idPrefix, id) - existErr := fmt.Sprintf("Member exists in Teleport. Either remove it (tctl rm access_list_member/%v)"+ - " or import it to the existing state (terraform import teleport_access_list_member.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("Member exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member")) - return - } - - _, err = r.p.Client().AccessListClient().UpsertStaticAccessListMember(ctx, accessListMemberResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating Member", trace.Wrap(err), "access_list_member")) - return - } - var accessListMemberI = accessListMemberResource - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - accessListMemberI, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, id) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(ctx.Err()), "access_list_member")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Member (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "access_list_member") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member")) - return - } - - accessListMemberResource = accessListMemberI - - accessListMember = convert.ToMemberProto(accessListMemberResource) - - - diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: formatID(idPrefix, accessListMember.Header.Metadata.Name)} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport Member -func (r resourceTeleportMember) 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 idPrefix types.String - diags = req.State.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - var id types.String - diags = req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - accessListMemberI, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix.Value, id.Value) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member")) - return - } - accessListMember := convert.ToMemberProto(accessListMemberI) - diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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 Member -func (r resourceTeleportMember) 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 - } - - accessListMember := &accesslist.Member{} - diags = schemav1.CopyMemberFromTerraform(ctx, plan, accessListMember) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - accessListMemberResource, err := convert.FromMemberProto(accessListMember) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Errorf("Can not convert %T to Member: %s", accessListMemberResource, err), "access_list_member")) - return - } - - if err := accessListMemberResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Member", err, "access_list_member")) - return - } - idPrefix := accessListMemberResource.Spec.AccessList - name := accessListMemberResource.Metadata.Name - - accessListMemberBefore, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", err, "access_list_member")) - return - } - - _, err = r.p.Client().AccessListClient().UpsertStaticAccessListMember(ctx, accessListMemberResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Member", err, "access_list_member")) - return - } - var accessListMemberI = accessListMemberResource - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - accessListMemberI, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", err, "access_list_member")) - return - } - if accessListMemberBefore.GetMetadata().Revision != accessListMemberI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(ctx.Err()), "access_list_member")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Member (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "access_list_member") - return - } - } - - accessListMemberResource = accessListMemberI - - diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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 Member -func (r resourceTeleportMember) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - var idPrefix types.String - diags := req.State.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - var id types.String - diags = req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := r.p.Client().AccessListClient().DeleteStaticAccessListMember(ctx, idPrefix.Value, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting Member", trace.Wrap(err), "access_list_member")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports Member state -func (r resourceTeleportMember) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - idPrefix, name, err := parseID(req.ID) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error parsing Member ID", trace.Wrap(err), "access_list_member")) - return - } - accessListMember, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member")) - return - } - - accessListMemberResource := convert.ToMemberProto(accessListMember) - - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = schemav1.CopyMemberToTerraform(ctx, accessListMemberResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := req.ID - - 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/provider/internal/legacy/resource_teleport_kube_cluster.go b/integrations/terraform/provider/internal/legacy/resource_teleport_kube_cluster.go deleted file mode 100755 index 701294e90109..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_kube_cluster.go +++ /dev/null @@ -1,346 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - apitypes "github.com/gravitational/teleport/api/types" - - "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/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportKubeClusterType is the resource metadata type -type resourceTeleportKubeClusterType struct{} - -// resourceTeleportKubeCluster is the resource -type resourceTeleportKubeCluster struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportKubeClusterType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaKubernetesClusterV3(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportKubeClusterType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportKubeCluster{ - p: p.(Provider), - }, nil -} - -// Create creates the KubeCluster -func (r resourceTeleportKubeCluster) 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 - } - - kubeCluster := &apitypes.KubernetesClusterV3{} - diags = tfschema.CopyKubernetesClusterV3FromTerraform(ctx, plan, kubeCluster) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - kubeClusterResource := kubeCluster - - err = kubeClusterResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting KubeCluster defaults", trace.Wrap(err), "kube_cluster")) - return - } - - id := kubeClusterResource.Metadata.Name - - _, err = r.p.Client().GetKubernetesCluster(ctx, id) - if !trace.IsNotFound(err) { - if err == nil { - existErr := fmt.Sprintf("KubeCluster exists in Teleport. Either remove it (tctl rm kube_cluster/%v)"+ - " or import it to the existing state (terraform import teleport_kube_cluster.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("KubeCluster exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - err = r.p.Client().CreateKubernetesCluster(ctx, kubeClusterResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - // Not really an inferface, just using the same name for easier templating. - var kubeClusterI apitypes.KubeCluster - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - kubeClusterI, err = r.p.Client().GetKubernetesCluster(ctx, id) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(ctx.Err()), "kube_cluster")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading KubeCluster (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "kube_cluster") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - kubeClusterResource, ok := kubeClusterI.(*apitypes.KubernetesClusterV3) - if !ok { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Errorf("Can not convert %T to KubernetesClusterV3", kubeClusterI), "kube_cluster")) - return - } - kubeCluster = kubeClusterResource - - diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: kubeCluster.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport KubeCluster -func (r resourceTeleportKubeCluster) 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 - } - - kubeClusterI, err := r.p.Client().GetKubernetesCluster(ctx, id.Value) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - kubeCluster := kubeClusterI.(*apitypes.KubernetesClusterV3) - diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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 KubeCluster -func (r resourceTeleportKubeCluster) 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 - } - - kubeCluster := &apitypes.KubernetesClusterV3{} - diags = tfschema.CopyKubernetesClusterV3FromTerraform(ctx, plan, kubeCluster) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - kubeClusterResource := kubeCluster - - - if err := kubeClusterResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating KubeCluster", err, "kube_cluster")) - return - } - name := kubeClusterResource.Metadata.Name - - kubeClusterBefore, err := r.p.Client().GetKubernetesCluster(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", err, "kube_cluster")) - return - } - - err = r.p.Client().UpdateKubernetesCluster(ctx, kubeClusterResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating KubeCluster", err, "kube_cluster")) - return - } - - // Not really an inferface, just using the same name for easier templating. - var kubeClusterI apitypes.KubeCluster - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - kubeClusterI, err = r.p.Client().GetKubernetesCluster(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", err, "kube_cluster")) - return - } - if kubeClusterBefore.GetMetadata().Revision != kubeClusterI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(ctx.Err()), "kube_cluster")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading KubeCluster (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "kube_cluster") - return - } - } - - kubeClusterResource, ok := kubeClusterI.(*apitypes.KubernetesClusterV3) - if !ok { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Errorf("Can not convert %T to KubernetesClusterV3", kubeClusterI), "kube_cluster")) - return - } - diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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 KubeCluster -func (r resourceTeleportKubeCluster) 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().DeleteKubernetesCluster(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting KubernetesClusterV3", trace.Wrap(err), "kube_cluster")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports KubeCluster state -func (r resourceTeleportKubeCluster) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - kubeCluster, err := r.p.Client().GetKubernetesCluster(ctx, req.ID) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster")) - return - } - - - kubeClusterResource := kubeCluster.(*apitypes.KubernetesClusterV3) - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeClusterResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := kubeClusterResource.GetName() - - 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/provider/internal/resources/access_list.go b/integrations/terraform/provider/internal/resources/access_list.go new file mode 100644 index 000000000000..89b5c07086cc --- /dev/null +++ b/integrations/terraform/provider/internal/resources/access_list.go @@ -0,0 +1,100 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "context" + + "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" + + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" + apitypes "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" + convertv1 "github.com/gravitational/teleport/api/types/accesslist/convert/v1" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1" +) + +// NewAccessListDataSourceType returns the app data source type. +func NewAccessListDataSourceType() tfdriver.DataSourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewAccessListClient(clientFromProvider(p)) + }, + Kind: apitypes.KindAccessList, + Codec: tfdriver.DataSourceCodecFuncs[accesslist.AccessList]{ + SchemaFunc: schemav1.GenSchemaAccessList, + ToStateFunc: func(ctx context.Context, al *accesslist.AccessList, o *types.Object) diag.Diagnostics { + return schemav1.CopyAccessListToTerraform(ctx, convertv1.ToProto(al), o) + }, + }, + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath( + path.Root("header").AtName("metadata").AtName("name"), + path.Root("scope"), + ), + } +} + +// NewAccessListResourceType returns the app resource type. +func NewAccessListResourceType() tfdriver.ResourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewAccessListClient(clientFromProvider(p)) + }, + Kind: apitypes.KindAccessList, + Codec: tfdriver.ResourceCodecFuncs[accesslist.AccessList]{ + SchemaFunc: schemav1.GenSchemaAccessList, + ToStateFunc: func(ctx context.Context, al *accesslist.AccessList, o *types.Object) diag.Diagnostics { + + return schemav1.CopyAccessListToTerraform(ctx, convertv1.ToProto(al), o) + }, + FromPlanFunc: func(ctx context.Context, o types.Object, al *accesslist.AccessList) diag.Diagnostics { + protoACL := new(accesslistv1.AccessList) + diags := schemav1.CopyAccessListFromTerraform(ctx, o, protoACL) + if diags.HasError() { + return diags + } + + converted, err := convertv1.FromProto(protoACL) + if err != nil { + diags.AddError("Error converting access list", err.Error()) + return diags + } + + *al = *converted + return diags + + }, + }, + Normalizer: tfdriver.CheckAndSetDefaults[accesslist.AccessList](), + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy( + path.Root("header").AtName("metadata").AtName("name"), + path.Root("scope"), + func(av *accesslist.AccessList) (string, string) { + return av.GetMetadata().Name, av.GetScope() + }, + ), + ResourceRevision: func(st *accesslist.AccessList) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/resources/access_list_member.go b/integrations/terraform/provider/internal/resources/access_list_member.go new file mode 100644 index 000000000000..ad32400120f3 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/access_list_member.go @@ -0,0 +1,99 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "context" + + "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" + + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" + apitypes "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" + convertv1 "github.com/gravitational/teleport/api/types/accesslist/convert/v1" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1" +) + +// NewAccessListMemberDataSourceType returns the app data source type. +func NewAccessListMemberDataSourceType() tfdriver.DataSourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] { + return tfdriver.DataSourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] { + return teleport.NewAccessListMemberClient(clientFromProvider(p)) + }, + Kind: apitypes.KindAccessListMember, + Codec: tfdriver.DataSourceCodecFuncs[accesslist.AccessListMember]{ + SchemaFunc: schemav1.GenSchemaMember, + ToStateFunc: func(ctx context.Context, alm *accesslist.AccessListMember, o *types.Object) diag.Diagnostics { + return schemav1.CopyMemberToTerraform(ctx, convertv1.ToMemberProto(alm), o) + }, + }, + Identifier: tfdriver.ScopeQualifiedCompositeIdentifierFromPath( + path.Root("spec").AtName("access_list"), + path.Root("header").AtName("metadata").AtName("name"), + ), + } +} + +// NewAccessListMemberResourceType returns the app resource type. +func NewAccessListMemberResourceType() tfdriver.ResourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] { + return tfdriver.ResourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] { + return teleport.NewAccessListMemberClient(clientFromProvider(p)) + }, + Kind: apitypes.KindAccessListMember, + Codec: tfdriver.ResourceCodecFuncs[accesslist.AccessListMember]{ + SchemaFunc: schemav1.GenSchemaMember, + ToStateFunc: func(ctx context.Context, alm *accesslist.AccessListMember, o *types.Object) diag.Diagnostics { + return schemav1.CopyMemberToTerraform(ctx, convertv1.ToMemberProto(alm), o) + + }, + FromPlanFunc: func(ctx context.Context, o types.Object, alm *accesslist.AccessListMember) diag.Diagnostics { + protoMember := new(accesslistv1.Member) + diags := schemav1.CopyMemberFromTerraform(ctx, o, protoMember) + if diags.HasError() { + return diags + } + + converted, err := convertv1.FromMemberProto(protoMember) + if err != nil { + diags.AddError("Error converting access list member", err.Error()) + return diags + } + + *alm = *converted + return diags + + }, + }, + Normalizer: tfdriver.CheckAndSetDefaults[accesslist.AccessListMember](), + Identifier: tfdriver.ScopeQualifiedCompositeIdentifierPolicy( + path.Root("spec").AtName("access_list"), + path.Root("header").AtName("metadata").AtName("name"), + func(av *accesslist.AccessListMember) (string, string) { + return av.Spec.AccessList, av.GetMetadata().Name + }), + ResourceRevision: func(st *accesslist.AccessListMember) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/resources/kubernetes.go b/integrations/terraform/provider/internal/resources/kubernetes.go new file mode 100644 index 000000000000..4daf0df00c41 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/kubernetes.go @@ -0,0 +1,71 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + apitypes "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + "github.com/gravitational/teleport/integrations/terraform/tfschema" +) + +// NewKubernetesClusterDataSourceType returns the app data source type. +func NewKubernetesClusterDataSourceType() tfdriver.DataSourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewKubernetesClient(clientFromProvider(p)) + }, + Kind: apitypes.KindKubernetesCluster, + Codec: tfdriver.DataSourceCodecFuncs[apitypes.KubernetesClusterV3]{ + SchemaFunc: tfschema.GenSchemaKubernetesClusterV3, + ToStateFunc: tfschema.CopyKubernetesClusterV3ToTerraform, + }, + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath( + path.Root("metadata").AtName("name"), + path.Root("scope"), + ), + } +} + +// NewKubernetesClusterResourceType returns the app resource type. +func NewKubernetesClusterResourceType() tfdriver.ResourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewKubernetesClient(clientFromProvider(p)) + }, + Kind: apitypes.KindKubernetesCluster, + Codec: tfdriver.ResourceCodecFuncs[apitypes.KubernetesClusterV3]{ + SchemaFunc: tfschema.GenSchemaKubernetesClusterV3, + ToStateFunc: tfschema.CopyKubernetesClusterV3ToTerraform, + FromPlanFunc: tfschema.CopyKubernetesClusterV3FromTerraform, + }, + Normalizer: tfdriver.CheckAndSetDefaults[apitypes.KubernetesClusterV3](), + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy( + path.Root("metadata").AtName("name"), + path.Root("scope"), + func(av *apitypes.KubernetesClusterV3) (string, string) { + return av.GetMetadata().Name, av.GetScope() + }), + ResourceRevision: func(st *apitypes.KubernetesClusterV3) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/resources/scoped_role.go b/integrations/terraform/provider/internal/resources/scoped_role.go new file mode 100644 index 000000000000..fedd7a0f4ff9 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/scoped_role.go @@ -0,0 +1,69 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/v1" +) + +// NewScopedRoleDataSourceType returns the scoped role data source type. +func NewScopedRoleDataSourceType() tfdriver.DataSourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewScopedRoleClient(clientFromProvider(p)) + }, + Identifier: tfdriver.ScopeQualifiedNameIdentifierFromPath(path.Root("metadata").AtName("name"), path.Root("scope")), + Kind: scopedaccess.KindScopedRole, + Codec: tfdriver.DataSourceCodecFuncs[accessv1.ScopedRole]{ + SchemaFunc: schemav1.GenSchemaScopedRole, + ToStateFunc: schemav1.CopyScopedRoleToTerraform, + }, + } +} + +// NewScopedRoleResourceType returns the scoped role resource type. +func NewScopedRoleResourceType() tfdriver.ResourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewScopedRoleClient(clientFromProvider(p)) + }, + Kind: scopedaccess.KindScopedRole, + Codec: tfdriver.ResourceCodecFuncs[accessv1.ScopedRole]{ + SchemaFunc: schemav1.GenSchemaScopedRole, + ToStateFunc: schemav1.CopyScopedRoleToTerraform, + FromPlanFunc: schemav1.CopyScopedRoleFromTerraform, + }, + Normalizer: tfdriver.ForceKind[accessv1.ScopedRole](scopedaccess.KindScopedRole), + Identifier: tfdriver.ScopeQualifiedNameIdentifierPolicy( + path.Root("metadata").AtName("name"), + path.Root("scope"), + func(st *accessv1.ScopedRole) (string, string) { + return st.GetMetadata().GetName(), st.GetScope() + }), + ResourceRevision: func(st *accessv1.ScopedRole) string { + return st.GetMetadata().GetRevision() + }, + } +} diff --git a/integrations/terraform/provider/internal/resources/scoped_role_assignment.go b/integrations/terraform/provider/internal/resources/scoped_role_assignment.go new file mode 100644 index 000000000000..27a085a9f67b --- /dev/null +++ b/integrations/terraform/provider/internal/resources/scoped_role_assignment.go @@ -0,0 +1,69 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1" +) + +// NewScopedRoleAssignmentDataSourceType returns the scoped token data source type. +func NewScopedRoleAssignmentDataSourceType() tfdriver.DataSourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewScopedRoleAssignmentClient(clientFromProvider(p)) + }, + Identifier: tfdriver.ScopeQualifiedNameIdentifierFromPath(path.Root("metadata").AtName("name"), path.Root("scope")), + Kind: scopedaccess.KindScopedRoleAssignment, + Codec: tfdriver.DataSourceCodecFuncs[accessv1.ScopedRoleAssignment]{ + SchemaFunc: schemav1.GenSchemaScopedRoleAssignment, + ToStateFunc: schemav1.CopyScopedRoleAssignmentToTerraform, + }, + } +} + +// NewScopedRoleAssignmentResourceType returns the scoped token resource type. +func NewScopedRoleAssignmentResourceType() tfdriver.ResourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewScopedRoleAssignmentClient(clientFromProvider(p)) + }, + Kind: scopedaccess.KindScopedRoleAssignment, + Codec: tfdriver.ResourceCodecFuncs[accessv1.ScopedRoleAssignment]{ + SchemaFunc: schemav1.GenSchemaScopedRoleAssignment, + ToStateFunc: schemav1.CopyScopedRoleAssignmentToTerraform, + FromPlanFunc: schemav1.CopyScopedRoleAssignmentFromTerraform, + }, + Normalizer: tfdriver.ForceKind[accessv1.ScopedRoleAssignment](scopedaccess.KindScopedRoleAssignment), + Identifier: tfdriver.ScopeQualifiedNameIdentifierPolicy( + path.Root("metadata").AtName("name"), + path.Root("scope"), + func(st *accessv1.ScopedRoleAssignment) (string, string) { + return st.GetMetadata().GetName(), st.GetScope() + }), + ResourceRevision: func(st *accessv1.ScopedRoleAssignment) string { + return st.GetMetadata().GetRevision() + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/access_list.go b/integrations/terraform/provider/internal/teleport/access_list.go new file mode 100644 index 000000000000..62d49f115ce4 --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/access_list.go @@ -0,0 +1,84 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" + "github.com/gravitational/teleport/api/types/accesslist" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewAccessListClient returns an access list client. +func NewAccessListClient(c *client.Client) AccessListClient { + return AccessListClient{client: c} +} + +// AccessListClient manages access list resources. +type AccessListClient struct { + client *client.Client +} + +// Get reads an access list by name. +func (r AccessListClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accesslist.AccessList, error) { + al, err := r.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{ + Name: id.Name, + Scope: id.Scope, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return al, nil +} + +// Create creates an access list. +func (r AccessListClient) Create(ctx context.Context, al *accesslist.AccessList) error { + if _, err := r.client.AccessListClient().UpsertAccessList(ctx, al); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// PrepareUpdate preserves server-computed fields that should not be reset on update. +func (r AccessListClient) PrepareUpdate(before, after *accesslist.AccessList) error { + after.Spec.Audit.NextAuditDate = before.Spec.Audit.NextAuditDate + return nil +} + +// Upsert updates an access list. +func (r AccessListClient) Upsert(ctx context.Context, al *accesslist.AccessList) error { + if _, err := r.client.AccessListClient().UpsertAccessList(ctx, al); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes an access list by name. +func (r AccessListClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + return trace.Wrap(r.client.AccessListClient().DeleteAccessListV2(ctx, &accesslistv1.DeleteAccessListRequest{ + Name: id.Name, + Scope: id.Scope, + })) +} diff --git a/integrations/terraform/provider/internal/teleport/access_list_member.go b/integrations/terraform/provider/internal/teleport/access_list_member.go new file mode 100644 index 000000000000..f8919faab37a --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/access_list_member.go @@ -0,0 +1,82 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" + "github.com/gravitational/teleport/api/types/accesslist" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewAccessListMemberClient returns an access list member client. +func NewAccessListMemberClient(c *client.Client) AccessListMemberClient { + return AccessListMemberClient{client: c} +} + +// AccessListMemberClient manages access list member resources. +type AccessListMemberClient struct { + client *client.Client +} + +// Get reads an access list member by name. +func (r AccessListMemberClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedCompositeIdentifier) (*accesslist.AccessListMember, error) { + member, err := r.client.AccessListClient().GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{ + AccessListScope: id.Prefix.Scope, + AccessList: id.Prefix.Name, + MemberScope: id.Name.Scope, + MemberName: id.Name.Name, + }.Build()) + if err != nil { + return nil, trace.Wrap(err) + } + + return member, nil +} + +// Create creates an access list member. +func (r AccessListMemberClient) Create(ctx context.Context, member *accesslist.AccessListMember) error { + if _, err := r.client.AccessListClient().UpsertStaticAccessListMember(ctx, member); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates an access list member. +func (r AccessListMemberClient) Upsert(ctx context.Context, member *accesslist.AccessListMember) error { + if _, err := r.client.AccessListClient().UpsertStaticAccessListMember(ctx, member); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes an access list member by name. +func (r AccessListMemberClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedCompositeIdentifier) error { + return trace.Wrap(r.client.AccessListClient().DeleteStaticAccessListMemberV2(ctx, accesslistv1.DeleteStaticAccessListMemberRequest_builder{ + AccessListScope: id.Prefix.Scope, + AccessList: id.Prefix.Name, + MemberScope: id.Name.Scope, + MemberName: id.Name.Name, + }.Build())) +} diff --git a/integrations/terraform/provider/internal/teleport/kubernetes.go b/integrations/terraform/provider/internal/teleport/kubernetes.go new file mode 100644 index 000000000000..028926d770dc --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/kubernetes.go @@ -0,0 +1,83 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewKubernetesClient returns a Teleport client to interact with Kubernetes Cluster resources. +func NewKubernetesClient(c *client.Client) KubernetesClient { + return KubernetesClient{client: c} +} + +// KubernetesClient manages Kubernetes Cluster resources in Teleport. +type KubernetesClient struct { + client *client.Client +} + +// Get reads a Kubernetes Cluster by name. +func (r KubernetesClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*types.KubernetesClusterV3, error) { + cluster, err := r.client.GetKubeCluster(ctx, presencev1.GetKubeClusterRequest_builder{ + Name: id.Name, + Scope: id.Scope, + }.Build()) + if err != nil { + return nil, trace.Wrap(err) + } + + clusterv3, ok := cluster.(*types.KubernetesClusterV3) + if !ok { + return nil, trace.BadParameter("unexpected Kubernetes cluster type: %T", cluster) + } + + return clusterv3, nil +} + +// Create creates a Kubernetes Cluster resource in Teleport. +func (r KubernetesClient) Create(ctx context.Context, cluster *types.KubernetesClusterV3) error { + if err := r.client.CreateKubernetesCluster(ctx, cluster); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates a Kubernetes Cluster resource in Teleport. +func (r KubernetesClient) Upsert(ctx context.Context, cluster *types.KubernetesClusterV3) error { + if err := r.client.UpdateKubernetesCluster(ctx, cluster); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes a Kubernetes Cluster resource from Teleport by name. +func (r KubernetesClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + return trace.Wrap(r.client.DeleteKubeCluster(ctx, presencev1.DeleteKubeClusterRequest_builder{ + Name: id.Name, + Scope: id.Scope, + }.Build())) +} diff --git a/integrations/terraform/provider/internal/teleport/scoped_role.go b/integrations/terraform/provider/internal/teleport/scoped_role.go new file mode 100644 index 000000000000..5e2b37f2aefe --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/scoped_role.go @@ -0,0 +1,84 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewScopedRoleClient returns a scoped role client. +func NewScopedRoleClient(c *client.Client) ScopedRoleClient { + return ScopedRoleClient{client: c} +} + +// ScopedRoleClient manages scoped role resources. +type ScopedRoleClient struct { + client *client.Client +} + +// Get reads a scoped role by name. +func (r ScopedRoleClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accessv1.ScopedRole, error) { + resp, err := r.client.ScopedAccessServiceClient().GetScopedRole(ctx, &accessv1.GetScopedRoleRequest{ + Name: id.Name, + Scope: id.Scope, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return resp.GetRole(), nil +} + +// Create creates a scoped role. +func (r ScopedRoleClient) Create(ctx context.Context, role *accessv1.ScopedRole) error { + _, err := r.client.ScopedAccessServiceClient().CreateScopedRole(ctx, &accessv1.CreateScopedRoleRequest{ + Role: role, + }) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates a scoped role. +func (r ScopedRoleClient) Upsert(ctx context.Context, role *accessv1.ScopedRole) error { + _, err := r.client.ScopedAccessServiceClient().UpsertScopedRole(ctx, &accessv1.UpsertScopedRoleRequest{ + Role: role, + }) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes a scoped role by name. +func (r ScopedRoleClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + _, err := r.client.ScopedAccessServiceClient().DeleteScopedRole(ctx, &accessv1.DeleteScopedRoleRequest{ + Name: id.Name, + Scope: id.Scope, + }) + return trace.Wrap(err) +} diff --git a/integrations/terraform/provider/internal/teleport/scoped_role_assignment.go b/integrations/terraform/provider/internal/teleport/scoped_role_assignment.go new file mode 100644 index 000000000000..51add71fead6 --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/scoped_role_assignment.go @@ -0,0 +1,88 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + "github.com/gravitational/teleport/lib/scopes/access" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewScopedRoleAssignmentClient returns a scoped role assignment client. +func NewScopedRoleAssignmentClient(c *client.Client) ScopedRoleAssignmentClient { + return ScopedRoleAssignmentClient{client: c} +} + +// ScopedRoleAssignmentClient manages scoped role assignment resources. +type ScopedRoleAssignmentClient struct { + client *client.Client +} + +// Get reads a scoped role assignment by name. +func (r ScopedRoleAssignmentClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accessv1.ScopedRoleAssignment, error) { + resp, err := r.client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id.Name, + Scope: id.Scope, + SubKind: access.SubKindDynamic, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return resp.GetAssignment(), nil +} + +// Create creates a scoped role assignment. +func (r ScopedRoleAssignmentClient) Create(ctx context.Context, sra *accessv1.ScopedRoleAssignment) error { + _, err := r.client.ScopedAccessServiceClient().CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{ + Assignment: sra, + }) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates a scoped role assignment. +func (r ScopedRoleAssignmentClient) Upsert(ctx context.Context, sra *accessv1.ScopedRoleAssignment) error { + _, err := r.client.ScopedAccessServiceClient().UpsertScopedRoleAssignment(ctx, &accessv1.UpsertScopedRoleAssignmentRequest{ + Assignment: sra, + }) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes a scoped role assignment by name. +func (r ScopedRoleAssignmentClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + _, err := r.client.ScopedAccessServiceClient().DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{ + Name: id.Name, + Scope: id.Scope, + SubKind: access.SubKindDynamic, + }) + + return trace.Wrap(err) +} diff --git a/integrations/terraform/provider/internal/tfdriver/identifier.go b/integrations/terraform/provider/internal/tfdriver/identifier.go index a8f29aa1ff55..1e4c9e2f65f3 100644 --- a/integrations/terraform/provider/internal/tfdriver/identifier.go +++ b/integrations/terraform/provider/internal/tfdriver/identifier.go @@ -84,6 +84,18 @@ func (n CompositeIdentifier) String() string { return n.Prefix + "/" + n.Name } +// ScopeQualifiedCompositeIdentifier identifies a resource by a possibly +// scope-qualified prefix and a possibly scope-qualified name. +type ScopeQualifiedCompositeIdentifier struct { + Prefix ScopeQualifiedNameIdentifier + Name ScopeQualifiedNameIdentifier +} + +// String returns the Terraform import ID. +func (n ScopeQualifiedCompositeIdentifier) String() string { + return n.Prefix.String() + "/" + n.Name.String() +} + // SingletonIdentifier identifies a resource with a fixed name. type SingletonIdentifier struct { Name string @@ -143,6 +155,16 @@ func NewScopedQualifiedNameIdentifier(s string) (ScopeQualifiedNameIdentifier, e return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, nil } +// NewPossiblyUnscopedScopeQualifiedNameIdentifier parses an identifier that may +// be either a scope-qualified name or an unscoped bare name. +func NewPossiblyUnscopedScopeQualifiedNameIdentifier(s string) (ScopeQualifiedNameIdentifier, error) { + if strings.Contains(s, scopes.QualifiedNameSeparator) { + return NewScopedQualifiedNameIdentifier(s) + } + + return ScopeQualifiedNameIdentifier{Name: s}, nil +} + // ScopeQualifiedNameIdentifierFromPath returns a scope qualified name extractor. func ScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedNameIdentifier] { return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedNameIdentifier, diag.Diagnostics) { @@ -168,6 +190,36 @@ func ScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) Terrafo } } +// PossiblyUnscopedScopeQualifiedNameIdentifierFromPath returns an extractor for +// identifiers that may be either scope-qualified or unscoped. +func PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedNameIdentifier] { + return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedNameIdentifier, diag.Diagnostics) { + var identifier types.String + diags := reader.GetAttribute(ctx, namePath, &identifier) + if diags.HasError() { + return ScopeQualifiedNameIdentifier{}, diags + } + + var scope types.String + diags.Append(reader.GetAttribute(ctx, scopePath, &scope)...) + if diags.HasError() { + return ScopeQualifiedNameIdentifier{}, diags + } + + if scope.Null || scope.Unknown || scope.Value == "" { + return ScopeQualifiedNameIdentifier{Name: identifier.Value}, diags + } + + sqn := scopes.QualifiedName{Name: identifier.Value, Scope: scope.Value} + if err := sqn.WeakValidate(); err != nil { + diags.Append(tfdiag.DiagFromErr("malformed scope qualified name", err)) + return ScopeQualifiedNameIdentifier{}, diags + } + + return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, diags + } +} + // ScopeQualifiedNameIdentifierPolicy returns a policy for scope qualified names. func ScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, resourceNameAndScope func(*T) (name, scope string)) IdentifierPolicy[T, ScopeQualifiedNameIdentifier] { return IdentifierPolicy[T, ScopeQualifiedNameIdentifier]{ @@ -180,6 +232,19 @@ func ScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, re } } +// PossiblyUnscopedScopeQualifiedNameIdentifierPolicy returns a policy for +// identifiers that may be either scope-qualified or unscoped. +func PossiblyUnscopedScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, resourceNameAndScope func(*T) (name, scope string)) IdentifierPolicy[T, ScopeQualifiedNameIdentifier] { + return IdentifierPolicy[T, ScopeQualifiedNameIdentifier]{ + FromState: PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(namePath, scopePath), + FromResource: func(resource *T) ScopeQualifiedNameIdentifier { + name, scope := resourceNameAndScope(resource) + return ScopeQualifiedNameIdentifier{Name: name, Scope: scope} + }, + FromImportID: NewPossiblyUnscopedScopeQualifiedNameIdentifier, + } +} + // NewCompositeIdentifier parses a composite identifier. func NewCompositeIdentifier(s string) (CompositeIdentifier, error) { split := strings.Split(s, "/") @@ -231,6 +296,130 @@ func CompositeIdentifierPolicy[T any](prefixPath, namePath path.Path, resourcePr } } +// NewScopeQualifiedCompositeIdentifier parses a scope-qualified composite +// identifier in "prefix/name" form, where either side may be a scope-qualified +// name. For example: "access-list/alice", "/scope::access-list/alice", or +// "/scope::access-list//scope::child-list". +func NewScopeQualifiedCompositeIdentifier(s string) (ScopeQualifiedCompositeIdentifier, error) { + prefix, name, err := splitScopeQualifiedCompositeIdentifier(s) + if err != nil { + return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err) + } + + return newScopeQualifiedCompositeIdentifier(prefix, name) +} + +func splitScopeQualifiedCompositeIdentifier(s string) (prefix, name string, err error) { + if strings.HasPrefix(s, "/") { + scope, rest, ok := strings.Cut(s, scopes.QualifiedNameSeparator) + if !ok { + return "", "", trace.BadParameter("expected scoped composite id %q prefix to contain %q", s, scopes.QualifiedNameSeparator) + } + + prefixName, name, ok := strings.Cut(rest, "/") + if !ok { + return "", "", trace.BadParameter("expected id %q to have a %q separator after prefix", s, "/") + } + return scope + scopes.QualifiedNameSeparator + prefixName, name, nil + } + + prefix, name, ok := strings.Cut(s, "/") + if !ok { + return "", "", trace.BadParameter("expected id %q to have a %q separator", s, "/") + } + + if strings.Contains(name, "/") && !strings.HasPrefix(name, "/") { + return "", "", trace.BadParameter("expected unscoped member name in id %q not to contain %q", s, "/") + } + + return prefix, name, nil +} + +func newScopeQualifiedCompositeIdentifier(prefix, name string) (ScopeQualifiedCompositeIdentifier, error) { + if prefix == "" { + return ScopeQualifiedCompositeIdentifier{}, trace.BadParameter("prefix must be non-empty") + } + if name == "" { + return ScopeQualifiedCompositeIdentifier{}, trace.BadParameter("name must be non-empty") + } + + prefixSQN, err := NewPossiblyUnscopedScopeQualifiedNameIdentifier(prefix) + if err != nil { + return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err) + } + + nameSQN, err := NewPossiblyUnscopedScopeQualifiedNameIdentifier(name) + if err != nil { + return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err) + } + + return ScopeQualifiedCompositeIdentifier{Prefix: prefixSQN, Name: nameSQN}, nil +} + +// ScopeQualifiedCompositeIdentifierFromPath returns an extractor for a +// scope-qualified composite identifier. +func ScopeQualifiedCompositeIdentifierFromPath(prefixPath, namePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedCompositeIdentifier] { + return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedCompositeIdentifier, diag.Diagnostics) { + var prefix types.String + diags := reader.GetAttribute(ctx, prefixPath, &prefix) + if diags.HasError() { + return ScopeQualifiedCompositeIdentifier{}, diags + } + + var name types.String + diags.Append(reader.GetAttribute(ctx, namePath, &name)...) + if diags.HasError() { + return ScopeQualifiedCompositeIdentifier{}, diags + } + + prefixSQN, err := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(prefix.Value) + if err != nil { + diags.Append(tfdiag.DiagFromErr("malformed scope qualified prefix", err)) + return ScopeQualifiedCompositeIdentifier{}, diags + } + + nameSQN, err := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(name.Value) + if err != nil { + diags.Append(tfdiag.DiagFromErr("malformed scope qualified name", err)) + return ScopeQualifiedCompositeIdentifier{}, diags + } + + return ScopeQualifiedCompositeIdentifier{Prefix: prefixSQN, Name: nameSQN}, diags + } +} + +func scopeQualifiedNameIdentifierFromPossiblyQualifiedString(s string) (ScopeQualifiedNameIdentifier, error) { + if !strings.Contains(s, scopes.QualifiedNameSeparator) { + return ScopeQualifiedNameIdentifier{Name: s}, nil + } + + sqn, err := scopes.ParseQualifiedName(s) + if err != nil { + return ScopeQualifiedNameIdentifier{}, trace.Wrap(err) + } + + if err := sqn.WeakValidate(); err != nil { + return ScopeQualifiedNameIdentifier{}, trace.Wrap(err) + } + + return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, nil +} + +// ScopeQualifiedCompositeIdentifierPolicy returns a policy for scope-qualified +// composite identifiers. +func ScopeQualifiedCompositeIdentifierPolicy[T any](prefixPath, namePath path.Path, resourcePrefixAndName func(*T) (prefix, name string)) IdentifierPolicy[T, ScopeQualifiedCompositeIdentifier] { + return IdentifierPolicy[T, ScopeQualifiedCompositeIdentifier]{ + FromState: ScopeQualifiedCompositeIdentifierFromPath(prefixPath, namePath), + FromResource: func(resource *T) ScopeQualifiedCompositeIdentifier { + prefix, name := resourcePrefixAndName(resource) + prefixID, _ := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(prefix) + nameID, _ := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(name) + return ScopeQualifiedCompositeIdentifier{Prefix: prefixID, Name: nameID} + }, + FromImportID: NewScopeQualifiedCompositeIdentifier, + } +} + // NewSingletonIdentifier returns a singleton identifier. func NewSingletonIdentifier(s string) (SingletonIdentifier, error) { return SingletonIdentifier{Name: s}, nil diff --git a/integrations/terraform/provider/internal/tfdriver/identifier_test.go b/integrations/terraform/provider/internal/tfdriver/identifier_test.go index e7d02e81b266..d5f31d82f6a6 100644 --- a/integrations/terraform/provider/internal/tfdriver/identifier_test.go +++ b/integrations/terraform/provider/internal/tfdriver/identifier_test.go @@ -191,6 +191,100 @@ func TestScopeQualifiedNameIdentifierFromPath(t *testing.T) { } } +func TestPossiblyUnscopedScopeQualifiedNameIdentifierFromPath(t *testing.T) { + cases := []struct { + name string + attributer attributeReaderFunc + expectedIdentifier tfdriver.ScopeQualifiedNameIdentifier + expectError bool + }{ + { + name: "unscoped empty scope", + attributer: func(p path.Path, v any) diag.Diagnostics { + s, ok := v.(*types.String) + if !ok { + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))} + } + + switch { + case p.Equal(path.Root("name")): + s.Value = "testing" + case p.Equal(path.Root("scope")): + s.Value = "" + } + + return nil + }, + expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"}, + }, + { + name: "unscoped null scope", + attributer: func(p path.Path, v any) diag.Diagnostics { + s, ok := v.(*types.String) + if !ok { + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))} + } + + switch { + case p.Equal(path.Root("name")): + s.Value = "testing" + case p.Equal(path.Root("scope")): + s.Null = true + } + + return nil + }, + expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"}, + }, + { + name: "scoped", + attributer: func(p path.Path, v any) diag.Diagnostics { + s, ok := v.(*types.String) + if !ok { + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))} + } + + switch { + case p.Equal(path.Root("name")): + s.Value = "testing" + case p.Equal(path.Root("scope")): + s.Value = "/foo/bar" + } + + return nil + }, + expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing", Scope: "/foo/bar"}, + }, + { + name: "invalid scoped", + attributer: func(p path.Path, v any) diag.Diagnostics { + s, ok := v.(*types.String) + if !ok { + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))} + } + + switch { + case p.Equal(path.Root("name")): + s.Value = "testing" + case p.Equal(path.Root("scope")): + s.Value = "/foo/bar&[]/" + } + + return nil + }, + expectError: true, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + identifier, d := tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(path.Root("name"), path.Root("scope"))(t.Context(), test.attributer) + assert.Equal(t, test.expectError, d.HasError()) + assert.Equal(t, test.expectedIdentifier, identifier) + }) + } +} + func TestNewScopeQualifiedNameIdentifier(t *testing.T) { cases := []struct { name string @@ -226,6 +320,73 @@ func TestNewScopeQualifiedNameIdentifier(t *testing.T) { } +func TestNewPossiblyUnscopedScopeQualifiedNameIdentifier(t *testing.T) { + cases := []struct { + name string + input string + errorAssertion require.ErrorAssertionFunc + expectedIdentifier tfdriver.ScopeQualifiedNameIdentifier + }{ + { + name: "unscoped", + input: "testing", + errorAssertion: require.NoError, + expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"}, + }, + { + name: "scoped", + input: "/animals/llama::testing", + errorAssertion: require.NoError, + expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing", Scope: "/animals/llama"}, + }, + { + name: "invalid scoped", + input: "/animals/llama::testing&[]/", + errorAssertion: require.Error, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + identifier, err := tfdriver.NewPossiblyUnscopedScopeQualifiedNameIdentifier(test.input) + test.errorAssertion(t, err) + require.Equal(t, test.expectedIdentifier, identifier) + }) + } +} + +func TestPossiblyUnscopedScopeQualifiedNameIdentifierPolicy(t *testing.T) { + type testResource struct { + Name string + Scope string + } + + policy := tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy( + path.Root("name"), + path.Root("scope"), + func(resource *testResource) (string, string) { + return resource.Name, resource.Scope + }, + ) + + require.Equal(t, + tfdriver.ScopeQualifiedNameIdentifier{Name: "unscoped"}, + policy.FromResource(&testResource{Name: "unscoped"}), + ) + require.Equal(t, + tfdriver.ScopeQualifiedNameIdentifier{Name: "scoped", Scope: "/foo/bar"}, + policy.FromResource(&testResource{Name: "scoped", Scope: "/foo/bar"}), + ) + + identifier, err := policy.FromImportID("unscoped") + require.NoError(t, err) + require.Equal(t, tfdriver.ScopeQualifiedNameIdentifier{Name: "unscoped"}, identifier) + + identifier, err = policy.FromImportID("/foo/bar::scoped") + require.NoError(t, err) + require.Equal(t, tfdriver.ScopeQualifiedNameIdentifier{Name: "scoped", Scope: "/foo/bar"}, identifier) +} + func TestCompositeIdentifierFromPath(t *testing.T) { cases := []struct { name string @@ -424,6 +585,64 @@ func TestCompositeIdentifierPolicy(t *testing.T) { require.Equal(t, tfdriver.CompositeIdentifier{Prefix: "import-prefix", Name: "import-name"}, fromImport) } +func TestScopeQualifiedCompositeIdentifierPolicy(t *testing.T) { + type resource struct { + prefix string + name string + } + policy := tfdriver.ScopeQualifiedCompositeIdentifierPolicy( + path.Root("prefix"), + path.Root("name"), + func(r *resource) (prefix, name string) { + return r.prefix, r.name + }, + ) + + fromState, diags := policy.FromState(t.Context(), attributeReaderFunc(func(p path.Path, v any) diag.Diagnostics { + s, ok := v.(*types.String) + if !ok { + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))} + } + switch { + case p.Equal(path.Root("prefix")): + s.Value = "/state/scope::state-prefix" + case p.Equal(path.Root("name")): + s.Value = "/state/scope::state-name" + default: + return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("unexpected path %s", p.String()))} + } + return nil + })) + require.False(t, diags.HasError(), diags) + require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{ + Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "state-prefix", Scope: "/state/scope"}, + Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "state-name", Scope: "/state/scope"}, + }, fromState) + require.Equal(t, "/state/scope::state-prefix//state/scope::state-name", fromState.String()) + + fromResource := policy.FromResource(&resource{prefix: "/resource/scope::resource-prefix", name: "resource-name"}) + require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{ + Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "resource-prefix", Scope: "/resource/scope"}, + Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "resource-name"}, + }, fromResource) + require.Equal(t, "/resource/scope::resource-prefix/resource-name", fromResource.String()) + + fromImport, err := policy.FromImportID("/import/scope::import-prefix//import/scope::import-name") + require.NoError(t, err) + require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{ + Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-prefix", Scope: "/import/scope"}, + Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-name", Scope: "/import/scope"}, + }, fromImport) + + legacyImport, err := policy.FromImportID("import-prefix/import-name") + require.NoError(t, err) + require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{ + Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-prefix"}, + Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-name"}, + }, legacyImport) + require.Equal(t, "import-prefix/import-name", legacyImport.String()) +} + func TestScopeQualifiedNameIdentifierPolicy(t *testing.T) { type resource struct { name string diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 2d2e26131cda..b6f87097ade5 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -564,8 +564,13 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp resourceTypes := legacy.ResourceTypes() genericResourceTypes := map[string]tfsdk.ResourceType{ - "teleport_app": resources.NewAppResourceType(), - "teleport_scoped_token": resources.NewScopedTokenResourceType(), + "teleport_access_list": resources.NewAccessListResourceType(), + "teleport_access_list_member": resources.NewAccessListMemberResourceType(), + "teleport_app": resources.NewAppResourceType(), + "teleport_kube_cluster": resources.NewKubernetesClusterResourceType(), + "teleport_scoped_role": resources.NewScopedRoleResourceType(), + "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentResourceType(), + "teleport_scoped_token": resources.NewScopedTokenResourceType(), } maps.Insert(resourceTypes, maps.All(genericResourceTypes)) @@ -578,8 +583,13 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc dataSourceTypes := legacy.DataSourceTypes() genericDataSourceTypes := map[string]tfsdk.DataSourceType{ - "teleport_app": resources.NewAppDataSourceType(), - "teleport_scoped_token": resources.NewScopedTokenDataSourceType(), + "teleport_access_list": resources.NewAccessListDataSourceType(), + "teleport_access_list_member": resources.NewAccessListMemberDataSourceType(), + "teleport_app": resources.NewAppDataSourceType(), + "teleport_kube_cluster": resources.NewKubernetesClusterDataSourceType(), + "teleport_scoped_role": resources.NewScopedRoleDataSourceType(), + "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentDataSourceType(), + "teleport_scoped_token": resources.NewScopedTokenDataSourceType(), } maps.Insert(dataSourceTypes, maps.All(genericDataSourceTypes)) diff --git a/integrations/terraform/testlib/access_list_member_test.go b/integrations/terraform/testlib/access_list_member_test.go new file mode 100644 index 000000000000..2d428991b300 --- /dev/null +++ b/integrations/terraform/testlib/access_list_member_test.go @@ -0,0 +1,221 @@ +// Teleport +// Copyright (C) 2026 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 testlib + +import ( + "context" + "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" + + "github.com/gravitational/teleport/api/client/accesslist" + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" + "github.com/gravitational/teleport/lib/scopes" +) + +func checkAccessListMemberExists(ctx context.Context, clt *accesslist.Client, accessList, memberName string, expires time.Time) resource.TestCheckFunc { + return checkAccessListMemberExistsWithScopes(ctx, clt, "", accessList, "", memberName, "", accessList, expires) +} + +func checkAccessListMemberExistsWithScopes(ctx context.Context, clt *accesslist.Client, accessListScope, accessListName, memberScope, memberName, expectedScope, expectedAccessList string, expires time.Time) resource.TestCheckFunc { + return func(state *terraform.State) error { + member, err := clt.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{ + AccessListScope: accessListScope, + AccessList: accessListName, + MemberScope: memberScope, + MemberName: memberName, + }.Build()) + if err != nil { + return trace.Wrap(err) + } + if member.GetName() != memberName { + return trace.CompareFailed("member name mismatch: got %q, want %q", member.GetName(), memberName) + } + if member.Spec.Name != memberName { + return trace.CompareFailed("member spec name mismatch: got %q, want %q", member.Spec.Name, memberName) + } + if member.Scope != expectedScope { + return trace.CompareFailed("member scope mismatch: got %q, want %q", member.Scope, expectedScope) + } + if member.Spec.AccessList != expectedAccessList { + return trace.CompareFailed("member access list mismatch: got %q, want %q", member.Spec.AccessList, expectedAccessList) + } + if !member.Spec.Expires.Equal(expires) { + return trace.CompareFailed("member expiry mismatch: got %q, want %q", member.Spec.Expires, expires) + } + return nil + } +} + +func checkScopedListAccessListMemberExists(ctx context.Context, clt *accesslist.Client, scope, accessListName, memberName string, expires time.Time) resource.TestCheckFunc { + return func(state *terraform.State) error { + member, err := clt.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{ + AccessListScope: scope, + AccessList: accessListName, + MemberScope: scope, + MemberName: memberName, + }.Build()) + if err != nil { + return trace.Wrap(err) + } + + accessListSQN := scopes.QualifiedName{Scope: scope, Name: accessListName}.String() + memberSQN := scopes.QualifiedName{Scope: scope, Name: memberName}.String() + if member.GetName() != memberSQN { + return trace.CompareFailed("member name mismatch: got %q, want %q", member.GetName(), memberSQN) + } + if member.Spec.Name != memberSQN { + return trace.CompareFailed("member spec name mismatch: got %q, want %q", member.Spec.Name, memberSQN) + } + if member.Scope != scope { + return trace.CompareFailed("member scope mismatch: got %q, want %q", member.Scope, scope) + } + if member.Spec.AccessList != accessListSQN { + return trace.CompareFailed("member access list mismatch: got %q, want %q", member.Spec.AccessList, accessListSQN) + } + if member.Spec.MembershipKind != "MEMBERSHIP_KIND_SCOPED_LIST" { + return trace.CompareFailed("member kind mismatch: got %q, want %q", member.Spec.MembershipKind, "MEMBERSHIP_KIND_SCOPED_LIST") + } + if !member.Spec.Expires.Equal(expires) { + return trace.CompareFailed("member expiry mismatch: got %q, want %q", member.Spec.Expires, expires) + } + return nil + } +} + +func (s *TerraformSuiteEnterprise) TestAccessListMember() { + require.True(s.T(), + s.teleportFeatures.GetAdvancedAccessWorkflows(), + "Test requires Advanced Access Workflows", + ) + + ctx := s.T().Context() + accessListName := "test-member-list" + memberName := "fighter" + resourceName := "teleport_access_list_member.fighter" + + checkDestroyed := func(state *terraform.State) error { + _, err := s.client.AccessListClient().GetStaticAccessListMember(ctx, accessListName, memberName) + if trace.IsNotFound(err) { + return nil + } + return trace.Wrap(err) + } + + createdExpires := time.Date(2038, 1, 1, 0, 0, 0, 0, time.UTC) + updatedExpires := time.Date(2038, 2, 1, 0, 0, 0, 0, time.UTC) + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("access_list_member_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "header.metadata.name", memberName), + resource.TestCheckResourceAttr(resourceName, "spec.access_list", accessListName), + resource.TestCheckResourceAttr(resourceName, "spec.membership_kind", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.expires", createdExpires.Format(time.RFC3339)), + checkAccessListMemberExists(ctx, s.client.AccessListClient(), accessListName, memberName, createdExpires), + ), + }, + { + Config: s.getFixture("access_list_member_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("access_list_member_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "header.metadata.labels.class", "veteran"), + resource.TestCheckResourceAttr(resourceName, "spec.expires", updatedExpires.Format(time.RFC3339)), + checkAccessListMemberExists(ctx, s.client.AccessListClient(), accessListName, memberName, updatedExpires), + ), + }, + { + Config: s.getFixture("access_list_member_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuiteEnterpriseScopedResources) TestAccessListMemberScopedAndUnscoped() { + require.True(s.T(), + s.teleportFeatures.GetAdvancedAccessWorkflows(), + "Test requires Advanced Access Workflows", + ) + + ctx := s.T().Context() + accessListClient := s.client.AccessListClient() + + checkDestroyed := func(state *terraform.State) error { + for _, tc := range []struct { + accessListScope string + accessListName string + memberScope string + memberName string + }{ + {accessListName: "test-unscoped-member-list", memberName: "unscoped-fighter"}, + {accessListScope: "/foo/bar", accessListName: "test-scoped-member-list", memberName: "scoped-fighter"}, + {accessListScope: "/foo/bar", accessListName: "test-scoped-member-list", memberScope: "/foo/bar", memberName: "test-scoped-child-member-list"}, + } { + _, err := accessListClient.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{ + AccessListScope: tc.accessListScope, + AccessList: tc.accessListName, + MemberScope: tc.memberScope, + MemberName: tc.memberName, + }.Build()) + if err != nil && !trace.IsNotFound(err) { + return trace.Wrap(err) + } + } + return nil + } + + expires := time.Date(2038, 3, 1, 0, 0, 0, 0, time.UTC) + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("access_list_member_scoped_and_unscoped.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("teleport_access_list_member.unscoped", "header.metadata.name", "unscoped-fighter"), + resource.TestCheckResourceAttr("teleport_access_list_member.unscoped", "spec.access_list", "test-unscoped-member-list"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "header.metadata.name", "scoped-fighter"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "spec.access_list", "/foo/bar::test-scoped-member-list"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "scope", "/foo/bar"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "header.metadata.name", "/foo/bar::test-scoped-child-member-list"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "spec.access_list", "/foo/bar::test-scoped-member-list"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "spec.membership_kind", "3"), + resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "scope", "/foo/bar"), + checkAccessListMemberExistsWithScopes(ctx, accessListClient, "", "test-unscoped-member-list", "", "unscoped-fighter", "", "test-unscoped-member-list", expires), + checkAccessListMemberExistsWithScopes(ctx, accessListClient, "/foo/bar", "test-scoped-member-list", "", "scoped-fighter", "/foo/bar", "/foo/bar::test-scoped-member-list", expires), + checkScopedListAccessListMemberExists(ctx, accessListClient, "/foo/bar", "test-scoped-member-list", "test-scoped-child-member-list", expires), + ), + }, + { + Config: s.getFixture("access_list_member_scoped_and_unscoped.tf"), + PlanOnly: true, + }, + }, + }) +} diff --git a/integrations/terraform/testlib/access_list_test.go b/integrations/terraform/testlib/access_list_test.go index 859350d5e924..3326205d98a7 100644 --- a/integrations/terraform/testlib/access_list_test.go +++ b/integrations/terraform/testlib/access_list_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/accesslist" + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" ) type nextAuditDateComparer struct { @@ -35,20 +37,29 @@ type nextAuditDateComparer struct { nextAuditDate time.Time } -func (c *nextAuditDateComparer) CaptureNextAuditDate(name string) resource.TestCheckFunc { +func (c *nextAuditDateComparer) CaptureNextAuditDate(ctx context.Context, scope, name string) resource.TestCheckFunc { return func(state *terraform.State) error { - al, err := c.client.AccessListClient().GetAccessList(context.TODO(), name) + al, err := c.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{ + Name: name, + Scope: scope, + }) if err != nil { return trace.Wrap(err) } + if al.Spec.Audit.NextAuditDate.IsZero() { + return trace.BadParameter("NextAuditDate must not be zero") + } c.nextAuditDate = al.Spec.Audit.NextAuditDate return nil } } -func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(name string) resource.TestCheckFunc { +func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(ctx context.Context, scope, name string) resource.TestCheckFunc { return func(state *terraform.State) error { - al, err := c.client.AccessListClient().GetAccessList(context.TODO(), name) + al, err := c.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{ + Name: name, + Scope: scope, + }) if err != nil { return trace.Wrap(err) } @@ -60,6 +71,22 @@ func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(name string) resource } } +func checkAccessListExists(ctx context.Context, clt *accesslist.Client, scope, name string) resource.TestCheckFunc { + return func(state *terraform.State) error { + al, err := clt.GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{ + Name: name, + Scope: scope, + }) + if err != nil { + return trace.Wrap(err) + } + if al.GetScope() != scope { + return trace.CompareFailed("access list scope mismatch: got %q, want %q", al.GetScope(), scope) + } + return nil + } +} + func (s *TerraformSuiteEnterprise) TestAccessList() { require.True(s.T(), s.teleportFeatures.GetAdvancedAccessWorkflows(), @@ -67,7 +94,9 @@ func (s *TerraformSuiteEnterprise) TestAccessList() { ) checkAccessListDestroyed := func(state *terraform.State) error { - _, err := s.client.AccessListClient().GetAccessList(context.TODO(), "test") + _, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{ + Name: "test", + }) if trace.IsNotFound(err) { return nil } @@ -91,7 +120,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() { resource.TestCheckResourceAttr(name, "spec.membership_requires.roles.0", "minion"), resource.TestCheckResourceAttr(name, "spec.grants.roles.0", "crane-operator"), resource.TestCheckResourceAttr(name, "spec.audit.recurrence.frequency", "3"), - auditDateChecker.CaptureNextAuditDate("test"), + auditDateChecker.CaptureNextAuditDate(s.T().Context(), "", "test"), ), }, { @@ -104,7 +133,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() { resource.TestCheckResourceAttr(name, "spec.grants.traits.0.key", "allowed-machines"), resource.TestCheckResourceAttr(name, "spec.grants.traits.0.values.0", "crane"), resource.TestCheckResourceAttr(name, "spec.grants.traits.0.values.1", "forklift"), - auditDateChecker.TestNextAuditDateUnchanged("test"), + auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test"), ), }, { @@ -115,7 +144,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() { Config: s.getFixture("access_list_2_expiring.tf"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(name, "header.metadata.expires", "2038-01-01T00:00:00Z"), - auditDateChecker.TestNextAuditDateUnchanged("test"), + auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test"), ), }, { @@ -125,3 +154,94 @@ func (s *TerraformSuiteEnterprise) TestAccessList() { }, }) } + +func (s *TerraformSuiteEnterprise) TestAccessListPreservesNextAuditDate() { + require.True(s.T(), + s.teleportFeatures.GetAdvancedAccessWorkflows(), + "Test requires Advanced Access Workflows", + ) + + checkAccessListDestroyed := func(state *terraform.State) error { + _, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{ + Name: "test-next-audit-date", + }) + if trace.IsNotFound(err) { + return nil + } + + return err + } + + name := "teleport_access_list.test" + auditDateChecker := nextAuditDateComparer{client: s.client} + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkAccessListDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("access_list_next_audit_date_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "header.metadata.name", "test-next-audit-date"), + resource.TestCheckResourceAttr(name, "spec.description", "next audit date test"), + auditDateChecker.CaptureNextAuditDate(s.T().Context(), "", "test-next-audit-date"), + ), + }, + { + Config: s.getFixture("access_list_next_audit_date_1_attempt_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "spec.description", "updated next audit date test"), + auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test-next-audit-date"), + ), + }, + }, + }) +} + +func (s *TerraformSuiteEnterpriseScopedResources) TestAccessListScopedAndUnscoped() { + require.True(s.T(), + s.teleportFeatures.GetAdvancedAccessWorkflows(), + "Test requires Advanced Access Workflows", + ) + + checkDestroyed := func(state *terraform.State) error { + for _, tc := range []struct { + scope string + name string + }{ + {name: "test-unscoped"}, + {scope: "/foo/bar", name: "test-scoped"}, + } { + _, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{ + Name: tc.name, + Scope: tc.scope, + }) + if err != nil && !trace.IsNotFound(err) { + return err + } + } + return nil + } + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("access_list_scoped_and_unscoped.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("teleport_access_list.unscoped", "header.metadata.name", "test-unscoped"), + resource.TestCheckResourceAttr("teleport_access_list.unscoped", "spec.membership_requires.roles.0", "minion"), + resource.TestCheckResourceAttr("teleport_access_list.scoped", "header.metadata.name", "test-scoped"), + resource.TestCheckResourceAttr("teleport_access_list.scoped", "scope", "/foo/bar"), + checkAccessListExists(s.T().Context(), s.client.AccessListClient(), "", "test-unscoped"), + checkAccessListExists(s.T().Context(), s.client.AccessListClient(), "/foo/bar", "test-scoped"), + ), + }, + { + Config: s.getFixture("access_list_scoped_and_unscoped.tf"), + PlanOnly: true, + }, + }, + }) +} diff --git a/integrations/terraform/testlib/fixtures/access_list_member_0_create.tf b/integrations/terraform/testlib/fixtures/access_list_member_0_create.tf new file mode 100644 index 000000000000..6db6cbf86b9b --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_member_0_create.tf @@ -0,0 +1,39 @@ +resource "teleport_access_list" "member_test" { + header = { + version = "v1" + metadata = { + name = "test-member-list" + } + } + spec = { + type = "static" + title = "Member test" + description = "Access list member Terraform test" + owners = [ + { + name = "gru" + } + ] + grants = { + roles = ["crane-operator"] + } + } +} + +resource "teleport_access_list_member" "fighter" { + header = { + version = "v1" + metadata = { + name = "fighter" + labels = { + class = "fighter" + } + } + } + spec = { + access_list = teleport_access_list.member_test.id + membership_kind = 1 + reason = "joined the party" + expires = "2038-01-01T00:00:00Z" + } +} diff --git a/integrations/terraform/testlib/fixtures/access_list_member_1_update.tf b/integrations/terraform/testlib/fixtures/access_list_member_1_update.tf new file mode 100644 index 000000000000..33a1aefb9e60 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_member_1_update.tf @@ -0,0 +1,39 @@ +resource "teleport_access_list" "member_test" { + header = { + version = "v1" + metadata = { + name = "test-member-list" + } + } + spec = { + type = "static" + title = "Member test" + description = "Access list member Terraform test" + owners = [ + { + name = "gru" + } + ] + grants = { + roles = ["crane-operator"] + } + } +} + +resource "teleport_access_list_member" "fighter" { + header = { + version = "v1" + metadata = { + name = "fighter" + labels = { + class = "veteran" + } + } + } + spec = { + access_list = teleport_access_list.member_test.id + membership_kind = 1 + reason = "joined the party" + expires = "2038-02-01T00:00:00Z" + } +} diff --git a/integrations/terraform/testlib/fixtures/access_list_member_scoped_and_unscoped.tf b/integrations/terraform/testlib/fixtures/access_list_member_scoped_and_unscoped.tf new file mode 100644 index 000000000000..4a8d17a8c51c --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_member_scoped_and_unscoped.tf @@ -0,0 +1,118 @@ +resource "teleport_access_list" "unscoped" { + header = { + version = "v1" + metadata = { + name = "test-unscoped-member-list" + } + } + spec = { + type = "static" + title = "Unscoped member list" + description = "unscoped access list member test" + owners = [ + { + name = "gru" + } + ] + grants = { + roles = ["crane-operator"] + } + } +} + +resource "teleport_access_list" "scoped" { + depends_on = [teleport_access_list.unscoped] + + header = { + version = "v1" + metadata = { + name = "test-scoped-member-list" + } + } + scope = "/foo/bar" + spec = { + type = "static" + title = "Scoped member list" + description = "scoped access list member test" + owners = [ + { + name = "gru" + } + ] + } +} + +resource "teleport_access_list" "scoped_child" { + depends_on = [teleport_access_list.scoped] + + header = { + version = "v1" + metadata = { + name = "test-scoped-child-member-list" + } + } + scope = "/foo/bar" + spec = { + type = "static" + title = "Scoped child member list" + description = "scoped child access list member test" + owners = [ + { + name = "gru" + } + ] + } +} + +resource "teleport_access_list_member" "unscoped" { + depends_on = [teleport_access_list.scoped_child] + + header = { + version = "v1" + metadata = { + name = "unscoped-fighter" + } + } + spec = { + access_list = teleport_access_list.unscoped.id + membership_kind = 1 + reason = "unscoped member" + expires = "2038-03-01T00:00:00Z" + } +} + +resource "teleport_access_list_member" "scoped" { + depends_on = [teleport_access_list_member.unscoped] + + header = { + version = "v1" + metadata = { + name = "scoped-fighter" + } + } + scope = "/foo/bar" + spec = { + access_list = teleport_access_list.scoped.id + membership_kind = 1 + reason = "scoped member" + expires = "2038-03-01T00:00:00Z" + } +} + +resource "teleport_access_list_member" "scoped_list" { + depends_on = [teleport_access_list_member.scoped] + + header = { + version = "v1" + metadata = { + name = teleport_access_list.scoped_child.id + } + } + scope = "/foo/bar" + spec = { + access_list = teleport_access_list.scoped.id + membership_kind = 3 + reason = "scoped list member" + expires = "2038-03-01T00:00:00Z" + } +} diff --git a/integrations/terraform/testlib/fixtures/access_list_next_audit_date_0_create.tf b/integrations/terraform/testlib/fixtures/access_list_next_audit_date_0_create.tf new file mode 100644 index 000000000000..94bd53ca92ed --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_next_audit_date_0_create.tf @@ -0,0 +1,35 @@ +resource "teleport_access_list" "test" { + header = { + version = "v1" + metadata = { + name = "test-next-audit-date" + labels = { + example = "yes" + } + } + } + spec = { + description = "next audit date test" + owners = [ + { + name = "gru" + description = "The supervillain." + } + ] + membership_requires = { + roles = ["minion"] + } + ownership_requires = { + roles = ["supervillain"] + } + grants = { + roles = ["crane-operator"] + } + title = "Next Audit Date" + audit = { + recurrence = { + frequency = 3 + } + } + } +} diff --git a/integrations/terraform/testlib/fixtures/access_list_next_audit_date_1_attempt_update.tf b/integrations/terraform/testlib/fixtures/access_list_next_audit_date_1_attempt_update.tf new file mode 100644 index 000000000000..2389567d1ac6 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_next_audit_date_1_attempt_update.tf @@ -0,0 +1,38 @@ +resource "teleport_access_list" "test" { + header = { + version = "v1" + metadata = { + name = "test-next-audit-date" + labels = { + example = "yes" + } + } + } + spec = { + description = "updated next audit date test" + owners = [ + { + name = "gru" + description = "The supervillain." + } + ] + membership_requires = { + roles = ["minion"] + } + ownership_requires = { + roles = ["supervillain"] + } + grants = { + roles = ["crane-operator"] + } + title = "Next Audit Date" + audit = { + // Terraform should not clear or recompute the server-maintained next audit date during update. + next_audit_date = null + recurrence = { + frequency = 3 + day_of_month = 15 + } + } + } +} diff --git a/integrations/terraform/testlib/fixtures/access_list_scoped_and_unscoped.tf b/integrations/terraform/testlib/fixtures/access_list_scoped_and_unscoped.tf new file mode 100644 index 000000000000..f6cbf5ab3d8b --- /dev/null +++ b/integrations/terraform/testlib/fixtures/access_list_scoped_and_unscoped.tf @@ -0,0 +1,59 @@ +resource "teleport_access_list" "unscoped" { + header = { + version = "v1" + metadata = { + name = "test-unscoped" + } + } + spec = { + description = "unscoped access list" + owners = [ + { + name = "gru" + description = "The supervillain." + } + ] + membership_requires = { + roles = ["minion"] + } + ownership_requires = { + roles = ["supervillain"] + } + grants = { + roles = ["crane-operator"] + } + title = "Unscoped" + audit = { + recurrence = { + frequency = 3 + } + } + } +} + +resource "teleport_access_list" "scoped" { + depends_on = [teleport_access_list.unscoped] + + header = { + version = "v1" + metadata = { + name = "test-scoped" + } + } + scope = "/foo/bar" + spec = { + description = "scoped access list" + owners = [ + { + name = "gru" + description = "The supervillain." + } + ] + title = "Scoped" + audit = { + recurrence = { + frequency = 3 + } + } + } +} diff --git a/integrations/terraform/testlib/fixtures/classifier_data_source.tf b/integrations/terraform/testlib/fixtures/classifier_data_source.tf index e25931ac4b3d..be1a81d4f352 100644 --- a/integrations/terraform/testlib/fixtures/classifier_data_source.tf +++ b/integrations/terraform/testlib/fixtures/classifier_data_source.tf @@ -1,4 +1,5 @@ data "teleport_classifier" "test" { + kind = "classifier" version = "v1" metadata = { name = "test-classifier" diff --git a/integrations/terraform/testlib/main_test.go b/integrations/terraform/testlib/main_test.go index 8ae9fb8e572b..b8844e843847 100644 --- a/integrations/terraform/testlib/main_test.go +++ b/integrations/terraform/testlib/main_test.go @@ -78,6 +78,9 @@ type TerraformSuiteOSSScopedResources struct { type TerraformSuiteEnterprise struct { TerraformBaseSuite } +type TerraformSuiteEnterpriseScopedResources struct { + TerraformBaseSuite +} type TerraformSuiteEnterpriseWithCache struct { TerraformBaseSuite } diff --git a/integrations/terraform/testlib/terraform_enterprise_test.go b/integrations/terraform/testlib/terraform_enterprise_test.go index 52aa2f27c61e..2143ee92ec21 100644 --- a/integrations/terraform/testlib/terraform_enterprise_test.go +++ b/integrations/terraform/testlib/terraform_enterprise_test.go @@ -29,16 +29,19 @@ import ( "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/modules/modulestest" "github.com/gravitational/teleport/lib/plugin" + "github.com/gravitational/teleport/lib/scopes" ) var testModules = &modulestest.Modules{ TestFeatures: modules.Features{ AdvancedAccessWorkflows: true, Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.OIDC: {Enabled: true}, - entitlements.SAML: {Enabled: true}, - entitlements.DeviceTrust: {Enabled: true}, - entitlements.Policy: {Enabled: true}, + entitlements.OIDC: {Enabled: true}, + entitlements.SAML: {Enabled: true}, + entitlements.DeviceTrust: {Enabled: true}, + entitlements.Policy: {Enabled: true}, + entitlements.SessionSummaries: {Enabled: true}, + entitlements.AccessLists: {Enabled: true}, }, }, } @@ -82,6 +85,28 @@ func TestTerraformEnterprise(t *testing.T) { }) } +func TestTerraformEnterpriseScopedResources(t *testing.T) { + authPlugin, err := NewPlugin(testModules) + if trace.IsNotImplemented(err) { + t.Skip(entTestSkipMessage) + } + require.NoError(t, err) + + registry := plugin.NewRegistry() + require.NoError(t, registry.Add(authPlugin)) + + suite.Run(t, &TerraformSuiteEnterpriseScopedResources{ + TerraformBaseSuite: TerraformBaseSuite{ + AuthHelper: &integration.MinimalAuthHelper{ + PluginRegistry: registry, + AuthConfig: authtest.AuthServerConfig{ + Modules: testModules, + ScopesFeatures: scopes.Features{Enabled: true}, + }, + }, + }, + }) +} func TestTerraformEnterpriseCloud(t *testing.T) { authPlugin, err := NewPlugin(testModulesCloud) if trace.IsNotImplemented(err) { diff --git a/integrations/terraform/tfschema/accesslist/v1/accesslist_terraform.go b/integrations/terraform/tfschema/accesslist/v1/accesslist_terraform.go index 1c526bb7cbf0..a5959886528e 100644 --- a/integrations/terraform/tfschema/accesslist/v1/accesslist_terraform.go +++ b/integrations/terraform/tfschema/accesslist/v1/accesslist_terraform.go @@ -88,13 +88,6 @@ func GenSchemaAccessList(ctx context.Context) (github_com_hashicorp_terraform_pl 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": { - Computed: true, - 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, - 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, - }, }), Description: "metadata is resource metadata.", Optional: true, @@ -121,9 +114,10 @@ func GenSchemaAccessList(ctx context.Context) (github_com_hashicorp_terraform_pl Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, "scope": { - Description: "scope is the scope of the Access List.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Description: "scope is the scope of the Access List.", + 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, }, "spec": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ @@ -390,13 +384,6 @@ func GenSchemaMember(ctx context.Context) (github_com_hashicorp_terraform_plugin 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": { - Computed: true, - 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, - 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, - }, }), Description: "metadata is resource metadata.", Optional: true, @@ -423,9 +410,10 @@ func GenSchemaMember(ctx context.Context) (github_com_hashicorp_terraform_plugin Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, "scope": { - Description: "scope is the scope of the Access List member, it must be equal to the scope of the parent Access List.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Description: "scope is the scope of the Access List member, it must be equal to the scope of the parent Access List.", + 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, }, "spec": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ @@ -644,23 +632,6 @@ func CopyAccessListFromTerraform(_ context.Context, tf github_com_hashicorp_terr } CopyFromTimestamp(diags, a, &obj.Expires) } - { - a, ok := tf.Attrs["revision"] - if !ok { - diags.Append(attrReadMissingDiag{"AccessList.header.metadata.revision"}) - } else { - v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrReadConversionFailureDiag{"AccessList.header.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 - } - } - } } } } @@ -1789,28 +1760,6 @@ func CopyAccessListToTerraform(ctx context.Context, obj *github_com_gravitationa tf.Attrs["expires"] = v } } - { - t, ok := tf.AttrTypes["revision"] - if !ok { - diags.Append(attrWriteMissingDiag{"AccessList.header.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{"AccessList.header.metadata.revision", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"AccessList.header.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 @@ -3471,23 +3420,6 @@ func CopyMemberFromTerraform(_ context.Context, tf github_com_hashicorp_terrafor } CopyFromTimestamp(diags, a, &obj.Expires) } - { - a, ok := tf.Attrs["revision"] - if !ok { - diags.Append(attrReadMissingDiag{"Member.header.metadata.revision"}) - } else { - v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrReadConversionFailureDiag{"Member.header.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 - } - } - } } } } @@ -3884,28 +3816,6 @@ func CopyMemberToTerraform(ctx context.Context, obj *github_com_gravitational_te tf.Attrs["expires"] = v } } - { - t, ok := tf.AttrTypes["revision"] - if !ok { - diags.Append(attrWriteMissingDiag{"Member.header.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{"Member.header.metadata.revision", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"Member.header.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 diff --git a/integrations/terraform/tfschema/types_terraform.go b/integrations/terraform/tfschema/types_terraform.go index 24ef24b543d8..f9ee7e387eff 100644 --- a/integrations/terraform/tfschema/types_terraform.go +++ b/integrations/terraform/tfschema/types_terraform.go @@ -911,9 +911,10 @@ func GenSchemaAppV3(ctx context.Context) (github_com_hashicorp_terraform_plugin_ Optional: true, }, "scope": { - Description: "The App's scope.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Description: "The App's scope.", + 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, }, "spec": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ @@ -4467,9 +4468,10 @@ func GenSchemaKubernetesClusterV3(ctx context.Context) (github_com_hashicorp_ter Optional: true, }, "scope": { - Description: "The scope of the kube cluster.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Description: "The scope of the kube cluster.", + 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, }, "spec": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ diff --git a/lib/auth/authtest/authtest.go b/lib/auth/authtest/authtest.go index 251bac7e8fd0..19a1b8ca631c 100644 --- a/lib/auth/authtest/authtest.go +++ b/lib/auth/authtest/authtest.go @@ -349,8 +349,10 @@ func NewAuthServer(cfg AuthServerConfig) (*AuthServer, error) { } accessLists, err := local.NewAccessListServiceV2(local.AccessListServiceConfig{ - Backend: srv.Backend, - Modules: cfg.Modules, + Backend: srv.Backend, + Modules: cfg.Modules, + ScopesFeatures: cfg.ScopesFeatures, + RunWhileLockedRetryInterval: cfg.RunWhileLockedRetryInterval, }) if err != nil { return nil, trace.Wrap(err) From 7589e7989bd5983f8812e7a20c03505bfb989042 Mon Sep 17 00:00:00 2001 From: Nibras Ohin Date: Mon, 27 Jul 2026 16:21:47 -0400 Subject: [PATCH 015/347] feat: make beam join tokens in web-ui non-editable (#67938) * feat: added filtering to the get tokens api to filter out join tokens marked as system resources * feat: make beam join tokens non-editable in web-ui * chore: added tests * chore: updated system managed token tooltip message --- lib/web/join_tokens_test.go | 26 +++++++++ lib/web/ui/join_token.go | 23 ++++---- .../src/JoinTokens/JoinTokens.test.tsx | 56 ++++++++++++++++++- .../teleport/src/JoinTokens/JoinTokens.tsx | 12 ++-- .../src/services/joinToken/makeJoinToken.ts | 2 + .../teleport/src/services/joinToken/types.ts | 2 + 6 files changed, 105 insertions(+), 16 deletions(-) diff --git a/lib/web/join_tokens_test.go b/lib/web/join_tokens_test.go index ffe09c7af709..9b57c101b328 100644 --- a/lib/web/join_tokens_test.go +++ b/lib/web/join_tokens_test.go @@ -174,6 +174,32 @@ func TestGetTokens(t *testing.T) { }, }, }, + { + name: "system tokens are shown but marked as system resources", + tokenData: []tokenData{ + { + name: "beam-system-token", + spec: types.ProvisionTokenSpecV2{ + Roles: types.SystemRoles{types.RoleNode}, + }, + labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + expiry: expiry, + }, + }, + expected: []ui.JoinToken{ + { + ID: "beam-system-token", + SafeName: "************token", + Expiry: expiry, + Roles: types.SystemRoles{types.RoleNode}, + IsSystemResource: true, + Method: "token", + }, + staticUIToken, + }, + }, { name: "all tokens", tokenData: []tokenData{ diff --git a/lib/web/ui/join_token.go b/lib/web/ui/join_token.go index 5c888093b170..8650f55c0a03 100644 --- a/lib/web/ui/join_token.go +++ b/lib/web/ui/join_token.go @@ -43,6 +43,8 @@ type JoinToken struct { IsStatic bool `json:"isStatic"` // IsCloudSystem is true if the token is managed by the Teleport Cloud system. IsCloudSystem bool `json:"isCloudSystem"` + // IsSystemResource is true if the token is a Teleport-internal system resource + IsSystemResource bool `json:"isSystemResource"` // Method is the join method that the token supports Method types.JoinMethod `json:"method"` // Allow is a list of allow rules @@ -69,16 +71,17 @@ func MakeJoinToken(token types.ProvisionToken) (*JoinToken, error) { // site reflects scopedness. botName, _ := token.GetBot() uiToken := &JoinToken{ - ID: token.GetName(), - SafeName: token.GetSafeName(), - BotName: botName, - Expiry: token.Expiry(), - Roles: token.GetRoles(), - IsStatic: token.IsStatic(), - IsCloudSystem: isCloudSystem, - Method: token.GetJoinMethod(), - Allow: token.GetAWSAllowRules(), - Content: string(content[:]), + ID: token.GetName(), + SafeName: token.GetSafeName(), + BotName: botName, + Expiry: token.Expiry(), + Roles: token.GetRoles(), + IsStatic: token.IsStatic(), + IsCloudSystem: isCloudSystem, + IsSystemResource: types.IsSystemResource(token), + Method: token.GetJoinMethod(), + Allow: token.GetAWSAllowRules(), + Content: string(content[:]), } if uiToken.Method == types.JoinMethodGCP { diff --git a/web/packages/teleport/src/JoinTokens/JoinTokens.test.tsx b/web/packages/teleport/src/JoinTokens/JoinTokens.test.tsx index 52251fe75a5b..8721b22cf12a 100644 --- a/web/packages/teleport/src/JoinTokens/JoinTokens.test.tsx +++ b/web/packages/teleport/src/JoinTokens/JoinTokens.test.tsx @@ -241,14 +241,66 @@ describe('JoinTokens', () => { }); }); }); + + describe('system resource (e.g. beam) tokens', () => { + const beamToken = { + id: 'beam-066c847c-eea8-457c-b498-0ba95b704059', + safeName: 'beam-066c847c-eea8-457c-b498-0ba95b704059', + bot_name: 'beam-bot', + expiry: '3024-07-26T11:52:48.320045Z', + roles: ['Bot'], + isStatic: false, + isSystemResource: true, + method: 'bound_keypair', + content: 'fake content', + }; + + test('the options menu is disabled so it cannot be edited or deleted', async () => { + render(); + + await act(tick); + + const optionsButton = await screen.findByRole('button', { + name: /options/i, + }); + expect(optionsButton).toBeDisabled(); + + await userEvent.click(optionsButton); + expect(screen.queryByText(/view\/edit/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/delete/i)).not.toBeInTheDocument(); + }); + + test('a non-system token of the same kind keeps an enabled options menu', async () => { + const editableToken = { + ...beamToken, + id: 'editable-token', + safeName: 'editable-token', + isSystemResource: false, + }; + + render(); + await act(tick); + + const optionsButton = await screen.findByRole('button', { + name: /options/i, + }); + expect(optionsButton).toBeEnabled(); + }); + }); }); -const Component = ({ parseYamlMock }: { parseYamlMock?: object }) => { +const Component = ({ + parseYamlMock, + tokenList = tokens, +}: { + parseYamlMock?: object; + tokenList?: any[]; +}) => { const ctx = createTeleportContext(); jest .spyOn(ctx.joinTokenService, 'fetchJoinTokens') - .mockResolvedValue({ items: tokens.map(makeJoinToken) }); + .mockResolvedValue({ items: tokenList.map(makeJoinToken) }); jest.spyOn(ctx.yamlService, 'parse').mockResolvedValue( parseYamlMock ?? { diff --git a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx index f01bea577989..f65f796a4458 100644 --- a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx +++ b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx @@ -91,7 +91,7 @@ export const JoinTokens = () => { ); function getRowStyle(row: JoinToken): React.CSSProperties { - if (row.isCloudSystem) { + if (row.isCloudSystem || row.isSystemResource) { return { background: theme.colors.interactive.tonal.neutral[0], color: theme.colors.text.muted, @@ -474,7 +474,7 @@ const ActionCell = ({ ); } - if (token.isCloudSystem) { + if (token.isCloudSystem || token.isSystemResource) { buttonProps.disabled = true; } @@ -487,10 +487,14 @@ const ActionCell = ({ return ( - {token.isCloudSystem ? ( + {token.isCloudSystem || token.isSystemResource ? ( {Button} diff --git a/web/packages/teleport/src/services/joinToken/makeJoinToken.ts b/web/packages/teleport/src/services/joinToken/makeJoinToken.ts index d7032ed6ec3c..291518148738 100644 --- a/web/packages/teleport/src/services/joinToken/makeJoinToken.ts +++ b/web/packages/teleport/src/services/joinToken/makeJoinToken.ts @@ -29,6 +29,7 @@ export default function makeToken(json: any): JoinToken { roles, isStatic, isCloudSystem, + isSystemResource, allow, gcp, github, @@ -47,6 +48,7 @@ export default function makeToken(json: any): JoinToken { id, isStatic, isCloudSystem, + isSystemResource, safeName, bot_name, method, diff --git a/web/packages/teleport/src/services/joinToken/types.ts b/web/packages/teleport/src/services/joinToken/types.ts index 212d7e90aaaa..f93a07fd904d 100644 --- a/web/packages/teleport/src/services/joinToken/types.ts +++ b/web/packages/teleport/src/services/joinToken/types.ts @@ -29,6 +29,8 @@ export type JoinToken = { isStatic: boolean; // the token is managed by the Teleport Cloud system and should not be edited by end users isCloudSystem?: boolean; + // the token is a Teleport-internal system resource (e.g. a beam join token) + isSystemResource?: boolean; // the join method of the token method: string; // Roles are the roles granted to the token From 0a0616b872d8bc35c00a36dfcbae010d80bbc60d Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 27 Jul 2026 14:25:43 -0600 Subject: [PATCH 016/347] Deflake TestUploadEncryptedRecordingSizes (#69011) The test was racy because it selected on 2 different channels that are both written to and needs to pick the correct one in order to pass. The fix is to do a separate select in the success and error scenarios so that there is no race. Closes #68158 --- lib/events/filesessions/fileasync_test.go | 29 +++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/events/filesessions/fileasync_test.go b/lib/events/filesessions/fileasync_test.go index 57667afbde0a..6e8be115217b 100644 --- a/lib/events/filesessions/fileasync_test.go +++ b/lib/events/filesessions/fileasync_test.go @@ -781,21 +781,30 @@ func TestUploadEncryptedRecordingSizes(t *testing.T) { // block period clock.Advance(p.scanPeriod + time.Second) + // On a successful encrypted upload the uploader emits an event on + // both channels: the memory uploader signals completion on + // memEventsC, and the file uploader forwards a (nil) error event on + // eventsC. + // + // Wait on the specific channel that corresponds to the expected outcome. + // If we expect an error - wait on only eventsC, otherwise, wait only on + // memEventsC (because we need the upload ID from here) var event events.UploadEvent + if tc.expectEncryptedUploadErr { + select { + case event = <-p.eventsC: + require.Error(t, event.Error) + require.True(t, isSessionError(event.Error), event.Error) + return + case <-ctx.Done(): + t.Fatalf("Timeout waiting for async upload, try `go test -v` to get more logs for details") + } + } + select { case event = <-p.memEventsC: - if tc.expectEncryptedUploadErr { - t.Fatalf("Unexpected upload event") - } require.Equal(t, event.SessionID, sid) require.NoError(t, event.Error) - case event = <-p.eventsC: - if !tc.expectEncryptedUploadErr { - t.Fatalf("Unexpected upload event") - } - require.Error(t, event.Error) - require.True(t, isSessionError(event.Error), event.Error) - return case <-ctx.Done(): t.Fatalf("Timeout waiting for async upload, try `go test -v` to get more logs for details") } From 93db5e02781bfe31eb9c69cbdcff00694ca4a10f Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:46:14 -0400 Subject: [PATCH 017/347] Namespace scoped tokens (#68981) * Namespce scoped tokens Scope tokens are now referred to by SQNs and presented as an SQN to users in all flows. Collisions between unscoped and scoped tokens are no longer possible. The ScopedToken Get and Delete APIs have been updated to take a request object, and the IaC tooling has been updated to exclusively consume namespaced scoped tokens. * fix: validate static scoped tokens * use new scopes.MaybeSQN to determine if an identier is a SQN or not * fix: get scoped token secrets in TF * fix: scoped events delete parsing * fix: scoped token cursors * add commentary and TODOs * fix: bound key pair lock name * fix: only consume legacy tokens when scopes are disabled --- api/client/client.go | 19 +- integrations/lib/embeddedtbot/bot_test.go | 7 +- .../resources/scopedtoken_controller.go | 12 +- .../resources/scopedtoken_controller_test.go | 11 +- .../data_source_teleport_scoped_token.go | 97 ----- .../legacy/resource_teleport_scoped_token.go | 330 ------------------ .../internal/resources/scoped_token.go | 29 +- .../internal/teleport/scoped_token.go | 15 +- .../terraform/testlib/machineid_join_test.go | 2 +- .../terraform/testlib/scoped_token_test.go | 14 +- lib/auth/bot_test.go | 4 +- lib/auth/join.go | 4 +- lib/auth/join/join.go | 11 + lib/auth/scopes/joining/service.go | 29 +- lib/auth/scopes/joining/service_test.go | 66 +++- lib/join/azurejoin/join_azure_test.go | 22 +- lib/join/boundkeypair/boundkeypair.go | 11 +- lib/join/join_azuredevops_test.go | 8 +- lib/join/join_bound_keypair_test.go | 27 +- lib/join/join_ec2_test.go | 8 +- lib/join/join_gcp_test.go | 10 +- lib/join/join_iam_test.go | 10 +- lib/join/join_kubernetes_test.go | 8 +- lib/join/join_test.go | 142 +++++++- lib/join/oraclejoin/join_test.go | 8 +- lib/join/server.go | 116 +++--- lib/join/server_generic_oidc_test.go | 5 +- lib/scopes/qualified.go | 15 +- lib/scopes/qualified_test.go | 8 + lib/service/connect.go | 20 +- lib/service/service_test.go | 3 +- lib/service/servicecfg/config.go | 6 +- lib/services/local/provisioning_test.go | 11 +- lib/services/local/scoped_tokens.go | 134 ++++--- lib/services/local/scoped_tokens_test.go | 249 ++++++------- lib/services/scoped_tokens.go | 3 +- lib/tbot/bot/onboarding/config.go | 9 + lib/tbot/tbot_test.go | 4 +- lib/tbot/workloadidentity/issue.go | 3 +- tool/tctl/common/edit_command_test.go | 6 +- tool/tctl/common/resource_command_test.go | 2 +- tool/tctl/common/resources/scoped_token.go | 25 +- tool/tctl/common/scoped_ref.go | 2 +- tool/tctl/common/scoped_token_command.go | 31 +- tool/tctl/common/scoped_token_command_test.go | 2 +- tool/tctl/common/terraform_command.go | 2 +- tool/tctl/common/workload_identity_command.go | 2 +- 47 files changed, 682 insertions(+), 880 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_scoped_token.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_scoped_token.go diff --git a/api/client/client.go b/api/client/client.go index 7b8e8772a9d3..7f9409d0446f 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -6321,19 +6321,18 @@ func (c *Client) ListScopedTokens(ctx context.Context, req *joiningv1.ListScoped return res, trace.Wrap(err) } -func (c *Client) GetScopedToken(ctx context.Context, name string, withSecret bool) (*joiningv1.ScopedToken, error) { - res, err := c.grpc.GetScopedToken(ctx, &joiningv1.GetScopedTokenRequest{ - Name: name, - WithSecret: withSecret, - }) - return res.GetToken(), trace.Wrap(err) +func (c *Client) GetScopedToken(ctx context.Context, req *joiningv1.GetScopedTokenRequest) (*joiningv1.ScopedToken, error) { + res, err := c.grpc.GetScopedToken(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + + return res.GetToken(), nil } // DeleteScopedToken deletes an existing scoped token. -func (c *Client) DeleteScopedToken(ctx context.Context, name string) error { - _, err := c.grpc.DeleteScopedToken(ctx, &joiningv1.DeleteScopedTokenRequest{ - Name: name, - }) +func (c *Client) DeleteScopedToken(ctx context.Context, req *joiningv1.DeleteScopedTokenRequest) error { + _, err := c.grpc.DeleteScopedToken(ctx, req) return trace.Wrap(err) } diff --git a/integrations/lib/embeddedtbot/bot_test.go b/integrations/lib/embeddedtbot/bot_test.go index 78bb7132f50b..6e0f62fc20dc 100644 --- a/integrations/lib/embeddedtbot/bot_test.go +++ b/integrations/lib/embeddedtbot/bot_test.go @@ -415,7 +415,7 @@ func TestScopedBotJoinAuth(t *testing.T) { }.Build() // Somehow the admin client interface doesn't expose CreateScopedToken ¯\_(ツ)_/¯ // We use the auth server directly to create the scoped token. - _, err = teleportServer.Process.GetAuthServer().CreateScopedToken(ctx, scopedjoiningv1.CreateScopedTokenRequest_builder{ + scopedToken, err := teleportServer.Process.GetAuthServer().CreateScopedToken(ctx, scopedjoiningv1.CreateScopedTokenRequest_builder{ Token: token, }.Build()) require.NoError(t, err) @@ -426,7 +426,10 @@ func TestScopedBotJoinAuth(t *testing.T) { botConfig := &BotConfig{ AuthServer: authAddr.Addr, Onboarding: onboarding.Config{ - TokenValue: testTokenName, + TokenValue: scopes.QualifiedName{ + Name: scopedToken.GetToken().GetMetadata().GetName(), + Scope: scopedToken.GetToken().GetScope(), + }.String(), JoinMethod: types.JoinMethodKubernetes, Kubernetes: onboarding.KubernetesOnboardingConfig{ TokenPath: tokenPath, diff --git a/integrations/operator/controllers/resources/scopedtoken_controller.go b/integrations/operator/controllers/resources/scopedtoken_controller.go index 7cc7e13e78b3..939bf3247d8a 100644 --- a/integrations/operator/controllers/resources/scopedtoken_controller.go +++ b/integrations/operator/controllers/resources/scopedtoken_controller.go @@ -39,11 +39,17 @@ func (s *scopedTokenClient) Create(ctx context.Context, token *tokenv1.ScopedTok } func (s *scopedTokenClient) Delete(ctx context.Context, key reconcilers.ResourceKey) error { - return s.teleportClient.DeleteScopedToken(ctx, key.String()) + return s.teleportClient.DeleteScopedToken(ctx, tokenv1.DeleteScopedTokenRequest_builder{ + Name: key.Name, + Scope: key.Scope, + }.Build()) } func (s *scopedTokenClient) Get(ctx context.Context, key reconcilers.ResourceKey) (*tokenv1.ScopedToken, error) { - return s.teleportClient.GetScopedToken(ctx, key.String(), false) + return s.teleportClient.GetScopedToken(ctx, tokenv1.GetScopedTokenRequest_builder{ + Name: key.Name, + Scope: key.Scope, + }.Build()) } func (s *scopedTokenClient) Update(ctx context.Context, token *tokenv1.ScopedToken) error { @@ -52,7 +58,7 @@ func (s *scopedTokenClient) Update(ctx context.Context, token *tokenv1.ScopedTok } func NewScopedTokenV1Reconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) { - return reconcilers.NewTeleportResource153Reconciler[*tokenv1.ScopedToken, *resourcesv1.TeleportScopedTokenV1]( + return reconcilers.NewTeleportScopedResource153Reconciler[*tokenv1.ScopedToken, *resourcesv1.TeleportScopedTokenV1]( client, &scopedTokenClient{ teleportClient: tClient, diff --git a/integrations/operator/controllers/resources/scopedtoken_controller_test.go b/integrations/operator/controllers/resources/scopedtoken_controller_test.go index 1cc92a18c51c..43160f18a799 100644 --- a/integrations/operator/controllers/resources/scopedtoken_controller_test.go +++ b/integrations/operator/controllers/resources/scopedtoken_controller_test.go @@ -80,11 +80,18 @@ func (g *scopedTokenTestingPrimitives) CreateTeleportResource(ctx context.Contex } func (g *scopedTokenTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (*tokenv1.ScopedToken, error) { - return g.setup.TeleportClient.GetScopedToken(ctx, name, true) + return g.setup.TeleportClient.GetScopedToken(ctx, tokenv1.GetScopedTokenRequest_builder{ + Name: name, + Scope: "/staging", + WithSecret: true, + }.Build()) } func (g *scopedTokenTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error { - return trace.Wrap(g.setup.TeleportClient.DeleteScopedToken(ctx, name)) + return trace.Wrap(g.setup.TeleportClient.DeleteScopedToken(ctx, tokenv1.DeleteScopedTokenRequest_builder{ + Name: name, + Scope: "/staging", + }.Build())) } func (g *scopedTokenTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error { diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_scoped_token.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_scoped_token.go deleted file mode 100755 index 9361d2980824..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_scoped_token.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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 legacy - -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/scopes/joining/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportScopedTokenType is the data source metadata type -type dataSourceTeleportScopedTokenType struct{} - -// dataSourceTeleportScopedToken is the resource -type dataSourceTeleportScopedToken struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportScopedTokenType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaScopedToken(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportScopedTokenType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportScopedToken{ - p: p.(Provider), - }, nil -} - -// Read reads teleport ScopedToken -func (r dataSourceTeleportScopedToken) 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 - } - - scopedTokenI, err := r.p.Client().GetScopedToken(ctx, id.Value, true) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = id - } - - scopedToken := scopedTokenI - - diags = schemav1.CopyScopedTokenToTerraform(ctx, scopedToken, &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/internal/legacy/resource_teleport_scoped_token.go b/integrations/terraform/provider/internal/legacy/resource_teleport_scoped_token.go deleted file mode 100755 index 87e27814d4d8..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_scoped_token.go +++ /dev/null @@ -1,330 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - apitypes "github.com/gravitational/teleport/api/types" - - joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" - - "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/scopes/joining/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportScopedTokenType is the resource metadata type -type resourceTeleportScopedTokenType struct{} - -// resourceTeleportScopedToken is the resource -type resourceTeleportScopedToken struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportScopedTokenType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaScopedToken(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportScopedTokenType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportScopedToken{ - p: p.(Provider), - }, nil -} - -// Create creates the ScopedToken -func (r resourceTeleportScopedToken) 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 - } - - scopedToken := &joiningv1.ScopedToken{} - diags = schemav1.CopyScopedTokenFromTerraform(ctx, plan, scopedToken) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - scopedTokenResource := scopedToken - - scopedTokenResource.Kind = apitypes.KindScopedToken - - id := scopedTokenResource.Metadata.Name - - _, err = r.p.Client().GetScopedToken(ctx, id, true) - if !trace.IsNotFound(err) { - if err == nil { - existErr := fmt.Sprintf("ScopedToken exists in Teleport. Either remove it (tctl rm scoped_token/%v)"+ - " or import it to the existing state (terraform import teleport_scoped_token.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("ScopedToken exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - - _, err = r.p.Client().CreateScopedToken(ctx, scopedTokenResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - var scopedTokenI *joiningv1.ScopedToken - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - scopedTokenI, err = r.p.Client().GetScopedToken(ctx, id, true) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(ctx.Err()), "scoped_token")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading ScopedToken (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "scoped_token") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - - scopedTokenResource = scopedTokenI - - scopedToken = scopedTokenResource - - diags = schemav1.CopyScopedTokenToTerraform(ctx, scopedToken, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: scopedToken.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport ScopedToken -func (r resourceTeleportScopedToken) 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 - } - - scopedTokenI, err := r.p.Client().GetScopedToken(ctx, id.Value, true) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - scopedToken := scopedTokenI - diags = schemav1.CopyScopedTokenToTerraform(ctx, scopedToken, &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 ScopedToken -func (r resourceTeleportScopedToken) 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 - } - - scopedToken := &joiningv1.ScopedToken{} - diags = schemav1.CopyScopedTokenFromTerraform(ctx, plan, scopedToken) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - scopedTokenResource := scopedToken - - scopedTokenResource.Kind = apitypes.KindScopedToken - - - name := scopedTokenResource.Metadata.Name - - scopedTokenBefore, err := r.p.Client().GetScopedToken(ctx, name, true) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", err, "scoped_token")) - return - } - - _, err = r.p.Client().UpsertScopedToken(ctx, scopedTokenResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating ScopedToken", err, "scoped_token")) - return - } - var scopedTokenI *joiningv1.ScopedToken - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - scopedTokenI, err = r.p.Client().GetScopedToken(ctx, name, true) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", err, "scoped_token")) - return - } - if scopedTokenBefore.GetMetadata().Revision != scopedTokenI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(ctx.Err()), "scoped_token")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading ScopedToken (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "scoped_token") - return - } - } - - scopedTokenResource = scopedTokenI - - diags = schemav1.CopyScopedTokenToTerraform(ctx, scopedToken, &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 ScopedToken -func (r resourceTeleportScopedToken) 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().DeleteScopedToken(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports ScopedToken state -func (r resourceTeleportScopedToken) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - scopedToken, err := r.p.Client().GetScopedToken(ctx, req.ID, true) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading ScopedToken", trace.Wrap(err), "scoped_token")) - return - } - - scopedTokenResource := scopedToken - - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = schemav1.CopyScopedTokenToTerraform(ctx, scopedTokenResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := scopedToken.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/provider/internal/resources/scoped_token.go b/integrations/terraform/provider/internal/resources/scoped_token.go index 56f96fa62737..b4b8ec74c1ec 100644 --- a/integrations/terraform/provider/internal/resources/scoped_token.go +++ b/integrations/terraform/provider/internal/resources/scoped_token.go @@ -29,13 +29,16 @@ import ( ) // NewScopedTokenDataSourceType returns the scoped token data source type. -func NewScopedTokenDataSourceType() tfdriver.DataSourceType[joiningv1.ScopedToken, tfdriver.NameIdentifier] { - return tfdriver.DataSourceType[joiningv1.ScopedToken, tfdriver.NameIdentifier]{ - NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[joiningv1.ScopedToken, tfdriver.NameIdentifier] { +func NewScopedTokenDataSourceType() tfdriver.DataSourceType[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier] { return teleport.NewScopedTokenClient(clientFromProvider(p)) }, - Identifier: tfdriver.NameIdentifierFromPath(path.Root("metadata").AtName("name")), - Kind: apitypes.KindScopedToken, + Identifier: tfdriver.ScopeQualifiedNameIdentifierFromPath( + path.Root("metadata").AtName("name"), + path.Root("scope"), + ), + Kind: apitypes.KindScopedToken, Codec: tfdriver.DataSourceCodecFuncs[joiningv1.ScopedToken]{ SchemaFunc: schemav1.GenSchemaScopedToken, ToStateFunc: schemav1.CopyScopedTokenToTerraform, @@ -44,9 +47,9 @@ func NewScopedTokenDataSourceType() tfdriver.DataSourceType[joiningv1.ScopedToke } // NewScopedTokenResourceType returns the scoped token resource type. -func NewScopedTokenResourceType() tfdriver.ResourceType[joiningv1.ScopedToken, tfdriver.NameIdentifier] { - return tfdriver.ResourceType[joiningv1.ScopedToken, tfdriver.NameIdentifier]{ - NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[joiningv1.ScopedToken, tfdriver.NameIdentifier] { +func NewScopedTokenResourceType() tfdriver.ResourceType[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[joiningv1.ScopedToken, tfdriver.ScopeQualifiedNameIdentifier] { return teleport.NewScopedTokenClient(clientFromProvider(p)) }, Kind: apitypes.KindScopedToken, @@ -56,9 +59,13 @@ func NewScopedTokenResourceType() tfdriver.ResourceType[joiningv1.ScopedToken, t FromPlanFunc: schemav1.CopyScopedTokenFromTerraform, }, Normalizer: tfdriver.ForceKind[joiningv1.ScopedToken](apitypes.KindScopedToken), - Identifier: tfdriver.NameIdentifierPolicy(path.Root("metadata").AtName("name"), func(st *joiningv1.ScopedToken) string { - return st.GetMetadata().GetName() - }), + Identifier: tfdriver.ScopeQualifiedNameIdentifierPolicy( + path.Root("metadata").AtName("name"), + path.Root("scope"), + func(st *joiningv1.ScopedToken) (string, string) { + return st.GetMetadata().GetName(), st.GetScope() + }, + ), ResourceRevision: func(st *joiningv1.ScopedToken) string { return st.GetMetadata().GetRevision() }, diff --git a/integrations/terraform/provider/internal/teleport/scoped_token.go b/integrations/terraform/provider/internal/teleport/scoped_token.go index 7d1efc55710a..d1a76ed72807 100644 --- a/integrations/terraform/provider/internal/teleport/scoped_token.go +++ b/integrations/terraform/provider/internal/teleport/scoped_token.go @@ -38,8 +38,12 @@ type ScopedTokenClient struct { } // Get reads a scoped token by name. -func (r ScopedTokenClient) Get(ctx context.Context, id tfdriver.NameIdentifier) (*joiningv1.ScopedToken, error) { - scopedToken, err := r.client.GetScopedToken(ctx, id.Name, true) +func (r ScopedTokenClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*joiningv1.ScopedToken, error) { + scopedToken, err := r.client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: id.Name, + Scope: id.Scope, + WithSecret: true, + }.Build()) if err != nil { return nil, trace.Wrap(err) } @@ -68,6 +72,9 @@ func (r ScopedTokenClient) Upsert(ctx context.Context, id *joiningv1.ScopedToken } // Delete deletes a scoped token by name. -func (r ScopedTokenClient) Delete(ctx context.Context, id tfdriver.NameIdentifier) error { - return trace.Wrap(r.client.DeleteScopedToken(ctx, id.Name)) +func (r ScopedTokenClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + return trace.Wrap(r.client.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ + Name: id.Name, + Scope: id.Scope, + }.Build())) } diff --git a/integrations/terraform/testlib/machineid_join_test.go b/integrations/terraform/testlib/machineid_join_test.go index f946941135c6..cb26889157eb 100644 --- a/integrations/terraform/testlib/machineid_join_test.go +++ b/integrations/terraform/testlib/machineid_join_test.go @@ -553,7 +553,7 @@ func TestTerraformJoinScoped(t *testing.T) { kubernetes_token_path = %q scoped = true } - `, tt.addr, testTokenName, types.JoinMethodKubernetes, tokenPath) + `, tt.addr, scopes.QualifiedName{Scope: testRootScope, Name: testTokenName}.String(), types.JoinMethodKubernetes, tokenPath) terraformProvider := provider.New() terraformProviders := make(map[string]func() (tfprotov6.ProviderServer, error)) diff --git a/integrations/terraform/testlib/scoped_token_test.go b/integrations/terraform/testlib/scoped_token_test.go index 6ea605dcd961..a7d66ca3fce1 100644 --- a/integrations/terraform/testlib/scoped_token_test.go +++ b/integrations/terraform/testlib/scoped_token_test.go @@ -29,6 +29,7 @@ import ( headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/scopes" ) func (s *TerraformSuiteOSS) TestScopedToken() { @@ -36,7 +37,10 @@ func (s *TerraformSuiteOSS) TestScopedToken() { ctx := t.Context() checkDestroyed := func(state *terraform.State) error { - _, err := s.client.GetScopedToken(ctx, "test-scoped-token", false) + _, err := s.client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: "test-scoped-token", + Scope: "/staging/aa", + }.Build()) if !trace.IsNotFound(err) { return trace.Errorf("expected not found, actual: %v", err) } @@ -107,7 +111,11 @@ func (s *TerraformSuiteOSS) TestImportScopedToken() { require.NoError(t, err) require.EventuallyWithT(t, func(t *assert.CollectT) { - _, err := s.client.GetScopedToken(ctx, id, true) + _, err := s.client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: id, + Scope: "/staging", + WithSecret: true, + }.Build()) require.NoError(t, err) }, 5*time.Second, time.Second) @@ -119,7 +127,7 @@ func (s *TerraformSuiteOSS) TestImportScopedToken() { Config: fmt.Sprintf("%s\nresource %q %q { }", s.terraformConfig, r, id), ResourceName: name, ImportState: true, - ImportStateId: id, + ImportStateId: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetMetadata().GetName()}.String(), ImportStateCheck: func(state []*terraform.InstanceState) error { require.Equal(t, types.KindScopedToken, state[0].Attributes["kind"]) require.Equal(t, "/staging", state[0].Attributes["scope"]) diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 2f01dee808ab..8655b661af24 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -1494,7 +1494,7 @@ func TestRegisterBotWithScopedKubernetesToken(t *testing.T) { require.NoError(t, err) result, err := joinclient.Join(ctx, joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: "/test", Name: scopedToken.GetMetadata().GetName()}.String(), JoinMethod: types.JoinMethodKubernetes, ID: state.IdentityID{ Role: types.RoleBot, @@ -1514,7 +1514,7 @@ func TestRegisterBotWithScopedKubernetesToken(t *testing.T) { require.NotNil(t, ident.ScopePin) require.Equal(t, "/test", ident.ScopePin.GetScope()) require.True(t, ident.BotInternal) - require.Equal(t, "example-token", ident.JoinToken) + require.Equal(t, "/test::example-token", ident.JoinToken) require.Equal(t, "/test", ident.BotScope) var certIssueEvent *events.CertificateCreate diff --git a/lib/auth/join.go b/lib/auth/join.go index 113487cb5de4..cf518dddf2a4 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -414,7 +414,7 @@ func (a *Server) GenerateBotCertsForJoin( // TODO: GetSafeName may not return an appropriate value for later // comparison / locking purposes, and this also shouldn't contain // secrets. Should we hash it? - JoinToken: token.GetSafeName(), + JoinToken: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetSafeName()}.String(), JoinMethod: string(token.GetJoinMethod()), PublicKey: params.PublicTLSKey, JoinAttrs: params.Attrs, @@ -557,7 +557,7 @@ func (a *Server) GenerateHostCertsForJoin( // plaintext and we don't automatically target locks at `token`-type // tokens. Other join methods (especially bound_keypair) return the // full token name. - JoinToken: token.GetSafeName(), + JoinToken: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetSafeName()}.String(), }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/join/join.go b/lib/auth/join/join.go index 919f624714c2..35012c4b4427 100644 --- a/lib/auth/join/join.go +++ b/lib/auth/join/join.go @@ -62,6 +62,7 @@ import ( "github.com/gravitational/teleport/lib/join/terraformcloud" "github.com/gravitational/teleport/lib/jwt" kubetoken "github.com/gravitational/teleport/lib/kube/token" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/tpm" "github.com/gravitational/teleport/lib/utils" @@ -358,6 +359,16 @@ func Register(ctx context.Context, params RegisterParams) (result *RegisterResul // on disk. token, err := utils.TryReadValueAsFile(params.Token) if err != nil { + // A scoped token with a Scope Qualified Name looks like a file path + // and will result in file not found errors. If the provided token is + // a SQN, then the request is rejected. Scoped tokens can only be used + // in the new join service. + if _, err := scopes.ParseQualifiedName(params.Token); err == nil { + // TODO(tross/scopes): This error matches the one returend from + // the unscoped path. Look into unifying them, or exporting the + // TokenNotFoundError to an importable package. + return nil, trace.AccessDenied("token expired or not found") + } return nil, trace.Wrap(err) } diff --git a/lib/auth/scopes/joining/service.go b/lib/auth/scopes/joining/service.go index d39a706c2653..4e4064f5991d 100644 --- a/lib/auth/scopes/joining/service.go +++ b/lib/auth/scopes/joining/service.go @@ -19,8 +19,6 @@ package joining import ( "cmp" "context" - "crypto/sha256" - "encoding/base64" "iter" "log/slog" "time" @@ -154,7 +152,8 @@ func (s *Server) DeleteScopedToken(ctx context.Context, req *scopedjoiningv1.Del // fetch the token so we can determine the resource scope preAuthzRes, err := s.backend.GetScopedToken(ctx, scopedjoiningv1.GetScopedTokenRequest_builder{ - Name: req.GetName(), + Name: req.GetName(), + Scope: req.GetScope(), }.Build()) if err != nil { return nil, trace.Wrap(err) @@ -214,8 +213,7 @@ func makeCursor(token *scopedjoiningv1.ScopedToken) string { if token == nil { return "" } - hash := sha256.Sum256([]byte(token.GetMetadata().GetName())) - return base64.StdEncoding.EncodeToString(hash[:]) + return scopes.MakeResourceCursor(token.GetScope(), token.GetMetadata().GetName()) } func (s *Server) scopedTokenIter(ctx context.Context, req *scopedjoiningv1.ListScopedTokensRequest) iter.Seq2[*scopedjoiningv1.ScopedToken, error] { @@ -223,20 +221,24 @@ func (s *Server) scopedTokenIter(ctx context.Context, req *scopedjoiningv1.ListS iterReq := proto.CloneOf(req) iterReq.SetLimit(s.maxPageSize) - var cursorFound bool + // Public cursors identify the last token returned. Backend cursors are + // inclusive, so skip that token once if it is still present. + cursor := req.GetCursor() for { res, err := s.backend.ListScopedTokens(ctx, iterReq) if err != nil { - if !yield(nil, trace.Wrap(err)) { - return - } + yield(nil, trace.Wrap(err)) + return } for _, tok := range res.GetTokens() { - if !cursorFound && req.GetCursor() != "" && makeCursor(tok) != req.GetCursor() { - continue + if cursor != "" { + if makeCursor(tok) == cursor { + cursor = "" + continue + } + cursor = "" } - cursorFound = true if !yield(tok, nil) { return } @@ -357,7 +359,8 @@ func (s *Server) UpdateScopedToken(ctx context.Context, req *scopedjoiningv1.Upd } extant, err := s.backend.GetScopedToken(ctx, scopedjoiningv1.GetScopedTokenRequest_builder{ - Name: req.GetToken().GetMetadata().GetName(), + Name: req.GetToken().GetMetadata().GetName(), + Scope: req.GetToken().GetScope(), }.Build()) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/scopes/joining/service_test.go b/lib/auth/scopes/joining/service_test.go index e68ce9df88b9..9805fd44b3dd 100644 --- a/lib/auth/scopes/joining/service_test.go +++ b/lib/auth/scopes/joining/service_test.go @@ -126,6 +126,7 @@ func TestScopedJoiningService(t *testing.T) { // fetch a token fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -133,7 +134,8 @@ func TestScopedJoiningService(t *testing.T) { // delete a token _, err = service.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: namedToken.GetMetadata().GetName(), + Name: namedToken.GetMetadata().GetName(), + Scope: namedToken.GetScope(), }.Build()) require.NoError(t, err) @@ -156,6 +158,34 @@ func TestScopedJoiningService(t *testing.T) { tokenStagingCC2, err = createToken(ctx, service, tokenStagingCC2) require.NoError(t, err) + // list multiple pages using the public cursor returned by the service. + firstPage, err := service.ListScopedTokens(ctx, joiningv1.ListScopedTokensRequest_builder{ + Limit: 1, + ScopeFilter: scopesv1.Filter_builder{ + Mode: scopesv1.Mode_MODE_DESCENDANTS, + Scope: "/staging/cc", + }.Build(), + }.Build()) + require.NoError(t, err) + require.Len(t, firstPage.GetTokens(), 1) + require.NotEmpty(t, firstPage.GetCursor()) + require.True(t, scopes.IsScopedResourceCursor(firstPage.GetCursor())) + + secondPage, err := service.ListScopedTokens(ctx, joiningv1.ListScopedTokensRequest_builder{ + Limit: 1, + Cursor: firstPage.GetCursor(), + ScopeFilter: scopesv1.Filter_builder{ + Mode: scopesv1.Mode_MODE_DESCENDANTS, + Scope: "/staging/cc", + }.Build(), + }.Build()) + require.NoError(t, err) + require.Len(t, secondPage.GetTokens(), 1) + require.NotEqual(t, + scopes.QualifiedName{Scope: firstPage.GetTokens()[0].GetScope(), Name: firstPage.GetTokens()[0].GetMetadata().GetName()}, + scopes.QualifiedName{Scope: secondPage.GetTokens()[0].GetScope(), Name: secondPage.GetTokens()[0].GetMetadata().GetName()}, + ) + // list tokens while filtering their resource scope res, err := service.ListScopedTokens(ctx, joiningv1.ListScopedTokensRequest_builder{ WithSecrets: true, @@ -292,13 +322,15 @@ func TestScopedJoiningService(t *testing.T) { t.Run("user with readnosecret role cannot read secret at accessible scope", func(t *testing.T) { t.Parallel() getRes, err := readerNoSecrets.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: stageTokenAA.GetMetadata().GetName(), + Name: stageTokenAA.GetMetadata().GetName(), + Scope: stageTokenAA.GetScope(), }.Build()) require.NoError(t, err) assert.Empty(t, getRes.GetToken().GetStatus().GetSecret()) _, err = readerNoSecrets.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: stageTokenAA.GetMetadata().GetName(), + Scope: stageTokenAA.GetScope(), WithSecret: true, }.Build()) require.True(t, trace.IsAccessDenied(err)) @@ -308,6 +340,7 @@ func TestScopedJoiningService(t *testing.T) { t.Parallel() getRes, err := reader.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: stageTokenAA.GetMetadata().GetName(), + Scope: stageTokenAA.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -319,7 +352,8 @@ func TestScopedJoiningService(t *testing.T) { t.Run("reader cannot get token at orthogonal scope", func(t *testing.T) { t.Parallel() _, err := reader.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: stageTokenBB.GetMetadata().GetName(), + Name: stageTokenBB.GetMetadata().GetName(), + Scope: stageTokenBB.GetScope(), }.Build()) require.True(t, trace.IsAccessDenied(err)) }) @@ -329,7 +363,8 @@ func TestScopedJoiningService(t *testing.T) { t.Parallel() for _, ident := range nonReaderIdents { _, err := ident.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: stageTokenAA.GetMetadata().GetName(), + Name: stageTokenAA.GetMetadata().GetName(), + Scope: stageTokenAA.GetScope(), }.Build()) require.True(t, trace.IsAccessDenied(err)) } @@ -359,7 +394,8 @@ func TestScopedJoiningService(t *testing.T) { t.Parallel() for _, ident := range nonDeleterIdents { _, err := ident.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: stageTokenAA.GetMetadata().GetName(), + Name: stageTokenAA.GetMetadata().GetName(), + Scope: stageTokenAA.GetScope(), }.Build()) require.True(t, trace.IsAccessDenied(err)) } @@ -370,7 +406,8 @@ func TestScopedJoiningService(t *testing.T) { tokenForDelete, err := createToken(ctx, admin, baseToken) require.NoError(t, err) _, err = deleter.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: tokenForDelete.GetMetadata().GetName(), + Name: tokenForDelete.GetMetadata().GetName(), + Scope: tokenForDelete.GetScope(), }.Build()) require.NoError(t, err) }) @@ -378,7 +415,8 @@ func TestScopedJoiningService(t *testing.T) { t.Run("ensure deleter can't delete a token at an orthogonal scope", func(t *testing.T) { t.Parallel() _, err := deleter.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: stageTokenBB.GetMetadata().GetName(), + Name: stageTokenBB.GetMetadata().GetName(), + Scope: stageTokenBB.GetScope(), }.Build()) require.True(t, trace.IsAccessDenied(err)) }) @@ -461,17 +499,6 @@ func TestScopedJoiningService(t *testing.T) { }.Build()) require.True(t, trace.IsAccessDenied(err)) }) - - t.Run("ensure updater cannot bypass scope auth by spoofing scope in request", func(t *testing.T) { - tokenUpdate := proto.CloneOf(stageTokenBB) - tokenUpdate.SetScope("/staging/aa") - tokenUpdate.GetSpec().SetAssignedScope("/staging/aa") - - _, err := updater.UpdateScopedToken(ctx, joiningv1.UpdateScopedTokenRequest_builder{ - Token: tokenUpdate, - }.Build()) - require.True(t, trace.IsBadParameter(err)) - }) }) // ensure audit events are emitted when creating, updating, upserting, or deleting scoped tokens @@ -526,7 +553,8 @@ func TestScopedJoiningService(t *testing.T) { require.Len(t, pack.emitter.Events(), 3) _, err = service.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: token.GetMetadata().GetName(), + Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), }.Build()) require.NoError(t, err) require.Equal(t, events.ScopedTokenDeleteEvent, pack.emitter.LastEvent().GetType()) diff --git a/lib/join/azurejoin/join_azure_test.go b/lib/join/azurejoin/join_azure_test.go index 47574d69eba5..822705aeecf0 100644 --- a/lib/join/azurejoin/join_azure_test.go +++ b/lib/join/azurejoin/join_azure_test.go @@ -563,7 +563,7 @@ func TestJoinAzure(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tc.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -578,7 +578,8 @@ func TestJoinAzure(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := a.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -638,7 +639,8 @@ func TestJoinAzure(t *testing.T) { }) t.Run("scoped", func(t *testing.T) { _, err = joinclient.Join(ctx, joinclient.JoinParams{ - Token: "scoped_" + tc.requestTokenName, + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: tc.requestTokenName}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, }, @@ -1028,7 +1030,7 @@ func TestJoinAzureClaims(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tc.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -1037,13 +1039,15 @@ func TestJoinAzureClaims(t *testing.T) { }.Build()) require.NoError(t, err) - _, err = a.CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{ + resp, err := a.CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{ Token: scopedToken, }.Build()) require.NoError(t, err) + scopedToken = resp.GetToken() t.Cleanup(func() { _, err := a.DeleteScopedToken(t.Context(), joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -1114,7 +1118,8 @@ func TestJoinAzureClaims(t *testing.T) { t.Run("scoped", func(t *testing.T) { // Try to join via the new join service. _, err = joinclient.Join(ctx, joinclient.JoinParams{ - Token: "scoped_" + tc.requestTokenName, + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: tc.requestTokenName}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, }, @@ -1140,7 +1145,8 @@ func TestJoinAzureClaims(t *testing.T) { require.NoError(t, a.UpsertToken(ctx, token)) result, err := joinclient.Join(ctx, joinclient.JoinParams{ - Token: tc.requestTokenName, + Token: tc.requestTokenName, + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleBot, }, diff --git a/lib/join/boundkeypair/boundkeypair.go b/lib/join/boundkeypair/boundkeypair.go index 37ddb55b8f2f..f508ed181c63 100644 --- a/lib/join/boundkeypair/boundkeypair.go +++ b/lib/join/boundkeypair/boundkeypair.go @@ -44,6 +44,7 @@ import ( "github.com/gravitational/teleport/lib/join/internal/messages" "github.com/gravitational/teleport/lib/join/provision" "github.com/gravitational/teleport/lib/jwt" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/scopes/joining" "github.com/gravitational/teleport/lib/services/readonly" libsshutils "github.com/gravitational/teleport/lib/sshutils" @@ -539,7 +540,7 @@ func patchToken(ctx context.Context, params *JoinParams, mutators ...boundKeypai return patched, nil case *joining.Token: - patched, err := params.ScopedTokenService.PatchScopedToken(ctx, token.GetName(), func(st *joiningv1.ScopedToken) (*joiningv1.ScopedToken, error) { + patched, err := params.ScopedTokenService.PatchScopedToken(ctx, scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetName()}, func(st *joiningv1.ScopedToken) (*joiningv1.ScopedToken, error) { if st.GetStatus().GetUsage().GetBoundKeypair() == nil { return nil, trace.BadParameter("scoped bound keypair tokens must have non-nil status.usage.bound_keypair") } @@ -719,7 +720,7 @@ func tryLockTokenInvalidJoinState( // Create a lock against this token. lock, err := types.NewLock(uuid.New().String(), types.LockSpecV2{ Target: types.LockTarget{ - JoinToken: token.GetName(), + JoinToken: scopes.QualifiedName{Name: token.GetName(), Scope: token.GetScope()}.String(), }, Message: message, CreatedAt: params.Clock.Now(), @@ -813,7 +814,9 @@ func verifyLocksForBoundKeypairToken(ctx context.Context, params *JoinParams, to return trace.Wrap(params.AuthService.CheckLockInForce( readOnlyAuthPref.GetLockingMode(), - []types.LockTarget{{JoinToken: token.GetName()}}, + []types.LockTarget{ + {JoinToken: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetName()}.String()}, + }, )) } @@ -913,7 +916,7 @@ type AuthService interface { type ScopedTokenService interface { PatchScopedToken( ctx context.Context, - tokenName string, + tokenName scopes.QualifiedName, updateFn func(*joiningv1.ScopedToken) (*joiningv1.ScopedToken, error), ) (*joiningv1.ScopedToken, error) } diff --git a/lib/join/join_azuredevops_test.go b/lib/join/join_azuredevops_test.go index e2225b6e29e0..df214ab21687 100644 --- a/lib/join/join_azuredevops_test.go +++ b/lib/join/join_azuredevops_test.go @@ -334,7 +334,7 @@ func TestJoinAzureDevops(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tt.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -349,7 +349,8 @@ func TestJoinAzureDevops(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := auth.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -368,7 +369,8 @@ func TestJoinAzureDevops(t *testing.T) { }) t.Run("scoped", func(t *testing.T) { _, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: "scoped_" + token.GetName(), + Token: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, NodeName: "testnode", diff --git a/lib/join/join_bound_keypair_test.go b/lib/join/join_bound_keypair_test.go index 0b2b50029d6e..ceb8245b4ee3 100644 --- a/lib/join/join_bound_keypair_test.go +++ b/lib/join/join_bound_keypair_test.go @@ -1970,7 +1970,8 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { // Perform the first join. joinResult, err := joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleBot, }, @@ -2056,7 +2057,10 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { }, 5*time.Second, 5*time.Millisecond, "expected bound keypair recovery event not found") // Status should be updated. - token, err := adminClient.GetScopedToken(ctx, "example-token", false) + token, err := adminClient.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: "example-token", + Scope: "/test", + }.Build()) require.NoError(t, err) require.Equal(t, firstInstance, token.GetStatus().GetUsage().GetBoundKeypair().GetBoundBotInstanceId()) require.Equal(t, correctPublicKey, token.GetStatus().GetUsage().GetBoundKeypair().GetBoundPublicKey()) @@ -2069,7 +2073,8 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { require.NoError(t, err) joinResult, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleBot, }, @@ -2083,7 +2088,10 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { require.EqualValues(t, 2, generation, "new certs should be issued with an incremented generation counter") // Status should be updated (or not) - token, err = adminClient.GetScopedToken(ctx, "example-token", false) + token, err = adminClient.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: "example-token", + Scope: "/test", + }.Build()) require.NoError(t, err) require.Equal(t, firstInstance, token.GetStatus().GetUsage().GetBoundKeypair().GetBoundBotInstanceId()) require.Equal(t, correctPublicKey, token.GetStatus().GetUsage().GetBoundKeypair().GetBoundPublicKey()) @@ -2092,7 +2100,8 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { // Now, discard those certs and attempt a recovery. joinResult, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleBot, }, @@ -2106,7 +2115,10 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { require.EqualValues(t, 1, generation, "generation counter should be reset for the new bot instance") // Status should indicate a recovery - thirdToken, err := adminClient.GetScopedToken(ctx, "example-token", false) + thirdToken, err := adminClient.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: "example-token", + Scope: "/test", + }.Build()) require.NoError(t, err) require.Equal(t, thirdInstance, thirdToken.GetStatus().GetUsage().GetBoundKeypair().GetBoundBotInstanceId()) require.Equal(t, correctPublicKey, thirdToken.GetStatus().GetUsage().GetBoundKeypair().GetBoundPublicKey()) @@ -2119,7 +2131,8 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { // Try once more - should hit a limit. _, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleBot, }, diff --git a/lib/join/join_ec2_test.go b/lib/join/join_ec2_test.go index 20d1cf4be6ba..b3557478faa3 100644 --- a/lib/join/join_ec2_test.go +++ b/lib/join/join_ec2_test.go @@ -488,7 +488,7 @@ func TestJoinEC2(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tc.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -503,7 +503,8 @@ func TestJoinEC2(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := testServer.Auth().DeleteScopedToken(t.Context(), joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -554,7 +555,8 @@ func TestJoinEC2(t *testing.T) { t.Skip() } _, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: scopedToken.GetMetadata().GetName(), + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, NodeName: "testnode", diff --git a/lib/join/join_gcp_test.go b/lib/join/join_gcp_test.go index 6c22a9ce0f92..e056d4fdbb23 100644 --- a/lib/join/join_gcp_test.go +++ b/lib/join/join_gcp_test.go @@ -231,7 +231,7 @@ func TestJoinGCP(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tc.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -246,7 +246,8 @@ func TestJoinGCP(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := auth.DeleteScopedToken(t.Context(), joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -296,8 +297,9 @@ func TestJoinGCP(t *testing.T) { t.Run("scoped", func(t *testing.T) { _, err := joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: "scoped_" + tc.request.Token, - JoinMethod: types.JoinMethodGCP, + Token: tc.request.Token, + TokenSecret: scopedToken.GetStatus().GetSecret(), + JoinMethod: types.JoinMethodGCP, ID: state.IdentityID{ Role: types.RoleInstance, // RoleNode is not allowed NodeName: "testnode", diff --git a/lib/join/join_iam_test.go b/lib/join/join_iam_test.go index 25c3f0cbbe75..9a98e920eb24 100644 --- a/lib/join/join_iam_test.go +++ b/lib/join/join_iam_test.go @@ -872,7 +872,7 @@ func testIAMJoin(t *testing.T, tc *iamJoinTestCase) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tc.tokenSpec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -887,7 +887,8 @@ func testIAMJoin(t *testing.T, tc *iamJoinTestCase) { require.NoError(t, err) t.Cleanup(func() { _, err := tc.authServer.Auth().DeleteScopedToken(t.Context(), joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -984,7 +985,8 @@ func testIAMJoin(t *testing.T, tc *iamJoinTestCase) { }) t.Run("scoped", func(t *testing.T) { _, err := joinclient.Join(ctx, joinclient.JoinParams{ - Token: "scoped_" + tc.requestTokenName, + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: tc.requestTokenName}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, NodeName: "test-node", @@ -1022,7 +1024,7 @@ func testIAMJoin(t *testing.T, tc *iamJoinTestCase) { Role: "Instance", Method: "iam", NodeName: "test-node", - TokenName: "scoped_test-token", + TokenName: "test-token", Scope: "/test/one", Roles: []string{types.RoleNode.String()}, }, diff --git a/lib/join/join_kubernetes_test.go b/lib/join/join_kubernetes_test.go index 94e1678bf7ad..f0c753bfda0a 100644 --- a/lib/join/join_kubernetes_test.go +++ b/lib/join/join_kubernetes_test.go @@ -291,7 +291,7 @@ func TestJoinKubernetes(t *testing.T) { scoped, err := jointest.ScopedTokenFromProvisionTokenSpec(ptv2.Spec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + pt.GetName(), + Name: pt.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -535,9 +535,11 @@ func TestJoinKubernetes(t *testing.T) { }) t.Run("scoped join", func(t *testing.T) { + secret, _ := tt.provisionToken.GetSecret() _, err := joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: "scoped_" + tt.provisionToken.GetName(), - JoinMethod: types.JoinMethodKubernetes, + Token: scopes.QualifiedName{Scope: tt.provisionToken.GetScope(), Name: tt.provisionToken.GetName()}.String(), + TokenSecret: secret, + JoinMethod: types.JoinMethodKubernetes, ID: state.IdentityID{ Role: types.RoleInstance, // RoleNode is not allowed NodeName: "testnode", diff --git a/lib/join/join_test.go b/lib/join/join_test.go index bb8b11fe1056..53bf73533638 100644 --- a/lib/join/join_test.go +++ b/lib/join/join_test.go @@ -40,6 +40,7 @@ import ( "google.golang.org/grpc/test/bufconn" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api" "github.com/gravitational/teleport/api/constants" @@ -229,8 +230,8 @@ func TestJoinToken(t *testing.T) { ctx := t.Context() // Node initially joins by connecting to the proxy's gRPC service. identity, err := joinViaProxyWithSecret( - ctx, - scopedToken1.GetMetadata().GetName(), + t.Context(), + scopes.QualifiedName{Scope: scopedToken1.GetScope(), Name: scopedToken1.GetMetadata().GetName()}.String(), scopedToken1.GetStatus().GetSecret(), proxyListener.Addr(), ) @@ -290,7 +291,7 @@ func TestJoinToken(t *testing.T) { // its original certificate and the new token. newIdentity, err := rejoinViaAuthClientWithSecret( t.Context(), - scopedToken3.GetMetadata().GetName(), + scopes.QualifiedName{Scope: scopedToken3.GetScope(), Name: scopedToken3.GetMetadata().GetName()}.String(), scopedToken3.GetStatus().GetSecret(), authClient, ) @@ -313,7 +314,7 @@ func TestJoinToken(t *testing.T) { // Node initially joins by connecting to the proxy's gRPC service. identity, err := joinViaProxyWithSecret( t.Context(), - scopedToken1.GetMetadata().GetName(), + scopes.QualifiedName{Scope: scopedToken1.GetScope(), Name: scopedToken1.GetMetadata().GetName()}.String(), scopedToken1.GetStatus().GetSecret(), proxyListener.Addr(), ) @@ -337,7 +338,7 @@ func TestJoinToken(t *testing.T) { // Node cannot rejoin with a different token assigning a different scope. _, err = rejoinViaAuthClient( t.Context(), - scopedToken2.GetMetadata().GetName(), + scopes.QualifiedName{Scope: scopedToken2.GetScope(), Name: scopedToken2.GetMetadata().GetName()}.String(), authClient, ) require.Error(t, err) @@ -401,8 +402,8 @@ func TestJoinToken(t *testing.T) { t.Run("join with single use scoped token", func(t *testing.T) { ctx := t.Context() identity, err := joinViaProxyWithSecret( - ctx, - singleUseToken.GetMetadata().GetName(), + t.Context(), + scopes.QualifiedName{Scope: singleUseToken.GetScope(), Name: singleUseToken.GetMetadata().GetName()}.String(), singleUseToken.GetStatus().GetSecret(), proxyListener.Addr(), ) @@ -421,7 +422,7 @@ func TestJoinToken(t *testing.T) { // ensure subsequent join attempts fail _, err = joinViaProxyWithSecret( t.Context(), - singleUseToken.GetMetadata().GetName(), + scopes.QualifiedName{Scope: singleUseToken.GetScope(), Name: singleUseToken.GetMetadata().GetName()}.String(), singleUseToken.GetStatus().GetSecret(), proxyListener.Addr(), ) @@ -515,7 +516,7 @@ func TestJoinToken(t *testing.T) { // Join with the original assigned scope. identity, err := joinViaProxyWithSecret( t.Context(), - token.GetMetadata().GetName(), + scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetMetadata().GetName()}.String(), token.GetStatus().GetSecret(), proxyListener.Addr(), ) @@ -525,6 +526,7 @@ func TestJoinToken(t *testing.T) { // Change and upsert token fetchedRes, err := authService.Auth().GetScopedToken(t.Context(), joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -544,7 +546,7 @@ func TestJoinToken(t *testing.T) { newIdentity, err := rejoinViaAuthClientWithSecret( t.Context(), - token.GetMetadata().GetName(), + scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetMetadata().GetName()}.String(), token.GetStatus().GetSecret(), authClient, ) @@ -846,13 +848,15 @@ type fakeAuthService struct { } func newFakeAuthService(t *testing.T) *fakeAuthService { + return newFakeAuthServiceWithScopesFeatures(t, scopes.Features{Enabled: true}) +} + +func newFakeAuthServiceWithScopesFeatures(t *testing.T, features scopes.Features) *fakeAuthService { testServer, err := authtest.NewTestServer(authtest.ServerConfig{ Auth: authtest.AuthServerConfig{ - Dir: t.TempDir(), - ClusterName: "testcluster", - ScopesFeatures: scopes.Features{ - Enabled: true, - }, + Dir: t.TempDir(), + ClusterName: "testcluster", + ScopesFeatures: features, }, }) require.NoError(t, err) @@ -993,6 +997,15 @@ func rejoinViaAuthClientWithSecret( token string, tokenSecret string, authClient authjoin.AuthJoinClient, +) (*state.Identity, error) { + return joinViaAuthClientWithSecret(ctx, token, tokenSecret, authClient) +} + +func joinViaAuthClientWithSecret( + ctx context.Context, + token string, + tokenSecret string, + authClient authjoin.AuthJoinClient, ) (*state.Identity, error) { joinResult, err := joinclient.Join(ctx, joinclient.JoinParams{ Token: token, @@ -1013,3 +1026,102 @@ func rejoinViaAuthClientWithSecret( } return state.ReadIdentityFromKeyPair(privateKeyPEM, joinResult.Certs) } + +func TestStaticScopedTokensAreValidated(t *testing.T) { + t.Parallel() + + expired := time.Now().Add(-time.Hour) + + tests := []struct { + name string + role types.SystemRole + expires *timestamppb.Timestamp + features scopes.Features + wantErr string + wantTokenScope string + }{ + { + name: "expired token is rejected", + role: types.RoleNode, + expires: timestamppb.New(expired), + features: scopes.Features{Enabled: true}, + wantErr: joining.ErrTokenExpired.Error(), + }, + { + name: "agent pin role gate is enforced", + role: types.RoleApp, + features: scopes.Features{Enabled: true}, + wantErr: "scoped token cannot be used to join [App] role(s) without TELEPORT_UNSTABLE_AGENT_SCOPE_PIN=yes", + }, + { + name: "valid token is accepted", + role: types.RoleNode, + features: scopes.Features{Enabled: true}, + wantTokenScope: "/test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + token := joiningv1.ScopedToken_builder{ + Kind: types.KindScopedToken, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "static-token", + Expires: tt.expires, + }.Build(), + Scope: scopes.Root, + Spec: joiningv1.ScopedTokenSpec_builder{ + AssignedScope: "/test", + Roles: []string{string(tt.role)}, + JoinMethod: string(types.JoinMethodToken), + UsageMode: string(joining.TokenUsageModeUnlimited), + }.Build(), + Status: joiningv1.ScopedTokenStatus_builder{ + Secret: "secret", + }.Build(), + }.Build() + + authService := newFakeAuthServiceWithScopesFeatures(t, tt.features) + require.NoError(t, authService.Auth().SetStaticScopedTokens(t.Context(), joiningv1.StaticScopedTokens_builder{ + Kind: types.KindStaticScopedTokens, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: types.MetaNameStaticScopedTokens, + }.Build(), + Scope: scopes.Root, + Spec: joiningv1.StaticScopedTokensSpec_builder{ + Tokens: []*joiningv1.ScopedToken{token}, + }.Build(), + }.Build())) + + unauthenticatedAuthClt, err := authService.NewClient(authtest.TestNop()) + require.NoError(t, err) + + tokenName := scopes.QualifiedName{ + Scope: token.GetScope(), + Name: token.GetMetadata().GetName(), + }.String() + if tt.wantErr != "" { + stream, err := joinv1.NewClient(unauthenticatedAuthClt.JoinV1Client()).Join(t.Context()) + require.NoError(t, err) + defer stream.CloseSend() + + require.NoError(t, stream.Send(&messages.ClientInit{ + TokenName: tokenName, + SystemRole: types.RoleInstance.String(), + })) + + _, err = messages.RecvResponse[*messages.ServerInit](stream) + require.ErrorContains(t, err, tt.wantErr) + return + } + + identity, err := joinViaAuthClientWithSecret(t.Context(), tokenName, token.GetStatus().GetSecret(), unauthenticatedAuthClt) + require.NoError(t, err) + require.Equal(t, tt.wantTokenScope, identity.AgentScope) + }) + } +} diff --git a/lib/join/oraclejoin/join_test.go b/lib/join/oraclejoin/join_test.go index b7ddcd1145e5..b7fc42be6bd1 100644 --- a/lib/join/oraclejoin/join_test.go +++ b/lib/join/oraclejoin/join_test.go @@ -364,7 +364,7 @@ func TestJoinOracle(t *testing.T) { scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(spec, joiningv1.ScopedToken_builder{ Scope: "/test", Metadata: headerv1.Metadata_builder{ - Name: "scoped_" + token.GetName(), + Name: token.GetName(), }.Build(), Spec: joiningv1.ScopedTokenSpec_builder{ AssignedScope: "/test/one", @@ -378,7 +378,8 @@ func TestJoinOracle(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := server.Auth().DeleteScopedToken(t.Context(), joiningv1.DeleteScopedTokenRequest_builder{ - Name: scopedToken.GetMetadata().GetName(), + Name: scopedToken.GetMetadata().GetName(), + Scope: scopedToken.GetScope(), }.Build()) require.NoError(t, err) }) @@ -397,7 +398,8 @@ func TestJoinOracle(t *testing.T) { t.Run("scoped", func(t *testing.T) { _, err = joinclient.Join(t.Context(), joinclient.JoinParams{ - Token: "scoped_" + tc.requestTokenName, + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: tc.requestTokenName}.String(), + TokenSecret: scopedToken.GetStatus().GetSecret(), ID: state.IdentityID{ Role: types.RoleInstance, }, diff --git a/lib/join/server.go b/lib/join/server.go index e1f6c10335e9..4e7752aa2b9b 100644 --- a/lib/join/server.go +++ b/lib/join/server.go @@ -171,95 +171,75 @@ func NewServer(cfg *ServerConfig) *Server { } } -// resolveScopedToken returns a scoped token by name from either the static scoped tokens configured -// for the cluster or by fetching a scoped token from the scoped token service. -func (s *Server) resolveScopedToken(ctx context.Context, name string) (*joiningv1.ScopedToken, error) { - staticTokens, err := s.cfg.AuthService.GetStaticScopedTokens(ctx) - if err != nil { - if !trace.IsNotFound(err) { - s.cfg.Logger.ErrorContext(ctx, "could not fetch static scoped tokens", "error", err) +// getProvisionToken attempts to resolve a [provision.Token] from the provided name. If +// the name is a Scope Qualified Name, then only [joiningv1.ScopedTokens] are considered +// for a match. If the name is _not_ a Scope Qualified Name, then a legacy [types.ProvisionTokenV2] +// is returned. +func (s *Server) getProvisionToken(ctx context.Context, name string) (provision.Token, error) { + // The token name was NOT a SQN so it must only match a [types.ProvisionTokenV2]. + if !scopes.MaybeSQN(name) || !s.cfg.ScopesFeatures.Enabled { + classic, err := s.cfg.AuthService.ValidateToken(ctx, name) + if err != nil { + return nil, trace.Wrap(err) } - } - // short circuit if a matching static scoped token is found - for _, tok := range staticTokens.GetSpec().GetTokens() { - if tok.GetMetadata().GetName() == name { - return tok, nil - } + return classic, nil } - res, err := s.cfg.ScopedTokenService.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: name, - WithSecret: true, - }.Build()) + qn, err := scopes.ParseQualifiedName(name) if err != nil { return nil, trace.Wrap(err) } - return res.GetToken(), nil -} -// getProvisionToken attempts to resolve a name to a [provision.Token] by first attempting to -// fetch a [joiningv1.ScopedToken] and then falling back to a [types.ProvisionTokenV2] if a -// scoped token can not be found. -func (s *Server) getProvisionToken(ctx context.Context, name string) (provision.Token, error) { - var scoped provision.Token - var scopedErr error - var foundScopedToken bool + // The token name was a SQN so it must only match a [joiningv1.ScopedToken]. + if err := qn.WeakValidate(); err != nil { + return nil, trace.Wrap(err) + } - var classic provision.Token - var classicErr error + staticTokens, err := s.cfg.AuthService.GetStaticScopedTokens(ctx) + if err != nil && !trace.IsNotFound(err) { + s.cfg.Logger.ErrorContext(ctx, "could not fetch static scoped tokens", "error", err) + } - wg := &sync.WaitGroup{} - wg.Go(func() { - token, err := s.resolveScopedToken(ctx, name) - if err != nil { - scopedErr = err - return - } - // we should deny on ambiguous token names regardless of whether or not scoped token validation - // returns an error, so we track that with foundScopedToken - foundScopedToken = true - if err := joining.ValidateTokenForUse(token, s.cfg.ScopesFeatures); err != nil { - scopedErr = err - return - } - scoped, scopedErr = joining.NewToken(token) - }) - wg.Go(func() { - // Fetch the provision token and validate that it is not expired. - classic, classicErr = s.cfg.AuthService.ValidateToken(ctx, name) - }) - wg.Wait() + // short circuit if a matching static scoped token is found + for _, tok := range staticTokens.GetSpec().GetTokens() { + if tok.GetMetadata().GetName() == qn.Name && tok.GetScope() == qn.Scope { + if err := joining.ValidateTokenForUse(tok, s.cfg.ScopesFeatures); err != nil { + return nil, trace.Wrap(err) + } - // we explicitly disallow a join if the provided token name returns both a scoped and classic provision token - if foundScopedToken && classic != nil { - return nil, trace.AccessDenied("joining with an ambiguous token name is not permitted") - } + token, err := joining.NewToken(tok) + if err != nil { + return nil, trace.Wrap(err) + } - if scoped != nil { - return scoped, nil + return token, nil + } } - if classic != nil { - return classic, nil - } + res, err := s.cfg.ScopedTokenService.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: qn.Name, + Scope: qn.Scope, + WithSecret: true, + }.Build()) + if err != nil { + if trace.IsNotFound(err) { + return nil, trace.AccessDenied("token expired or not found") + } - // if both errors are [trace.NotFoundError], just return a single err - if trace.IsNotFound(scopedErr) && trace.IsNotFound(classicErr) { - return nil, trace.NotFound("token expired or not found") + return nil, trace.Wrap(err) } - // prefer reporting errors other than [trace.NotFoundError] - if trace.IsNotFound(scopedErr) { - return nil, trace.Wrap(classicErr) + if err := joining.ValidateTokenForUse(res.GetToken(), s.cfg.ScopesFeatures); err != nil { + return nil, trace.Wrap(err) } - if trace.IsNotFound(classicErr) { - return nil, trace.Wrap(scopedErr) + token, err := joining.NewToken(res.GetToken()) + if err != nil { + return nil, trace.Wrap(err) } - // return both errors as an aggregate if we couldn't reasonably return one - return nil, trace.NewAggregate(scopedErr, classicErr) + return token, nil } // Join implements cluster joining for nodes and bots. diff --git a/lib/join/server_generic_oidc_test.go b/lib/join/server_generic_oidc_test.go index 168b3de80cce..038fd900c789 100644 --- a/lib/join/server_generic_oidc_test.go +++ b/lib/join/server_generic_oidc_test.go @@ -641,7 +641,8 @@ func TestJoinGenericOIDCScoped(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _, err := authServer.Auth().DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: token.GetMetadata().GetName(), + Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), }.Build()) assert.NoError(t, err) }) @@ -659,7 +660,7 @@ func TestJoinGenericOIDCScoped(t *testing.T) { } _, err = joinclient.Join(ctx, joinclient.JoinParams{ - Token: cmp.Or(tt.requestTokenName, token.GetMetadata().GetName()), + Token: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetMetadata().GetName()}.String(), ID: state.IdentityID{ Role: types.RoleInstance, NodeName: "test-node", diff --git a/lib/scopes/qualified.go b/lib/scopes/qualified.go index 5aa9f98bf66c..bc6fd8ad753d 100644 --- a/lib/scopes/qualified.go +++ b/lib/scopes/qualified.go @@ -112,20 +112,25 @@ func (q QualifiedName) WeakValidate() error { return nil } +// MaybeSQN returns true if the given string *might* be a scope-qualified name. This function is intended to be used +// for testing fields that may contain a mix of scope-qualified and unscoped names. Generally, any string that trips +// this check should be considered to have been intended to be an SQN by the user, and treated as a typo if it fails +// to parse as one. +func MaybeSQN(s string) bool { + return strings.HasPrefix(s, separator) || strings.Contains(s, QualifiedNameSeparator) +} + // ParseQualifiedName parses a scope-qualified name string into its scope and name // components by splitting on the first occurrence of "::". Returns an error if the // separator is absent or either component is empty. This function does not validate // the format of the scope or name components; use [QualifiedName.StrongValidate] or // [QualifiedName.WeakValidate] for validation. func ParseQualifiedName(sqn string) (QualifiedName, error) { - idx := strings.Index(sqn, QualifiedNameSeparator) - if idx < 0 { + scope, name, ok := strings.Cut(sqn, QualifiedNameSeparator) + if !ok { return QualifiedName{}, trace.BadParameter("scope-qualified name %q missing %q separator", sqn, QualifiedNameSeparator) } - scope := sqn[:idx] - name := sqn[idx+len(QualifiedNameSeparator):] - if scope == "" { return QualifiedName{}, trace.BadParameter("scope-qualified name %q has empty scope component", sqn) } diff --git a/lib/scopes/qualified_test.go b/lib/scopes/qualified_test.go index e16aebef8e8f..990bcb298c28 100644 --- a/lib/scopes/qualified_test.go +++ b/lib/scopes/qualified_test.go @@ -353,3 +353,11 @@ func TestSet(t *testing.T) { }) } } + +func TestMaybeQualifiedName(t *testing.T) { + require.True(t, MaybeSQN("/foo/bar::llama")) + require.True(t, MaybeSQN("/llama")) + require.False(t, MaybeSQN("llama")) + require.False(t, MaybeSQN("llama/")) + require.False(t, MaybeSQN("")) +} diff --git a/lib/service/connect.go b/lib/service/connect.go index 7b004faa20f7..33d75b7e4457 100644 --- a/lib/service/connect.go +++ b/lib/service/connect.go @@ -58,6 +58,7 @@ import ( grpcmetrics "github.com/gravitational/teleport/lib/observability/metrics/grpc" "github.com/gravitational/teleport/lib/openssh" "github.com/gravitational/teleport/lib/reversetunnelclient" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/scopes/joining" servicebreaker "github.com/gravitational/teleport/lib/service/breaker" "github.com/gravitational/teleport/lib/service/servicecfg" @@ -757,9 +758,22 @@ func (process *TeleportProcess) makeJoinParams( } tokenName, tokenSecret := token, "" - if name, secret, ok := joining.DecodeScopedToken(token); ok { - tokenName = name - tokenSecret = secret + if scopes.MaybeSQN(token) { + qn, err := scopes.ParseQualifiedName(token) + if err != nil { + return nil, trace.Wrap(err) + } + + if name, secret, ok := joining.DecodeScopedToken(qn.Name); ok { + qn.Name = name + tokenName = qn.String() + tokenSecret = secret + } + } else { + if name, secret, ok := joining.DecodeScopedToken(token); ok { + tokenName = name + tokenSecret = secret + } } dataDir := cmp.Or(process.Config.DataDir, defaults.DataDir) diff --git a/lib/service/service_test.go b/lib/service/service_test.go index 7dc16fff929d..d6278895db28 100644 --- a/lib/service/service_test.go +++ b/lib/service/service_test.go @@ -2729,7 +2729,6 @@ func TestInitScopedAppsFromConfig(t *testing.T) { require.NoError(t, err) authC := authProc.GetAuthServer() - encodedToken := joining.EncodeScopedToken(tokenName, tokenSecret) watchCtx, watchCancel := context.WithTimeout(t.Context(), 30*time.Second) t.Cleanup(watchCancel) @@ -2754,7 +2753,7 @@ func TestInitScopedAppsFromConfig(t *testing.T) { agentCfg.Version = defaults.TeleportConfigVersionV3 agentCfg.DataDir = makeTempDir(t) agentCfg.ProxyServer = utils.NetAddr{AddrNetwork: "tcp", Addr: proxyAddr} - agentCfg.SetToken(encodedToken) + agentCfg.SetToken(scopes.QualifiedName{Scope: scopes.Root, Name: joining.EncodeScopedToken(tokenName, tokenSecret)}.String()) agentCfg.JoinMethod = types.JoinMethodToken agentCfg.ScopesFeatures = scopes.Features{Enabled: true, AgentPinEnabled: true} agentCfg.Auth.Enabled = false diff --git a/lib/service/servicecfg/config.go b/lib/service/servicecfg/config.go index 6a45a5998bfd..522efb94a7dc 100644 --- a/lib/service/servicecfg/config.go +++ b/lib/service/servicecfg/config.go @@ -663,7 +663,11 @@ func (cfg *Config) ProxyWebAddr() utils.NetAddr { func (cfg *Config) Token() (string, error) { token, err := utils.TryReadValueAsFile(cfg.token) if err != nil { - return "", trace.Wrap(err) + if _, parseErr := scopes.ParseQualifiedName(cfg.token); parseErr != nil { + return "", trace.Wrap(err) + } + + return cfg.token, nil } return token, nil diff --git a/lib/services/local/provisioning_test.go b/lib/services/local/provisioning_test.go index 9f1e38ce2823..27e3d97879f2 100644 --- a/lib/services/local/provisioning_test.go +++ b/lib/services/local/provisioning_test.go @@ -365,12 +365,11 @@ func TestProvisioningServiceTokenNameConflict(t *testing.T) { require.NoError(t, err) token.SetName(scopedToken.GetMetadata().GetName()) - // assert that creating or upserting an unscoped token with a name that conflicts - // with a scoped token fails - err = service.CreateToken(ctx, token) - require.True(t, trace.IsAlreadyExists(err)) - err = service.UpsertToken(ctx, token) - require.True(t, trace.IsAlreadyExists(err)) + + // Assert that scoped tokens and unscoped tokens do not conflict + // if they have the same name. + require.NoError(t, service.CreateToken(ctx, token)) + require.NoError(t, service.UpsertToken(ctx, token)) } func newProvisioningService(t *testing.T, clock clockwork.Clock) *local.ProvisioningService { diff --git a/lib/services/local/scoped_tokens.go b/lib/services/local/scoped_tokens.go index 6e7d2443c3d3..f8fb1e153926 100644 --- a/lib/services/local/scoped_tokens.go +++ b/lib/services/local/scoped_tokens.go @@ -20,7 +20,6 @@ import ( "cmp" "context" "crypto/sha256" - "errors" "slices" "time" @@ -28,6 +27,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/retryutils" @@ -47,21 +47,21 @@ const ( // ScopedTokenService exposes backend functionality for working with scoped token resources. type ScopedTokenService struct { - svc *generic.ServiceWrapper[*joiningv1.ScopedToken] - backend backend.Backend + svc *generic.ScopeAwareServiceWrapper[*joiningv1.ScopedToken] scopesFeatures scopes.Features } // NewScopedTokenService creates a new ScopedTokenService. func NewScopedTokenService(b backend.Backend, scopesFeatures scopes.Features) (*ScopedTokenService, error) { const pageLimit = 100 - svc, err := generic.NewServiceWrapper(generic.ServiceConfig[*joiningv1.ScopedToken]{ - Backend: b, - PageLimit: pageLimit, - ResourceKind: types.KindScopedToken, - BackendPrefix: backend.NewKey(scopedTokenPrefix), - MarshalFunc: services.MarshalProtoResource[*joiningv1.ScopedToken], - UnmarshalFunc: services.UnmarshalProtoResource[*joiningv1.ScopedToken], + svc, err := generic.NewScopeAwareServiceWrapper(generic.ScopeAwareServiceWrapperConfig[*joiningv1.ScopedToken]{ + ScopedOnly: true, + Backend: b, + PageLimit: pageLimit, + ResourceKind: types.KindScopedToken, + ScopedBackendPrefix: backend.NewKey(scopedTokenPrefix), + MarshalFunc: services.MarshalProtoResource[*joiningv1.ScopedToken], + UnmarshalFunc: services.UnmarshalProtoResource[*joiningv1.ScopedToken], }) if err != nil { return nil, trace.Wrap(err) @@ -69,33 +69,10 @@ func NewScopedTokenService(b backend.Backend, scopesFeatures scopes.Features) (* return &ScopedTokenService{ svc: svc, - backend: b, scopesFeatures: scopesFeatures, }, nil } -func itemFromScopedToken(token *joiningv1.ScopedToken) (backend.Item, error) { - key := backend.NewKey(scopedTokenPrefix, token.GetMetadata().GetName()) - value, err := services.MarshalProtoResource(token) - if err != nil { - return backend.Item{}, trace.Wrap(err) - } - - // need to make sure expires is the zero value of [time.Time] unless an - // expiry is explicitly set, otherwise the backend item will be created - // in an expired state - var expires time.Time - if ex := token.GetMetadata().GetExpires(); ex != nil { - expires = ex.AsTime() - } - return backend.Item{ - Key: key, - Value: value, - Expires: expires, - Revision: token.GetMetadata().GetRevision(), - }, nil -} - // CreateScopedToken adds a scoped token to the auth server. func (s *ScopedTokenService) CreateScopedToken(ctx context.Context, req *joiningv1.CreateScopedTokenRequest) (*joiningv1.CreateScopedTokenResponse, error) { token := req.GetToken() @@ -112,35 +89,11 @@ func (s *ScopedTokenService) CreateScopedToken(ctx context.Context, req *joining return nil, trace.Wrap(err) } - item, err := itemFromScopedToken(token) + created, err := s.svc.CreateResource(ctx, token) if err != nil { return nil, trace.Wrap(err) } - revision, err := s.backend.AtomicWrite(ctx, []backend.ConditionalAction{ - { - Key: backend.NewKey(scopedTokenPrefix, token.GetMetadata().GetName()), - Condition: backend.NotExists(), - Action: backend.Put(item), - }, - { - Key: backend.NewKey(tokensPrefix, token.GetMetadata().GetName()), - Condition: backend.NotExists(), - // the second action is a no-op because we only need to - // execute a single action to create the scoped token, - // but both conditions must be met - Action: backend.Nop(), - }, - }) - if err != nil { - if errors.Is(err, backend.ErrConditionFailed) { - return nil, trace.AlreadyExists("scoped token could not be created due to name conflict with an existing scoped or unscoped token, please try again with a different name or delete the conflicting token") - } - return nil, trace.Wrap(err) - } - - created := proto.CloneOf(req.GetToken()) - created.GetMetadata().SetRevision(revision) return joiningv1.CreateScopedTokenResponse_builder{ Token: created, }.Build(), nil @@ -148,7 +101,12 @@ func (s *ScopedTokenService) CreateScopedToken(ctx context.Context, req *joining // GetScopedToken finds and returns a scoped token by name. func (s *ScopedTokenService) GetScopedToken(ctx context.Context, req *joiningv1.GetScopedTokenRequest) (*joiningv1.GetScopedTokenResponse, error) { - token, err := s.svc.GetResource(ctx, req.GetName()) + qn := scopes.QualifiedName{Scope: req.GetScope(), Name: req.GetName()} + if err := qn.WeakValidate(); err != nil { + return nil, trace.Wrap(err) + } + + token, err := s.svc.GetResource(ctx, qn) if err != nil { return nil, trace.Wrap(err) } @@ -304,15 +262,26 @@ func (s *ScopedTokenService) ListScopedTokens(ctx context.Context, req *joiningv // DeleteScopedToken deletes a scoped token by name. func (s *ScopedTokenService) DeleteScopedToken(ctx context.Context, req *joiningv1.DeleteScopedTokenRequest) (*joiningv1.DeleteScopedTokenResponse, error) { - return nil, trace.Wrap(s.svc.DeleteResource(ctx, req.GetName())) + qn := scopes.QualifiedName{Scope: req.GetScope(), Name: req.GetName()} + if err := qn.WeakValidate(); err != nil { + return nil, trace.Wrap(err) + } + return nil, trace.Wrap(s.svc.DeleteResource(ctx, qn)) } // UpsertScopedToken updates or creates a scoped token. If updating an existing token, the scope and status must not be modified. func (s *ScopedTokenService) UpsertScopedToken(ctx context.Context, req *joiningv1.UpsertScopedTokenRequest) (*joiningv1.UpsertScopedTokenResponse, error) { tokenUpsert := req.GetToken() + qn := scopes.QualifiedName{Scope: tokenUpsert.GetScope(), Name: tokenUpsert.GetMetadata().GetName()} + if err := qn.StrongValidate(); err != nil { + return nil, trace.Wrap(err) + } + // We handle 4 retry attempts to try and handle some concurrency. Handling more retries than this // indicates that something may be going wrong. + // + // TODO(scopes): reconsider upsert behavior now that scoped tokens are namespaced. for attempt := range maxTokenUpsertAttempts { if attempt != 0 { select { @@ -322,7 +291,7 @@ func (s *ScopedTokenService) UpsertScopedToken(ctx context.Context, req *joining } } - existingToken, err := s.svc.GetResource(ctx, tokenUpsert.GetMetadata().GetName()) + existingToken, err := s.svc.GetResource(ctx, qn) if err != nil { if !trace.IsNotFound(err) { return nil, trace.Wrap(err) @@ -395,7 +364,12 @@ func (s *ScopedTokenService) UpsertScopedToken(ctx context.Context, req *joining func (s *ScopedTokenService) UpdateScopedToken(ctx context.Context, req *joiningv1.UpdateScopedTokenRequest) (*joiningv1.UpdateScopedTokenResponse, error) { tokenUpdate := req.GetToken() - existingToken, err := s.svc.GetResource(ctx, tokenUpdate.GetMetadata().GetName()) + qn := scopes.QualifiedName{Scope: tokenUpdate.GetScope(), Name: tokenUpdate.GetMetadata().GetName()} + if err := qn.StrongValidate(); err != nil { + return nil, trace.Wrap(err) + } + + existingToken, err := s.svc.GetResource(ctx, qn) if err != nil { return nil, trace.Wrap(err) } @@ -426,12 +400,16 @@ func (s *ScopedTokenService) UpdateScopedToken(ctx context.Context, req *joining // update fails due to a revision comparison failure. func (s *ScopedTokenService) PatchScopedToken( ctx context.Context, - tokenName string, + tokenName scopes.QualifiedName, updateFn func(*joiningv1.ScopedToken) (*joiningv1.ScopedToken, error), ) (*joiningv1.ScopedToken, error) { const iterLimit = 3 - for i := 0; i < iterLimit; i++ { + if err := tokenName.StrongValidate(); err != nil { + return nil, trace.Wrap(err) + } + + for range iterLimit { existing, err := s.svc.GetResource(ctx, tokenName) if err != nil { return nil, trace.Wrap(err) @@ -514,17 +492,31 @@ func (p *scopedTokenParser) parse(event backend.Event) (types.Resource, error) { } return types.Resource153ToLegacy(scopedToken), nil case types.OpDelete: - name := event.Item.Key.TrimPrefix(backend.NewKey(scopedTokenPrefix)).String() - if name == "" { - return nil, trace.NotFound("failed parsing %v", event.Item.Key) + key := event.Item.Key.TrimPrefix(backend.NewKey(scopedTokenPrefix)) + + if len(key.Components()) != 2 { + return nil, trace.BadParameter("malformed scoped token key %q", event.Item.Key.String()) } - return &types.ResourceHeader{ + + encodedScope := key.Components()[0] + name := key.Components()[1] + if encodedScope == "" || name == "" { + return nil, trace.NotFound("failed parsing %q", event.Item.Key.String()) + } + + scope, err := scopes.DecodeFromKey(encodedScope) + if err != nil { + return nil, trace.Wrap(err, "failed decoding scope from scoped token key %q", event.Item.Key.String()) + } + + return types.Resource153ToLegacy(joiningv1.ScopedToken_builder{ Kind: types.KindScopedToken, Version: types.V1, - Metadata: types.Metadata{ + Scope: scope, + Metadata: headerv1.Metadata_builder{ Name: name, - }, - }, nil + }.Build(), + }.Build()), nil default: return nil, trace.BadParameter("event %v is not supported", event.Type) } diff --git a/lib/services/local/scoped_tokens_test.go b/lib/services/local/scoped_tokens_test.go index 6325256fbad9..f5c2d33c9f69 100644 --- a/lib/services/local/scoped_tokens_test.go +++ b/lib/services/local/scoped_tokens_test.go @@ -21,7 +21,6 @@ import ( "crypto/sha256" "fmt" "slices" - "sync" "testing" "testing/synctest" "time" @@ -47,6 +46,79 @@ import ( "github.com/gravitational/teleport/lib/services/local" ) +func TestScopedTokenEvents(t *testing.T) { + t.Parallel() + + synctest.Test(t, func(t *testing.T) { + ctx := t.Context() + + bk, err := memory.New(memory.Config{Context: ctx}) + require.NoError(t, err) + defer bk.Close() + + service, err := local.NewScopedTokenService(bk, scopes.Features{Enabled: true}) + require.NoError(t, err) + events := local.NewEventsService(bk) + token := newToken() + + watcher, err := events.NewWatcher(ctx, types.Watch{ + Kinds: []types.WatchKind{{ + Kind: types.KindScopedToken, + ScopeFilter: types.ScopeFilterFromProto(scopesv1.Filter_builder{Scope: token.GetScope(), Mode: scopesv1.Mode_MODE_EXACT}.Build()), + }}, + }) + require.NoError(t, err) + defer watcher.Close() + + getNextEvent := func() types.Event { + t.Helper() + synctest.Wait() + select { + case event := <-watcher.Events(): + return event + case <-watcher.Done(): + require.FailNow(t, "Watcher exited with error", watcher.Error()) + default: + require.FailNow(t, "No event ready, synctest bubble is durably blocked") + } + panic("unreachable") + } + + event := getNextEvent() + require.Equal(t, types.OpInit, event.Type) + + created, err := service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{ + Token: token, + }.Build()) + require.NoError(t, err) + + event = getNextEvent() + require.Equal(t, types.OpPut, event.Type) + putToken, ok := event.Resource.(types.Resource153UnwrapperT[*joiningv1.ScopedToken]) + require.True(t, ok) + require.Empty(t, gocmp.Diff(created.GetToken(), putToken.UnwrapT(), protocmp.Transform())) + + _, err = service.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ + Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), + }.Build()) + require.NoError(t, err) + + event = getNextEvent() + require.Equal(t, types.OpDelete, event.Type) + deletedToken, ok := event.Resource.(types.Resource153UnwrapperT[*joiningv1.ScopedToken]) + require.True(t, ok) + require.Empty(t, gocmp.Diff(joiningv1.ScopedToken_builder{ + Kind: types.KindScopedToken, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: token.GetMetadata().GetName(), + }.Build(), + Scope: token.GetScope(), + }.Build(), deletedToken.UnwrapT(), protocmp.Transform())) + }) +} + func TestScopedTokenService(t *testing.T) { bk, err := memory.New(memory.Config{}) require.NoError(t, err) @@ -92,7 +164,8 @@ func TestScopedTokenService(t *testing.T) { require.NoError(t, err) fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: token.GetMetadata().GetName(), + Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), }.Build()) require.NoError(t, err) require.Equal(t, fetched.GetToken().GetSpec().GetAssignedScope(), updated.GetToken().GetSpec().GetAssignedScope()) @@ -104,6 +177,7 @@ func TestScopedTokenService(t *testing.T) { fetched, err = service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -111,11 +185,13 @@ func TestScopedTokenService(t *testing.T) { assert.Empty(t, gocmp.Diff(updated.GetToken(), fetched.GetToken(), cmpOpts...)) _, err = service.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ - Name: fetched.GetToken().GetMetadata().GetName(), + Name: fetched.GetToken().GetMetadata().GetName(), + Scope: fetched.GetToken().GetScope(), }.Build()) require.NoError(t, err) _, err = service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: fetched.GetToken().GetMetadata().GetName(), + Name: fetched.GetToken().GetMetadata().GetName(), + Scope: fetched.GetToken().GetScope(), }.Build()) require.True(t, trace.IsNotFound(err)) @@ -145,12 +221,14 @@ func TestScopedTokenService(t *testing.T) { require.Nil(t, expiredToken) _, err = service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ - Name: expiredRes.GetToken().GetMetadata().GetName(), + Name: expiredRes.GetToken().GetMetadata().GetName(), + Scope: expiredRes.GetToken().GetScope(), }.Build()) require.True(t, trace.IsNotFound(err)) fetchedActive, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: activeRes.GetToken().GetMetadata().GetName(), + Scope: activeRes.GetToken().GetScope(), WithSecret: true, }.Build()) @@ -164,8 +242,6 @@ func TestScopedTokenList(t *testing.T) { service, err := local.NewScopedTokenService(bk, scopes.Features{Enabled: true}) require.NoError(t, err) - ctx := t.Context() - test := joiningv1.ScopedToken_builder{ Kind: types.KindScopedToken, Version: types.V1, @@ -226,7 +302,7 @@ func TestScopedTokenList(t *testing.T) { allTokens := []*joiningv1.ScopedToken{test, test1, test2, test3, test4, stage, stage1, stage2} for _, token := range allTokens { - _, err = service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{Token: token}.Build()) + _, err = service.CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) } @@ -379,6 +455,7 @@ func TestScopedTokenList(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { + ctx := t.Context() req := proto.CloneOf(c.req) req.SetLimit(10) res, err := service.ListScopedTokens(ctx, req) @@ -404,112 +481,6 @@ func TestScopedTokenList(t *testing.T) { } } -func TestScopedTokenNameCollisions(t *testing.T) { - bk, err := memory.New(memory.Config{}) - require.NoError(t, err) - service, err := local.NewScopedTokenService(bk, scopes.Features{Enabled: true}) - require.NoError(t, err) - - provisioningService := local.NewProvisioningService(bk) - - ctx := t.Context() - - token := joiningv1.ScopedToken_builder{ - Kind: types.KindScopedToken, - Version: types.V1, - Metadata: headerv1.Metadata_builder{ - Name: "testtoken", - }.Build(), - Scope: "/test", - Spec: joiningv1.ScopedTokenSpec_builder{ - AssignedScope: "/test/one", - JoinMethod: "token", - Roles: []string{types.RoleNode.String()}, - UsageMode: string(joining.TokenUsageModeUnlimited), - }.Build(), - Status: joiningv1.ScopedTokenStatus_builder{ - Secret: "secret", - }.Build(), - }.Build() - - t.Run("basic", func(t *testing.T) { - // create initial scoped token - _, err = service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{ - Token: token, - }.Build()) - require.NoError(t, err) - - // assert that creating another scoped token with the same name fails - _, err = service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{ - Token: token, - }.Build()) - require.True(t, trace.IsAlreadyExists(err)) - - // create a 'classic' token - classicToken := &types.ProvisionTokenV2{ - Metadata: types.Metadata{ - Name: "testtoken2", - }, - Spec: types.ProvisionTokenSpecV2{ - Roles: []types.SystemRole{ - types.RoleAdmin, - }, - }, - } - err = provisioningService.CreateToken(ctx, classicToken) - require.NoError(t, err) - - // assert that creating a scoped token with a name that conflicts with - // a classic token fails - conflictWithClassic := proto.CloneOf(token) - conflictWithClassic.GetMetadata().SetName(classicToken.GetName()) - _, err = service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{ - Token: conflictWithClassic, - }.Build()) - require.True(t, trace.IsAlreadyExists(err)) - }) - - t.Run("concurrent", func(t *testing.T) { - for i := range 50 { - name := fmt.Sprintf("testtoken-%d", i) - var classicErr error - var scopedErr error - wg := sync.WaitGroup{} - wg.Go(func() { - classicErr = provisioningService.CreateToken(ctx, &types.ProvisionTokenV2{ - Metadata: types.Metadata{ - Name: name, - }, - Spec: types.ProvisionTokenSpecV2{ - Roles: []types.SystemRole{ - types.RoleAdmin, - }, - }, - }) - }) - - wg.Go(func() { - token := proto.CloneOf(token) - token.GetMetadata().SetName(name) - _, scopedErr = service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{ - Token: token, - }.Build()) - }) - wg.Wait() - - // One token type should always succeed and the other should always fail. When one succeeds, - // the other should always be a [trace.AlreadyExistsError] due to the name conflict. - if classicErr == nil { - require.True(t, trace.IsAlreadyExists(scopedErr)) - } else if scopedErr == nil { - require.True(t, trace.IsAlreadyExists(classicErr)) - } else { - require.Fail(t, "unexpected failure to create either a scoped or classic token", name) - } - } - }) -} - func TestScopedTokenUse(t *testing.T) { synctest.Test(t, func(t *testing.T) { bk, err := memory.New(memory.Config{}) @@ -623,8 +594,6 @@ func TestScopedTokenUpdate(t *testing.T) { service, err := local.NewScopedTokenService(backend.NewSanitizer(bk), scopes.Features{Enabled: true}) require.NoError(t, err) - ctx := t.Context() - cases := []struct { name string mutate func(t *testing.T, update *joiningv1.ScopedToken) @@ -641,16 +610,6 @@ func TestScopedTokenUpdate(t *testing.T) { require.Equal(t, "test", result.GetMetadata().GetLabels()["env"]) }, }, - { - name: "scope changes result in an error", - mutate: func(t *testing.T, update *joiningv1.ScopedToken) { - update.SetScope("/other") - }, - assert: func(t *testing.T, created, result *joiningv1.ScopedToken, err error) { - require.ErrorContains(t, err, "cannot modify scope of existing scoped token") - - }, - }, { name: "usage mode changes result in an error", mutate: func(t *testing.T, update *joiningv1.ScopedToken) { @@ -683,6 +642,7 @@ func TestScopedTokenUpdate(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() created, err := service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) @@ -699,12 +659,13 @@ func TestScopedTokenUpdate(t *testing.T) { t.Run("fails for nonexistent token", func(t *testing.T) { t.Parallel() token := newToken() - _, err := service.UpdateScopedToken(ctx, joiningv1.UpdateScopedTokenRequest_builder{Token: token}.Build()) + _, err := service.UpdateScopedToken(t.Context(), joiningv1.UpdateScopedTokenRequest_builder{Token: token}.Build()) require.True(t, trace.IsNotFound(err)) }) t.Run("editing concurrently fails since revision doesn't match", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() created, err := service.CreateScopedToken(ctx, joiningv1.CreateScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) @@ -732,8 +693,6 @@ func TestScopedTokenUpsert(t *testing.T) { service, err := local.NewScopedTokenService(backend.NewSanitizer(bk), scopes.Features{Enabled: true}) require.NoError(t, err) - ctx := t.Context() - cmpOpts := []gocmp.Option{ protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), protocmp.Transform(), @@ -741,6 +700,7 @@ func TestScopedTokenUpsert(t *testing.T) { t.Run("upsert creates a new entry", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() upsertedToken, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) @@ -748,6 +708,7 @@ func TestScopedTokenUpsert(t *testing.T) { fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: upsertedToken.GetToken().GetMetadata().GetName(), + Scope: upsertedToken.GetToken().GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -773,7 +734,7 @@ func TestScopedTokenUpsert(t *testing.T) { }.Build(), }.Build() - upserted, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) + upserted, err := service.UpsertScopedToken(t.Context(), joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) require.NotEmpty(t, upserted.GetToken().GetMetadata().GetName(), "name should be generated") require.Equal(t, string(types.JoinMethodToken), upserted.GetToken().GetSpec().GetJoinMethod(), "join method should default to token") @@ -782,6 +743,7 @@ func TestScopedTokenUpsert(t *testing.T) { t.Run("upsert updates existing entry", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() _, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) @@ -795,6 +757,7 @@ func TestScopedTokenUpsert(t *testing.T) { fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -807,7 +770,7 @@ func TestScopedTokenUpsert(t *testing.T) { token := newBoundKeypairToken() token.GetSpec().ClearBoundKeypair() - _, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) + _, err := service.UpsertScopedToken(t.Context(), joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.ErrorContains(t, err, "bound_keypair tokens require a non-nil spec.bound_keypair") }) @@ -816,7 +779,7 @@ func TestScopedTokenUpsert(t *testing.T) { token := newBoundKeypairToken() - upserted, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) + upserted, err := service.UpsertScopedToken(t.Context(), joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) spec := upserted.GetToken().GetSpec().GetBoundKeypair() @@ -839,7 +802,7 @@ func TestScopedTokenUpsert(t *testing.T) { RegistrationSecret: "abc123", }.Build()) - upserted, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) + upserted, err := service.UpsertScopedToken(t.Context(), joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) status := upserted.GetToken().GetStatus().GetUsage().GetBoundKeypair() @@ -854,7 +817,7 @@ func TestScopedTokenUpsert(t *testing.T) { InitialPublicKey: "abc123", }.Build()) - upserted, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) + upserted, err := service.UpsertScopedToken(t.Context(), joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) status := upserted.GetToken().GetStatus().GetUsage().GetBoundKeypair() @@ -868,14 +831,6 @@ func TestScopedTokenUpsert(t *testing.T) { mutate func(t *testing.T, update *joiningv1.ScopedToken) wantErr string }{ - { - name: "scope is changed", - mutate: func(t *testing.T, update *joiningv1.ScopedToken) { - update.SetScope("/other") - update.GetSpec().SetAssignedScope("/other/one") - }, - wantErr: "cannot modify scope of existing scoped token", - }, { name: "usage mode is changed", mutate: func(t *testing.T, update *joiningv1.ScopedToken) { @@ -889,6 +844,7 @@ func TestScopedTokenUpsert(t *testing.T) { for _, tc := range rejectCases { t.Run("upsert fails because "+tc.name, func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() _, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) @@ -903,6 +859,7 @@ func TestScopedTokenUpsert(t *testing.T) { t.Run("secret is preserved when not included", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() created, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) require.NoError(t, err) @@ -920,6 +877,7 @@ func TestScopedTokenUpsert(t *testing.T) { // confirm the original secret is still in place fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -928,6 +886,7 @@ func TestScopedTokenUpsert(t *testing.T) { t.Run("upsert succeeds when revisions don't match", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() _, err := service.UpsertScopedToken(ctx, joiningv1.UpsertScopedTokenRequest_builder{Token: token}.Build()) @@ -935,6 +894,7 @@ func TestScopedTokenUpsert(t *testing.T) { fetched1, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -954,6 +914,7 @@ func TestScopedTokenUpsert(t *testing.T) { // Verify that update2 succeeded and overwrote update1's label final, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -962,6 +923,7 @@ func TestScopedTokenUpsert(t *testing.T) { t.Run("concurrent upserts succeed", func(t *testing.T) { t.Parallel() + ctx := t.Context() token := newToken() // Simulate 4 users concurrently upserting the same token with @@ -985,6 +947,7 @@ func TestScopedTokenUpsert(t *testing.T) { // Verify the token still exists and is consistent. final, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -1019,6 +982,7 @@ func TestScopedTokenCreate(t *testing.T) { fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: created.GetToken().GetMetadata().GetName(), + Scope: created.GetToken().GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) @@ -1034,6 +998,7 @@ func TestScopedTokenCreate(t *testing.T) { fetched, err := service.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ Name: created.GetToken().GetMetadata().GetName(), + Scope: created.GetToken().GetScope(), WithSecret: true, }.Build()) require.NoError(t, err) diff --git a/lib/services/scoped_tokens.go b/lib/services/scoped_tokens.go index 52aca92e26a5..3c553ebbfc60 100644 --- a/lib/services/scoped_tokens.go +++ b/lib/services/scoped_tokens.go @@ -20,6 +20,7 @@ import ( "context" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" + "github.com/gravitational/teleport/lib/scopes" ) // ScopedTokenService handles CRUD operations for the ScopedToken resource. @@ -55,7 +56,7 @@ type ScopedTokenService interface { // update fails due to a revision comparison failure. PatchScopedToken( ctx context.Context, - tokenName string, + tokenName scopes.QualifiedName, updateFn func(*joiningv1.ScopedToken) (*joiningv1.ScopedToken, error), ) (*joiningv1.ScopedToken, error) } diff --git a/lib/tbot/bot/onboarding/config.go b/lib/tbot/bot/onboarding/config.go index 09a12d76b088..dc9a4c2164da 100644 --- a/lib/tbot/bot/onboarding/config.go +++ b/lib/tbot/bot/onboarding/config.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/utils" ) @@ -282,6 +283,14 @@ func (conf *Config) SetToken(token string) { func (conf *Config) Token() (string, error) { token, err := utils.TryReadValueAsFile(conf.TokenValue) if err != nil { + // A scoped token with a Scope Qualified Name looks like a file path + // and will result in file not found errors. If the provided token is + // a SQN, then the request is rejected. Scoped tokens can only be used + // in the new join service. + if _, err := scopes.ParseQualifiedName(conf.TokenValue); err == nil { + return conf.TokenValue, nil + } + return "", trace.Wrap(err) } diff --git a/lib/tbot/tbot_test.go b/lib/tbot/tbot_test.go index c990e3885a80..a7bf6e02ec88 100644 --- a/lib/tbot/tbot_test.go +++ b/lib/tbot/tbot_test.go @@ -1481,7 +1481,7 @@ func TestScopedBotSSH(t *testing.T) { nodeCfg.ScopesFeatures = scopes.Features{Enabled: true} nodeCfg.Hostname = nodeHostname nodeCfg.DataDir = t.TempDir() - nodeCfg.SetToken(jointoken.EncodeScopedToken(nodeTokenResp.GetToken().GetMetadata().GetName(), nodeTokenResp.GetToken().GetStatus().GetSecret())) + nodeCfg.SetToken(scopes.QualifiedName{Scope: scopeName, Name: jointoken.EncodeScopedToken(nodeTokenResp.GetToken().GetMetadata().GetName(), nodeTokenResp.GetToken().GetStatus().GetSecret())}.String()) nodeCfg.SetAuthServerAddress(process.Config.Auth.ListenAddr) nodeCfg.Auth.Enabled = false nodeCfg.Proxy.Enabled = false @@ -2162,7 +2162,7 @@ func createScopedBot( waitForSRACache(t, process.GetAuthServer(), sraResp) return &onboarding.Config{ - TokenValue: botTokenResp.GetToken().GetMetadata().GetName(), + TokenValue: scopes.QualifiedName{Scope: scopeName, Name: botTokenResp.GetToken().GetMetadata().GetName()}.String(), JoinMethod: types.JoinMethodBoundKeypair, BoundKeypair: onboarding.BoundKeypairOnboardingConfig{ StaticPrivateKeyPath: botKeyPath, diff --git a/lib/tbot/workloadidentity/issue.go b/lib/tbot/workloadidentity/issue.go index b9b1702b1fb7..169bf59ed283 100644 --- a/lib/tbot/workloadidentity/issue.go +++ b/lib/tbot/workloadidentity/issue.go @@ -21,7 +21,6 @@ import ( "crypto" "crypto/x509" "log/slog" - "strings" "time" "github.com/gravitational/trace" @@ -40,7 +39,7 @@ import ( // travels in the issuance request and the server matches it against the named // resource's scope. func parseNameSelector(selector string) (name, scope string, err error) { - if !strings.Contains(selector, scopes.QualifiedNameSeparator) { + if !scopes.MaybeSQN(selector) { return selector, "", nil } qn, err := scopes.ParseQualifiedName(selector) diff --git a/tool/tctl/common/edit_command_test.go b/tool/tctl/common/edit_command_test.go index b8b54de62477..a1a98309116a 100644 --- a/tool/tctl/common/edit_command_test.go +++ b/tool/tctl/common/edit_command_test.go @@ -753,7 +753,11 @@ func testEditScopedToken(t *testing.T, clt *authclient.Client) { }, withEditor(editor)) require.NoError(t, err) - actual, err := clt.GetScopedToken(ctx, created.GetMetadata().GetName(), true) + actual, err := clt.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: created.GetMetadata().GetName(), + Scope: created.GetScope(), + WithSecret: true, + }.Build()) require.NoError(t, err) require.Equal(t, "test", actual.GetMetadata().GetLabels()["env"]) diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index 51fdf745adaf..c82d9a58b353 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -848,7 +848,7 @@ spec: // to provide hints as namespacing may prevent it. _, err = runResourceCommand(t, clt, []string{"get", "scoped_token", "/other-scope::gcp-test-token", "--format=json"}) require.True(t, trace.IsNotFound(err), "expected NotFound for scope mismatch, got: %v", err) - require.ErrorContains(t, err, "tctl get scoped_token /::gcp-test-token") + require.ErrorContains(t, err, `scoped_token "gcp-test-token" doesn't exist`) } // TestScopedAndUnscopedNodeResource exercises tctl get/rm on nodes which are double diff --git a/tool/tctl/common/resources/scoped_token.go b/tool/tctl/common/resources/scoped_token.go index 484562dc4a54..24ec9cf804f2 100644 --- a/tool/tctl/common/resources/scoped_token.go +++ b/tool/tctl/common/resources/scoped_token.go @@ -119,13 +119,14 @@ func getScopedToken(ctx context.Context, client *authclient.Client, subKind stri } if sqn != nil { - token, err := client.GetScopedToken(ctx, sqn.Name, opts.WithSecrets) + token, err := client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: sqn.Name, + Scope: sqn.Scope, + WithSecret: opts.WithSecrets, + }.Build()) if err != nil { return nil, trace.Wrap(err) } - if token.GetScope() != sqn.Scope { - return nil, scopeMismatchNotFound(types.KindScopedToken, *sqn, token.GetScope()) - } if !opts.WithSecrets && token.GetStatus().GetSecret() != "" { token.GetStatus().SetSecret("******") } @@ -172,7 +173,10 @@ func deleteScopedToken(ctx context.Context, client *authclient.Client, subKind s } // Fetch first to verify scope before deleting. - token, err := client.GetScopedToken(ctx, sqn.Name, false) + token, err := client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: sqn.Name, + Scope: sqn.Scope, + }.Build()) if err != nil { return trace.Wrap(err) } @@ -180,7 +184,10 @@ func deleteScopedToken(ctx context.Context, client *authclient.Client, subKind s return scopeMismatchNotFound(types.KindScopedToken, sqn, token.GetScope()) } - if err := client.DeleteScopedToken(ctx, sqn.Name); err != nil { + if err := client.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ + Name: sqn.Name, + Scope: sqn.Scope, + }.Build()); err != nil { return trace.Wrap(err) } fmt.Printf( @@ -193,7 +200,6 @@ func deleteScopedToken(ctx context.Context, client *authclient.Client, subKind s func ScopedTokenTextHelper(tokens []*joiningv1.ScopedToken, withSecrets bool) *bytes.Buffer { headers := []string{ - "ID", "Token", "Type", "Assigns Scope", @@ -212,14 +218,13 @@ func ScopedTokenTextHelper(tokens []*joiningv1.ScopedToken, withSecrets bool) *b expiry = fmt.Sprintf("%s (%s)", exptime, expdur.String()) } - token := t.GetMetadata().GetName() + ":*****" + token := t.GetMetadata().GetName() if withSecrets { token = joining.EncodeScopedToken(t.GetMetadata().GetName(), t.GetStatus().GetSecret()) } row := []string{ - scopes.QualifiedName{Scope: t.GetScope(), Name: t.GetMetadata().GetName()}.String(), - token, + scopes.QualifiedName{Scope: t.GetScope(), Name: token}.String(), strings.Join(t.GetSpec().GetRoles(), ","), t.GetSpec().GetAssignedScope(), PrintMetadataLabels(t.GetMetadata().GetLabels()), diff --git a/tool/tctl/common/scoped_ref.go b/tool/tctl/common/scoped_ref.go index 52a804a3c541..86b3f6eb1c9e 100644 --- a/tool/tctl/common/scoped_ref.go +++ b/tool/tctl/common/scoped_ref.go @@ -106,7 +106,7 @@ func ParseScopedRef(ref, id string) (ScopedRef, error) { // the old format always treats token/token as kind/name, but if id was set then the second token // is actually a subkind in the new format. subKind := r.Name - if strings.Contains(id, scopes.QualifiedNameSeparator) { + if scopes.MaybeSQN(id) { qn, err := scopes.ParseQualifiedName(id) if err != nil { return ScopedRef{}, trace.Wrap(err) diff --git a/tool/tctl/common/scoped_token_command.go b/tool/tctl/common/scoped_token_command.go index 1bb0b959a381..c6386f183c8d 100644 --- a/tool/tctl/common/scoped_token_command.go +++ b/tool/tctl/common/scoped_token_command.go @@ -42,6 +42,7 @@ import ( "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/scopes/joining" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/parse" @@ -226,15 +227,17 @@ func (c *ScopedTokensCommand) Add(ctx context.Context, client *authclient.Client return trace.Wrap(err, "creating scoped token") } - token := joining.EncodeScopedToken(tok.GetMetadata().GetName(), tok.GetStatus().GetSecret()) + token := scopes.QualifiedName{ + Name: joining.EncodeScopedToken(tok.GetMetadata().GetName(), tok.GetStatus().GetSecret()), + Scope: tok.GetScope(), + } // Print token information formatted with JSON, YAML, or just print the raw token. switch c.format { case teleport.JSON, teleport.YAML: expires := time.Now().Add(c.ttl) tokenInfo := map[string]any{ - "token": token, + "token": token.String(), "roles": roles, - "scope": tok.GetScope(), "assign_scope": tok.GetSpec().GetAssignedScope(), "expires": expires, } @@ -263,7 +266,7 @@ func (c *ScopedTokensCommand) Add(ctx context.Context, client *authclient.Client out: c.Stdout, ttl: c.ttl, roles: roles, - token: token, + token: token.String(), client: client, })) } @@ -273,7 +276,23 @@ func (c *ScopedTokensCommand) Del(ctx context.Context, client *authclient.Client if c.name == "" { return trace.BadParameter("Need an argument: token") } - if err := client.DeleteScopedToken(ctx, c.name); err != nil { + + qn, err := scopes.ParseQualifiedName(c.name) + if err != nil { + return trace.Wrap(err) + } + + if err := qn.WeakValidate(); err != nil { + return trace.Wrap(err) + } + + // strip any secrets included in the token name + name, _, _ := joining.DecodeScopedToken(qn.Name) + + if err := client.DeleteScopedToken(ctx, joiningv1.DeleteScopedTokenRequest_builder{ + Name: name, + Scope: qn.Scope, + }.Build()); err != nil { return trace.Wrap(err) } fmt.Fprintf(c.Stdout, "Token %s has been deleted\n", c.name) @@ -323,7 +342,7 @@ func (c *ScopedTokensCommand) List(ctx context.Context, client *authclient.Clien } case teleport.Text: for _, token := range tokens { - fmt.Fprintln(c.Stdout, token.GetMetadata().GetName()) + fmt.Fprintln(c.Stdout, scopes.QualifiedName{Name: token.GetMetadata().GetName(), Scope: token.GetScope()}.String()) } default: fmt.Fprint(c.Stdout, resources.ScopedTokenTextHelper(tokens, c.withSecrets).String()) diff --git a/tool/tctl/common/scoped_token_command_test.go b/tool/tctl/common/scoped_token_command_test.go index 1faaea36da8c..a60db4798f4a 100644 --- a/tool/tctl/common/scoped_token_command_test.go +++ b/tool/tctl/common/scoped_token_command_test.go @@ -125,7 +125,7 @@ func TestScopedTokens(t *testing.T) { // Test all output formats of "tokens ls". buf, err = runScopedCommand(t, clt, []string{"tokens", "ls"}) require.NoError(t, err) - require.True(t, strings.HasPrefix(buf.String(), "ID")) + require.True(t, strings.HasPrefix(buf.String(), "Token")) require.Equal(t, 8, strings.Count(buf.String(), "\n")) // account for header lines buf, err = runScopedCommand(t, clt, []string{"tokens", "ls", "--format", teleport.Text}) diff --git a/tool/tctl/common/terraform_command.go b/tool/tctl/common/terraform_command.go index 0ba75c268bf9..ea41fb15c2b2 100644 --- a/tool/tctl/common/terraform_command.go +++ b/tool/tctl/common/terraform_command.go @@ -210,7 +210,7 @@ func (c *TerraformCommand) RunEnvCommand(ctx context.Context, client *authclient }() onboard = onboarding.Config{ - TokenValue: token.GetMetadata().GetName(), + TokenValue: scopes.QualifiedName{Scope: token.GetScope(), Name: token.GetMetadata().GetName()}.String(), JoinMethod: types.JoinMethodBoundKeypair, BoundKeypair: onboarding.BoundKeypairOnboardingConfig{ RegistrationSecretValue: token.GetStatus().GetUsage().GetBoundKeypair().GetRegistrationSecret(), diff --git a/tool/tctl/common/workload_identity_command.go b/tool/tctl/common/workload_identity_command.go index f67973f994c8..3b7467fbf92d 100644 --- a/tool/tctl/common/workload_identity_command.go +++ b/tool/tctl/common/workload_identity_command.go @@ -255,7 +255,7 @@ func (c *WorkloadIdentityCommand) DeleteWorkloadIdentity( // Provided name may be unscoped or an SQN name := c.workloadIdentityName var scope string - if strings.Contains(name, scopes.QualifiedNameSeparator) { + if scopes.MaybeSQN(name) { qn, err := scopes.ParseQualifiedName(name) if err != nil { return trace.Wrap(err) From b7c0bcdbffe24df44f9005a6c789544f1947ef7e Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Mon, 27 Jul 2026 18:10:51 -0400 Subject: [PATCH 018/347] adding scoped kube server backend and dynamic scoped kube cluster (#68987) registration --- api/client/client.go | 24 +- .../teleport/presence/v1/service_grpc.pb.go | 40 +++ .../presence/v1/service_protohybrid.pb.go | 256 ++++++++++++++---- .../presence/v1/service_protoopaque.pb.go | 252 +++++++++++++---- api/proto/teleport/presence/v1/service.proto | 17 ++ lib/auth/auth_with_roles.go | 35 ++- lib/auth/auth_with_roles_test.go | 29 +- lib/auth/grpcserver.go | 7 +- lib/auth/presence/presencev1/service.go | 37 +++ lib/cache/cache.go | 3 +- lib/cache/kube.go | 41 +-- lib/cache/kube_test.go | 96 ++++++- lib/inventory/controller.go | 8 +- lib/inventory/controller_test.go | 2 +- lib/join/join_ec2_test.go | 5 +- lib/kube/proxy/exec_test.go | 2 +- lib/kube/proxy/server.go | 4 - lib/kube/proxy/utils_test.go | 35 ++- lib/kube/proxy/watcher.go | 9 +- lib/kube/proxy/watcher_test.go | 157 ++++++++++- lib/services/app.go | 7 +- lib/services/kubernetes.go | 7 + lib/services/local/events.go | 49 +++- lib/services/local/presence.go | 203 ++++++++------ lib/services/local/presence_test.go | 39 ++- lib/services/presence.go | 4 +- lib/services/unified_resource_test.go | 12 +- lib/services/watcher.go | 11 +- lib/services/watcher_test.go | 12 +- tool/tctl/common/resources/kube_server.go | 7 +- 30 files changed, 1125 insertions(+), 285 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 7f9409d0446f..c20af2ffff59 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -1538,11 +1538,29 @@ func (c *Client) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, } // DeleteKubernetesServer deletes a named kubernetes server. +// +// TODO (eriktate): remove in v20 func (c *Client) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { - _, err := c.grpc.DeleteKubernetesServer(ctx, &proto.DeleteKubernetesServerRequest{ - HostID: hostID, + return c.DeleteKubeServer(ctx, presencepb.DeleteKubeServerRequest_builder{ + HostId: hostID, Name: name, - }) + }.Build()) +} + +// DeleteKubeServer deletes a named kubernetes server with respect to scopes. +func (c *Client) DeleteKubeServer(ctx context.Context, req *presencepb.DeleteKubeServerRequest) error { + _, err := c.PresenceServiceClient().DeleteKubeServer(ctx, req) + if trace.IsNotImplemented(err) { + if req.GetScope() != "" { + return trace.BadParameter("requesting deletion of scoped kube server from an outdated Teleport control plane that does not support it") + } + //nolint:staticcheck // TODO(eriktate): deprecated, to be removed in v20 + _, err := c.grpc.DeleteKubernetesServer(ctx, &proto.DeleteKubernetesServerRequest{ + HostID: req.GetHostId(), + Name: req.GetName(), + }) + return trace.Wrap(err) + } return trace.Wrap(err) } diff --git a/api/gen/proto/go/teleport/presence/v1/service_grpc.pb.go b/api/gen/proto/go/teleport/presence/v1/service_grpc.pb.go index 6d194188f59a..a4706e46b479 100644 --- a/api/gen/proto/go/teleport/presence/v1/service_grpc.pb.go +++ b/api/gen/proto/go/teleport/presence/v1/service_grpc.pb.go @@ -52,6 +52,7 @@ const ( PresenceService_GetKubeCluster_FullMethodName = "/teleport.presence.v1.PresenceService/GetKubeCluster" PresenceService_ListKubeClusters_FullMethodName = "/teleport.presence.v1.PresenceService/ListKubeClusters" PresenceService_DeleteKubeCluster_FullMethodName = "/teleport.presence.v1.PresenceService/DeleteKubeCluster" + PresenceService_DeleteKubeServer_FullMethodName = "/teleport.presence.v1.PresenceService/DeleteKubeServer" PresenceService_DeleteAppServer_FullMethodName = "/teleport.presence.v1.PresenceService/DeleteAppServer" ) @@ -95,6 +96,8 @@ type PresenceServiceClient interface { ListKubeClusters(ctx context.Context, in *ListKubeClustersRequest, opts ...grpc.CallOption) (*ListKubeClustersResponse, error) // Deletes a kube cluster resource from the backend. DeleteKubeCluster(ctx context.Context, in *DeleteKubeClusterRequest, opts ...grpc.CallOption) (*DeleteKubeClusterResponse, error) + // Deletes a specific scoped or unscoped kube server from the backend. + DeleteKubeServer(ctx context.Context, in *DeleteKubeServerRequest, opts ...grpc.CallOption) (*DeleteKubeServerResponse, error) // Deletes a specific scoped or unscoped application server. DeleteAppServer(ctx context.Context, in *DeleteAppServerRequest, opts ...grpc.CallOption) (*DeleteAppServerResponse, error) } @@ -277,6 +280,16 @@ func (c *presenceServiceClient) DeleteKubeCluster(ctx context.Context, in *Delet return out, nil } +func (c *presenceServiceClient) DeleteKubeServer(ctx context.Context, in *DeleteKubeServerRequest, opts ...grpc.CallOption) (*DeleteKubeServerResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteKubeServerResponse) + err := c.cc.Invoke(ctx, PresenceService_DeleteKubeServer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *presenceServiceClient) DeleteAppServer(ctx context.Context, in *DeleteAppServerRequest, opts ...grpc.CallOption) (*DeleteAppServerResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteAppServerResponse) @@ -327,6 +340,8 @@ type PresenceServiceServer interface { ListKubeClusters(context.Context, *ListKubeClustersRequest) (*ListKubeClustersResponse, error) // Deletes a kube cluster resource from the backend. DeleteKubeCluster(context.Context, *DeleteKubeClusterRequest) (*DeleteKubeClusterResponse, error) + // Deletes a specific scoped or unscoped kube server from the backend. + DeleteKubeServer(context.Context, *DeleteKubeServerRequest) (*DeleteKubeServerResponse, error) // Deletes a specific scoped or unscoped application server. DeleteAppServer(context.Context, *DeleteAppServerRequest) (*DeleteAppServerResponse, error) mustEmbedUnimplementedPresenceServiceServer() @@ -390,6 +405,9 @@ func (UnimplementedPresenceServiceServer) ListKubeClusters(context.Context, *Lis func (UnimplementedPresenceServiceServer) DeleteKubeCluster(context.Context, *DeleteKubeClusterRequest) (*DeleteKubeClusterResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteKubeCluster not implemented") } +func (UnimplementedPresenceServiceServer) DeleteKubeServer(context.Context, *DeleteKubeServerRequest) (*DeleteKubeServerResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteKubeServer not implemented") +} func (UnimplementedPresenceServiceServer) DeleteAppServer(context.Context, *DeleteAppServerRequest) (*DeleteAppServerResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteAppServer not implemented") } @@ -720,6 +738,24 @@ func _PresenceService_DeleteKubeCluster_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _PresenceService_DeleteKubeServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteKubeServerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PresenceServiceServer).DeleteKubeServer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PresenceService_DeleteKubeServer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PresenceServiceServer).DeleteKubeServer(ctx, req.(*DeleteKubeServerRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _PresenceService_DeleteAppServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteAppServerRequest) if err := dec(in); err != nil { @@ -813,6 +849,10 @@ var PresenceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteKubeCluster", Handler: _PresenceService_DeleteKubeCluster_Handler, }, + { + MethodName: "DeleteKubeServer", + Handler: _PresenceService_DeleteKubeServer_Handler, + }, { MethodName: "DeleteAppServer", Handler: _PresenceService_DeleteAppServer_Handler, diff --git a/api/gen/proto/go/teleport/presence/v1/service_protohybrid.pb.go b/api/gen/proto/go/teleport/presence/v1/service_protohybrid.pb.go index 7b34fc3e9311..a0f15e0410e9 100644 --- a/api/gen/proto/go/teleport/presence/v1/service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/presence/v1/service_protohybrid.pb.go @@ -2098,6 +2098,144 @@ func (b0 DeleteKubeClusterResponse_builder) Build() *DeleteKubeClusterResponse { return m0 } +// The request to delete a specific scoped or unscoped kube server. +type DeleteKubeServerRequest struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The kube server host uuid. + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty"` + // The name of the kube server to delete. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // The scope of the kube server to delete. Empty for unscoped + // servers. + Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteKubeServerRequest) Reset() { + *x = DeleteKubeServerRequest{} + mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteKubeServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteKubeServerRequest) ProtoMessage() {} + +func (x *DeleteKubeServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *DeleteKubeServerRequest) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *DeleteKubeServerRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeleteKubeServerRequest) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *DeleteKubeServerRequest) SetHostId(v string) { + x.HostId = v +} + +func (x *DeleteKubeServerRequest) SetName(v string) { + x.Name = v +} + +func (x *DeleteKubeServerRequest) SetScope(v string) { + x.Scope = v +} + +type DeleteKubeServerRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The kube server host uuid. + HostId string + // The name of the kube server to delete. + Name string + // The scope of the kube server to delete. Empty for unscoped + // servers. + Scope string +} + +func (b0 DeleteKubeServerRequest_builder) Build() *DeleteKubeServerRequest { + m0 := &DeleteKubeServerRequest{} + b, x := &b0, m0 + _, _ = b, x + x.HostId = b.HostId + x.Name = b.Name + x.Scope = b.Scope + return m0 +} + +// The response for deleting a kube server. +type DeleteKubeServerResponse struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteKubeServerResponse) Reset() { + *x = DeleteKubeServerResponse{} + mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteKubeServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteKubeServerResponse) ProtoMessage() {} + +func (x *DeleteKubeServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +type DeleteKubeServerResponse_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + +} + +func (b0 DeleteKubeServerResponse_builder) Build() *DeleteKubeServerResponse { + m0 := &DeleteKubeServerResponse{} + b, x := &b0, m0 + _, _ = b, x + return m0 +} + // The request to delete a specific scoped or unscoped application server. type DeleteAppServerRequest struct { state protoimpl.MessageState `protogen:"hybrid.v1"` @@ -2114,7 +2252,7 @@ type DeleteAppServerRequest struct { func (x *DeleteAppServerRequest) Reset() { *x = DeleteAppServerRequest{} - mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + mi := &file_teleport_presence_v1_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2126,7 +2264,7 @@ func (x *DeleteAppServerRequest) String() string { func (*DeleteAppServerRequest) ProtoMessage() {} func (x *DeleteAppServerRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + mi := &file_teleport_presence_v1_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2201,7 +2339,7 @@ type DeleteAppServerResponse struct { func (x *DeleteAppServerResponse) Reset() { *x = DeleteAppServerResponse{} - mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + mi := &file_teleport_presence_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2213,7 +2351,7 @@ func (x *DeleteAppServerResponse) String() string { func (*DeleteAppServerResponse) ProtoMessage() {} func (x *DeleteAppServerResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + mi := &file_teleport_presence_v1_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2318,12 +2456,17 @@ const file_teleport_presence_v1_service_proto_rawDesc = "" + "\x18DeleteKubeClusterRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05scope\x18\x02 \x01(\tR\x05scope\"\x1b\n" + - "\x19DeleteKubeClusterResponse\"[\n" + + "\x19DeleteKubeClusterResponse\"\\\n" + + "\x17DeleteKubeServerRequest\x12\x17\n" + + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05scope\x18\x03 \x01(\tR\x05scope\"\x1a\n" + + "\x18DeleteKubeServerResponse\"[\n" + "\x16DeleteAppServerRequest\x12\x17\n" + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + "\x05scope\x18\x03 \x01(\tR\x05scope\"\x19\n" + - "\x17DeleteAppServerResponse2\xcd\x0f\n" + + "\x17DeleteAppServerResponse2\xc0\x10\n" + "\x0fPresenceService\x12Y\n" + "\x10GetRemoteCluster\x12-.teleport.presence.v1.GetRemoteClusterRequest\x1a\x16.types.RemoteClusterV3\x12w\n" + "\x12ListRemoteClusters\x12/.teleport.presence.v1.ListRemoteClustersRequest\x1a0.teleport.presence.v1.ListRemoteClustersResponse\x12_\n" + @@ -2341,10 +2484,11 @@ const file_teleport_presence_v1_service_proto_rawDesc = "" + "\x11DeleteProxyServer\x12..teleport.presence.v1.DeleteProxyServerRequest\x1a/.teleport.presence.v1.DeleteProxyServerResponse\x12k\n" + "\x0eGetKubeCluster\x12+.teleport.presence.v1.GetKubeClusterRequest\x1a,.teleport.presence.v1.GetKubeClusterResponse\x12q\n" + "\x10ListKubeClusters\x12-.teleport.presence.v1.ListKubeClustersRequest\x1a..teleport.presence.v1.ListKubeClustersResponse\x12t\n" + - "\x11DeleteKubeCluster\x12..teleport.presence.v1.DeleteKubeClusterRequest\x1a/.teleport.presence.v1.DeleteKubeClusterResponse\x12n\n" + + "\x11DeleteKubeCluster\x12..teleport.presence.v1.DeleteKubeClusterRequest\x1a/.teleport.presence.v1.DeleteKubeClusterResponse\x12q\n" + + "\x10DeleteKubeServer\x12-.teleport.presence.v1.DeleteKubeServerRequest\x1a..teleport.presence.v1.DeleteKubeServerResponse\x12n\n" + "\x0fDeleteAppServer\x12,.teleport.presence.v1.DeleteAppServerRequest\x1a-.teleport.presence.v1.DeleteAppServerResponseBTZRgithub.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1;presencev1b\x06proto3" -var file_teleport_presence_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_teleport_presence_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_teleport_presence_v1_service_proto_goTypes = []any{ (*GetRemoteClusterRequest)(nil), // 0: teleport.presence.v1.GetRemoteClusterRequest (*ListRemoteClustersRequest)(nil), // 1: teleport.presence.v1.ListRemoteClustersRequest @@ -2375,32 +2519,34 @@ var file_teleport_presence_v1_service_proto_goTypes = []any{ (*ListKubeClustersResponse)(nil), // 26: teleport.presence.v1.ListKubeClustersResponse (*DeleteKubeClusterRequest)(nil), // 27: teleport.presence.v1.DeleteKubeClusterRequest (*DeleteKubeClusterResponse)(nil), // 28: teleport.presence.v1.DeleteKubeClusterResponse - (*DeleteAppServerRequest)(nil), // 29: teleport.presence.v1.DeleteAppServerRequest - (*DeleteAppServerResponse)(nil), // 30: teleport.presence.v1.DeleteAppServerResponse - (*types.RemoteClusterV3)(nil), // 31: types.RemoteClusterV3 - (*fieldmaskpb.FieldMask)(nil), // 32: google.protobuf.FieldMask - (*types.ReverseTunnelV2)(nil), // 33: types.ReverseTunnelV2 - (*RelayServer)(nil), // 34: teleport.presence.v1.RelayServer - (*types.ServerV2)(nil), // 35: types.ServerV2 - (*types.KubernetesClusterV3)(nil), // 36: types.KubernetesClusterV3 - (*v1.Filter)(nil), // 37: teleport.scopes.v1.Filter - (*emptypb.Empty)(nil), // 38: google.protobuf.Empty + (*DeleteKubeServerRequest)(nil), // 29: teleport.presence.v1.DeleteKubeServerRequest + (*DeleteKubeServerResponse)(nil), // 30: teleport.presence.v1.DeleteKubeServerResponse + (*DeleteAppServerRequest)(nil), // 31: teleport.presence.v1.DeleteAppServerRequest + (*DeleteAppServerResponse)(nil), // 32: teleport.presence.v1.DeleteAppServerResponse + (*types.RemoteClusterV3)(nil), // 33: types.RemoteClusterV3 + (*fieldmaskpb.FieldMask)(nil), // 34: google.protobuf.FieldMask + (*types.ReverseTunnelV2)(nil), // 35: types.ReverseTunnelV2 + (*RelayServer)(nil), // 36: teleport.presence.v1.RelayServer + (*types.ServerV2)(nil), // 37: types.ServerV2 + (*types.KubernetesClusterV3)(nil), // 38: types.KubernetesClusterV3 + (*v1.Filter)(nil), // 39: teleport.scopes.v1.Filter + (*emptypb.Empty)(nil), // 40: google.protobuf.Empty } var file_teleport_presence_v1_service_proto_depIdxs = []int32{ - 31, // 0: teleport.presence.v1.ListRemoteClustersResponse.remote_clusters:type_name -> types.RemoteClusterV3 - 31, // 1: teleport.presence.v1.UpdateRemoteClusterRequest.remote_cluster:type_name -> types.RemoteClusterV3 - 32, // 2: teleport.presence.v1.UpdateRemoteClusterRequest.update_mask:type_name -> google.protobuf.FieldMask - 33, // 3: teleport.presence.v1.ListReverseTunnelsResponse.reverse_tunnels:type_name -> types.ReverseTunnelV2 - 33, // 4: teleport.presence.v1.UpsertReverseTunnelRequest.reverse_tunnel:type_name -> types.ReverseTunnelV2 - 34, // 5: teleport.presence.v1.GetRelayServerResponse.relay_server:type_name -> teleport.presence.v1.RelayServer - 34, // 6: teleport.presence.v1.ListRelayServersResponse.relays:type_name -> teleport.presence.v1.RelayServer - 35, // 7: teleport.presence.v1.ListAuthServersResponse.servers:type_name -> types.ServerV2 - 35, // 8: teleport.presence.v1.ListProxyServersResponse.servers:type_name -> types.ServerV2 - 35, // 9: teleport.presence.v1.UpsertProxyServerRequest.server:type_name -> types.ServerV2 - 35, // 10: teleport.presence.v1.UpsertProxyServerResponse.server:type_name -> types.ServerV2 - 36, // 11: teleport.presence.v1.GetKubeClusterResponse.cluster:type_name -> types.KubernetesClusterV3 - 37, // 12: teleport.presence.v1.ListKubeClustersRequest.scope_filter:type_name -> teleport.scopes.v1.Filter - 36, // 13: teleport.presence.v1.ListKubeClustersResponse.clusters:type_name -> types.KubernetesClusterV3 + 33, // 0: teleport.presence.v1.ListRemoteClustersResponse.remote_clusters:type_name -> types.RemoteClusterV3 + 33, // 1: teleport.presence.v1.UpdateRemoteClusterRequest.remote_cluster:type_name -> types.RemoteClusterV3 + 34, // 2: teleport.presence.v1.UpdateRemoteClusterRequest.update_mask:type_name -> google.protobuf.FieldMask + 35, // 3: teleport.presence.v1.ListReverseTunnelsResponse.reverse_tunnels:type_name -> types.ReverseTunnelV2 + 35, // 4: teleport.presence.v1.UpsertReverseTunnelRequest.reverse_tunnel:type_name -> types.ReverseTunnelV2 + 36, // 5: teleport.presence.v1.GetRelayServerResponse.relay_server:type_name -> teleport.presence.v1.RelayServer + 36, // 6: teleport.presence.v1.ListRelayServersResponse.relays:type_name -> teleport.presence.v1.RelayServer + 37, // 7: teleport.presence.v1.ListAuthServersResponse.servers:type_name -> types.ServerV2 + 37, // 8: teleport.presence.v1.ListProxyServersResponse.servers:type_name -> types.ServerV2 + 37, // 9: teleport.presence.v1.UpsertProxyServerRequest.server:type_name -> types.ServerV2 + 37, // 10: teleport.presence.v1.UpsertProxyServerResponse.server:type_name -> types.ServerV2 + 38, // 11: teleport.presence.v1.GetKubeClusterResponse.cluster:type_name -> types.KubernetesClusterV3 + 39, // 12: teleport.presence.v1.ListKubeClustersRequest.scope_filter:type_name -> teleport.scopes.v1.Filter + 38, // 13: teleport.presence.v1.ListKubeClustersResponse.clusters:type_name -> types.KubernetesClusterV3 0, // 14: teleport.presence.v1.PresenceService.GetRemoteCluster:input_type -> teleport.presence.v1.GetRemoteClusterRequest 1, // 15: teleport.presence.v1.PresenceService.ListRemoteClusters:input_type -> teleport.presence.v1.ListRemoteClustersRequest 3, // 16: teleport.presence.v1.PresenceService.UpdateRemoteCluster:input_type -> teleport.presence.v1.UpdateRemoteClusterRequest @@ -2418,27 +2564,29 @@ var file_teleport_presence_v1_service_proto_depIdxs = []int32{ 23, // 28: teleport.presence.v1.PresenceService.GetKubeCluster:input_type -> teleport.presence.v1.GetKubeClusterRequest 25, // 29: teleport.presence.v1.PresenceService.ListKubeClusters:input_type -> teleport.presence.v1.ListKubeClustersRequest 27, // 30: teleport.presence.v1.PresenceService.DeleteKubeCluster:input_type -> teleport.presence.v1.DeleteKubeClusterRequest - 29, // 31: teleport.presence.v1.PresenceService.DeleteAppServer:input_type -> teleport.presence.v1.DeleteAppServerRequest - 31, // 32: teleport.presence.v1.PresenceService.GetRemoteCluster:output_type -> types.RemoteClusterV3 - 2, // 33: teleport.presence.v1.PresenceService.ListRemoteClusters:output_type -> teleport.presence.v1.ListRemoteClustersResponse - 31, // 34: teleport.presence.v1.PresenceService.UpdateRemoteCluster:output_type -> types.RemoteClusterV3 - 38, // 35: teleport.presence.v1.PresenceService.DeleteRemoteCluster:output_type -> google.protobuf.Empty - 6, // 36: teleport.presence.v1.PresenceService.ListReverseTunnels:output_type -> teleport.presence.v1.ListReverseTunnelsResponse - 33, // 37: teleport.presence.v1.PresenceService.UpsertReverseTunnel:output_type -> types.ReverseTunnelV2 - 38, // 38: teleport.presence.v1.PresenceService.DeleteReverseTunnel:output_type -> google.protobuf.Empty - 10, // 39: teleport.presence.v1.PresenceService.GetRelayServer:output_type -> teleport.presence.v1.GetRelayServerResponse - 12, // 40: teleport.presence.v1.PresenceService.ListRelayServers:output_type -> teleport.presence.v1.ListRelayServersResponse - 14, // 41: teleport.presence.v1.PresenceService.DeleteRelayServer:output_type -> teleport.presence.v1.DeleteRelayServerResponse - 16, // 42: teleport.presence.v1.PresenceService.ListAuthServers:output_type -> teleport.presence.v1.ListAuthServersResponse - 18, // 43: teleport.presence.v1.PresenceService.ListProxyServers:output_type -> teleport.presence.v1.ListProxyServersResponse - 20, // 44: teleport.presence.v1.PresenceService.UpsertProxyServer:output_type -> teleport.presence.v1.UpsertProxyServerResponse - 22, // 45: teleport.presence.v1.PresenceService.DeleteProxyServer:output_type -> teleport.presence.v1.DeleteProxyServerResponse - 24, // 46: teleport.presence.v1.PresenceService.GetKubeCluster:output_type -> teleport.presence.v1.GetKubeClusterResponse - 26, // 47: teleport.presence.v1.PresenceService.ListKubeClusters:output_type -> teleport.presence.v1.ListKubeClustersResponse - 28, // 48: teleport.presence.v1.PresenceService.DeleteKubeCluster:output_type -> teleport.presence.v1.DeleteKubeClusterResponse - 30, // 49: teleport.presence.v1.PresenceService.DeleteAppServer:output_type -> teleport.presence.v1.DeleteAppServerResponse - 32, // [32:50] is the sub-list for method output_type - 14, // [14:32] is the sub-list for method input_type + 29, // 31: teleport.presence.v1.PresenceService.DeleteKubeServer:input_type -> teleport.presence.v1.DeleteKubeServerRequest + 31, // 32: teleport.presence.v1.PresenceService.DeleteAppServer:input_type -> teleport.presence.v1.DeleteAppServerRequest + 33, // 33: teleport.presence.v1.PresenceService.GetRemoteCluster:output_type -> types.RemoteClusterV3 + 2, // 34: teleport.presence.v1.PresenceService.ListRemoteClusters:output_type -> teleport.presence.v1.ListRemoteClustersResponse + 33, // 35: teleport.presence.v1.PresenceService.UpdateRemoteCluster:output_type -> types.RemoteClusterV3 + 40, // 36: teleport.presence.v1.PresenceService.DeleteRemoteCluster:output_type -> google.protobuf.Empty + 6, // 37: teleport.presence.v1.PresenceService.ListReverseTunnels:output_type -> teleport.presence.v1.ListReverseTunnelsResponse + 35, // 38: teleport.presence.v1.PresenceService.UpsertReverseTunnel:output_type -> types.ReverseTunnelV2 + 40, // 39: teleport.presence.v1.PresenceService.DeleteReverseTunnel:output_type -> google.protobuf.Empty + 10, // 40: teleport.presence.v1.PresenceService.GetRelayServer:output_type -> teleport.presence.v1.GetRelayServerResponse + 12, // 41: teleport.presence.v1.PresenceService.ListRelayServers:output_type -> teleport.presence.v1.ListRelayServersResponse + 14, // 42: teleport.presence.v1.PresenceService.DeleteRelayServer:output_type -> teleport.presence.v1.DeleteRelayServerResponse + 16, // 43: teleport.presence.v1.PresenceService.ListAuthServers:output_type -> teleport.presence.v1.ListAuthServersResponse + 18, // 44: teleport.presence.v1.PresenceService.ListProxyServers:output_type -> teleport.presence.v1.ListProxyServersResponse + 20, // 45: teleport.presence.v1.PresenceService.UpsertProxyServer:output_type -> teleport.presence.v1.UpsertProxyServerResponse + 22, // 46: teleport.presence.v1.PresenceService.DeleteProxyServer:output_type -> teleport.presence.v1.DeleteProxyServerResponse + 24, // 47: teleport.presence.v1.PresenceService.GetKubeCluster:output_type -> teleport.presence.v1.GetKubeClusterResponse + 26, // 48: teleport.presence.v1.PresenceService.ListKubeClusters:output_type -> teleport.presence.v1.ListKubeClustersResponse + 28, // 49: teleport.presence.v1.PresenceService.DeleteKubeCluster:output_type -> teleport.presence.v1.DeleteKubeClusterResponse + 30, // 50: teleport.presence.v1.PresenceService.DeleteKubeServer:output_type -> teleport.presence.v1.DeleteKubeServerResponse + 32, // 51: teleport.presence.v1.PresenceService.DeleteAppServer:output_type -> teleport.presence.v1.DeleteAppServerResponse + 33, // [33:52] is the sub-list for method output_type + 14, // [14:33] is the sub-list for method input_type 14, // [14:14] is the sub-list for extension type_name 14, // [14:14] is the sub-list for extension extendee 0, // [0:14] is the sub-list for field type_name @@ -2456,7 +2604,7 @@ func file_teleport_presence_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_presence_v1_service_proto_rawDesc), len(file_teleport_presence_v1_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 31, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/presence/v1/service_protoopaque.pb.go b/api/gen/proto/go/teleport/presence/v1/service_protoopaque.pb.go index cd99c443e462..5957a77ff8ce 100644 --- a/api/gen/proto/go/teleport/presence/v1/service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/presence/v1/service_protoopaque.pb.go @@ -2057,6 +2057,140 @@ func (b0 DeleteKubeClusterResponse_builder) Build() *DeleteKubeClusterResponse { return m0 } +// The request to delete a specific scoped or unscoped kube server. +type DeleteKubeServerRequest struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3"` + xxx_hidden_Name string `protobuf:"bytes,2,opt,name=name,proto3"` + xxx_hidden_Scope string `protobuf:"bytes,3,opt,name=scope,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteKubeServerRequest) Reset() { + *x = DeleteKubeServerRequest{} + mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteKubeServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteKubeServerRequest) ProtoMessage() {} + +func (x *DeleteKubeServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *DeleteKubeServerRequest) GetHostId() string { + if x != nil { + return x.xxx_hidden_HostId + } + return "" +} + +func (x *DeleteKubeServerRequest) GetName() string { + if x != nil { + return x.xxx_hidden_Name + } + return "" +} + +func (x *DeleteKubeServerRequest) GetScope() string { + if x != nil { + return x.xxx_hidden_Scope + } + return "" +} + +func (x *DeleteKubeServerRequest) SetHostId(v string) { + x.xxx_hidden_HostId = v +} + +func (x *DeleteKubeServerRequest) SetName(v string) { + x.xxx_hidden_Name = v +} + +func (x *DeleteKubeServerRequest) SetScope(v string) { + x.xxx_hidden_Scope = v +} + +type DeleteKubeServerRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The kube server host uuid. + HostId string + // The name of the kube server to delete. + Name string + // The scope of the kube server to delete. Empty for unscoped + // servers. + Scope string +} + +func (b0 DeleteKubeServerRequest_builder) Build() *DeleteKubeServerRequest { + m0 := &DeleteKubeServerRequest{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_HostId = b.HostId + x.xxx_hidden_Name = b.Name + x.xxx_hidden_Scope = b.Scope + return m0 +} + +// The response for deleting a kube server. +type DeleteKubeServerResponse struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteKubeServerResponse) Reset() { + *x = DeleteKubeServerResponse{} + mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteKubeServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteKubeServerResponse) ProtoMessage() {} + +func (x *DeleteKubeServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +type DeleteKubeServerResponse_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + +} + +func (b0 DeleteKubeServerResponse_builder) Build() *DeleteKubeServerResponse { + m0 := &DeleteKubeServerResponse{} + b, x := &b0, m0 + _, _ = b, x + return m0 +} + // The request to delete a specific scoped or unscoped application server. type DeleteAppServerRequest struct { state protoimpl.MessageState `protogen:"opaque.v1"` @@ -2069,7 +2203,7 @@ type DeleteAppServerRequest struct { func (x *DeleteAppServerRequest) Reset() { *x = DeleteAppServerRequest{} - mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + mi := &file_teleport_presence_v1_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2081,7 +2215,7 @@ func (x *DeleteAppServerRequest) String() string { func (*DeleteAppServerRequest) ProtoMessage() {} func (x *DeleteAppServerRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_presence_v1_service_proto_msgTypes[29] + mi := &file_teleport_presence_v1_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2156,7 +2290,7 @@ type DeleteAppServerResponse struct { func (x *DeleteAppServerResponse) Reset() { *x = DeleteAppServerResponse{} - mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + mi := &file_teleport_presence_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2168,7 +2302,7 @@ func (x *DeleteAppServerResponse) String() string { func (*DeleteAppServerResponse) ProtoMessage() {} func (x *DeleteAppServerResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_presence_v1_service_proto_msgTypes[30] + mi := &file_teleport_presence_v1_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2273,12 +2407,17 @@ const file_teleport_presence_v1_service_proto_rawDesc = "" + "\x18DeleteKubeClusterRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05scope\x18\x02 \x01(\tR\x05scope\"\x1b\n" + - "\x19DeleteKubeClusterResponse\"[\n" + + "\x19DeleteKubeClusterResponse\"\\\n" + + "\x17DeleteKubeServerRequest\x12\x17\n" + + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05scope\x18\x03 \x01(\tR\x05scope\"\x1a\n" + + "\x18DeleteKubeServerResponse\"[\n" + "\x16DeleteAppServerRequest\x12\x17\n" + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + "\x05scope\x18\x03 \x01(\tR\x05scope\"\x19\n" + - "\x17DeleteAppServerResponse2\xcd\x0f\n" + + "\x17DeleteAppServerResponse2\xc0\x10\n" + "\x0fPresenceService\x12Y\n" + "\x10GetRemoteCluster\x12-.teleport.presence.v1.GetRemoteClusterRequest\x1a\x16.types.RemoteClusterV3\x12w\n" + "\x12ListRemoteClusters\x12/.teleport.presence.v1.ListRemoteClustersRequest\x1a0.teleport.presence.v1.ListRemoteClustersResponse\x12_\n" + @@ -2296,10 +2435,11 @@ const file_teleport_presence_v1_service_proto_rawDesc = "" + "\x11DeleteProxyServer\x12..teleport.presence.v1.DeleteProxyServerRequest\x1a/.teleport.presence.v1.DeleteProxyServerResponse\x12k\n" + "\x0eGetKubeCluster\x12+.teleport.presence.v1.GetKubeClusterRequest\x1a,.teleport.presence.v1.GetKubeClusterResponse\x12q\n" + "\x10ListKubeClusters\x12-.teleport.presence.v1.ListKubeClustersRequest\x1a..teleport.presence.v1.ListKubeClustersResponse\x12t\n" + - "\x11DeleteKubeCluster\x12..teleport.presence.v1.DeleteKubeClusterRequest\x1a/.teleport.presence.v1.DeleteKubeClusterResponse\x12n\n" + + "\x11DeleteKubeCluster\x12..teleport.presence.v1.DeleteKubeClusterRequest\x1a/.teleport.presence.v1.DeleteKubeClusterResponse\x12q\n" + + "\x10DeleteKubeServer\x12-.teleport.presence.v1.DeleteKubeServerRequest\x1a..teleport.presence.v1.DeleteKubeServerResponse\x12n\n" + "\x0fDeleteAppServer\x12,.teleport.presence.v1.DeleteAppServerRequest\x1a-.teleport.presence.v1.DeleteAppServerResponseBTZRgithub.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1;presencev1b\x06proto3" -var file_teleport_presence_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_teleport_presence_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_teleport_presence_v1_service_proto_goTypes = []any{ (*GetRemoteClusterRequest)(nil), // 0: teleport.presence.v1.GetRemoteClusterRequest (*ListRemoteClustersRequest)(nil), // 1: teleport.presence.v1.ListRemoteClustersRequest @@ -2330,32 +2470,34 @@ var file_teleport_presence_v1_service_proto_goTypes = []any{ (*ListKubeClustersResponse)(nil), // 26: teleport.presence.v1.ListKubeClustersResponse (*DeleteKubeClusterRequest)(nil), // 27: teleport.presence.v1.DeleteKubeClusterRequest (*DeleteKubeClusterResponse)(nil), // 28: teleport.presence.v1.DeleteKubeClusterResponse - (*DeleteAppServerRequest)(nil), // 29: teleport.presence.v1.DeleteAppServerRequest - (*DeleteAppServerResponse)(nil), // 30: teleport.presence.v1.DeleteAppServerResponse - (*types.RemoteClusterV3)(nil), // 31: types.RemoteClusterV3 - (*fieldmaskpb.FieldMask)(nil), // 32: google.protobuf.FieldMask - (*types.ReverseTunnelV2)(nil), // 33: types.ReverseTunnelV2 - (*RelayServer)(nil), // 34: teleport.presence.v1.RelayServer - (*types.ServerV2)(nil), // 35: types.ServerV2 - (*types.KubernetesClusterV3)(nil), // 36: types.KubernetesClusterV3 - (*v1.Filter)(nil), // 37: teleport.scopes.v1.Filter - (*emptypb.Empty)(nil), // 38: google.protobuf.Empty + (*DeleteKubeServerRequest)(nil), // 29: teleport.presence.v1.DeleteKubeServerRequest + (*DeleteKubeServerResponse)(nil), // 30: teleport.presence.v1.DeleteKubeServerResponse + (*DeleteAppServerRequest)(nil), // 31: teleport.presence.v1.DeleteAppServerRequest + (*DeleteAppServerResponse)(nil), // 32: teleport.presence.v1.DeleteAppServerResponse + (*types.RemoteClusterV3)(nil), // 33: types.RemoteClusterV3 + (*fieldmaskpb.FieldMask)(nil), // 34: google.protobuf.FieldMask + (*types.ReverseTunnelV2)(nil), // 35: types.ReverseTunnelV2 + (*RelayServer)(nil), // 36: teleport.presence.v1.RelayServer + (*types.ServerV2)(nil), // 37: types.ServerV2 + (*types.KubernetesClusterV3)(nil), // 38: types.KubernetesClusterV3 + (*v1.Filter)(nil), // 39: teleport.scopes.v1.Filter + (*emptypb.Empty)(nil), // 40: google.protobuf.Empty } var file_teleport_presence_v1_service_proto_depIdxs = []int32{ - 31, // 0: teleport.presence.v1.ListRemoteClustersResponse.remote_clusters:type_name -> types.RemoteClusterV3 - 31, // 1: teleport.presence.v1.UpdateRemoteClusterRequest.remote_cluster:type_name -> types.RemoteClusterV3 - 32, // 2: teleport.presence.v1.UpdateRemoteClusterRequest.update_mask:type_name -> google.protobuf.FieldMask - 33, // 3: teleport.presence.v1.ListReverseTunnelsResponse.reverse_tunnels:type_name -> types.ReverseTunnelV2 - 33, // 4: teleport.presence.v1.UpsertReverseTunnelRequest.reverse_tunnel:type_name -> types.ReverseTunnelV2 - 34, // 5: teleport.presence.v1.GetRelayServerResponse.relay_server:type_name -> teleport.presence.v1.RelayServer - 34, // 6: teleport.presence.v1.ListRelayServersResponse.relays:type_name -> teleport.presence.v1.RelayServer - 35, // 7: teleport.presence.v1.ListAuthServersResponse.servers:type_name -> types.ServerV2 - 35, // 8: teleport.presence.v1.ListProxyServersResponse.servers:type_name -> types.ServerV2 - 35, // 9: teleport.presence.v1.UpsertProxyServerRequest.server:type_name -> types.ServerV2 - 35, // 10: teleport.presence.v1.UpsertProxyServerResponse.server:type_name -> types.ServerV2 - 36, // 11: teleport.presence.v1.GetKubeClusterResponse.cluster:type_name -> types.KubernetesClusterV3 - 37, // 12: teleport.presence.v1.ListKubeClustersRequest.scope_filter:type_name -> teleport.scopes.v1.Filter - 36, // 13: teleport.presence.v1.ListKubeClustersResponse.clusters:type_name -> types.KubernetesClusterV3 + 33, // 0: teleport.presence.v1.ListRemoteClustersResponse.remote_clusters:type_name -> types.RemoteClusterV3 + 33, // 1: teleport.presence.v1.UpdateRemoteClusterRequest.remote_cluster:type_name -> types.RemoteClusterV3 + 34, // 2: teleport.presence.v1.UpdateRemoteClusterRequest.update_mask:type_name -> google.protobuf.FieldMask + 35, // 3: teleport.presence.v1.ListReverseTunnelsResponse.reverse_tunnels:type_name -> types.ReverseTunnelV2 + 35, // 4: teleport.presence.v1.UpsertReverseTunnelRequest.reverse_tunnel:type_name -> types.ReverseTunnelV2 + 36, // 5: teleport.presence.v1.GetRelayServerResponse.relay_server:type_name -> teleport.presence.v1.RelayServer + 36, // 6: teleport.presence.v1.ListRelayServersResponse.relays:type_name -> teleport.presence.v1.RelayServer + 37, // 7: teleport.presence.v1.ListAuthServersResponse.servers:type_name -> types.ServerV2 + 37, // 8: teleport.presence.v1.ListProxyServersResponse.servers:type_name -> types.ServerV2 + 37, // 9: teleport.presence.v1.UpsertProxyServerRequest.server:type_name -> types.ServerV2 + 37, // 10: teleport.presence.v1.UpsertProxyServerResponse.server:type_name -> types.ServerV2 + 38, // 11: teleport.presence.v1.GetKubeClusterResponse.cluster:type_name -> types.KubernetesClusterV3 + 39, // 12: teleport.presence.v1.ListKubeClustersRequest.scope_filter:type_name -> teleport.scopes.v1.Filter + 38, // 13: teleport.presence.v1.ListKubeClustersResponse.clusters:type_name -> types.KubernetesClusterV3 0, // 14: teleport.presence.v1.PresenceService.GetRemoteCluster:input_type -> teleport.presence.v1.GetRemoteClusterRequest 1, // 15: teleport.presence.v1.PresenceService.ListRemoteClusters:input_type -> teleport.presence.v1.ListRemoteClustersRequest 3, // 16: teleport.presence.v1.PresenceService.UpdateRemoteCluster:input_type -> teleport.presence.v1.UpdateRemoteClusterRequest @@ -2373,27 +2515,29 @@ var file_teleport_presence_v1_service_proto_depIdxs = []int32{ 23, // 28: teleport.presence.v1.PresenceService.GetKubeCluster:input_type -> teleport.presence.v1.GetKubeClusterRequest 25, // 29: teleport.presence.v1.PresenceService.ListKubeClusters:input_type -> teleport.presence.v1.ListKubeClustersRequest 27, // 30: teleport.presence.v1.PresenceService.DeleteKubeCluster:input_type -> teleport.presence.v1.DeleteKubeClusterRequest - 29, // 31: teleport.presence.v1.PresenceService.DeleteAppServer:input_type -> teleport.presence.v1.DeleteAppServerRequest - 31, // 32: teleport.presence.v1.PresenceService.GetRemoteCluster:output_type -> types.RemoteClusterV3 - 2, // 33: teleport.presence.v1.PresenceService.ListRemoteClusters:output_type -> teleport.presence.v1.ListRemoteClustersResponse - 31, // 34: teleport.presence.v1.PresenceService.UpdateRemoteCluster:output_type -> types.RemoteClusterV3 - 38, // 35: teleport.presence.v1.PresenceService.DeleteRemoteCluster:output_type -> google.protobuf.Empty - 6, // 36: teleport.presence.v1.PresenceService.ListReverseTunnels:output_type -> teleport.presence.v1.ListReverseTunnelsResponse - 33, // 37: teleport.presence.v1.PresenceService.UpsertReverseTunnel:output_type -> types.ReverseTunnelV2 - 38, // 38: teleport.presence.v1.PresenceService.DeleteReverseTunnel:output_type -> google.protobuf.Empty - 10, // 39: teleport.presence.v1.PresenceService.GetRelayServer:output_type -> teleport.presence.v1.GetRelayServerResponse - 12, // 40: teleport.presence.v1.PresenceService.ListRelayServers:output_type -> teleport.presence.v1.ListRelayServersResponse - 14, // 41: teleport.presence.v1.PresenceService.DeleteRelayServer:output_type -> teleport.presence.v1.DeleteRelayServerResponse - 16, // 42: teleport.presence.v1.PresenceService.ListAuthServers:output_type -> teleport.presence.v1.ListAuthServersResponse - 18, // 43: teleport.presence.v1.PresenceService.ListProxyServers:output_type -> teleport.presence.v1.ListProxyServersResponse - 20, // 44: teleport.presence.v1.PresenceService.UpsertProxyServer:output_type -> teleport.presence.v1.UpsertProxyServerResponse - 22, // 45: teleport.presence.v1.PresenceService.DeleteProxyServer:output_type -> teleport.presence.v1.DeleteProxyServerResponse - 24, // 46: teleport.presence.v1.PresenceService.GetKubeCluster:output_type -> teleport.presence.v1.GetKubeClusterResponse - 26, // 47: teleport.presence.v1.PresenceService.ListKubeClusters:output_type -> teleport.presence.v1.ListKubeClustersResponse - 28, // 48: teleport.presence.v1.PresenceService.DeleteKubeCluster:output_type -> teleport.presence.v1.DeleteKubeClusterResponse - 30, // 49: teleport.presence.v1.PresenceService.DeleteAppServer:output_type -> teleport.presence.v1.DeleteAppServerResponse - 32, // [32:50] is the sub-list for method output_type - 14, // [14:32] is the sub-list for method input_type + 29, // 31: teleport.presence.v1.PresenceService.DeleteKubeServer:input_type -> teleport.presence.v1.DeleteKubeServerRequest + 31, // 32: teleport.presence.v1.PresenceService.DeleteAppServer:input_type -> teleport.presence.v1.DeleteAppServerRequest + 33, // 33: teleport.presence.v1.PresenceService.GetRemoteCluster:output_type -> types.RemoteClusterV3 + 2, // 34: teleport.presence.v1.PresenceService.ListRemoteClusters:output_type -> teleport.presence.v1.ListRemoteClustersResponse + 33, // 35: teleport.presence.v1.PresenceService.UpdateRemoteCluster:output_type -> types.RemoteClusterV3 + 40, // 36: teleport.presence.v1.PresenceService.DeleteRemoteCluster:output_type -> google.protobuf.Empty + 6, // 37: teleport.presence.v1.PresenceService.ListReverseTunnels:output_type -> teleport.presence.v1.ListReverseTunnelsResponse + 35, // 38: teleport.presence.v1.PresenceService.UpsertReverseTunnel:output_type -> types.ReverseTunnelV2 + 40, // 39: teleport.presence.v1.PresenceService.DeleteReverseTunnel:output_type -> google.protobuf.Empty + 10, // 40: teleport.presence.v1.PresenceService.GetRelayServer:output_type -> teleport.presence.v1.GetRelayServerResponse + 12, // 41: teleport.presence.v1.PresenceService.ListRelayServers:output_type -> teleport.presence.v1.ListRelayServersResponse + 14, // 42: teleport.presence.v1.PresenceService.DeleteRelayServer:output_type -> teleport.presence.v1.DeleteRelayServerResponse + 16, // 43: teleport.presence.v1.PresenceService.ListAuthServers:output_type -> teleport.presence.v1.ListAuthServersResponse + 18, // 44: teleport.presence.v1.PresenceService.ListProxyServers:output_type -> teleport.presence.v1.ListProxyServersResponse + 20, // 45: teleport.presence.v1.PresenceService.UpsertProxyServer:output_type -> teleport.presence.v1.UpsertProxyServerResponse + 22, // 46: teleport.presence.v1.PresenceService.DeleteProxyServer:output_type -> teleport.presence.v1.DeleteProxyServerResponse + 24, // 47: teleport.presence.v1.PresenceService.GetKubeCluster:output_type -> teleport.presence.v1.GetKubeClusterResponse + 26, // 48: teleport.presence.v1.PresenceService.ListKubeClusters:output_type -> teleport.presence.v1.ListKubeClustersResponse + 28, // 49: teleport.presence.v1.PresenceService.DeleteKubeCluster:output_type -> teleport.presence.v1.DeleteKubeClusterResponse + 30, // 50: teleport.presence.v1.PresenceService.DeleteKubeServer:output_type -> teleport.presence.v1.DeleteKubeServerResponse + 32, // 51: teleport.presence.v1.PresenceService.DeleteAppServer:output_type -> teleport.presence.v1.DeleteAppServerResponse + 33, // [33:52] is the sub-list for method output_type + 14, // [14:33] is the sub-list for method input_type 14, // [14:14] is the sub-list for extension type_name 14, // [14:14] is the sub-list for extension extendee 0, // [0:14] is the sub-list for field type_name @@ -2411,7 +2555,7 @@ func file_teleport_presence_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_presence_v1_service_proto_rawDesc), len(file_teleport_presence_v1_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 31, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/teleport/presence/v1/service.proto b/api/proto/teleport/presence/v1/service.proto index be1fff08aacf..a4914d5442af 100644 --- a/api/proto/teleport/presence/v1/service.proto +++ b/api/proto/teleport/presence/v1/service.proto @@ -66,6 +66,9 @@ service PresenceService { // Deletes a kube cluster resource from the backend. rpc DeleteKubeCluster(DeleteKubeClusterRequest) returns (DeleteKubeClusterResponse); + // Deletes a specific scoped or unscoped kube server from the backend. + rpc DeleteKubeServer(DeleteKubeServerRequest) returns (DeleteKubeServerResponse); + // Deletes a specific scoped or unscoped application server. rpc DeleteAppServer(DeleteAppServerRequest) returns (DeleteAppServerResponse); } @@ -286,6 +289,20 @@ message DeleteKubeClusterRequest { // The response for deleting a specific scoped or unscoped kube cluster resource. message DeleteKubeClusterResponse {} +// The request to delete a specific scoped or unscoped kube server. +message DeleteKubeServerRequest { + // The kube server host uuid. + string host_id = 1; + // The name of the kube server to delete. + string name = 2; + // The scope of the kube server to delete. Empty for unscoped + // servers. + string scope = 3; +} + +// The response for deleting a kube server. +message DeleteKubeServerResponse {} + // The request to delete a specific scoped or unscoped application server. message DeleteAppServerRequest { // HostId is the app server host uuid. diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 3df1eb637519..c5b2c13915b3 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -1279,7 +1279,8 @@ func supportedScopedWatchKind(kind string) bool { types.KindAccessListMember, types.KindAccessListReview, scopedaccess.KindScopedRole, - scopedaccess.KindScopedRoleAssignment: + scopedaccess.KindScopedRoleAssignment, + types.KindKubernetesCluster: return true default: return false @@ -7003,13 +7004,37 @@ func (a *ServerWithRoles) UpsertKubernetesServer(ctx context.Context, s types.Ku } // DeleteKubernetesServer deletes specified kubernetes server. -func (a *ServerWithRoles) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { - if err := a.ScopedServerWithRoles().scopedContext.AgentOwnedResourceAction("", hostID, types.RoleKube); err != nil { - if err := a.authorizeAction(types.KindKubeServer, types.VerbDelete); err != nil { +// +// Deprecated: Use DeleteKubeServer in the presence service instead. +// TODO (eriktate): remove in v20 +func (a *ScopedServerWithRoles) DeleteKubernetesServer(ctx context.Context, req *proto.DeleteKubernetesServerRequest) error { + var existingServer types.KubeServer + for server, err := range a.authServer.RangeKubernetesServersWithName(ctx, req.GetName()) { + if err != nil { return trace.Wrap(err) } + // only allow unscoped kube servers to be deleted using this deprecated DeleteKubernetesServer method + if server.GetScope() == "" && server.GetHostID() == req.GetHostID() { + existingServer = server + break + } + } + if existingServer == nil { + return trace.NotFound("%s %q doesn't exist", types.KindKubeServer, req.GetName()) } - return a.authServer.DeleteKubernetesServer(ctx, hostID, name) + if err := a.scopedContext.AgentOwnedResourceAction(existingServer.GetScope(), existingServer.GetHostID(), types.RoleKube); err != nil { + ruleCtx := a.scopedContext.RuleContext() + if err := a.scopedContext.CheckerContext.Decision(ctx, existingServer.GetScope(), func(checker *services.ScopedAccessChecker) error { + return checker.CheckAccessToRules(&ruleCtx, types.KindKubeServer, types.VerbDelete) + }); err != nil { + return trace.Wrap(err) + } + } + return a.authServer.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: existingServer.GetScope(), + Name: req.GetName(), + HostId: req.GetHostID(), + }.Build()) } // DeleteAllKubernetesServers deletes all registered kubernetes servers. diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index c024d5425904..e00ba8844c38 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -3552,11 +3552,12 @@ func TestKubeCRUDFromKubeService(t *testing.T) { })) const scope = "/test" + const hostID = "host-id" scopedIdent := authtest.TestScopePinnedHost(srv.ClusterName(), "scoped-host", scope, types.RoleKube) scopedKubeClient, err := srv.NewClient(scopedIdent) require.NoError(t, err) - unscopedKubeClient, err := srv.NewClient(authtest.TestServerID(types.RoleKube, "host-id")) + unscopedKubeClient, err := srv.NewClient(authtest.TestServerID(types.RoleKube, hostID)) require.NoError(t, err) // create an admin client to create resources and setup for test cases @@ -3593,7 +3594,7 @@ func TestKubeCRUDFromKubeService(t *testing.T) { err = adminClient.CreateKubernetesCluster(ctx, scopedKubeCluster) require.NoError(t, err) - kubeServer, err := types.NewKubernetesServerV3FromCluster(kubeCluster, "hostname", "host-id") + kubeServer, err := types.NewKubernetesServerV3FromCluster(kubeCluster, "hostname", hostID) require.NoError(t, err) _, err = adminClient.UpsertKubernetesServer(ctx, kubeServer) require.NoError(t, err) @@ -3747,12 +3748,16 @@ func TestKubeCRUDFromKubeService(t *testing.T) { _, err := adminClient.UpsertKubernetesServer(ctx, ks) require.NoError(t, err) - // unscoped kube clients SHOULD be able to delete a kube server - err = unscopedKubeClient.DeleteKubernetesServer(ctx, "host-id", ks.GetName()) + // unscoped kube clients SHOULD be able to delete their own kube server + // TODO (eriktate): remove in v20 + //nolint:staticcheck // SA1019 + err = unscopedKubeClient.DeleteKubernetesServer(ctx, hostID, ks.GetName()) require.NoError(t, err) // scoped kube clients SHOULD NOT be able to delete a kube server - err = scopedKubeClient.DeleteKubernetesServer(ctx, "hostname", "host-id") + // TODO (eriktate): remove in v20 + //nolint:staticcheck // SA1019 + err = scopedKubeClient.DeleteKubernetesServer(ctx, "hostname", hostID) require.Error(t, err) require.True(t, trace.IsAccessDenied(err), "expected access denied error") }) @@ -14701,11 +14706,21 @@ func TestRoleKubeLeastPrivilege(t *testing.T) { require.NoError(t, err) t.Run("delete own kube server allowed", func(t *testing.T) { - require.NoError(t, kube.DeleteKubernetesServer(ctx, ownID, "kube")) + // TODO (eriktate): remove in v20 + //nolint:staticcheck // SA1019 + require.NoError(t, kube.ScopedServerWithRoles().DeleteKubernetesServer(ctx, &proto.DeleteKubernetesServerRequest{ + HostID: ownID, + Name: "kube", + })) }) t.Run("delete other kube server denied", func(t *testing.T) { - err := kube.DeleteKubernetesServer(ctx, otherID, "kube") + // TODO (eriktate): remove in v20 + //nolint:staticcheck // SA1019 + err := kube.ScopedServerWithRoles().DeleteKubernetesServer(ctx, &proto.DeleteKubernetesServerRequest{ + HostID: otherID, + Name: "kube", + }) require.True(t, trace.IsAccessDenied(err), "expected access denied, got: %v", err) }) diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 2826fb9d53bc..3a7235ca38b7 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -2264,12 +2264,15 @@ func (g *GRPCServer) UpsertKubernetesServer(ctx context.Context, req *authpb.Ups } // DeleteKubernetesServer deletes a kubernetes server. +// +// Deprecated: Use DeleteKubeServer in the presence service instead. +// TODO (eriktate): remove in v20 func (g *GRPCServer) DeleteKubernetesServer(ctx context.Context, req *authpb.DeleteKubernetesServerRequest) (*emptypb.Empty, error) { - auth, err := g.authenticate(ctx) + auth, err := g.scopedAuthenticate(ctx) if err != nil { return nil, trace.Wrap(err) } - err = auth.DeleteKubernetesServer(ctx, req.GetHostID(), req.GetName()) + err = auth.DeleteKubernetesServer(ctx, req) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/presence/presencev1/service.go b/lib/auth/presence/presencev1/service.go index 2e91ad8fff7d..886e3fecff18 100644 --- a/lib/auth/presence/presencev1/service.go +++ b/lib/auth/presence/presencev1/service.go @@ -62,6 +62,8 @@ type Backend interface { GetKubeCluster(ctx context.Context, req *presencepb.GetKubeClusterRequest) (types.KubeCluster, error) RangeKubeClusters(ctx context.Context, req *presencepb.ListKubeClustersRequest) iter.Seq2[types.KubeCluster, error] DeleteKubeCluster(ctx context.Context, req *presencepb.DeleteKubeClusterRequest) error + + DeleteKubeServer(ctx context.Context, req *presencepb.DeleteKubeServerRequest) error } type Cache interface { @@ -813,3 +815,38 @@ func (s *Service) DeleteKubeCluster(ctx context.Context, req *presencepb.DeleteK } return presencepb.DeleteKubeClusterResponse_builder{}.Build(), nil } + +// DeleteKubeServer deletes a scoped or unscoped kube server. +func (s *Service) DeleteKubeServer( + ctx context.Context, req *presencepb.DeleteKubeServerRequest, +) (*presencepb.DeleteKubeServerResponse, error) { + authzCtx, err := s.scopedAuthorizer.AuthorizeScoped(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + switch { + case req.GetHostId() == "": + return nil, trace.BadParameter("host_id: must be specified") + case req.GetName() == "": + return nil, trace.BadParameter("name: must be specified") + } + + ruleCtx := authzCtx.RuleContext() + if err := authzCtx.CheckerContext.Decision(ctx, req.GetScope(), func(checker *services.ScopedAccessChecker) error { + // Kube agents can delete their own kube servers + // (builtin RoleKube only grants read on kube_server rules); anyone else + // needs an app_server delete rule granted in the target scope. + if err := authzCtx.AgentOwnedResourceAction(req.GetScope(), req.GetHostId(), types.RoleKube); err == nil { + return nil + } + return checker.CheckAccessToRules(&ruleCtx, types.KindKubeServer, types.VerbDelete) + }); err != nil { + return nil, trace.Wrap(err) + } + + if err := s.backend.DeleteKubeServer(ctx, req); err != nil { + return nil, trace.Wrap(err) + } + return presencepb.DeleteKubeServerResponse_builder{}.Build(), nil +} diff --git a/lib/cache/cache.go b/lib/cache/cache.go index 9a05d2a4c822..b35b8d12c0bd 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -383,7 +383,6 @@ func ForNode(cfg Config) Config { // ForKubernetes sets up watch configuration for a kubernetes service. func ForKubernetes(cfg Config) Config { cfg.target = "kube" - unscoped := types.ScopeFilterFromProto(scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSCOPED}.Build()) cfg.Watches = []types.WatchKind{ {Kind: types.KindCertAuthority, LoadSecrets: false, Filter: makeAllKnownCAsFilter().IntoMap()}, {Kind: types.KindClusterName}, @@ -394,7 +393,7 @@ func ForKubernetes(cfg Config) Config { {Kind: types.KindUser}, {Kind: types.KindRole}, {Kind: types.KindKubeServer}, - {Kind: types.KindKubernetesCluster, ScopeFilter: unscoped}, + {Kind: types.KindKubernetesCluster}, {Kind: types.KindKubeWaitingContainer}, {Kind: types.KindHealthCheckConfig}, } diff --git a/lib/cache/kube.go b/lib/cache/kube.go index e21c9bde5c91..75e53c52ef18 100644 --- a/lib/cache/kube.go +++ b/lib/cache/kube.go @@ -19,7 +19,6 @@ package cache import ( "context" "iter" - "strings" "github.com/gravitational/trace" "google.golang.org/protobuf/proto" @@ -29,10 +28,8 @@ import ( "github.com/gravitational/teleport/api/defaults" kubewaitingcontainerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" - scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/clientutils" - "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/itertools/stream" "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" @@ -52,7 +49,11 @@ func kubeServerByClusterNameKey(s types.KubeServer) string { if cluster == nil { return "" } - return string(ordered.Encode(cluster.GetName(), s.GetHostID(), s.GetName())) + // The second component is the scope-aware resource cursor, so within each + // kube cluster name the in-memory ordering matches the backend listing order + // (unscoped first, then scoped) and index keys are interchangeable with + // the fallback pagination tokens. + return string(ordered.Encode(cluster.GetName(), services.GetCursorForKubeServer(s))) } func newKubernetesServerCollection(p services.Presence, w types.WatchKind) (*collection[types.KubeServer, kubeServerIndex], error) { @@ -65,9 +66,7 @@ func newKubernetesServerCollection(p services.Presence, w types.WatchKind) (*col types.KindKubeServer, types.KubeServer.Copy, map[kubeServerIndex]func(types.KubeServer) string{ - kubeServerNameIndex: func(u types.KubeServer) string { - return u.GetHostID() + "/" + u.GetName() - }, + kubeServerNameIndex: services.GetCursorForKubeServer, kubeServerClusterNameIndex: kubeServerByClusterNameKey, }), fetcher: func(ctx context.Context, loadSecrets bool) ([]types.KubeServer, error) { @@ -137,20 +136,19 @@ func (c *Cache) RangeKubernetesServersWithName(ctx context.Context, clusterName return nil, "", trace.BadParameter("pagination token does not match the requested kubernetes cluster name") } - backendKey := "" + // The remainder of the token is the scope aware resource + startKey := "" if len(rest) > 0 { - var hostID, serverName string - if err := ordered.Decode(rest, &hostID, &serverName); err != nil { + if err := ordered.Decode(rest, &startKey); err != nil { return nil, "", trace.Wrap(err) } - backendKey = hostID + "/" + serverName } resp, err := c.Config.Presence.ListResources(ctx, clientproto.ListResourcesRequest{ ResourceType: types.KindKubeServer, Namespace: defaults.Namespace, Limit: int32(pageSize), - StartKey: backendKey, + StartKey: startKey, }) if err != nil { return nil, "", trace.Wrap(err) @@ -170,11 +168,7 @@ func (c *Cache) RangeKubernetesServersWithName(ctx context.Context, clusterName next := "" if resp.NextKey != "" { - hostID, serverName, ok := strings.Cut(resp.NextKey, backend.SeparatorString) - if !ok { - return nil, "", trace.BadParameter("invalid pagination token: %q", resp.NextKey) - } - next = string(ordered.Encode(clusterName, hostID, serverName)) + next = string(ordered.Encode(clusterName, resp.NextKey)) } return page, next, nil } @@ -213,17 +207,6 @@ func newKubernetesClusterCollection(upstream KubeClusterUpstream, w types.WatchK return nil, trace.BadParameter("missing parameter KubeClusterUpstream") } - var scopeFilter *scopesv1.Filter - if filter := w.ScopeFilter; filter != nil { - scopeFilter = scopesv1.Filter_builder{ - Scope: filter.Scope, - Mode: filter.Mode, - }.Build() - } else { - scopeFilter = scopesv1.Filter_builder{ - Mode: scopesv1.Mode_MODE_UNSCOPED, - }.Build() - } return &collection[types.KubeCluster, kubeClusterIndex]{ store: newStore( types.KindKubernetesCluster, @@ -236,7 +219,7 @@ func newKubernetesClusterCollection(upstream KubeClusterUpstream, w types.WatchK return upstream.ListKubeClusters(ctx, presencev1.ListKubeClustersRequest_builder{ PageSize: int32(pageSize), PageToken: pageToken, - ScopeFilter: scopeFilter, + ScopeFilter: w.ScopeFilter.ToProto(), }.Build()) })) }, diff --git a/lib/cache/kube_test.go b/lib/cache/kube_test.go index 909e4451fa8f..9723d758e7d0 100644 --- a/lib/cache/kube_test.go +++ b/lib/cache/kube_test.go @@ -20,16 +20,20 @@ import ( "context" "iter" "testing" + "testing/synctest" "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/client/proto" + apidefaults "github.com/gravitational/teleport/api/defaults" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/kubewaitingcontainer" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" ) @@ -227,7 +231,93 @@ func TestKubernetesServers(t *testing.T) { }, }) }) +} + +// TestListResourcesKubeServerScopedPagination verifies that ListResources +// pagination spanning unscoped and scoped kube servers returns every server +func TestListResourcesKubeServerScopedPagination(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + name string + neverOK bool + }{ + {name: "HealthyCache", neverOK: false}, + {name: "Fallback", neverOK: true}, + } { + t.Run(tt.name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ctx := t.Context() + + p := newTestPack(t, func(cfg Config) Config { + cfg = ForAuth(cfg) + cfg.neverOK = tt.neverOK + return cfg + }) + t.Cleanup(p.Close) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-p.eventsC: + // Discard events to avoid blocking the test. + } + } + }() + + key := func(s types.KubeServer) string { + return s.GetScope() + "/" + s.GetHostID() + "/" + s.GetName() + } + + var expectedKeys []string + for _, scope := range []string{"", "", "", "/prod", "/prod", "/staging"} { + server := mustCreateKubeServer(t, uuid.New().String(), "testcluster").(*types.KubernetesServerV3) + server.Scope = scope + server.Spec.Cluster.Scope = scope + _, err := p.presenceS.UpsertKubernetesServer(ctx, server) + require.NoError(t, err) + expectedKeys = append(expectedKeys, key(server)) + } + + // Wait for the cache to replicate all servers. + synctest.Wait() + + var actualKeys []string + startKey := "" + for { + resp, err := p.cache.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: types.KindKubeServer, + Namespace: apidefaults.Namespace, + Limit: 1, + StartKey: startKey, + }) + require.NoError(t, err) + r := resp.Resources[0] + server, ok := r.(types.KubeServer) + require.True(t, ok) + if startKey != "" { // first entry + if startKey == scopes.ResourceCursorScopedStart() { + // The unscoped range ended exactly at a page boundary; + // next entry should be scoped + require.NotEmpty(t, server.GetScope()) + } else { + require.Equal(t, startKey, services.GetCursorForKubeServer(server)) + } + } + actualKeys = append(actualKeys, key(server)) + + if resp.NextKey == "" { + break + } + startKey = resp.NextKey + } + + require.ElementsMatch(t, expectedKeys, actualKeys) + }) + }) + } } func mustCreateKubeServer(t testing.TB, hostID, clusterName string) types.KubeServer { @@ -250,7 +340,11 @@ var kubeServerRangeFuncs = rangeServersWithTargetNameFuncs[types.KubeServer]{ return err }, delete: func(ctx context.Context, presence services.Presence, s types.KubeServer) error { - return presence.DeleteKubernetesServer(ctx, s.GetHostID(), s.GetName()) + return presence.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + HostId: s.GetHostID(), + Name: s.GetName(), + Scope: s.GetScope(), + }.Build()) }, rangeByName: (*Cache).RangeKubernetesServersWithName, } diff --git a/lib/inventory/controller.go b/lib/inventory/controller.go index 363f02d89cfe..dc858980cf72 100644 --- a/lib/inventory/controller.go +++ b/lib/inventory/controller.go @@ -66,7 +66,7 @@ type Auth interface { DeleteDatabaseServer(ctx context.Context, namespace, hostID, name string) error UpsertKubernetesServer(context.Context, types.KubeServer) (*types.KeepAlive, error) - DeleteKubernetesServer(ctx context.Context, hostID, name string) error + DeleteKubeServer(ctx context.Context, req *presencev1.DeleteKubeServerRequest) error UpsertRelayServer(ctx context.Context, relayServer *presencev1.RelayServer) (*presencev1.RelayServer, error) DeleteRelayServer(ctx context.Context, name string) error @@ -831,7 +831,11 @@ func (c *Controller) doResourceCleanup(handle *upstreamHandle) { return } - if err := c.auth.DeleteKubernetesServer(c.closeContext, kube.resource.GetHostID(), kube.resource.GetName()); err != nil && !trace.IsNotFound(err) { + if err := c.auth.DeleteKubeServer(c.closeContext, presencev1.DeleteKubeServerRequest_builder{ + Scope: kube.resource.GetScope(), + HostId: kube.resource.GetHostID(), + Name: kube.resource.GetName(), + }.Build()); err != nil && !trace.IsNotFound(err) { if cleanupCtx.Err() != nil { slog.WarnContext(c.closeContext, "halting remaining resource cleanup", "instance_id", handle.Hello().GetServerID(), "error", err) return diff --git a/lib/inventory/controller_test.go b/lib/inventory/controller_test.go index 07017784c322..477d695bd6af 100644 --- a/lib/inventory/controller_test.go +++ b/lib/inventory/controller_test.go @@ -182,7 +182,7 @@ func (a *fakeAuth) UpsertKubernetesServer(_ context.Context, server types.KubeSe }, a.err } -func (a *fakeAuth) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { +func (a *fakeAuth) DeleteKubeServer(ctx context.Context, req *presencev1.DeleteKubeServerRequest) error { return nil } diff --git a/lib/join/join_ec2_test.go b/lib/join/join_ec2_test.go index b3557478faa3..2a8d5abdcf3f 100644 --- a/lib/join/join_ec2_test.go +++ b/lib/join/join_ec2_test.go @@ -678,7 +678,10 @@ func TestHostUniqueCheck(t *testing.T) { require.NoError(t, err) }, deleter: func(t *testing.T, hostID string) { - require.NoError(t, a.DeleteKubernetesServer(t.Context(), hostID, "test-kube-cluster")) + require.NoError(t, a.DeleteKubeServer(t.Context(), presencev1.DeleteKubeServerRequest_builder{ + HostId: hostID, + Name: "test-kube-cluster", + }.Build())) }, }, { diff --git a/lib/kube/proxy/exec_test.go b/lib/kube/proxy/exec_test.go index 098728ae646c..732c4dcf83da 100644 --- a/lib/kube/proxy/exec_test.go +++ b/lib/kube/proxy/exec_test.go @@ -513,7 +513,7 @@ func TestExecKubeServiceWithFaultyPrimary(t *testing.T) { // creates a Kubernetes service with a configured cluster pointing to mock api server testCtx := SetupTestContext(t.Context(), t, TestConfig{ Clusters: []KubeClusterConfig{{Name: kubeCluster, APIEndpoint: kubeMock.URL}}, - WrapAuthClient: func(client authclient.ClientI) authclient.ClientI { + WrapProxyAccessPoint: func(client authclient.ClientI) authclient.ClientI { failingAccessPoint.ClientI = client return failingAccessPoint }, diff --git a/lib/kube/proxy/server.go b/lib/kube/proxy/server.go index 2430f887c987..b45ae0afed49 100644 --- a/lib/kube/proxy/server.go +++ b/lib/kube/proxy/server.go @@ -243,10 +243,6 @@ func NewTLSServer(cfg TLSServerConfig) (*TLSServer, error) { return nil, trace.Wrap(err) } - // TODO(eriktate/scopes): remove this validation once dynamic cluster registration supports scopes - if cfg.GetScope() != "" && len(cfg.ResourceMatchers) > 0 { - return nil, trace.BadParameter("dynamic cluster registration not supported for scoped kube_service, resource matchers must be empty") - } cfg.ForwarderConfig.log = log fwd, err := NewForwarder(cfg.ForwarderConfig) if err != nil { diff --git a/lib/kube/proxy/utils_test.go b/lib/kube/proxy/utils_test.go index cf8a2a3075bb..b0cf5db5f5a5 100644 --- a/lib/kube/proxy/utils_test.go +++ b/lib/kube/proxy/utils_test.go @@ -120,6 +120,7 @@ type TestConfig struct { ClusterFeatures func() proto.Features CreateAuditStreamErr error WrapAuthClient func(authclient.ClientI) authclient.ClientI + WrapProxyAccessPoint func(authclient.ClientI) authclient.ClientI ScopesFeatures scopes.Features Scope string } @@ -150,12 +151,13 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo // Create and start test auth server. authServer, err := authtest.NewAuthServer(authtest.AuthServerConfig{ - Clock: clockwork.NewFakeClockAt(time.Now()), - ClusterName: testCtx.ClusterName, - Streamer: streamer, - UploadHandler: testCtx.UploadHandler, - Dir: t.TempDir(), - Modules: cmp.Or(cfg.Modules, modulestest.OSSModules()), + Clock: clockwork.NewFakeClockAt(time.Now()), + ClusterName: testCtx.ClusterName, + Streamer: streamer, + UploadHandler: testCtx.UploadHandler, + Dir: t.TempDir(), + Modules: cmp.Or(cfg.Modules, modulestest.OSSModules()), + ScopesFeatures: cfg.ScopesFeatures, }) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, authServer.Close()) }) @@ -280,9 +282,11 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo }, ) require.NoError(t, err) - err = healthCheckManager.Start(testCtx.Context) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, healthCheckManager.Close()) }) + if cfg.Scope == "" { + err = healthCheckManager.Start(testCtx.Context) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, healthCheckManager.Close()) }) + } var authClient authclient.ClientI = client if cfg.WrapAuthClient != nil { @@ -293,6 +297,15 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo if cfg.WrapAuthClient != nil { accessPoint = cfg.WrapAuthClient(client) } + // The kube service must use its scoped identity to watch kube_cluster + // resources. The proxy, however, watches kube_servers in every scope. + proxyAccessPoint := accessPoint + if cfg.Scope != "" { + proxyAccessPoint = proxyAuthClient + } + if cfg.WrapProxyAccessPoint != nil { + proxyAccessPoint = cfg.WrapProxyAccessPoint(proxyAccessPoint) + } // Create kubernetes service server. testCtx.KubeServer, err = NewTLSServer(TLSServerConfig{ @@ -352,7 +365,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo testCtx.Context, kubewatcher.ProxyKubeServerWatcherConfig{ Logger: logtest.NewLogger(), - AccessPoint: accessPoint, + AccessPoint: proxyAccessPoint, FallbackGetter: proxyAuthClient, PrimaryTimeout: time.Second, FallbackInterval: time.Second, @@ -415,7 +428,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo PROXYSigner: &multiplexer.PROXYSigner{}, }, TLS: proxyTLSConfig.Clone(), - AccessPoint: accessPoint, + AccessPoint: proxyAccessPoint, KubernetesServersWatcher: kubeServersWatcher, LimiterConfig: limiter.Config{ MaxConnections: 1000, diff --git a/lib/kube/proxy/watcher.go b/lib/kube/proxy/watcher.go index c1df88d9b8cc..fad64649bd06 100644 --- a/lib/kube/proxy/watcher.go +++ b/lib/kube/proxy/watcher.go @@ -27,6 +27,7 @@ import ( "github.com/gravitational/trace" + presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/readonly" @@ -93,7 +94,7 @@ func (s *TLSServer) startReconciler(ctx context.Context) (err error) { // startKubeClusterResourceWatcher starts watching changes to Kube Clusters resources and // registers/unregisters the proxied Kube Cluster accordingly. func (s *TLSServer) startKubeClusterResourceWatcher(ctx context.Context) (*services.GenericWatcher[types.KubeCluster, readonly.KubeCluster], error) { - if len(s.ResourceMatchers) == 0 || s.KubeServiceType != KubeService || s.GetScope() != "" { + if len(s.ResourceMatchers) == 0 || s.KubeServiceType != KubeService { s.log.DebugContext(ctx, "Not initializing Kube Cluster resource watcher") return nil, nil } @@ -264,7 +265,11 @@ func (s *TLSServer) unregisterKubeCluster(ctx context.Context, cluster types.Kub // deleteKubernetesServer deletes kubernetes server for the specified cluster. func (s *TLSServer) deleteKubernetesServer(ctx context.Context, name string) error { - err := s.AuthClient.DeleteKubernetesServer(ctx, s.HostID, name) + err := s.AuthClient.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: s.GetScope(), + HostId: s.HostID, + Name: name, + }.Build()) if err != nil && !trace.IsNotFound(err) { return trace.Wrap(err) } diff --git a/lib/kube/proxy/watcher_test.go b/lib/kube/proxy/watcher_test.go index d4369b693fb9..f2daadbae2fd 100644 --- a/lib/kube/proxy/watcher_test.go +++ b/lib/kube/proxy/watcher_test.go @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" ) @@ -51,13 +52,13 @@ func newMockAuthClient() *mockAuthClient { } } -func (m *mockAuthClient) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { +func (m *mockAuthClient) DeleteKubeServer(ctx context.Context, req *presencev1.DeleteKubeServerRequest) error { select { - case m.deleteCh <- name: + case m.deleteCh <- scopes.MakeResourceCursor(req.GetScope(), req.GetName()): case <-time.After(5 * time.Second): return fmt.Errorf("failed to signal kube server deletion") } - return m.ClientI.DeleteKubernetesServer(ctx, hostID, name) + return m.ClientI.DeleteKubeServer(ctx, req) } // TestWatcher verifies that kubernetes agent properly detects and applies @@ -211,7 +212,7 @@ func TestWatcher(t *testing.T) { } select { case deleted := <-authClient.deleteCh: - require.Equal(t, kube1.GetName(), deleted) + require.Equal(t, services.GetCursorForKubeCluster(kube1), deleted) case <-time.After(time.Second): t.Fatal("Kube server wasn't deleted after 1s.") } @@ -234,7 +235,153 @@ func TestWatcher(t *testing.T) { } select { case deleted := <-authClient.deleteCh: - require.Equal(t, kube2.GetName(), deleted) + require.Equal(t, services.GetCursorForKubeCluster(kube2), deleted) + case <-time.After(time.Second): + t.Fatal("Kube server wasn't deleted after 1s.") + } +} + +// TestScopedWatcher verifies that kubernetes agent properly detects and applies +// changes to scoped kube_cluster resources. +func TestScopedWatcher(t *testing.T) { + kubeMock, err := testingkubemock.NewKubeAPIMock() + require.NoError(t, err) + t.Cleanup(func() { kubeMock.Close() }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + reconcileCh := make(chan types.KubeClusters) + + const scope = "/aa" + authClient := newMockAuthClient() + // Setup kubernetes server that proxies one static kube cluster and + // watches for kube_clusters with label group=a. + testCtx := SetupTestContext(ctx, t, TestConfig{ + Clusters: []KubeClusterConfig{{"kube0", kubeMock.URL}}, + ResourceMatchers: []services.ResourceMatcher{ + {Labels: types.Labels{ + "group": []string{"a"}, + }}, + }, + OnReconcile: func(kcs types.KubeClusters) { + select { + case reconcileCh <- kcs: + case <-ctx.Done(): + return + } + }, + WrapAuthClient: func(client authclient.ClientI) authclient.ClientI { + authClient.ClientI = client + return authClient + }, + Scope: scope, + ScopesFeatures: scopes.Features{ + Enabled: true, + AgentPinEnabled: true, + }, + }) + + require.Len(t, testCtx.KubeServer.fwd.kubeClusters(), 1) + kube0 := testCtx.KubeServer.fwd.kubeClusters()[0] + + // Only kube0 should be registered initially. + select { + case a := <-reconcileCh: + sort.Sort(a) + require.Empty(t, cmp.Diff(types.KubeClusters{kube0}, a, + cmpopts.IgnoreFields(types.Metadata{}, "Revision"), + )) + case <-time.After(time.Second): + t.Fatal("Didn't receive reconcile event after 1s.") + } + + // Create an unscoped kube1 with label group=a + kube1, err := makeDynamicKubeCluster(t, "kube1", kubeMock.URL, map[string]string{"group": "a"}) + require.NoError(t, err) + err = testCtx.AuthServer.CreateKubernetesCluster(ctx, kube1) + require.NoError(t, err) + + // It should NOT be registered due to scope mismatch + select { + case <-reconcileCh: + require.FailNow(t, "unexpected reconcile event for an unscoped kube cluster") + case <-time.After(time.Second): + } + + // Create kube_cluster with label group=a and scope=/aa + kube2, err := makeDynamicKubeCluster(t, "kube2", kubeMock.URL, map[string]string{"group": "a"}) + require.NoError(t, err) + kube2.Scope = scope + err = testCtx.AuthServer.CreateKubernetesCluster(ctx, kube2) + require.NoError(t, err) + + // It should be registered. + select { + case a := <-reconcileCh: + sort.Sort(a) + require.Empty(t, cmp.Diff(types.KubeClusters{kube0, kube2}, a, + cmpopts.IgnoreFields(types.Metadata{}, "Revision"), + )) + case <-time.After(time.Second): + t.Fatal("Didn't receive reconcile event after 1s.") + } + + // Create kube_cluster with label group=b in scope=/aa + kube3, err := makeDynamicKubeCluster(t, "kube3", kubeMock.URL, map[string]string{"group": "b"}) + require.NoError(t, err) + kube3.Scope = scope + err = testCtx.AuthServer.CreateKubernetesCluster(ctx, kube3) + require.NoError(t, err) + + // It shouldn't be registered. + select { + case a := <-reconcileCh: + sort.Sort(a) + require.Empty(t, cmp.Diff(types.KubeClusters{kube0, kube2}, a, + cmpopts.IgnoreFields(types.Metadata{}, "Revision"), + )) + case <-time.After(time.Second): + t.Fatal("Didn't receive reconcile event after 1s.") + } + + // Update kube3 labels so it matches. + kube3.SetStaticLabels(map[string]string{"group": "a", types.OriginLabel: types.OriginDynamic}) + err = testCtx.AuthServer.UpdateKubernetesCluster(ctx, kube3) + require.NoError(t, err) + + // kube0, kube2, and kube3 should be registered now. + select { + case a := <-reconcileCh: + sort.Sort(a) + require.Empty(t, cmp.Diff(types.KubeClusters{kube0, kube2, kube3}, a, + cmpopts.IgnoreFields(types.Metadata{}, "Revision"), + )) + case <-time.After(time.Second): + t.Fatal("Didn't receive reconcile event after 1s.") + } + + // Remove kube2. + err = testCtx.AuthServer.DeleteKubeCluster(ctx, presencev1.DeleteKubeClusterRequest_builder{ + Name: kube2.GetName(), + Scope: kube2.GetScope(), + }.Build()) + require.NoError(t, err) + + // Only kube0 and kube3 should remain. + select { + case a := <-reconcileCh: + sort.Sort(a) + require.Empty(t, cmp.Diff(types.KubeClusters{kube0, kube3}, a, + cmpopts.IgnoreFields(types.Metadata{}, "Revision"), + )) + case <-time.After(time.Second): + t.Fatal("Didn't receive reconcile event after 1s.") + } + + // Confirm delete event is received + select { + case deleted := <-authClient.deleteCh: + require.Equal(t, services.GetCursorForKubeCluster(kube2), deleted) case <-time.After(time.Second): t.Fatal("Kube server wasn't deleted after 1s.") } diff --git a/lib/services/app.go b/lib/services/app.go index 4a4719c644bd..af0a6625200e 100644 --- a/lib/services/app.go +++ b/lib/services/app.go @@ -347,8 +347,11 @@ func GetCursorForAppServer(server types.AppServer) string { // GetCursorForResource returns the pagination cursor for a // resource used in ListResources. func GetCursorForResource(r types.ResourceWithLabels) string { - if s, ok := r.(types.AppServer); ok { - return GetCursorForAppServer(s) + switch res := r.(type) { + case types.AppServer: + return GetCursorForAppServer(res) + case types.KubeServer: + return GetCursorForKubeServer(res) } return backend.GetPaginationKey(r) } diff --git a/lib/services/kubernetes.go b/lib/services/kubernetes.go index d40b97c31334..113156118e0d 100644 --- a/lib/services/kubernetes.go +++ b/lib/services/kubernetes.go @@ -178,3 +178,10 @@ func UnmarshalKubeCluster(data []byte, opts ...MarshalOption) (types.KubeCluster func GetCursorForKubeCluster(cluster types.KubeCluster) string { return scopes.MakeResourceCursor(cluster.GetScope(), cluster.GetName()) } + +// GetCursorForKubeServer returns the resource cursor identifying a kube server +// in the logical resource stream: "/" for unscoped kube servers +// and "~scoped///" for scoped kube servers. +func GetCursorForKubeServer(server types.KubeServer) string { + return scopes.MakeResourceCursorWithHost(server.GetScope(), server.GetHostID(), server.GetName()) +} diff --git a/lib/services/local/events.go b/lib/services/local/events.go index 802772723e23..3cbf24161de1 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -1693,7 +1693,7 @@ func (p *webTokenParser) parse(event backend.Event) (types.Resource, error) { func newKubeServerParser() *kubeServerParser { return &kubeServerParser{ - baseParser: newBaseParser(backend.NewKey(kubeServersPrefix)), + baseParser: newBaseParser(kubeServersUnscopedPrefix(), kubeServersScopedPrefix()), } } @@ -1704,18 +1704,24 @@ type kubeServerParser struct { func (p *kubeServerParser) parse(event backend.Event) (types.Resource, error) { switch event.Type { case types.OpDelete: - components := event.Item.Key.Components() - if len(components) != 3 { - return nil, trace.NotFound("failed parsing %v", event.Item.Key.String()) + // Scoped kube servers live under + // /scoped/kubeServers///. + sqn, hostID, err := kubeServerNameFromKey(event.Item.Key) + if err != nil { + return nil, trace.Wrap(err) } - return &types.ResourceHeader{ + return &types.KubernetesServerV3{ Kind: types.KindKubeServer, Version: types.V3, Metadata: types.Metadata{ - Name: components[2], + Name: sqn.Name, Namespace: apidefaults.Namespace, - Description: components[1], + Description: hostID, + }, + Scope: sqn.Scope, + Spec: types.KubernetesServerSpecV3{ + HostID: hostID, }, }, nil case types.OpPut: @@ -1872,6 +1878,35 @@ func kubeNameFromKey(key backend.Key) (scopes.QualifiedName, error) { } } +func kubeServerNameFromKey(key backend.Key) (scopes.QualifiedName, string, error) { + switch { + case key.HasPrefix(kubeServersScopedPrefix()): + components := key.TrimPrefix(kubeServersScopedPrefix()).Components() + if len(components) != 3 { + return scopes.QualifiedName{}, "", trace.NotFound("failed parsing %v", key.String()) + } + encodedScope, hostID, name := components[0], components[1], components[2] + scope, err := scopes.DecodeFromKey(encodedScope) + if err != nil { + return scopes.QualifiedName{}, "", trace.Wrap(err) + } + return scopes.QualifiedName{ + Scope: scope, + Name: name, + }, hostID, nil + case key.HasPrefix(kubeServersUnscopedPrefix()): + components := key.TrimPrefix(kubeServersUnscopedPrefix()).Components() + if len(components) != 2 { + return scopes.QualifiedName{}, "", trace.NotFound("failed parsing %v", key.String()) + } + return scopes.QualifiedName{ + Name: components[1], + }, components[0], nil + default: + return scopes.QualifiedName{}, "", trace.NotFound("failed parsing %v", key.String()) + } +} + func newCrownJewelParser() *crownJewelParser { return &crownJewelParser{ baseParser: newBaseParser(backend.NewKey(crownJewelsKey)), diff --git a/lib/services/local/presence.go b/lib/services/local/presence.go index 223e412a199d..975de0b81727 100644 --- a/lib/services/local/presence.go +++ b/lib/services/local/presence.go @@ -40,6 +40,7 @@ import ( "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" scopecache "github.com/gravitational/teleport/lib/scopes/cache" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/local/generic" @@ -57,6 +58,7 @@ type PresenceService struct { relayServers *generic.ServiceWrapper[*presencev1.RelayServer] appServers *generic.ScopeAwareService[types.AppServer] + kubeServers *generic.ScopeAwareService[types.KubeServer] } type appServerServiceParams struct { @@ -85,6 +87,25 @@ func appServerServiceForHost( return service.WithPrefix(params.Host), nil } +// kubeServerServiceForHost returns a [*generic.Service] prefixed for the kube +// servers of a single host: +// - unscoped: /kubeServers/default// +// - scoped: /scoped/kubeServers/// +// +// Since a kube server represents a single forwarded kube cluster, there may be +// multiple kube clusters on a single host, so the hostID prefix is needed. +func kubeServerServiceForHost( + kubeServers *generic.ScopeAwareService[types.KubeServer], + sqn scopes.QualifiedName, +) (*generic.Service[types.KubeServer], error) { + service, err := kubeServers.WithScopePrefix(sqn.Scope) + if err != nil { + return nil, trace.Wrap(err) + } + + return service.WithPrefix(sqn.Name), nil +} + var _ services.PresenceInternal = (*PresenceService)(nil) // backendItemToResourceFunc defines a function that unmarshals a @@ -117,6 +138,22 @@ func NewPresenceService(b backend.Backend) *PresenceService { panic("impossible: failed to construct app_server service wrapper") } + kubeServers, err := generic.NewScopeAwareService(&generic.ScopeAwareServiceConfig[types.KubeServer]{ + Backend: b, + ResourceKind: types.KindKubeServer, + UnscopedBackendPrefix: kubeServersUnscopedPrefix(), + ScopedBackendPrefix: kubeServersScopedPrefix(), + MarshalFunc: func(server types.KubeServer, option ...services.MarshalOption) ([]byte, error) { + return services.MarshalKubeServer(server, option...) + }, + UnmarshalFunc: func(bytes []byte, option ...services.MarshalOption) (types.KubeServer, error) { + server, err := services.UnmarshalKubeServer(bytes, option...) + return server, trace.Wrap(err) + }, + }) + if err != nil { + panic("impossible: failed to construct kube_server service wrapper") + } return &PresenceService{ logger: slog.With(teleport.ComponentKey, "Presence"), jitter: retryutils.FullJitter, @@ -124,6 +161,7 @@ func NewPresenceService(b backend.Backend) *PresenceService { relayServers: relayServers, appServers: appServers, + kubeServers: kubeServers, } } @@ -1006,30 +1044,19 @@ func (s *PresenceService) DeleteSemaphore(ctx context.Context, filter types.Sema // UpsertKubernetesServer registers an kubernetes server. func (s *PresenceService) UpsertKubernetesServer(ctx context.Context, server types.KubeServer) (*types.KeepAlive, error) { - if err := services.CheckAndSetDefaults(server); err != nil { - return nil, trace.Wrap(err) - } - rev := server.GetRevision() - value, err := services.MarshalKubeServer(server) + svc, err := s.kubeServers.WithScopedResourcePrefix(scopes.QualifiedName{ + Scope: server.GetScope(), + Name: server.GetHostID(), + }) if err != nil { return nil, trace.Wrap(err) } - // Since a kube server represents a single proxied cluster, there may - // be multiple kubernetes servers on a single host, so they are stored under - // the following path in the backend: - // /kubeServers// - _, err = s.Put(ctx, backend.Item{ - Key: backend.NewKey(kubeServersPrefix, - server.GetHostID(), - server.GetName()), - Value: value, - Expires: server.Expiry(), - Revision: rev, - }) + + upserted, err := svc.UpsertResource(ctx, server) if err != nil { return nil, trace.Wrap(err) } - if server.Expiry().IsZero() { + if upserted.Expiry().IsZero() { return &types.KeepAlive{}, nil } return &types.KeepAlive{ @@ -1037,33 +1064,38 @@ func (s *PresenceService) UpsertKubernetesServer(ctx context.Context, server typ Name: server.GetName(), Namespace: server.GetNamespace(), HostID: server.GetHostID(), - Expires: server.Expiry(), + Expires: upserted.Expiry(), Scope: server.GetScope(), }, nil } -// DeleteKubernetesServer removes specified kubernetes server. -func (s *PresenceService) DeleteKubernetesServer(ctx context.Context, hostID, name string) error { - if name == "" { +// DeleteKubeServer removes specified kubernetes server. +func (s *PresenceService) DeleteKubeServer(ctx context.Context, req *presencev1.DeleteKubeServerRequest) error { + if req.GetName() == "" { return trace.BadParameter("no name specified for kubernetes server deletion") } - if hostID == "" { + if req.GetHostId() == "" { return trace.BadParameter("no hostID specified for kubernetes server deletion") } - key := backend.NewKey(kubeServersPrefix, hostID, name) - return s.Delete(ctx, key) + + svc, err := kubeServerServiceForHost(s.kubeServers, scopes.QualifiedName{ + Scope: req.GetScope(), + Name: req.GetHostId(), + }) + if err != nil { + return trace.Wrap(err) + } + return svc.DeleteResource(ctx, req.GetName()) } // DeleteAllKubernetesServers removes all registered kubernetes servers. func (s *PresenceService) DeleteAllKubernetesServers(ctx context.Context) error { - startKey := backend.ExactKey(kubeServersPrefix) - return s.DeleteRange(ctx, startKey, backend.RangeEnd(startKey)) + return s.kubeServers.DeleteAllResources(ctx) } // GetKubernetesServers returns all registered kubernetes servers. func (s *PresenceService) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, error) { - servers, err := s.getKubernetesServers(ctx) - return servers, trace.Wrap(err) + return stream.Collect(s.kubeServers.Resources(ctx, "", "")) } // RangeKubernetesServersWithName returns an iterator over kubernetes servers for a given cluster name. @@ -1076,44 +1108,12 @@ func (s *PresenceService) RangeKubernetesServersWithName(ctx context.Context, cl // CheckAndSetDefaults invariant, this filter could check against the backend // key's trailing component before unmarshalling. Currently no such invariant // exists, so we unmarshal every item to read the embedded cluster name. - mapFn := func(item backend.Item) (types.KubeServer, bool) { - server, err := services.UnmarshalKubeServer( - item.Value, - services.WithExpires(item.Expires), - services.WithRevision(item.Revision), - ) - if err != nil { - s.logger.WarnContext(ctx, "Failed to unmarshal kubernetes server", "key", item.Key, "error", err) - return nil, false - } + mapFn := func(server types.KubeServer) (types.KubeServer, bool) { cluster := server.GetCluster() return server, cluster != nil && cluster.GetName() == clusterName } - startKey := backend.ExactKey(kubeServersPrefix) - endKey := backend.RangeEnd(startKey) - - return stream.FilterMap(s.Backend.Items(ctx, backend.ItemsParams{StartKey: startKey, EndKey: endKey}), mapFn) -} - -func (s *PresenceService) getKubernetesServers(ctx context.Context) ([]types.KubeServer, error) { - startKey := backend.ExactKey(kubeServersPrefix) - result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit) - if err != nil { - return nil, trace.Wrap(err) - } - servers := make([]types.KubeServer, len(result.Items)) - for i, item := range result.Items { - server, err := services.UnmarshalKubeServer( - item.Value, - services.WithExpires(item.Expires), - services.WithRevision(item.Revision)) - if err != nil { - return nil, trace.Wrap(err) - } - servers[i] = server - } - return servers, nil + return stream.FilterMap(s.kubeServers.Resources(ctx, "", ""), mapFn) } // GetDatabaseServers returns all registered database proxy servers. @@ -1345,6 +1345,14 @@ func (s *PresenceService) KeepAliveServer(ctx context.Context, h types.KeepAlive return trace.Wrap(err) } + var encodedScope string + if h.Scope != "" { + var err error + encodedScope, err = scopes.EncodeForKey(h.Scope) + if err != nil { + return trace.Wrap(err) + } + } // Update the prefix off the type information in the keep alive. var key backend.Key switch h.GetType() { @@ -1361,7 +1369,11 @@ func (s *PresenceService) KeepAliveServer(ctx context.Context, h types.KeepAlive case constants.KeepAliveWindowsDesktopService: key = backend.NewKey(windowsDesktopServicesPrefix, h.Name) case constants.KeepAliveKube: - key = backend.NewKey(kubeServersPrefix, h.HostID, h.Name) + if encodedScope == "" { + key = kubeServersUnscopedPrefix().AppendKey(backend.NewKey(h.HostID, h.Name)) + } else { + key = kubeServersScopedPrefix().AppendKey(backend.NewKey(encodedScope, h.HostID, h.Name)) + } case constants.KeepAliveDatabaseService: key = backend.NewKey(databaseServicePrefix, h.Name) default: @@ -1586,8 +1598,7 @@ func (s *PresenceService) listResources(ctx context.Context, req proto.ListResou keyPrefix = []string{windowsDesktopsPrefix} unmarshalItemFunc = backendItemToWindowsDesktop case types.KindKubeServer: - keyPrefix = []string{kubeServersPrefix} - unmarshalItemFunc = backendItemToKubernetesServer + return s.listKubeServers(ctx, req) case types.KindUserGroup: keyPrefix = []string{userGroupPrefix} unmarshalItemFunc = backendItemToUserGroup @@ -1692,6 +1703,47 @@ func (s *PresenceService) listAppServers(ctx context.Context, req proto.ListReso }, nil } +// listKubeServers returns a page of kube servers retrieving both the +// unscoped and the scoped backend entries. +func (s *PresenceService) listKubeServers(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) { + filter := services.MatchResourceFilter{ + ResourceKind: req.ResourceType, + Labels: req.Labels, + SearchKeywords: req.SearchKeywords, + } + if req.PredicateExpression != "" { + expression, err := services.NewResourceExpression(req.PredicateExpression) + if err != nil { + return nil, trace.Wrap(err) + } + filter.PredicateExpression = expression + } + + var matchErr error + servers, nextKey, err := s.kubeServers.ListResourcesWithFilter(ctx, int(req.Limit), req.StartKey, func(server types.KubeServer) bool { + if matchErr != nil { + return false + } + match, err := services.MatchResourceByFilters(server, filter, nil /* ignore dup matches */) + if err != nil { + matchErr = err + return false + } + return match + }) + if err != nil { + return nil, trace.Wrap(err) + } + if matchErr != nil { + return nil, trace.Wrap(matchErr) + } + + return &types.ListResourcesResponse{ + Resources: types.KubeServers(servers).AsResources(), + NextKey: nextKey, + }, nil +} + func getFakePaginationKey(ki backend.KeyedItem) string { // TODO(eriktate/scopes): this will need to be reassessed when we implement scoped namespacing if kubeCluster, ok := ki.(types.KubeCluster); ok { @@ -1976,16 +2028,6 @@ func backendItemToDatabaseService(item backend.Item) (types.ResourceWithLabels, ) } -// backendItemToKubernetesServer unmarshals `backend.Item` into a -// `types.KubeServer`, returning it as a `types.ResourceWithLabels`. -func backendItemToKubernetesServer(item backend.Item) (types.ResourceWithLabels, error) { - return services.UnmarshalKubeServer( - item.Value, - services.WithExpires(item.Expires), - services.WithRevision(item.Revision), - ) -} - // backendItemToServer returns `backendItemToResourceFunc` to unmarshal a // `backend.Item` into a `types.ServerV2` with a specific `kind`, returning it // as a `types.ResourceWithLabels`. @@ -2101,6 +2143,14 @@ func (relayServerParser) prefixes() []backend.Key { return []backend.Key{backend.ExactKey(relayServersPrefix)} } +func kubeServersUnscopedPrefix() backend.Key { + return backend.NewKey("kubeServers") +} + +func kubeServersScopedPrefix() backend.Key { + return backend.NewKey("scoped", "kubeServers") +} + const ( reverseTunnelsPrefix = "reverseTunnels" tunnelConnectionsPrefix = "tunnelConnections" @@ -2113,7 +2163,6 @@ const ( serversPrefix = "servers" dbServersPrefix = "databaseServers" appServersPrefix = "appServers" - kubeServersPrefix = "kubeServers" namespacesPrefix = "namespaces" authServersPrefix = "authservers" proxiesPrefix = "proxies" diff --git a/lib/services/local/presence_test.go b/lib/services/local/presence_test.go index 71a64cc64af4..321a9e3a5268 100644 --- a/lib/services/local/presence_test.go +++ b/lib/services/local/presence_test.go @@ -562,7 +562,7 @@ func mustCreateKubernetesServer(t *testing.T, clusterName string) types.KubeServ return server } -func TestRangeKubernetesServersWithName(t *testing.T) { +func TestKubeServersCRUD(t *testing.T) { t.Parallel() ctx := t.Context() @@ -570,18 +570,22 @@ func TestRangeKubernetesServersWithName(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _ = bk.Close() }) - presence := NewPresenceService(bk) + presence := NewPresenceService(backend.NewSanitizer(bk)) t.Run("ParameterValidation", func(t *testing.T) { _, err = iterstream.Collect(presence.RangeKubernetesServersWithName(ctx, "")) require.ErrorAs(t, err, new(*trace.BadParameterError)) }) + const scope = "/aa" server1 := mustCreateKubernetesServer(t, "shared-cluster") server2 := mustCreateKubernetesServer(t, "shared-cluster") server3 := mustCreateKubernetesServer(t, "standalone-cluster") + server4, ok := server2.Copy().(*types.KubernetesServerV3) + require.True(t, ok, "expected types.KubernetesServerV3") + server4.Scope = scope - for _, s := range []types.KubeServer{server1, server2, server3} { + for _, s := range []types.KubeServer{server1, server2, server3, server4} { _, err := presence.UpsertKubernetesServer(ctx, s) require.NoError(t, err) } @@ -589,7 +593,7 @@ func TestRangeKubernetesServersWithName(t *testing.T) { t.Run("MultipleServersSameCluster", func(t *testing.T) { servers, err := iterstream.Collect(presence.RangeKubernetesServersWithName(ctx, "shared-cluster")) require.NoError(t, err) - require.Len(t, servers, 2) + require.Len(t, servers, 3) for _, s := range servers { require.Equal(t, "shared-cluster", s.GetCluster().GetName()) } @@ -608,8 +612,27 @@ func TestRangeKubernetesServersWithName(t *testing.T) { require.Empty(t, servers) }) + t.Run("DeletedServerWithScope", func(t *testing.T) { + err := presence.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: scope, + HostId: server2.GetHostID(), + Name: server2.GetName(), + }.Build()) + require.NoError(t, err) + + servers, err := iterstream.Collect(presence.RangeKubernetesServersWithName(ctx, "shared-cluster")) + require.NoError(t, err) + require.Len(t, servers, 2) + for _, server := range servers { + require.Empty(t, server.GetScope()) + } + }) + t.Run("DeletedServersNotReturned", func(t *testing.T) { - err := presence.DeleteKubernetesServer(ctx, server1.GetHostID(), server1.GetName()) + err := presence.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + HostId: server1.GetHostID(), + Name: server1.GetName(), + }.Build()) require.NoError(t, err) servers, err := iterstream.Collect(presence.RangeKubernetesServersWithName(ctx, "shared-cluster")) @@ -2263,14 +2286,18 @@ func TestScopedLease(t *testing.T) { Scope: scope, } - // Create servers and check lease scopes + // Create servers, check lease scopes, and try a keepalive lease, err := presence.UpsertKubernetesServer(ctx, unscoped) require.NoError(t, err) require.Empty(t, lease.Scope) + err = presence.KeepAliveServer(ctx, *lease) + require.NoError(t, err) lease, err = presence.UpsertKubernetesServer(ctx, scoped) require.NoError(t, err) require.Equal(t, scope, lease.Scope) + err = presence.KeepAliveServer(ctx, *lease) + require.NoError(t, err) }) t.Run("nodes", func(t *testing.T) { diff --git a/lib/services/presence.go b/lib/services/presence.go index 984abad20da9..c46bf1a163d8 100644 --- a/lib/services/presence.go +++ b/lib/services/presence.go @@ -172,8 +172,8 @@ type Presence interface { // GetKubernetesServers returns a list of registered kubernetes servers. GetKubernetesServers(context.Context) ([]types.KubeServer, error) - // DeleteKubernetesServer deletes a named kubernetes servers. - DeleteKubernetesServer(ctx context.Context, hostID, name string) error + // DeleteKubeServer deletes a named kubernetes servers. + DeleteKubeServer(ctx context.Context, req *presencev1.DeleteKubeServerRequest) error // DeleteAllKubernetesServers deletes all registered kubernetes servers. DeleteAllKubernetesServers(context.Context) error diff --git a/lib/services/unified_resource_test.go b/lib/services/unified_resource_test.go index 98d59f4def41..dbbfc257a1f4 100644 --- a/lib/services/unified_resource_test.go +++ b/lib/services/unified_resource_test.go @@ -1208,7 +1208,11 @@ func TestUnifiedResourceWatcher_DeleteEvent(t *testing.T) { err = clt.DeleteWindowsDesktop(ctx, desktops[0].Spec.HostID, desktops[0].GetName()) require.NoError(t, err) desktops = desktops[1:] - err = clt.DeleteKubernetesServer(ctx, kubeServers[0].Spec.HostID, kubeServers[0].GetName()) + err = clt.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: kubeServers[0].GetScope(), + HostId: kubeServers[0].Spec.HostID, + Name: kubeServers[0].GetName(), + }.Build()) require.NoError(t, err) kubeServers = kubeServers[1:] @@ -1252,7 +1256,11 @@ func TestUnifiedResourceWatcher_DeleteEvent(t *testing.T) { require.NoError(t, err) } for _, kubeServer := range kubeServers { - err = clt.DeleteKubernetesServer(ctx, kubeServer.Spec.HostID, kubeServer.GetName()) + err = clt.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: kubeServer.GetScope(), + HostId: kubeServer.GetHostID(), + Name: kubeServer.GetName(), + }.Build()) require.NoError(t, err) } diff --git a/lib/services/watcher.go b/lib/services/watcher.go index 3a2309309f5e..ddc8760e691c 100644 --- a/lib/services/watcher.go +++ b/lib/services/watcher.go @@ -662,8 +662,15 @@ func NewKubeClusterWatcher(ctx context.Context, cfg KubeClusterWatcherConfig) (* ResourceGetter: func(ctx context.Context) ([]types.KubeCluster, error) { return iterstream.Collect(getter.RangeKubeClusters(ctx, nil)) }, - ResourceKey: types.KubeCluster.GetName, - ResourcesC: cfg.KubeClustersC, + ResourceKey: GetCursorForKubeCluster, + DeleteKey: func(res types.Resource) string { + cluster, ok := res.(types.KubeCluster) + if !ok { + return "" + } + return GetCursorForKubeCluster(cluster) + }, + ResourcesC: cfg.KubeClustersC, CloneFunc: func(resource types.KubeCluster) types.KubeCluster { return resource.Copy() }, diff --git a/lib/services/watcher_test.go b/lib/services/watcher_test.go index 07f6994bcef7..19db16f66ba4 100644 --- a/lib/services/watcher_test.go +++ b/lib/services/watcher_test.go @@ -1281,7 +1281,11 @@ func TestKubeServerWatcher(t *testing.T) { require.Len(t, filtered, 1) // Test Deleting a kube server. - require.NoError(t, presence.DeleteKubernetesServer(ctx, kubeServers[0].GetHostID(), kubeServers[0].GetName())) + require.NoError(t, presence.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: kubeServers[0].GetScope(), + HostId: kubeServers[0].GetHostID(), + Name: kubeServers[0].GetName(), + }.Build())) require.EventuallyWithT(t, func(t *assert.CollectT) { kube, err := w.CurrentResources(context.Background()) require.NoError(t, err) @@ -1312,7 +1316,11 @@ func TestKubeServerWatcher(t *testing.T) { }) assert.NoError(t, err) for _, server := range filtered { - require.NoError(t, presence.DeleteKubernetesServer(ctx, server.GetHostID(), server.GetName())) + require.NoError(t, presence.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: server.GetScope(), + HostId: server.GetHostID(), + Name: server.GetName(), + }.Build())) } require.EventuallyWithT(t, func(t *assert.CollectT) { filtered, err := w.CurrentResourcesWithFilter(context.Background(), func(ks readonly.KubeServer) bool { diff --git a/tool/tctl/common/resources/kube_server.go b/tool/tctl/common/resources/kube_server.go index b6da3e3e37df..595aecc41b3f 100644 --- a/tool/tctl/common/resources/kube_server.go +++ b/tool/tctl/common/resources/kube_server.go @@ -23,6 +23,7 @@ import ( "github.com/gravitational/trace" + presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth/authclient" @@ -112,7 +113,11 @@ func deleteKubeServer(ctx context.Context, client *authclient.Client, ref servic return trace.Wrap(err) } for _, s := range servers { - err := client.DeleteKubernetesServer(ctx, s.GetHostID(), name) + err := client.DeleteKubeServer(ctx, presencev1.DeleteKubeServerRequest_builder{ + Scope: s.GetScope(), + HostId: s.GetHostID(), + Name: name, + }.Build()) if err != nil { return trace.Wrap(err) } From 3ba255c01b57373994a9e11acd454beb6fa0a974 Mon Sep 17 00:00:00 2001 From: Pawel Kopiczko Date: Tue, 28 Jul 2026 08:27:55 +0100 Subject: [PATCH 019/347] SAML: Add unstable env var to disable descriptor redirect check (#68886) * SAML: Add unstable env var to disable descriptor redirect check * Inline env var name * Add a TODO to remove * Move to constants.go * Fix --- constants.go | 7 +++++++ lib/services/saml.go | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/constants.go b/constants.go index 89480c371025..cd5468415086 100644 --- a/constants.go +++ b/constants.go @@ -1076,3 +1076,10 @@ const ( // OktaReviewerRoleContext is the context used to name Okta Reviewer role created by Okta Access List sync OktaReviewerRoleContext = "reviewer-okta-acl-role" ) + +const ( + // EnvVarUnstableDisableSAMLRedirectDowngradeCheck allows disabling saml_connector + // entity_descriptor_url check preventing following redirects via HTTP originating from + // HTTPS route. + EnvVarUnstableDisableSAMLRedirectDowngradeCheck = "TELEPORT_UNSTABLE_DISABLE_SAML_REDIRECT_DOWNGRADE_CHECK" +) diff --git a/lib/services/saml.go b/lib/services/saml.go index 66929454568e..d37386905bf4 100644 --- a/lib/services/saml.go +++ b/lib/services/saml.go @@ -27,6 +27,7 @@ import ( "errors" "log/slog" "net/http" + "os" "slices" "strings" "sync" @@ -41,6 +42,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" + apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" ) @@ -256,8 +258,12 @@ func getEntityDescriptor(ctx context.Context, params getEntityDescriptorParams) }() if url != "" && !params.Options.NoFollowURLs { - httpClient := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { + var checkRedirect func(req *http.Request, via []*http.Request) error + // TODO(kopiczko): Remove this env var after Jul 2027 (one year since introduced) if no issue is reported. + if disableCheckRedirect, _ := apiutils.ParseBool(os.Getenv(teleport.EnvVarUnstableDisableSAMLRedirectDowngradeCheck)); disableCheckRedirect { + log.DebugContext(ctx, "Redirect HTTPS downgrade check disabled with the unstable environment variable") + } else { + checkRedirect = func(req *http.Request, via []*http.Request) error { if len(via) != 0 && strings.EqualFold(via[len(via)-1].URL.Scheme, "https") && !strings.EqualFold(req.URL.Scheme, "https") { return errors.New("connection downgrade not allowed for URL: " + req.URL.String()) } @@ -265,8 +271,12 @@ func getEntityDescriptor(ctx context.Context, params getEntityDescriptorParams) return errors.New("stopped after 10 redirects") } return nil - }, - Transport: params.Options.Transport, + } + } + + httpClient := &http.Client{ + CheckRedirect: checkRedirect, + Transport: params.Options.Transport, } ctx, cancel := context.WithTimeout(ctx, defaults.DefaultIOTimeout) From 50fa1923556d06e02eb404b1d3417abfc9fd8ff8 Mon Sep 17 00:00:00 2001 From: mica Date: Tue, 28 Jul 2026 04:17:34 -0700 Subject: [PATCH 020/347] docs: mwi guides - friction points found during agent testing (#68875) * docs: mwi guides - friction points found during agent testing * correcting syntax for tctl bots instances add * removing some excessive admonition tags per feedback --- docs/pages/includes/install-linux.mdx | 5 +- .../includes/machine-id/daemon-or-oneshot.mdx | 8 ++ .../access-guides/tctl.mdx | 9 ++ .../deployment/kubernetes-oidc.mdx | 7 ++ .../deployment/kubernetes.mdx | 89 +++++++++++++++++++ .../deployment/linux.mdx | 5 +- 6 files changed, 119 insertions(+), 4 deletions(-) diff --git a/docs/pages/includes/install-linux.mdx b/docs/pages/includes/install-linux.mdx index 0a88ee010199..c15ce768a905 100644 --- a/docs/pages/includes/install-linux.mdx +++ b/docs/pages/includes/install-linux.mdx @@ -1,6 +1,7 @@ To install Teleport binaries on your Linux server, the recommended installation -method is the cluster install script. It will select the correct version, -edition, and installation mode for your cluster. +method is the cluster install script. This script is served by your Teleport +cluster's Proxy Service and automatically selects the correct version, edition, +and installation mode to match your cluster. 1. Remove any existing Teleport binaries on your system: diff --git a/docs/pages/includes/machine-id/daemon-or-oneshot.mdx b/docs/pages/includes/machine-id/daemon-or-oneshot.mdx index 3259d40b8a39..0ffb24c392c3 100644 --- a/docs/pages/includes/machine-id/daemon-or-oneshot.mdx +++ b/docs/pages/includes/machine-id/daemon-or-oneshot.mdx @@ -1,3 +1,11 @@ + +If your Teleport Proxy Service uses a self-signed TLS certificate, `tbot` +output services that contact the proxy (such as the `identity` service) will +fail to generate some files (for example, `ssh_config` and `known_hosts`). +Add the proxy certificate to the system CA trust store, or configure ACME or +a valid TLS certificate for your Proxy Service. + + Now, you must decide if you want to run `tbot` as a daemon or in one-shot mode. In daemon mode, `tbot` runs continually, renewing the short-lived credentials diff --git a/docs/pages/machine-workload-identity/access-guides/tctl.mdx b/docs/pages/machine-workload-identity/access-guides/tctl.mdx index cd9e9d194896..2c3a37d8f934 100644 --- a/docs/pages/machine-workload-identity/access-guides/tctl.mdx +++ b/docs/pages/machine-workload-identity/access-guides/tctl.mdx @@ -140,6 +140,15 @@ the generated identity file if you have modified this: $ tctl --auth-server example.teleport.sh:443 -i /opt/machine-id/identity create -f roles/*.yaml ``` +If you run `tctl` on a host that also runs a Teleport service (with +`/etc/teleport.yaml` present), the local configuration may conflict with the +`--auth-server` flag. Set `TELEPORT_CONFIG_FILE=""` to bypass the local +configuration: + +```code +$ TELEPORT_CONFIG_FILE="" tctl --auth-server example.teleport.sh:443 -i /opt/machine-id/identity create -f roles/*.yaml +``` + Check your Teleport cluster, ensuring the role has been created. ```code diff --git a/docs/pages/machine-workload-identity/deployment/kubernetes-oidc.mdx b/docs/pages/machine-workload-identity/deployment/kubernetes-oidc.mdx index e3d6c3b5688a..1983df1f5da0 100644 --- a/docs/pages/machine-workload-identity/deployment/kubernetes-oidc.mdx +++ b/docs/pages/machine-workload-identity/deployment/kubernetes-oidc.mdx @@ -298,6 +298,13 @@ type of service using the `services` value of the Helm chart and setting Follow one of the [access guides](../access-guides/access-guides.mdx) to find out more about how to configure `tbot` for your use case. +## Manual deployment without Helm + +If you deploy `tbot` without the Helm chart, see +[Requirements for deployments without Helm](./kubernetes.mdx#requirements-for-deployments-without-helm) +in the static JWKS guide for the additional configuration Helm normally handles for you: token audience projection +via `KUBERNETES_TOKEN_PATH`, the `POD_NAMESPACE` environment variable, and service account RBAC for secret writing. + ## Further reading - Explore the [Helm chart configuration reference](../../reference/helm-reference/tbot.mdx). diff --git a/docs/pages/machine-workload-identity/deployment/kubernetes.mdx b/docs/pages/machine-workload-identity/deployment/kubernetes.mdx index 67b5d0651180..3c81939066b7 100644 --- a/docs/pages/machine-workload-identity/deployment/kubernetes.mdx +++ b/docs/pages/machine-workload-identity/deployment/kubernetes.mdx @@ -226,6 +226,95 @@ type of output using the `services` value of the Helm chart and setting Follow one of the [access guides](../access-guides/access-guides.mdx) to find out more about how to configure `tbot` for your use case. +## Requirements for deployments without Helm + +The Helm chart handles several requirements for `tbot` automatically. If you +deploy `tbot` yourself — with your own Deployment, ServiceAccount, and `tbot` +configuration — you must configure the following in addition to the standard +`tbot` setup. + +### Token audience projection + +The Kubernetes join method requires that the service account JWT presented to +Teleport has an audience matching the Teleport cluster name. The service +account token mounted in a Pod by default is not issued with the Teleport +cluster name as its audience. + +You must project a service account token volume with the correct audience and +tell `tbot` where to find it via the `KUBERNETES_TOKEN_PATH` environment +variable: + +```yaml +spec: + containers: + - name: tbot + env: + - name: KUBERNETES_TOKEN_PATH + value: "/var/run/secrets/teleport/token" + volumeMounts: + - name: teleport-token + mountPath: /var/run/secrets/teleport + volumes: + - name: teleport-token + projected: + sources: + - serviceAccountToken: + path: token + audience: example.teleport.sh + expirationSeconds: 600 +``` + +Replace `example.teleport.sh` with the name of your Teleport cluster (the +value of `clusterName`, not the proxy address). + +### Writing credentials to a Kubernetes secret + +When using `destination.type: kubernetes_secret` in the `tbot` configuration, +the following additional setup is required: + +- Set the `POD_NAMESPACE` environment variable so `tbot` knows which namespace + to write the secret to. Use the Kubernetes downward API: + + ```yaml + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ``` + +- Grant the `tbot` service account permission to manage secrets in its + namespace. The `create` verb is required because `tbot` creates the + destination secret automatically if it does not already exist: + + ```yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: tbot-secret-writer + namespace: default + rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch"] + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: tbot-secret-writer + namespace: default + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: tbot-secret-writer + subjects: + - kind: ServiceAccount + name: tbot + namespace: default + ``` + + Replace `default` with the namespace `tbot` runs in. + ## Further reading - Explore the [Helm chart configuration reference](../../reference/helm-reference/tbot.mdx). diff --git a/docs/pages/machine-workload-identity/deployment/linux.mdx b/docs/pages/machine-workload-identity/deployment/linux.mdx index da30247230d3..71530dce30d2 100644 --- a/docs/pages/machine-workload-identity/deployment/linux.mdx +++ b/docs/pages/machine-workload-identity/deployment/linux.mdx @@ -82,14 +82,15 @@ services: [] Replace: - The `join_uri` field value with the joining URI printed by `tctl` in step 2. - The first time that `tbot` runs, the registration secret contained within the joining URI will be used to authenticate the first connection. After the first successful join, the registration secret is invalidated. You may remove it from the join URI (shown as `secret` in the example token) in your configuration file after the first run has completed, but there is no tangible security benefit to doing so. - + +If the initial join fails (for example, due to a network or TLS error), the secret may already be consumed. Use `tctl bots instances add example` to ++generate a new join URI for the bot before retrying. ### Prepare the storage directory From 459fa0741637e7f51bed187bdc6806639c957595 Mon Sep 17 00:00:00 2001 From: Pavel <149388910+tangyatsu@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:18:35 +0100 Subject: [PATCH 021/347] [vnet] Reduce allocations by dropping DNS name compression (#68249) * add benchmark for DNS parse and A record build * remove EnableCompression to eliminate per-response allocations --- lib/vnet/dns/dns.go | 9 ++--- lib/vnet/dns/dns_bench_test.go | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 lib/vnet/dns/dns_bench_test.go diff --git a/lib/vnet/dns/dns.go b/lib/vnet/dns/dns.go index 4aa7700f01ca..b7ca786a8558 100644 --- a/lib/vnet/dns/dns.go +++ b/lib/vnet/dns/dns.go @@ -408,7 +408,7 @@ func buildAAAAResponse(buf []byte, requestHeader *dnsmessage.Header, question *d return buf, trace.Wrap(err, "serializing DNS response") } -func prepDNSResponse(buf []byte, requestHeader *dnsmessage.Header, question *dnsmessage.Question, rcode dnsmessage.RCode) (*dnsmessage.Builder, error) { +func prepDNSResponse(buf []byte, requestHeader *dnsmessage.Header, question *dnsmessage.Question, rcode dnsmessage.RCode) (dnsmessage.Builder, error) { buf = buf[:0] responseBuilder := dnsmessage.NewBuilder(buf, dnsmessage.Header{ ID: requestHeader.ID, @@ -416,12 +416,11 @@ func prepDNSResponse(buf []byte, requestHeader *dnsmessage.Header, question *dns Authoritative: true, RCode: dnsmessage.RCodeSuccess, }) - responseBuilder.EnableCompression() if err := responseBuilder.StartQuestions(); err != nil { - return nil, trace.Wrap(err, "starting questions section of DNS response") + return dnsmessage.Builder{}, trace.Wrap(err, "starting questions section of DNS response") } if err := responseBuilder.Question(*question); err != nil { - return nil, trace.Wrap(err, "adding question to DNS response") + return dnsmessage.Builder{}, trace.Wrap(err, "adding question to DNS response") } - return &responseBuilder, nil + return responseBuilder, nil } diff --git a/lib/vnet/dns/dns_bench_test.go b/lib/vnet/dns/dns_bench_test.go new file mode 100644 index 000000000000..6df4308e9244 --- /dev/null +++ b/lib/vnet/dns/dns_bench_test.go @@ -0,0 +1,70 @@ +// Teleport +// Copyright (C) 2026 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 dns + +import ( + "testing" + + "golang.org/x/net/dns/dnsmessage" +) + +func mustBuildQuery(b *testing.B, name string) []byte { + b.Helper() + msg := dnsmessage.Message{ + Header: dnsmessage.Header{ID: 1234, RecursionDesired: true}, + Questions: []dnsmessage.Question{{ + Name: dnsmessage.MustNewName(name), + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + }}, + } + raw, err := msg.Pack() + if err != nil { + b.Fatalf("packing query: %v", err) + } + return raw +} + +// go test ./lib/vnet/dns -run='^$' -bench=. +// goos: darwin +// goarch: arm64 +// pkg: github.com/gravitational/teleport/lib/vnet/dns +// cpu: Apple M4 Pro +// BenchmarkDNSParseAndBuildA-14 9789584 112.7 ns/op 0 B/op 0 allocs/op +// PASS +// ok github.com/gravitational/teleport/lib/vnet/dns 1.539s +func BenchmarkDNSParseAndBuildA(b *testing.B) { + query := mustBuildQuery(b, "app.example.com.") + respBuf := make([]byte, 0, maxUDPDNSMessageSize) + addr := [4]byte{100, 64, 0, 2} + + b.ReportAllocs() + for b.Loop() { + var parser dnsmessage.Parser + header, err := parser.Start(query) + if err != nil { + b.Fatalf("parsing header: %v", err) + } + question, err := parser.Question() + if err != nil { + b.Fatalf("parsing question: %v", err) + } + if _, err := buildAResponse(respBuf, &header, &question, addr); err != nil { + b.Fatalf("building response: %v", err) + } + } +} From 4be0b68d17fb6bbb00a790ac1ebfffe1e8c9faf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:39:55 +0000 Subject: [PATCH 022/347] Bump react-router from 8.1.0 to 8.3.0 (#69012) * Bump react-router from 8.1.0 to 8.3.0 Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 8.1.0 to 8.3.0. - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router@8.3.0/packages/react-router) --- updated-dependencies: - dependency-name: react-router dependency-version: 8.3.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Revert unnecessary updates --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Grzegorz Zdunek --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c629247b30cb..296a10d6b8c4 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "react-error-boundary": "^6.1.2", "react-hook-form": "^7.80.0", "react-is": "^19.2.7", - "react-router": "^8.1.0", + "react-router": "^8.3.0", "react-select": "^5.10.2", "react-transition-group": "^4.4.5", "styled-components": "^6.4.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 665233a42682..0d4d80e446c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,8 +97,8 @@ importers: specifier: ^19.2.7 version: 19.2.7 react-router: - specifier: ^8.1.0 - version: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^8.3.0 + version: 8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-select: specifier: ^5.10.2 version: 5.10.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -7032,8 +7032,8 @@ packages: react-is@19.2.7: resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==} - react-router@8.1.0: - resolution: {integrity: sha512-Mdfi61uObuvWNN9OhChOC0HV6YWOIfKRzEWOvCHRSuQg8IM+Nv10edaM/2HE8ZixBpUTdQbruyWqC3sDkkh9vw==} + react-router@8.3.0: + resolution: {integrity: sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==} engines: {node: '>=22.22.0'} peerDependencies: react: '>=19.2.7' @@ -15724,7 +15724,7 @@ snapshots: react-is@19.2.7: {} - react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-router@8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: cookie-es: 3.1.1 react: 19.2.7 From f865d1eac5c5ab93022693ccab24943988f7108b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:30:27 +0200 Subject: [PATCH 023/347] Update the authors on Maja's RFDs (#69080) --- rfd/0099-link-bundled-tsh.md | 2 +- rfd/0133-connect-my-computer.md | 2 +- rfd/0163-vnet.md | 2 +- rfd/0182-multi-port-tcp-app-access.md | 2 +- rfd/cspell.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rfd/0099-link-bundled-tsh.md b/rfd/0099-link-bundled-tsh.md index 5b2edc2de158..05f39d40c6fc 100644 --- a/rfd/0099-link-bundled-tsh.md +++ b/rfd/0099-link-bundled-tsh.md @@ -1,5 +1,5 @@ --- -authors: Rafał Cieślak (rafal.cieslak@goteleport.com) +authors: Maja Cieślak (maja.cieslak@goteleport.com) state: implemented --- diff --git a/rfd/0133-connect-my-computer.md b/rfd/0133-connect-my-computer.md index a15bcdc2b7a9..827f68f6da3e 100644 --- a/rfd/0133-connect-my-computer.md +++ b/rfd/0133-connect-my-computer.md @@ -1,5 +1,5 @@ --- -authors: Grzegorz Zdunek (grzegorz.zdunek@goteleport.com), Rafał Cieślak (rafal.cieslak@goteleport.com) +authors: Grzegorz Zdunek (grzegorz.zdunek@goteleport.com), Maja Cieślak (maja.cieslak@goteleport.com) state: implemented (14.1) --- diff --git a/rfd/0163-vnet.md b/rfd/0163-vnet.md index 02564576f693..930545f12e38 100644 --- a/rfd/0163-vnet.md +++ b/rfd/0163-vnet.md @@ -1,5 +1,5 @@ --- -authors: Nic Klaassen (nic@goteleport.com), Rafał Cieślak (rafal.cieslak@goteleport.com) +authors: Nic Klaassen (nic@goteleport.com), Maja Cieślak (maja.cieslak@goteleport.com) state: implemented --- diff --git a/rfd/0182-multi-port-tcp-app-access.md b/rfd/0182-multi-port-tcp-app-access.md index c6742e480e25..8a48b294e7dd 100644 --- a/rfd/0182-multi-port-tcp-app-access.md +++ b/rfd/0182-multi-port-tcp-app-access.md @@ -1,5 +1,5 @@ --- -authors: Rafał Cieślak (rafal.cieslak@goteleport.com) +authors: Maja Cieślak (maja.cieslak@goteleport.com) state: draft --- diff --git a/rfd/cspell.json b/rfd/cspell.json index 25aa405dffbb..df17c2155b2a 100644 --- a/rfd/cspell.json +++ b/rfd/cspell.json @@ -447,6 +447,7 @@ "Macbook", "machineid", "machineidv1", + "Maja", "managedclusters", "mant", "Mapred", @@ -657,7 +658,6 @@ "Quickstart", "quux", "QWERTYUIOP", - "Rafał", "ramdisk", "Rana", "ravicious", From 8135edae130b2cc6adc168a1355c4c7d0bd4d2a1 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 08:59:34 -0600 Subject: [PATCH 024/347] tsh recordings export: fix a couple special cases (#69072) This addresses a few rare corner cases that Codex identified during the backport. No backport necessary as these changes were made to the existing v18 backport. --- tool/tsh/common/recording_export.go | 67 +++++++++++++------ tool/tsh/common/recording_export_test.go | 82 ++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 18 deletions(-) diff --git a/tool/tsh/common/recording_export.go b/tool/tsh/common/recording_export.go index 08ecca8fd3a7..5e2b07731e1c 100644 --- a/tool/tsh/common/recording_export.go +++ b/tool/tsh/common/recording_export.go @@ -191,12 +191,44 @@ func (d *desktopRecordingExporter) run( var frameCount int var gotScreenData bool - lastEmitted := int64(-1) + + // startMillis is the timestamp of the first screen fragment. + startMillis := int64(-1) + + // endMillis is how far into the recording the video should run. + // There are no updates if the screen isn't changing, so the last + // screen update doesn't necessarily mark the end of the session. + endMillis := int64(-1) // canvas holds a max-size frame that smaller frames are composited onto, so the encoder always // receives a constant frame size even when the recording changes resolution. Created lazily. var canvas *image.RGBA + // catchUp emits howewever many frame are needed to catch up to this event. + catchUp := func(elapsedMillis, minTotal int64) error { + // The target is derived from the elapsed time since the start of the + // recording rather than from the gap since the previous event, so + // gaps shorter than a single frame interval accumulate instead of + // being truncated away. + target := max(int64(float64(elapsedMillis)/frameDelayMillis), minTotal) + framesToEmit := target - int64(frameCount) + if framesToEmit <= 0 { + return nil + } + + logger.DebugContext(ctx, "emitting frames", + "elapsed_ms", elapsedMillis, + "frames_to_emit", framesToEmit, + ) + var frame image.Image + frame, canvas = fitFrame(decoder.Image(), int(meta.maxWidth), int(meta.maxHeight), canvas) + if err := encoder.EmitFrames(frame, int(framesToEmit)); err != nil { + return trace.Wrap(err) + } + frameCount += int(framesToEmit) + return nil + } + evts, errs := d.ss.StreamSessionEvents(ctx, d.sid, 0) loop: for { @@ -222,6 +254,9 @@ loop: switch evt := evt.(type) { case *apievents.WindowsDesktopSessionStart: case *apievents.WindowsDesktopSessionEnd: + if !evt.StartTime.IsZero() && !evt.EndTime.IsZero() { + endMillis = max(endMillis, evt.EndTime.Sub(evt.StartTime).Milliseconds()) + } break loop case *apievents.DesktopRecording: @@ -235,36 +270,32 @@ loop: // if it's the very first screen fragment, mark the time and continue // (no need to emit a frame yet) - if lastEmitted == -1 { - lastEmitted = evt.DelayMilliseconds + if startMillis == -1 { + startMillis = evt.DelayMilliseconds continue loop } + endMillis = max(endMillis, evt.DelayMilliseconds) + // If we haven't received any image data yet there's nothing to emit. if !gotScreenData { continue loop } - // emit a frame if there's been enough of a time lapse between last event - delta := evt.DelayMilliseconds - lastEmitted - framesToEmit := int64(float64(delta) / frameDelayMillis) - if framesToEmit > 0 { - logger.DebugContext(ctx, "emitting frames", - "last_event_ms", delta, - "frames_to_emit", framesToEmit, - ) - var frame image.Image - frame, canvas = fitFrame(decoder.Image(), int(meta.maxWidth), int(meta.maxHeight), canvas) - if err := encoder.EmitFrames(frame, int(framesToEmit)); err != nil { - return frameCount, trace.Wrap(err) - } - frameCount += int(framesToEmit) + if err := catchUp(evt.DelayMilliseconds-startMillis, 0); err != nil { + return frameCount, trace.Wrap(err) } - lastEmitted = evt.DelayMilliseconds } } } + // Always emit at least one frame so that we don't produce an empty video. + if gotScreenData { + if err := catchUp(endMillis-startMillis, 1); err != nil { + return frameCount, trace.Wrap(err) + } + } + if err := encoder.Close(); err != nil { return frameCount, trace.Wrap(err, "closing encoder") } diff --git a/tool/tsh/common/recording_export_test.go b/tool/tsh/common/recording_export_test.go index 3e36dc634b12..ffd9e4f21d19 100644 --- a/tool/tsh/common/recording_export_test.go +++ b/tool/tsh/common/recording_export_test.go @@ -173,6 +173,88 @@ func TestWritesManyFrames(t *testing.T) { require.Equal(t, framesPerSecond, frames) } +func TestWritesFramesForSubFrameIntervalEvents(t *testing.T) { + t.Parallel() + + // Test recordings with events that arrive closer together than a single frame interval. + // Events every 20ms, with the last one just over 1s after the first + events := []apievents.AuditEvent{ + tdpEventMillis(t, legacy.ClientScreenSpec{Width: 128, Height: 128}, 0), + } + for millis := int64(0); millis <= 1000; millis += 20 { + events = append(events, tdpEventMillis(t, legacy.PNG2Frame(pngFrame), millis)) + } + events = append(events, tdpEventMillis(t, legacy.PNG2Frame(pngFrame), 1001)) + + exporter := &desktopRecordingExporter{ + ss: eventstest.NewFakeStreamer(events, 0), + sid: session.NewID(), + } + + frames, err := exporter.run( + t.Context(), + &recordingMetadata{}, + recordingexport.NewPNGDecoder(128, 128), + nopEncoder{}) + require.NoError(t, err) + require.Equal(t, framesPerSecond, frames) +} + +func TestEmitsFrameForVeryShortSession(t *testing.T) { + t.Parallel() + + // Every screen update lands within a single frame interval. + // The recording still has screen data, so it must not produce an empty video. + events := []apievents.AuditEvent{ + tdpEventMillis(t, legacy.ClientScreenSpec{Width: 128, Height: 128}, 0), + tdpEventMillis(t, legacy.PNG2Frame(pngFrame), 0), + tdpEventMillis(t, legacy.PNG2Frame(pngFrame), 10), + &apievents.WindowsDesktopSessionEnd{}, + } + + exporter := &desktopRecordingExporter{ + ss: eventstest.NewFakeStreamer(events, 0), + sid: session.NewID(), + } + + frames, err := exporter.run( + t.Context(), + &recordingMetadata{}, + recordingexport.NewPNGDecoder(128, 128), + nopEncoder{}) + require.NoError(t, err) + require.Equal(t, 1, frames) +} + +func TestPadsIdleTailToSessionEnd(t *testing.T) { + t.Parallel() + + // A session that goes idle after its last update should not be truncated. + start := time.Now().UTC() + events := []apievents.AuditEvent{ + tdpEventMillis(t, legacy.ClientScreenSpec{Width: 128, Height: 128}, 0), + tdpEventMillis(t, legacy.PNG2Frame(pngFrame), 0), + tdpEventMillis(t, legacy.PNG2Frame(pngFrame), 100), + &apievents.WindowsDesktopSessionEnd{ + StartTime: start, + EndTime: start.Add(2001 * time.Millisecond), + }, + } + + exporter := &desktopRecordingExporter{ + ss: eventstest.NewFakeStreamer(events, 0), + sid: session.NewID(), + } + + frames, err := exporter.run( + t.Context(), + &recordingMetadata{}, + recordingexport.NewPNGDecoder(128, 128), + nopEncoder{}) + require.NoError(t, err) + require.Equal(t, 2*framesPerSecond, frames) +} + func TestGetSessionMetadataRemoteFX(t *testing.T) { t.Parallel() From 079fc823e0539d3e40e9089e5eba42dfcd7b703e Mon Sep 17 00:00:00 2001 From: rhammonds-teleport Date: Tue, 28 Jul 2026 11:26:52 -0400 Subject: [PATCH 025/347] Refactor scard ioctl handling (#68079) * Refactor scard IOCTL handling The refactorings were mostly done in the context of `GetStatusChange` handling. * Revert returning an error for non-infinite/non-immediate timeout --------- Co-authored-by: Dawid Hanak --- Cargo.lock | 674 +++++----- lib/srv/desktop/rdp/rdpclient/Cargo.toml | 3 + lib/srv/desktop/rdp/rdpclient/src/client.rs | 12 +- .../desktop/rdp/rdpclient/src/rdpdr/scard.rs | 1194 +++++++++++++---- 4 files changed, 1274 insertions(+), 609 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2093a347aa8..d0696e4023cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.6.0-rc.10" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ "crypto-common 0.2.2", "inout", @@ -123,12 +123,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - [[package]] name = "asn1-rs" version = "0.7.2" @@ -152,7 +146,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] @@ -164,7 +158,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -194,7 +188,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -220,9 +214,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "zeroize", @@ -230,14 +224,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -275,7 +270,7 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -298,9 +293,9 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -319,9 +314,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -366,9 +361,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" [[package]] name = "byteorder" @@ -378,9 +373,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cbc" @@ -405,16 +400,16 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.117", + "syn 2.0.119", "tempfile", "toml", ] [[package]] name = "cc" -version = "1.2.63" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", "jobserver", @@ -439,9 +434,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -454,7 +449,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "crypto-common 0.2.2", "inout", ] @@ -472,18 +467,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -603,7 +598,7 @@ checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" dependencies = [ "cpubits", "ctutils", - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "num-traits", "rand_core 0.10.1", @@ -628,7 +623,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "rand_core 0.10.1", ] @@ -645,12 +640,11 @@ dependencies = [ [[package]] name = "crypto-primes" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" dependencies = [ "crypto-bigint", - "libm", "rand_core 0.10.1", ] @@ -719,7 +713,38 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", ] [[package]] @@ -737,9 +762,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", "pem-rfc7468 1.0.0", @@ -767,7 +792,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -775,9 +800,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "des" @@ -788,6 +810,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -805,7 +833,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", @@ -819,7 +847,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -843,7 +871,7 @@ version = "0.17.0-rc.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c72d1455753a703ad4b90ed2a759f2bc4562024a303176439cf6e593b5ade4" dependencies = [ - "der 0.8.0", + "der 0.8.1", "digest 0.11.3", "elliptic-curve", "rfc6979", @@ -993,12 +1021,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foreign-types" version = "0.5.0" @@ -1017,7 +1039,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1113,7 +1135,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1128,6 +1150,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.32" @@ -1184,17 +1212,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", "wasm-bindgen", ] @@ -1242,15 +1268,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - [[package]] name = "hashbrown" version = "0.17.1" @@ -1312,9 +1329,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1322,9 +1339,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1332,9 +1349,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -1500,12 +1517,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1534,9 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "hashbrown", ] [[package]] @@ -1561,7 +1570,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", - "getrandom 0.4.2", + "getrandom 0.4.3", "ironrdp-core", "ironrdp-graphics", "ironrdp-pdu", @@ -1622,7 +1631,7 @@ dependencies = [ "picky", "picky-asn1-der", "picky-asn1-x509", - "rand 0.9.4", + "rand 0.9.5", "sspi", "tracing", "url", @@ -1698,12 +1707,12 @@ dependencies = [ "ironrdp-core", "ironrdp-error", "md-5 0.10.6", - "num-bigint 0.4.6", + "num-bigint 0.4.8", "num-derive", "num-integer", "num-traits", "pkcs1 0.7.5", - "sha1 0.10.6", + "sha1 0.10.7", "tap", "x509-cert", ] @@ -1827,10 +1836,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -1840,34 +1850,33 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1896,12 +1905,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" @@ -1999,9 +2002,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "minimal-lexical" @@ -2021,9 +2024,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -2062,9 +2065,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -2081,7 +2084,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -2100,7 +2103,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2114,11 +2117,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2297,7 +2299,7 @@ dependencies = [ "picky-asn1-x509", "pkcs1 0.8.0-rc.4", "primeorder", - "rand 0.10.1", + "rand 0.10.2", "rand_core 0.10.1", "rc2", "rsa 0.10.0-rc.18", @@ -2374,7 +2376,7 @@ dependencies = [ "picky-asn1", "picky-asn1-der", "picky-asn1-x509", - "rand 0.10.1", + "rand 0.10.2", "rand_core 0.10.1", "serde", "sha1 0.11.0", @@ -2411,7 +2413,7 @@ version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der 0.8.0", + "der 0.8.1", "spki 0.8.0", ] @@ -2431,7 +2433,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.0", + "der 0.8.1", "spki 0.8.0", ] @@ -2443,9 +2445,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "polyval" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "b20f20e954175de5f463f67781b35583397d916b1d148738923711b2ad16bee8" dependencies = [ "cpubits", "cpufeatures 0.3.0", @@ -2473,7 +2475,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -2501,13 +2503,13 @@ dependencies = [ ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "pretty_assertions" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "proc-macro2", - "syn 2.0.117", + "diff", + "yansi", ] [[package]] @@ -2536,6 +2538,15 @@ dependencies = [ "serdect", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2565,9 +2576,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2592,9 +2603,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2603,9 +2614,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2613,12 +2624,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2705,11 +2716,13 @@ dependencies = [ "picky", "picky-asn1-der", "picky-asn1-x509", + "pretty_assertions", "qoi", - "rand 0.9.4", + "rand 0.9.5", "rdp-decoder", "reqwest 0.13.4", "rsa 0.9.10", + "rstest", "rustls", "sspi", "tempfile", @@ -2743,9 +2756,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -2755,9 +2768,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -2766,9 +2779,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" @@ -2899,6 +2918,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.119", + "unicode-ident", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2976,9 +3024,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -3003,9 +3051,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "zeroize", ] @@ -3024,9 +3072,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -3057,7 +3105,7 @@ checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", "ctutils", - "der 0.8.0", + "der 0.8.1", "hybrid-array", "subtle", "zeroize", @@ -3138,7 +3186,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3187,9 +3235,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -3282,9 +3330,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "slab" @@ -3294,15 +3342,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3310,9 +3358,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -3334,7 +3382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.0", + "der 0.8.1", ] [[package]] @@ -3345,9 +3393,9 @@ checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" [[package]] name = "sspi" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15294fb005e36e0b0871d8fc0a4f6aac19f9f5440baee229a9a2b2d7de5ed484" +checksum = "779917c9b8d57b17bb9e2acad050db131aff409201098bc31d550164d645818a" dependencies = [ "async-dnssd", "async-recursion", @@ -3379,7 +3427,7 @@ dependencies = [ "pkcs1 0.8.0-rc.4", "portpicker", "primeorder", - "rand 0.10.1", + "rand 0.10.2", "rand_core 0.10.1", "reqwest 0.12.28", "rsa 0.10.0-rc.18", @@ -3434,9 +3482,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -3460,7 +3508,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3476,7 +3524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3499,26 +3547,25 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "js-sys", "num-conv", "powerfmt", @@ -3529,15 +3576,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -3571,7 +3618,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3610,7 +3657,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3632,7 +3679,7 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", @@ -3647,20 +3694,41 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -3727,7 +3795,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3797,12 +3865,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.6.1" @@ -3854,11 +3916,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.4" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -3899,27 +3961,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3930,9 +3983,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3940,9 +3993,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3950,65 +4003,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.13.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -4095,7 +4114,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4106,7 +4125,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4253,9 +4272,12 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "winscard" @@ -4280,100 +4302,12 @@ dependencies = [ "widestring", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.13.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -4412,11 +4346,17 @@ dependencies = [ "tls_codec", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4431,37 +4371,37 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "yuv" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c90da4fb561f9750984de2c5e7f0ba01035d2eb29d69a7f375b1caef37fdf4" +checksum = "5d85a782d94ee43f078bcfd6fa82d4e6a5b2d1cfbbad168e4df5a9f7b39ef48c" dependencies = [ "num-traits", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4481,28 +4421,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4535,14 +4475,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/lib/srv/desktop/rdp/rdpclient/Cargo.toml b/lib/srv/desktop/rdp/rdpclient/Cargo.toml index 2780001a0f2c..37c9d8dc5469 100644 --- a/lib/srv/desktop/rdp/rdpclient/Cargo.toml +++ b/lib/srv/desktop/rdp/rdpclient/Cargo.toml @@ -53,10 +53,13 @@ ironrdp-graphics.workspace = true ironrdp-session.workspace = true ironrdp-session.features = ["qoiz"] base64 = "0.22.1" +rstest = "0.26" +pretty_assertions = "1.4" [build-dependencies] cbindgen = "0.29.3" tempfile = "3.27.0" + [features] fips = ["tokio-boring/fips", "boring/fips"] diff --git a/lib/srv/desktop/rdp/rdpclient/src/client.rs b/lib/srv/desktop/rdp/rdpclient/src/client.rs index 1ad532a18aeb..bae70d4888a4 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/client.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/client.rs @@ -75,6 +75,8 @@ use std::sync::{Arc, LazyLock, Mutex, MutexGuard}; use std::time::Duration; use tokio::io::{ReadHalf, WriteHalf}; use tokio::net::TcpStream as TokioTcpStream; +#[cfg(test)] +use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{channel, error::SendError, Receiver, Sender}; use tokio::task::{self, JoinError}; // Export this for crate level use. @@ -1164,7 +1166,7 @@ impl Drop for Client { /// /// This enum is used by [`ClientHandle`]'s methods to dispatch function calls to the corresponding [`Client`] instance. #[derive(Debug)] -enum ClientFunction { +pub(crate) enum ClientFunction { /// Corresponds to [`Client::write_rdp_pointer`] WriteRdpPointer(CGOMousePointerEvent), /// Corresponds to [`Client::write_rdp_key`] @@ -1214,7 +1216,7 @@ pub struct ClientHandle(Sender); impl ClientHandle { /// Creates a new `ClientHandle` and corresponding [`FunctionReceiver`] with a buffer of size `buffer`. - fn new(buffer: usize) -> (Self, FunctionReceiver) { + pub(super) fn new(buffer: usize) -> (Self, FunctionReceiver) { let (sender, receiver) = channel(buffer); (Self(sender), FunctionReceiver(receiver)) } @@ -1464,6 +1466,12 @@ impl FunctionReceiver { async fn recv(&mut self) -> Option { self.0.recv().await } + + /// Tries to receive a [`ClientFunction`] call from the `FunctionReceiver`. + #[cfg(test)] + pub(crate) fn try_recv(&mut self) -> Result { + self.0.try_recv() + } } type RdpReadStream = Framed>>>; diff --git a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/scard.rs b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/scard.rs index e8c43525dc3f..5a88bec327c7 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/scard.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/scard.rs @@ -16,23 +16,131 @@ use crate::client::ClientHandle; use crate::{piv, util}; -use ironrdp_pdu::pdu_other_err; +use ironrdp_core::{EncodeResult, WriteCursor}; use ironrdp_pdu::utils::CharacterSet; use ironrdp_pdu::PduResult; +use ironrdp_pdu::{pdu_other_err, PduError}; use ironrdp_rdpdr::pdu::efs::{DeviceControlRequest, DeviceControlResponse, NtStatus}; +use ironrdp_rdpdr::pdu::esc::rpce::Pdu; use ironrdp_rdpdr::pdu::esc::{ rpce, CardProtocol, CardState, CardStateFlags, ConnectCall, ConnectReturn, ContextCall, EstablishContextReturn, GetDeviceTypeIdCall, GetDeviceTypeIdReturn, GetReaderIconReturn, GetStatusChangeCall, GetStatusChangeReturn, HCardAndDispositionCall, ListReadersReturn, - LongReturn, ReadCacheCall, ReadCacheReturn, ReaderStateCommonCall, ReturnCode, ScardCall, - ScardContext, ScardHandle, ScardIoCtlCode, StatusReturn, TransmitCall, TransmitReturn, - WriteCacheCall, + LongReturn, ReadCacheCall, ReadCacheReturn, ReaderState, ReaderStateCommonCall, ReturnCode, + ScardCall, ScardContext, ScardHandle, ScardIoCtlCode, StatusReturn, TransmitCall, + TransmitReturn, WriteCacheCall, }; use iso7816::Command as CardCommand; use log::{debug, warn}; use std::collections::HashMap; use uuid::Uuid; +/// Carries the deferred state for an in-flight `SCARD_IOCTL_GETSTATUSCHANGE` request that was +/// received with an infinite timeout but had no state change to report at the time. +/// +/// The request (`req`) and the cancellation response (`response`) are stored here +/// until a matching `SCARD_IOCTL_CANCEL` arrives for the same context, at which point both +/// are flushed through the I/O channel by [`ScardBackend::handle`]. +#[derive(Debug, PartialEq)] +struct PendingGetStatusChange { + req: DeviceControlRequest, + response: ScardResponsePayload, +} + +/// Describes the outcome of dispatching a single smartcard IOCTL in [`ScardBackend::handle_impl`]. +#[derive(Debug, PartialEq)] +enum ScardHandleResponse { + /// No response should be sent to the server for this request yet. + /// + /// Used when a `SCARD_IOCTL_GETSTATUSCHANGE` with an infinite timeout has no change to report. + Deferred, + /// A single response PDU for the current IOCTL should be sent immediately. + Common(ScardResponsePayload), + /// A `SCARD_IOCTL_CANCEL` was dispatched. + /// + /// If `get_status_change_response` is `Some`, the previously deferred + /// `SCARD_IOCTL_GETSTATUSCHANGE` response must be sent first, followed by + /// `cancel_response` for the cancel request itself. + CancelDeferred { + get_status_change_response: Option, + cancel_response: ScardResponsePayload, + }, +} + +impl ScardHandleResponse { + fn success_long_return() -> Self { + Self::Common(ScardResponsePayload::Long( + LongReturn::new(ReturnCode::Success).into_inner(), + )) + } +} + +/// A unified container for all PDUs returned by the smartcard backend. +/// +/// Each variant wraps one concrete `*Return` type from `ironrdp_rdpdr::pdu::esc`. The enum +/// implements [`rpce::HeaderlessEncode`] by delegating to the inner type, so it can be passed +/// directly to [`ScardBackend::send_device_control_response`] as a `Pdu`. +#[derive(Debug, PartialEq)] +enum ScardResponsePayload { + Long(LongReturn), + EstablishContext(EstablishContextReturn), + ListReaders(ListReadersReturn), + GetStatusChange(GetStatusChangeReturn), + Connect(ConnectReturn), + Transmit(TransmitReturn), + Status(StatusReturn), + GetDeviceTypeId(GetDeviceTypeIdReturn), + ReadCache(ReadCacheReturn), + GetReaderIcon(GetReaderIconReturn), +} + +impl rpce::HeaderlessEncode for ScardResponsePayload { + fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> { + match self { + Self::Long(long_return) => long_return.encode(dst), + Self::EstablishContext(establish_context) => establish_context.encode(dst), + Self::ListReaders(list_readers) => list_readers.encode(dst), + Self::GetStatusChange(get_status_change) => get_status_change.encode(dst), + Self::Connect(connect) => connect.encode(dst), + Self::Transmit(transmit) => transmit.encode(dst), + Self::Status(status) => status.encode(dst), + Self::GetDeviceTypeId(get_device_type_id) => get_device_type_id.encode(dst), + Self::ReadCache(read_cache) => read_cache.encode(dst), + Self::GetReaderIcon(get_reader_icon) => get_reader_icon.encode(dst), + } + } + + fn name(&self) -> &'static str { + match self { + Self::Long(long_return) => long_return.name(), + Self::EstablishContext(establish_context) => establish_context.name(), + Self::ListReaders(list_readers) => list_readers.name(), + Self::GetStatusChange(get_status_change) => get_status_change.name(), + Self::Connect(connect) => connect.name(), + Self::Transmit(transmit) => transmit.name(), + Self::Status(status) => status.name(), + Self::GetDeviceTypeId(get_device_type_id) => get_device_type_id.name(), + Self::ReadCache(read_cache) => read_cache.name(), + Self::GetReaderIcon(get_reader_icon) => get_reader_icon.name(), + } + } + + fn size(&self) -> usize { + match self { + Self::Long(long_return) => long_return.size(), + Self::EstablishContext(establish_context) => establish_context.size(), + Self::ListReaders(list_readers) => list_readers.size(), + Self::GetStatusChange(get_status_change) => get_status_change.size(), + Self::Connect(connect) => connect.size(), + Self::Transmit(transmit) => transmit.size(), + Self::Status(status) => status.size(), + Self::GetDeviceTypeId(get_device_type_id) => get_device_type_id.size(), + Self::ReadCache(read_cache) => read_cache.size(), + Self::GetReaderIcon(get_reader_icon) => get_reader_icon.size(), + } + } +} + /// `ScardBackend` implements the smartcard device redirection backend as described in [\[MS-RDPESC\]: Remote Desktop Protocol: Smart Card Virtual Channel Extension] /// /// [\[MS-RDPESC\]: Remote Desktop Protocol: Smart Card Virtual Channel Extension]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/0428ca28-b4dc-46a3-97c3-01887fa44a90 @@ -86,74 +194,101 @@ impl ScardBackend { req: DeviceControlRequest, call: ScardCall, ) -> PduResult<()> { - match req.io_control_code { + match self.handle_impl(&req, call)? { + ScardHandleResponse::Deferred => Ok(()), + ScardHandleResponse::Common(response) => { + self.send_device_control_response(req, Pdu(response)) + } + ScardHandleResponse::CancelDeferred { + get_status_change_response, + cancel_response, + } => { + if let Some(get_status_change_response) = get_status_change_response { + self.send_device_control_response( + get_status_change_response.req, + Pdu(get_status_change_response.response), + )?; + } + self.send_device_control_response(req, Pdu(cancel_response)) + } + } + } + + fn handle_impl( + &mut self, + req: &DeviceControlRequest, + call: ScardCall, + ) -> PduResult { + Ok(match req.io_control_code { ScardIoCtlCode::AccessStartedEvent => match call { - ScardCall::AccessStartedEventCall(_) => self.handle_access_started_event(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::AccessStartedEventCall(_) => self.handle_access_started_event(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::EstablishContext => match call { - ScardCall::EstablishContextCall(_) => self.handle_establish_context(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::EstablishContextCall(_) => self.handle_establish_context(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::ListReadersW | ScardIoCtlCode::ListReadersA => match call { - ScardCall::ListReadersCall(_) => self.handle_list_readers(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ListReadersCall(_) => self.handle_list_readers(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::GetStatusChangeW | ScardIoCtlCode::GetStatusChangeA => match call { - ScardCall::GetStatusChangeCall(call) => self.handle_get_status_change(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::GetStatusChangeCall(call) => { + self.handle_get_status_change(req.clone(), call)? + } + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::ConnectW | ScardIoCtlCode::ConnectA => match call { - ScardCall::ConnectCall(call) => self.handle_connect(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ConnectCall(call) => self.handle_connect(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::BeginTransaction => match call { - ScardCall::HCardAndDispositionCall(_) => self.handle_begin_transaction(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::HCardAndDispositionCall(_) => self.handle_begin_transaction(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::Transmit => match call { - ScardCall::TransmitCall(call) => self.handle_transmit(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::TransmitCall(call) => self.handle_transmit(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::StatusW | ScardIoCtlCode::StatusA => match call { - ScardCall::StatusCall(_) => self.handle_status(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::StatusCall(_) => self.handle_status(req)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::ReleaseContext => match call { - ScardCall::ContextCall(call) => self.handle_release_context(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ContextCall(call) => self.handle_release_context(call), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::EndTransaction => match call { - ScardCall::HCardAndDispositionCall(_) => self.handle_end_transaction(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::HCardAndDispositionCall(_) => self.handle_end_transaction(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::Disconnect => match call { - ScardCall::HCardAndDispositionCall(call) => self.handle_disconnect(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::HCardAndDispositionCall(call) => self.handle_disconnect(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::Cancel => match call { - ScardCall::ContextCall(call) => self.handle_cancel(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ContextCall(call) => self.handle_cancel(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::IsValidContext => match call { - ScardCall::ContextCall(_) => self.handle_is_valid_context(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ContextCall(_) => self.handle_is_valid_context(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::GetDeviceTypeId => match call { - ScardCall::GetDeviceTypeIdCall(call) => self.handle_get_device_type_id(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::GetDeviceTypeIdCall(call) => self.handle_get_device_type_id(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::ReadCacheW | ScardIoCtlCode::ReadCacheA => match call { - ScardCall::ReadCacheCall(call) => self.handle_read_cache(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::ReadCacheCall(call) => self.handle_read_cache(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::WriteCacheW | ScardIoCtlCode::WriteCacheA => match call { - ScardCall::WriteCacheCall(call) => self.handle_write_cache(req, call), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::WriteCacheCall(call) => self.handle_write_cache(call)?, + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, ScardIoCtlCode::GetReaderIcon => match call { - ScardCall::GetReaderIconCall(_) => self.handle_get_reader_icon(req), - _ => Self::unsupported_combo_error(req.io_control_code, call), + ScardCall::GetReaderIconCall(_) => self.handle_get_reader_icon(), + _ => return Err(Self::unsupported_combo_error(req.io_control_code, call)), }, _ => Err(pdu_other_err!( "", @@ -161,48 +296,39 @@ impl ScardBackend { "received unhandled ScardIoCtlCode: {:?}", req.io_control_code )) - )), - }?; - - Ok(()) + ))?, + }) } - fn handle_access_started_event( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + fn handle_access_started_event(&mut self) -> ScardHandleResponse { + ScardHandleResponse::success_long_return() } - fn handle_establish_context( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { + fn handle_establish_context(&mut self) -> ScardHandleResponse { let ctx = self.contexts.establish(); - self.send_device_control_response( - req, - EstablishContextReturn::new(ReturnCode::Success, ctx), - ) + ScardHandleResponse::Common(ScardResponsePayload::EstablishContext( + EstablishContextReturn::new(ReturnCode::Success, ctx).into_inner(), + )) } - fn handle_list_readers(&mut self, req: DeviceControlRequest) -> PduResult<()> { - self.send_device_control_response( - req, - ListReadersReturn::new(ReturnCode::Success, vec![TELEPORT_READER_NAME.to_string()]), - ) + fn handle_list_readers(&mut self) -> ScardHandleResponse { + ScardHandleResponse::Common(ScardResponsePayload::ListReaders( + ListReadersReturn::new(ReturnCode::Success, vec![TELEPORT_READER_NAME.to_string()]) + .into_inner(), + )) } fn handle_get_status_change( &mut self, req: DeviceControlRequest, call: GetStatusChangeCall, - ) -> PduResult<()> { + ) -> PduResult { let timeout = call.timeout; let context_id = call.context.value; if timeout != TIMEOUT_INFINITE && timeout != TIMEOUT_IMMEDIATE { - // We've never seen one of these but we log a warning here in case we ever come + // We've never seen one of these, but we log a warning here in case we ever come // across one and need to debug a related issue. warn!( "logic for a non-infinite/non-immediate timeout [{}] is not implemented", @@ -210,107 +336,186 @@ impl ScardBackend { ); } - let reader_states = Self::create_get_status_change_reader_states(call); - - // We have no status change to report. - if Self::has_no_change(&reader_states) { - if timeout != TIMEOUT_INFINITE { - // Since our status never changes, we just return immediately here - // as if the call timed out. - debug!("got no change for non-infinite timeout"); - return self.send_device_control_response( - req, - GetStatusChangeReturn::new(ReturnCode::Timeout, reader_states), - ); - } + let mut reader_states = call.states; + + // Check the integrity of the reader state structures: + // https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1742 + if reader_states.iter().any(|state| state.reader.is_empty()) { + return Ok(ScardHandleResponse::Common( + ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::InvalidValue, + reader_states + .into_iter() + .map(|state| state.common) + .collect(), + ) + .into_inner(), + ), + )); + } - // Received a GetStatusChangeCall with an infinite timeout, so we're adding - // a corresponding DeviceControlResponse holding a GetStatusChangeReturn - // with its return code set to SCARD_E_CANCELLED to this Context. This value will - // be returned when we get an SCARD_IOCTL_CANCEL call for this Context. - self.contexts.set_scard_cancel_response( - context_id, - DeviceControlResponse::new( - req, - NtStatus::SUCCESS, - Some(Box::new(GetStatusChangeReturn::new( - ReturnCode::Cancelled, - reader_states, - ))), + // If there are no readers, or if all readers are in the SCARD_STATE_IGNORE state, + // we should return immediately with `ReturnCode::Success`. This mirrors the behavior + // of the reference pcsc-lite implementation: + // + // - All readers ignored: https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1749 + // - Reader list is empty: https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1766 + if reader_states.is_empty() + || reader_states.iter().all(|state| { + state + .common + .current_state + .contains(CardStateFlags::SCARD_STATE_IGNORE) + }) + { + return Ok(ScardHandleResponse::Common( + ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::Success, + reader_states + .into_iter() + .map(|state| state.common) + .collect(), + ) + .into_inner(), ), - )?; + )); + } - debug!("blocking GetStatusChange call indefinitely (since our status never changes) until we receive an SCARD_IOCTL_CANCEL"); + // Compute the `event_state` field for each reader. + Self::compute_reader_event_states(&mut reader_states); + + let reader_states_common: Vec = reader_states + .into_iter() + .map(|state| state.common) + .collect(); + + // Check whether the state of any reader has been changed. + let has_change = reader_states_common.iter().any(|state| { + state + .event_state + .contains(CardStateFlags::SCARD_STATE_CHANGED) + }); + + if timeout == TIMEOUT_IMMEDIATE { + let return_code = if has_change { + ReturnCode::Success + } else { + ReturnCode::Timeout + }; + return Ok(ScardHandleResponse::Common( + ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new(return_code, reader_states_common).into_inner(), + ), + )); + } - return Ok(()); + if has_change { + return Ok(ScardHandleResponse::Common( + ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new(ReturnCode::Success, reader_states_common) + .into_inner(), + ), + )); } - // We have some status change to report, send it to the server. - self.send_device_control_response( - req, - GetStatusChangeReturn::new(ReturnCode::Success, reader_states), - ) + // Received a GetStatusChangeCall with an infinite timeout, and we don't have a change. + // So we're adding a GetStatusChangeResponse holding a DeviceControlRequest and + // GetStatusChangeReturn with its return code set to SCARD_E_CANCELLED to this Context. + // This value will be returned when we get an SCARD_IOCTL_CANCEL call for this Context. + self.contexts.set_scard_cancel_response( + context_id, + PendingGetStatusChange { + req, + response: ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new(ReturnCode::Cancelled, reader_states_common) + .into_inner(), + ), + }, + )?; + + debug!("blocking GetStatusChange call indefinitely (since our status never changes) until we receive an SCARD_IOCTL_CANCEL"); + + Ok(ScardHandleResponse::Deferred) } - fn create_get_status_change_reader_states( - call: GetStatusChangeCall, - ) -> Vec { - let mut reader_states = vec![]; - for state in call.states { + fn compute_reader_event_states(reader_states: &mut [ReaderState]) { + for state in reader_states { + // Clear event_state before processing. + // + // Reference: https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1822 + state.common.event_state = CardStateFlags::empty(); + + // The reader must be ignored. + // https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1874 + if state + .common + .current_state + .contains(CardStateFlags::SCARD_STATE_IGNORE) + { + continue; + } + match state.reader.as_str() { - // PnP is Plug-and-Play. This special reader "name" is used to monitor for - // new readers being plugged in. - "\\\\?PnP?\\Notification" => { - reader_states.push(ReaderStateCommonCall { - current_state: state.common.current_state, - event_state: state.common.current_state, - atr_length: state.common.atr_length, - atr: state.common.atr, - }); - } - // This is our actual emulated smartcard reader. We always advertise its state as - // "present". + // This is our actual emulated smartcard reader. TELEPORT_READER_NAME => { let (atr_length, atr) = padded_atr::<36>(); - reader_states.push(ReaderStateCommonCall { - current_state: state.common.current_state, - event_state: CardStateFlags::SCARD_STATE_CHANGED - | CardStateFlags::SCARD_STATE_PRESENT, - atr_length, - atr, - }); + state.common.event_state = CardStateFlags::SCARD_STATE_PRESENT; + state.common.atr_length = atr_length; + state.common.atr = atr; + + // Strip SCARD_STATE_CHANGED from current_state before comparison. + // The caller typically feeds the previous event_state back as current_state, + // which may carry the SCARD_STATE_CHANGED bit. That bit is a transient output signal + // and must not influence the comparison - otherwise we would always detect a change. + // + // The reference pcsc-lite implementation avoids this issue implicitly, + // since it never compares the full state value directly. + // Instead, it checks individual flags (e.g. `dwCurrentState & SCARD_STATE_PRESENT`) + // and never tests against `SCARD_STATE_CHANGED` state. + // As a result, stale `SCARD_STATE_CHANGED` are naturally ignored. + // + // Because we compare full CardStateFlags values for equality here, we must mask out + // the `SCARD_STATE_CHANGED` bit explicitly to preserve the same behavior. + // + // Reference: https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1875-L2138 + let current_without_changed = + state.common.current_state & !CardStateFlags::SCARD_STATE_CHANGED; + if current_without_changed != state.common.event_state { + state.common.event_state |= CardStateFlags::SCARD_STATE_CHANGED; + } } + // This is the special reader name used to monitor for new readers being plugged in. + // We leave its event_state empty because we never add new readers. + // + // The reference pcsc-lite implementation only sets dwEventState for this reader + // when the reader count changes. If it does not change, dwEventState stays empty + // (it was cleared before the loop). Since we never have reader count changes, + // leaving event_state empty is the correct equivalent behavior. + // + // Reference: https://github.com/LudovicRousseau/PCSC/blob/7eea3e3e3bb7b4b2d815a3f390394b4d690f5ea8/src/winscard_clnt.c#L1903-L1918 + PLUG_AND_PLAY_READER_NAME => (), // All other reader names are unknown and unexpected. + // + // According to the specification, if the SCARD_STATE_UNKNOWN bit is set, + // then SCARD_STATE_CHANGED and SCARD_STATE_IGNORE will also be set. + // + // Reference: https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-scard_readerstatea _ => { warn!( "got unexpected reader name [{}], ignoring", state.reader.as_str() ); - reader_states.push(ReaderStateCommonCall { - current_state: state.common.current_state, - event_state: CardStateFlags::SCARD_STATE_CHANGED - | CardStateFlags::SCARD_STATE_UNKNOWN - | CardStateFlags::SCARD_STATE_IGNORE, - atr_length: state.common.atr_length, - atr: state.common.atr, - }); + state.common.event_state = CardStateFlags::SCARD_STATE_UNKNOWN + | CardStateFlags::SCARD_STATE_IGNORE + | CardStateFlags::SCARD_STATE_CHANGED; } } } - reader_states - } - - fn has_no_change(reader_states: &[ReaderStateCommonCall]) -> bool { - reader_states - .iter() - .all(|state| state.current_state == state.event_state) } - fn handle_connect( - &mut self, - req: DeviceControlRequest, - call: ConnectCall, - ) -> PduResult<()> { + fn handle_connect(&mut self, call: ConnectCall) -> PduResult { let handle = self.contexts.connect( call.common.context, call.common.context.value, @@ -320,48 +525,41 @@ impl ScardBackend { self.pin.clone(), )?; - self.send_device_control_response( - req, - ConnectReturn::new(ReturnCode::Success, handle, CardProtocol::SCARD_PROTOCOL_T1), - ) + Ok(ScardHandleResponse::Common(ScardResponsePayload::Connect( + ConnectReturn::new(ReturnCode::Success, handle, CardProtocol::SCARD_PROTOCOL_T1) + .into_inner(), + ))) } - fn handle_begin_transaction( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + fn handle_begin_transaction(&mut self) -> ScardHandleResponse { + ScardHandleResponse::success_long_return() } - fn handle_transmit( - &mut self, - req: DeviceControlRequest, - call: TransmitCall, - ) -> PduResult<()> { + fn handle_transmit(&mut self, call: TransmitCall) -> PduResult { let cmd = CardCommand::::try_from(&call.send_buffer); - match cmd { + let transmit_return = match cmd { Ok(cmd) => { let card = self.contexts.get_card(&call.handle)?; let resp = card.handle(cmd)?; - self.send_device_control_response( - req, - TransmitReturn::new(ReturnCode::Success, None, resp.encode()), - )?; + TransmitReturn::new(ReturnCode::Success, None, resp.encode()).into_inner() } Err(err) => { warn!("error parsing smart card command: {:?} - {:?}", call, err); - self.send_device_control_response( - req, - TransmitReturn::new(ReturnCode::InvalidValue, None, Vec::new()), - )?; + TransmitReturn::new(ReturnCode::InvalidValue, None, Vec::new()).into_inner() } - } - Ok(()) + }; + + Ok(ScardHandleResponse::Common(ScardResponsePayload::Transmit( + transmit_return, + ))) } - fn handle_status(&mut self, req: DeviceControlRequest) -> PduResult<()> { + fn handle_status( + &mut self, + req: &DeviceControlRequest, + ) -> PduResult { let enc = match req.io_control_code { ScardIoCtlCode::StatusW => CharacterSet::Unicode, ScardIoCtlCode::StatusA => CharacterSet::Ansi, @@ -378,8 +576,7 @@ impl ScardBackend { let (atr_length, atr) = padded_atr::<32>(); - self.send_device_control_response( - req, + Ok(ScardHandleResponse::Common(ScardResponsePayload::Status( StatusReturn::new( ReturnCode::Success, vec![TELEPORT_READER_NAME.to_string()], @@ -391,73 +588,62 @@ impl ScardBackend { atr, atr_length, enc, - ), - ) + ) + .into_inner(), + ))) } - fn handle_release_context( - &mut self, - req: DeviceControlRequest, - call: ContextCall, - ) -> PduResult<()> { + fn handle_release_context(&mut self, call: ContextCall) -> ScardHandleResponse { self.contexts.release(call.context.value); - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + ScardHandleResponse::success_long_return() } - fn handle_end_transaction( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + fn handle_end_transaction(&mut self) -> ScardHandleResponse { + ScardHandleResponse::success_long_return() } fn handle_disconnect( &mut self, - req: DeviceControlRequest, call: HCardAndDispositionCall, - ) -> PduResult<()> { + ) -> PduResult { self.contexts.disconnect(call.handle)?; - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + Ok(ScardHandleResponse::success_long_return()) } - fn handle_cancel( - &mut self, - req: DeviceControlRequest, - call: ContextCall, - ) -> PduResult<()> { + fn handle_cancel(&mut self, call: ContextCall) -> PduResult { debug!( "received SCARD_IOCTL_CANCEL for context [{}]", call.context.value ); - if let Some(resp) = self + + // Take the pending SCARD_IOCTL_GETSTATUSCHANGE response. + let get_status_change_response = self .contexts - .take_scard_cancel_response(call.context.value)? - { - // Take the pending SCARD_IOCTL_GETSTATUSCHANGE response and send it back to the server. - self.client_handle.write_rdpdr(resp.into())?; - } else { + .take_scard_cancel_response(call.context.value)?; + if get_status_change_response.is_none() { warn!("Received SCARD_IOCTL_CANCEL for a context without a pending SCARD_IOCTL_GETSTATUSCHANGE"); - }; + } - // Also return a response for the SCARD_IOCTL_CANCEL request itself. - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + Ok(ScardHandleResponse::CancelDeferred { + get_status_change_response, + // Set a response for the SCARD_IOCTL_CANCEL request itself. + cancel_response: ScardResponsePayload::Long( + LongReturn::new(ReturnCode::Success).into_inner(), + ), + }) } - fn handle_is_valid_context( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { + fn handle_is_valid_context(&mut self) -> ScardHandleResponse { // TODO: Currently we're always just sending ReturnCode::Success (based on awly's pre- // IronRDP code). Should we instead be checking if we have such a context in our cache // and returning an SCARD_E_INVALID_HANDLE (or something else)? - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + ScardHandleResponse::success_long_return() } fn handle_get_device_type_id( &mut self, - req: DeviceControlRequest, call: GetDeviceTypeIdCall, - ) -> PduResult<()> { + ) -> PduResult { if self.contexts.exists(call.context.value) { // Reader type describes the type of the physical connection to the smartcard reader (e.g. // USB/serial/TPM). Type "vendor" means a proprietary vendor bus. @@ -465,10 +651,12 @@ impl ScardBackend { // See "ReaderType" in // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/smclib/ns-smclib-_scard_reader_capabilities const SCARD_READER_TYPE_VENDOR: u32 = 0xF0; - self.send_device_control_response( - req, - GetDeviceTypeIdReturn::new(ReturnCode::Success, SCARD_READER_TYPE_VENDOR), - ) + Ok(ScardHandleResponse::Common( + ScardResponsePayload::GetDeviceTypeId( + GetDeviceTypeIdReturn::new(ReturnCode::Success, SCARD_READER_TYPE_VENDOR) + .into_inner(), + ), + )) } else { Err(pdu_other_err!( "", @@ -480,46 +668,36 @@ impl ScardBackend { } } - fn handle_read_cache( - &mut self, - req: DeviceControlRequest, - call: ReadCacheCall, - ) -> PduResult<()> { + fn handle_read_cache(&mut self, call: ReadCacheCall) -> PduResult { let (data, return_code) = match self.contexts.read_cache(call)? { Some(data) => (data, ReturnCode::Success), None => (vec![], ReturnCode::CacheItemNotFound), }; - self.send_device_control_response(req, ReadCacheReturn::new(return_code, data)) + Ok(ScardHandleResponse::Common( + ScardResponsePayload::ReadCache(ReadCacheReturn::new(return_code, data).into_inner()), + )) } - fn handle_write_cache( - &mut self, - req: DeviceControlRequest, - call: WriteCacheCall, - ) -> PduResult<()> { + fn handle_write_cache(&mut self, call: WriteCacheCall) -> PduResult { self.contexts.write_cache(call)?; - self.send_device_control_response(req, LongReturn::new(ReturnCode::Success)) + Ok(ScardHandleResponse::success_long_return()) } - fn handle_get_reader_icon( - &mut self, - req: DeviceControlRequest, - ) -> PduResult<()> { - self.send_device_control_response( - req, - GetReaderIconReturn::new(ReturnCode::UnsupportedFeature, vec![]), - ) + fn handle_get_reader_icon(&mut self) -> ScardHandleResponse { + ScardHandleResponse::Common(ScardResponsePayload::GetReaderIcon( + GetReaderIconReturn::new(ReturnCode::UnsupportedFeature, vec![]).into_inner(), + )) } /// This function returns the error for unsupported combinations of [`ScardIoCtlCode`] and [`ScardCall`]. - fn unsupported_combo_error(ioctl: ScardIoCtlCode, call: ScardCall) -> PduResult<()> { - Err(pdu_other_err!( + fn unsupported_combo_error(ioctl: ScardIoCtlCode, call: ScardCall) -> PduError { + pdu_other_err!( "", source:SmartcardBackendError(format!( "received unsupported combination of ScardIoCtlCode [{:?}] with ScardCall [{:?}]", ioctl, call )) - )) + ) } fn send_device_control_response( @@ -577,12 +755,16 @@ impl Contexts { Ok(()) } - fn set_scard_cancel_response(&mut self, id: u32, resp: DeviceControlResponse) -> PduResult<()> { + fn set_scard_cancel_response( + &mut self, + id: u32, + resp: PendingGetStatusChange, + ) -> PduResult<()> { debug!("setting SCARD_IOCTL_CANCEL response for context [{}]", id); self.get_internal_mut(id)?.set_scard_cancel_response(resp) } - fn take_scard_cancel_response(&mut self, id: u32) -> PduResult> { + fn take_scard_cancel_response(&mut self, id: u32) -> PduResult> { Ok(self.get_internal_mut(id)?.take_scard_cancel_response()) } @@ -625,13 +807,13 @@ struct ContextInternal { next_id: u32, cache: HashMap>, // If we receive a SCARD_IOCTL_GETSTATUSCHANGE with an infinite timeout, we need to - // return a GetStatusChange_Return (embedded in a DeviceControlResponse) with + // return a GetStatusChange_Return (embedded in a PendingGetStatusChange) with // its return code set to SCARD_E_CANCELLED in the case that we receive a // SCARD_IOCTL_CANCEL. // // This value will be set during the handling of the SCARD_IOCTL_GETSTATUSCHANGE, so that // it can be fetched and returned in response to a SCARD_IOCTL_CANCEL. - scard_cancel_response: Option, + scard_cancel_response: Option, } impl ContextInternal { @@ -644,15 +826,17 @@ impl ContextInternal { } } - fn set_scard_cancel_response(&mut self, resp: DeviceControlResponse) -> PduResult<()> { + fn set_scard_cancel_response(&mut self, resp: PendingGetStatusChange) -> PduResult<()> { if self.scard_cancel_response.is_some() { - return Err(pdu_other_err!("SCARD_IOCTL_CANCEL already received",)); + return Err(pdu_other_err!( + "a pending SCARD_IOCTL_GETSTATUSCHANGE response already exists for this context", + )); } self.scard_cancel_response = Some(resp); Ok(()) } - fn take_scard_cancel_response(&mut self) -> Option { + fn take_scard_cancel_response(&mut self) -> Option { self.scard_cancel_response.take() } @@ -712,6 +896,9 @@ fn padded_atr() -> (u32, [u8; SIZE]) { pub const SCARD_DEVICE_ID: u32 = 1; const TELEPORT_READER_NAME: &str = "Teleport"; +/// PnP is Plug-and-Play. This special reader "name" is used to monitor for +/// new readers being plugged in. +const PLUG_AND_PLAY_READER_NAME: &str = "\\\\?PnP?\\Notification"; // TRANSMIT_DATA_LIMIT is the maximum size of transmit request/response short data, in bytes. const TRANSMIT_DATA_LIMIT: usize = 1024; const TIMEOUT_INFINITE: u32 = 0xffffffff; @@ -729,3 +916,530 @@ impl std::fmt::Display for SmartcardBackendError { } impl std::error::Error for SmartcardBackendError {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::FunctionReceiver; + use ironrdp_rdpdr::pdu::efs::{DeviceIoRequest, MajorFunction, MinorFunction}; + use ironrdp_rdpdr::pdu::esc::{ + EstablishContextCall, ReaderState, ReaderStateCommonCall, Scope, + }; + + fn build_test_scard_backend() -> (ScardBackend, FunctionReceiver) { + let (client_handle, function_receiver) = ClientHandle::new(100); + let scard_backend = ScardBackend::new(client_handle, Vec::new(), Vec::new(), String::new()); + (scard_backend, function_receiver) + } + + fn build_device_control_request( + io_control_code: ScardIoCtlCode, + ) -> DeviceControlRequest { + DeviceControlRequest { + header: DeviceIoRequest { + device_id: 0, + file_id: 0, + completion_id: 0, + major_function: MajorFunction::DeviceControl, + minor_function: MinorFunction::from(0), + }, + output_buffer_length: 256, + input_buffer_length: 256, + io_control_code, + } + } + + mod handle { + use super::*; + use crate::client::ClientFunction; + use ironrdp_rdpdr::pdu::RdpdrPdu; + use tokio::sync::mpsc::error::TryRecvError; + + #[test] + fn all_scard_handle_response_variants_are_correctly_dispatched() { + let (mut scard_backend, mut function_receiver) = build_test_scard_backend(); + let establish_context_req = + build_device_control_request(ScardIoCtlCode::EstablishContext); + let get_status_change_req = + build_device_control_request(ScardIoCtlCode::GetStatusChangeW); + let cancel_req = build_device_control_request(ScardIoCtlCode::Cancel); + + // Verify that ScardBackend::handle correctly handles ScardHandleResponse::Common. + scard_backend + .handle( + establish_context_req.clone(), + ScardCall::EstablishContextCall(EstablishContextCall { scope: Scope::User }), + ) + .expect("EstablishContext failed"); + + // Receive the EstablishContextReturn PDU emitted by the backend. + let establish_context_pdu = match function_receiver.try_recv() { + Ok(ClientFunction::WriteRdpdr(pdu)) => pdu, + other => panic!("expected WriteRdpdr, got {:?}", other), + }; + + let expected_establish_context_pdu = + RdpdrPdu::DeviceControlResponse(DeviceControlResponse::new( + establish_context_req, + NtStatus::SUCCESS, + Some(Box::new(EstablishContextReturn::new( + ReturnCode::Success, + ScardContext::new(1), + ))), + )); + + pretty_assertions::assert_eq!( + ironrdp_core::encode_vec(&establish_context_pdu).unwrap(), + ironrdp_core::encode_vec(&expected_establish_context_pdu).unwrap() + ); + + // Verify that ScardBackend::handle correctly handles ScardHandleResponse::Deferred. + scard_backend + .handle( + get_status_change_req.clone(), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_INFINITE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: TELEPORT_READER_NAME.into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle failed"); + + match function_receiver.try_recv() { + Err(TryRecvError::Empty) => (), + other => panic!("expected empty channel, got {:?}", other), + }; + + // Verify that ScardBackend::handle correctly handles ScardHandleResponse::CancelDeferred. + scard_backend + .handle( + cancel_req.clone(), + ScardCall::ContextCall(ContextCall { + context: ScardContext::new(1), + }), + ) + .expect("ScardBackend::handle failed"); + + // Receive the GetStatusChangeReturn PDU emitted by the backend. + let get_status_change_pdu = match function_receiver.try_recv() { + Ok(ClientFunction::WriteRdpdr(pdu)) => pdu, + other => panic!("expected WriteRdpdr, got {:?}", other), + }; + + let (atr_length, atr) = padded_atr::<36>(); + let expected_get_status_change_pdu = + RdpdrPdu::DeviceControlResponse(DeviceControlResponse::new( + get_status_change_req, + NtStatus::SUCCESS, + Some(Box::new(GetStatusChangeReturn::new( + ReturnCode::Cancelled, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT, + event_state: CardStateFlags::SCARD_STATE_PRESENT, + atr_length, + atr, + }], + ))), + )); + + pretty_assertions::assert_eq!( + ironrdp_core::encode_vec(&get_status_change_pdu).unwrap(), + ironrdp_core::encode_vec(&expected_get_status_change_pdu).unwrap() + ); + + // Receive the cancel PDU emitted by the backend. + let cancel_pdu = match function_receiver.try_recv() { + Ok(ClientFunction::WriteRdpdr(pdu)) => pdu, + other => panic!("expected WriteRdpdr, got {:?}", other), + }; + + let expected_cancel_pdu = RdpdrPdu::DeviceControlResponse(DeviceControlResponse::new( + cancel_req, + NtStatus::SUCCESS, + Some(Box::new(LongReturn::new(ReturnCode::Success))), + )); + + pretty_assertions::assert_eq!( + ironrdp_core::encode_vec(&cancel_pdu).unwrap(), + ironrdp_core::encode_vec(&expected_cancel_pdu).unwrap() + ); + } + } + + mod scard_get_status_change { + use super::*; + use rstest::rstest; + + #[rstest] + #[case::infinite_timeout_unaware_state( + TIMEOUT_INFINITE, CardStateFlags::SCARD_STATE_UNAWARE, + CardStateFlags::SCARD_STATE_PRESENT | CardStateFlags::SCARD_STATE_CHANGED, + ReturnCode::Success, + )] + #[case::infinite_timeout_unpowered_state( + TIMEOUT_INFINITE, CardStateFlags::SCARD_STATE_UNPOWERED, + CardStateFlags::SCARD_STATE_PRESENT | CardStateFlags::SCARD_STATE_CHANGED, + ReturnCode::Success, + )] + #[case::immediate_timeout_changed_state( + TIMEOUT_IMMEDIATE, CardStateFlags::SCARD_STATE_UNPOWERED, + CardStateFlags::SCARD_STATE_PRESENT | CardStateFlags::SCARD_STATE_CHANGED, + ReturnCode::Success, + )] + #[case::immediate_timeout_stable_state( + TIMEOUT_IMMEDIATE, + CardStateFlags::SCARD_STATE_PRESENT, + CardStateFlags::SCARD_STATE_PRESENT, + ReturnCode::Timeout + )] + #[case::stale_changed_bit_in_current_state( + TIMEOUT_IMMEDIATE, CardStateFlags::SCARD_STATE_PRESENT | CardStateFlags::SCARD_STATE_CHANGED, + CardStateFlags::SCARD_STATE_PRESENT, + ReturnCode::Timeout, + )] + fn teleport_reader_event_state_computation( + #[case] timeout: u32, + #[case] current_state: CardStateFlags, + #[case] expected_event_state: CardStateFlags, + #[case] expected_return_code: ReturnCode, + ) { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: TELEPORT_READER_NAME.into(), + common: ReaderStateCommonCall { + current_state, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let (atr_length, atr) = padded_atr::<36>(); + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + expected_return_code, + vec![ReaderStateCommonCall { + current_state, + event_state: expected_event_state, + atr_length, + atr, + }], + ) + .into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + + #[test] + fn infinite_timeout_stable_state_blocks_and_cancels_on_ioctl_cancel() { + let (mut scard_backend, _) = build_test_scard_backend(); + let get_status_change_req = + build_device_control_request(ScardIoCtlCode::GetStatusChangeW); + + let establish_context_response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::EstablishContext), + ScardCall::EstablishContextCall(EstablishContextCall { scope: Scope::User }), + ) + .expect("EstablishContext failed"); + + assert_eq!( + establish_context_response, + ScardHandleResponse::Common(ScardResponsePayload::EstablishContext( + EstablishContextReturn::new(ReturnCode::Success, ScardContext::new(1)) + .into_inner() + )) + ); + + let get_status_change_response = scard_backend + .handle_impl( + &get_status_change_req, + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_INFINITE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: TELEPORT_READER_NAME.into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + // There are no changes. So, the function must defer sending a response + // until the cancel request is received. + pretty_assertions::assert_eq!( + get_status_change_response, + ScardHandleResponse::Deferred + ); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::Cancel), + ScardCall::ContextCall(ContextCall { + context: ScardContext::new(1), + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let (atr_length, atr) = padded_atr::<36>(); + let expected_response = ScardHandleResponse::CancelDeferred { + get_status_change_response: Some(PendingGetStatusChange { + req: get_status_change_req, + response: ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::Cancelled, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT, + event_state: CardStateFlags::SCARD_STATE_PRESENT, + atr_length, + atr, + }], + ) + .into_inner(), + ), + }), + cancel_response: ScardResponsePayload::Long( + LongReturn::new(ReturnCode::Success).into_inner(), + ), + }; + + pretty_assertions::assert_eq!(response, expected_response); + } + + #[test] + fn all_readers_ignored_returns_immediately() { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_INFINITE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: TELEPORT_READER_NAME.into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_IGNORE, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::Success, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_IGNORE, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }], + ) + .into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + + #[test] + fn no_readers_returns_immediately() { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_INFINITE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 0, + states: vec![], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new(ReturnCode::Success, vec![]).into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + + #[test] + fn pnp_reader_nonempty_current_state_does_not_trigger_state_change() { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_IMMEDIATE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: PLUG_AND_PLAY_READER_NAME.into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT + | CardStateFlags::SCARD_STATE_UNPOWERED, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::Timeout, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_PRESENT + | CardStateFlags::SCARD_STATE_UNPOWERED, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }], + ) + .into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + + #[test] + fn unknown_reader_is_flagged_with_unknown_state_and_triggers_change() { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_IMMEDIATE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: "Unknown Reader".into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_UNAWARE, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::Success, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_UNAWARE, + event_state: CardStateFlags::SCARD_STATE_UNKNOWN + | CardStateFlags::SCARD_STATE_IGNORE + | CardStateFlags::SCARD_STATE_CHANGED, + atr_length: 0, + atr: [0; 36], + }], + ) + .into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + + #[test] + fn empty_reader_name_returns_invalid_value() { + let (mut scard_backend, _) = build_test_scard_backend(); + + let response = scard_backend + .handle_impl( + &build_device_control_request(ScardIoCtlCode::GetStatusChangeW), + ScardCall::GetStatusChangeCall(GetStatusChangeCall { + context: ScardContext::new(1), + timeout: TIMEOUT_IMMEDIATE, + states_ptr_length: 0, + states_ptr: 0, + states_length: 1, + states: vec![ReaderState { + reader: "".into(), + common: ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_UNAWARE, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }, + }], + }), + ) + .expect("ScardBackend::handle_impl failed"); + + let expected_response = + ScardHandleResponse::Common(ScardResponsePayload::GetStatusChange( + GetStatusChangeReturn::new( + ReturnCode::InvalidValue, + vec![ReaderStateCommonCall { + current_state: CardStateFlags::SCARD_STATE_UNAWARE, + event_state: CardStateFlags::empty(), + atr_length: 0, + atr: [0; 36], + }], + ) + .into_inner(), + )); + + pretty_assertions::assert_eq!(response, expected_response,); + } + } +} From 6240c54434f526b6fb87153fc156dbc9de569319 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 09:48:58 -0600 Subject: [PATCH 026/347] Add skill for diagnosing flaky tests (#69020) --- .claude/skills/flaky-test/SKILL.md | 47 ++++++++++++++++++++++++++++++ .gitignore | 5 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .claude/skills/flaky-test/SKILL.md diff --git a/.claude/skills/flaky-test/SKILL.md b/.claude/skills/flaky-test/SKILL.md new file mode 100644 index 000000000000..d32264409ddd --- /dev/null +++ b/.claude/skills/flaky-test/SKILL.md @@ -0,0 +1,47 @@ +--- +name: flaky-test +description: Run when the user asks to investigate, troubleshoot, or fix a flaky Go test. +--- + +# Flaky Test + +You are debugging a flaky test. In Teleport, flaky tests often pass locally on +powerful developer machines, but then fail on constrained CI/CD hardware. + +Sometimes the problem is due to the way the test is written, but there are +plenty of occaisions where the flaky test is highlighting a problem in the +implementation itself. + +## Steps + +1. Try to reproduce the failure. Locate the flaky test and compile a test + binary from the directory containing the test: `go test -c -o flaky.test .` +2. Run the test under the `stress` tool: + `stress -failfast -p 30 ./flaky.test -test.run TestNameHere`. + If `stress` is not installed it can be installed with + `go install golang.org/x/tools/cmd/stress@latest`. + You may need to increase the `-p` (parallelism) flag in order to reproduce + the flake. Avoid increasing the parallelism too much. Anything above `-p 100` + starts to induce failures in common setup logic rather than in the test itself. +3. Read the test to understand what is happening. Add print statements to test + and debug your theories. +4. Attept to fix the flakiness. +5. Repeat step 2 to verify that the flakiness is no longer observed. If the + flakiness still occurs, continue to iterate and re-run the test under + `stress` until it passes consistently. +6. When complete, summarize the cause of the flakiness and the fix that you + came up with. Delete the `flaky.test` binary. Do not make any commits, + the developer will want to review your changes and make adjustments. + +## Rules + +Prefer deterministic changes like channel-based notifications or +`testing/synctest` over retry-based solutions like `require.Eventually`. +Extending the timeout of a test is usually an anti-pattern. Try to fix the bug +in the implementation or rewrite the test to run faster. + +Another way to make tests more reliable is to make them do less. Look for +opportunities to improve the runtime of the test. This might mean optimizing the +underlying code, better isolating the feature under test, or disabling features +that are enabled by default but otherwise unnecessary for the feature being +tested. diff --git a/.gitignore b/.gitignore index f1e028325db6..061ca8d1e2fa 100644 --- a/.gitignore +++ b/.gitignore @@ -132,5 +132,6 @@ msgfile/ unit-tests*.xml unit-tests*.json -# Local Claude config -.claude +# Local Claude config, except shared skills +.claude/* +!.claude/skills From b419da54a70f910af5635b3c88d903a367f4cf8f Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 28 Jul 2026 17:49:21 +0200 Subject: [PATCH 027/347] Update pnpm to 11.17 (#69083) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 296a10d6b8c4..2e941a2096b0 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "web/.storybook/public" ] }, - "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b", + "packageManager": "pnpm@11.17.0+sha512.cca3cea332ad254bb84145f966d19f4879615210346fc92c79a047f23a0d7b3cca3c3792f0076ba1f1831d277efbcf0a9119b31a9a60eca7fb3d6231f331ef72", "engines": { "node": "^24" } From 0706f5af3512a8480f1b8c34e2f278d68aef021d Mon Sep 17 00:00:00 2001 From: Dan Share <10600907+danielashare@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:58:30 +0100 Subject: [PATCH 028/347] Add Linux Desktop shortcut (#68701) --- lib/services/resource.go | 2 ++ lib/services/resource_test.go | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/services/resource.go b/lib/services/resource.go index bb2788f6724a..e09fff778860 100644 --- a/lib/services/resource.go +++ b/lib/services/resource.go @@ -215,6 +215,8 @@ func ParseShortcut(in string) (string, error) { return types.KindWindowsDesktop, nil case types.KindDynamicWindowsDesktop, "dynamic_win_desktop", "dynamic_desktop": return types.KindDynamicWindowsDesktop, nil + case types.KindLinuxDesktop, types.KindLinuxDesktop + "s": + return types.KindLinuxDesktop, nil case types.KindToken, "tokens": return types.KindToken, nil case types.KindInstaller: diff --git a/lib/services/resource_test.go b/lib/services/resource_test.go index 018a786df264..ef2e0a690174 100644 --- a/lib/services/resource_test.go +++ b/lib/services/resource_test.go @@ -137,6 +137,13 @@ func TestParseShortcut(t *testing.T) { "windows_desktop": {expectedOutput: types.KindWindowsDesktop}, "win_desktop": {expectedOutput: types.KindWindowsDesktop}, + "dynamic_windows_desktop": {expectedOutput: types.KindDynamicWindowsDesktop}, + "dynamic_win_desktop": {expectedOutput: types.KindDynamicWindowsDesktop}, + "dynamic_desktop": {expectedOutput: types.KindDynamicWindowsDesktop}, + + "linux_desktop": {expectedOutput: types.KindLinuxDesktop}, + "linux_desktops": {expectedOutput: types.KindLinuxDesktop}, + "token": {expectedOutput: types.KindToken}, "tokens": {expectedOutput: types.KindToken}, From 9afb63eeef1d9ffeb6a7a8ff8334bf13c33f054e Mon Sep 17 00:00:00 2001 From: Gustavo Medori Date: Tue, 28 Jul 2026 10:13:43 -0600 Subject: [PATCH 029/347] iOS: Add plumbing for new DebugView (#68648) * Add a new SheetPresentable protocol codifying the need for an ID for presentation purposes * Update existing presentationID in EnrollCameraScannerViewModel.swift to be conformant to SheetPresentable * Add new empty Debugging feature * Wire up DebugView to new button in `LandingView.toolbar` * Fix typo --- .../Architecture/SheetPresentable.swift | 26 +++++++++++++++ .../Verify/Features/Debugging/DebugView.swift | 33 +++++++++++++++++++ .../Features/Debugging/DebugViewModel.swift | 32 ++++++++++++++++++ .../EnrollCameraScannerViewModel.swift | 8 ++--- .../Verify/Features/Landing/LandingView.swift | 22 +++++++++++++ .../Features/Landing/LandingViewModel.swift | 9 +++++ 6 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 mobile/Verify/Verify/Architecture/SheetPresentable.swift create mode 100644 mobile/Verify/Verify/Features/Debugging/DebugView.swift create mode 100644 mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift diff --git a/mobile/Verify/Verify/Architecture/SheetPresentable.swift b/mobile/Verify/Verify/Architecture/SheetPresentable.swift new file mode 100644 index 000000000000..c75d43feb913 --- /dev/null +++ b/mobile/Verify/Verify/Architecture/SheetPresentable.swift @@ -0,0 +1,26 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +/// For the purposes of presentation, we often need some kind of identifier which will distinguish between two view +/// models of the same type. This protocol formalizes the concept and gives us a place to put convenience accessors +/// and documentation. +protocol SheetPresentable { + associatedtype PresentationID: Hashable + /// The ID to use for presentation purposes in things like `.sheet` SwiftUI modifiers + /// + /// If there is no real distinction between instances of a particular type, consider supplying a constant value. + var presentationID: PresentationID { get } +} diff --git a/mobile/Verify/Verify/Features/Debugging/DebugView.swift b/mobile/Verify/Verify/Features/Debugging/DebugView.swift new file mode 100644 index 000000000000..2f2998a55605 --- /dev/null +++ b/mobile/Verify/Verify/Features/Debugging/DebugView.swift @@ -0,0 +1,33 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +#if DEBUG + + import SwiftUI + + /// DebugView serves as the entrypoint for in-development ideas, UI component catalogs, and any other bits that we + /// want to be able to see when testing on a device, but which we don't want to ship to customers. + /// + /// - Note: DebugView and all its associated components should always be wrapped in `#if DEBUG` checks. + struct DebugView: View { + var viewModel: DebugViewModel + + var body: some View { + Text("Placeholder for DebugView") + } + } + +#endif diff --git a/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift b/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift new file mode 100644 index 000000000000..a4012f6bf321 --- /dev/null +++ b/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift @@ -0,0 +1,32 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +#if DEBUG + + import Observation + + @Observable + final class DebugViewModel {} + + // MARK: - SheetPresentable + + extension DebugViewModel: SheetPresentable { + var presentationID: some Hashable { + "DebugViewModel" + } + } + +#endif diff --git a/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift b/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift index b6f148383d13..c151cbe2b594 100644 --- a/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift +++ b/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift @@ -72,12 +72,10 @@ extension EnrollCameraScannerViewModel { } } -// MARK: - Navigation Helpers +// MARK: - SheetPresentable -extension EnrollCameraScannerViewModel { - /// For the purposes of presentation, there is no distinction between instances of EnrollCameraScannerViewModel, - /// so we vend this constant presentation ID to express that to SwiftUI. - var presentationID: String { +extension EnrollCameraScannerViewModel: SheetPresentable { + var presentationID: some Hashable { "EnrollCameraScannerViewModel" } } diff --git a/mobile/Verify/Verify/Features/Landing/LandingView.swift b/mobile/Verify/Verify/Features/Landing/LandingView.swift index 3f7bf7a03c4b..8f7e5f931adf 100644 --- a/mobile/Verify/Verify/Features/Landing/LandingView.swift +++ b/mobile/Verify/Verify/Features/Landing/LandingView.swift @@ -45,6 +45,9 @@ struct LandingView: View { } .padding(.horizontal) .background(Color.Background.depth3) + + // MARK: Toolbar + .toolbarVisibility(viewModel.shouldShowToolbar ? .visible : .hidden) .toolbar { ToolbarItem { @@ -55,6 +58,14 @@ struct LandingView: View { role: .destructive, action: viewModel.userTappedForgetAllClusters, ) + #if DEBUG + Divider() + Button( + "Debug", + systemImage: "apple.terminal", + action: viewModel.userTappedOnDebugButton, + ) + #endif } label: { Label("Menu", systemImage: "ellipsis") } @@ -80,6 +91,17 @@ struct LandingView: View { // MARK: Haptics .sensoryFeedback(.success, trigger: viewModel.sensoryFeedbackTrigger) + + // MARK: Debug + + // swiftformat:disable indent + #if DEBUG + .sheet(item: $viewModel.destination.debug, id: \.presentationID) { debugViewModel in + DebugView(viewModel: debugViewModel) + } + #endif + // swiftformat:enable indent + } } } diff --git a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift index 05e8130a3f4f..56403dba39d9 100644 --- a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift +++ b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift @@ -31,6 +31,9 @@ final class LandingViewModel { case enrollDevice(EnrollDeviceViewModel) case forgetAllClustersAlert(AlertState) case notice(AlertState) + #if DEBUG + case debug(DebugViewModel) + #endif } @ObservationIgnored @@ -120,6 +123,12 @@ extension LandingViewModel { func userConfirmedForgetAllClusters() async { await deleteClusters { Cluster.delete() } } + + #if DEBUG + func userTappedOnDebugButton() { + destination = .debug(DebugViewModel()) + } + #endif } // MARK: - Programmatic Navigation From 63257ad910fa7a808ef6883c68d93bf25c99e08a Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Tue, 28 Jul 2026 18:16:45 +0200 Subject: [PATCH 030/347] Temporarily skip unauthenticated e2e tests in Firefox (#69010) * Temporarily skip unauthenticated e2e tests in Firefox * Keep the project defined, but skip it --- e2e/playwright.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 05782a2f4bf3..c70157955da8 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -62,6 +62,12 @@ export default defineConfig({ name: `${browser}:unauthenticated`, testDir: './tests/web/unauthenticated', use: { ...browserDevices[browser] }, + // TODO(ryan): fix these tests + // Unauthenticated Firefox tests sometimes get rate-limited and sometimes think the cluster name is + // `localhost`, for reasons I haven't been able to figure out, so don't run them for now. The project + // stays defined because the runner selects projects by exact name, and Playwright fails selection + // outright on an unknown one. + ...(browser === 'firefox' ? { testIgnore: /.*/ } : {}), }, ]), From 6fa661007dcabfc9f36c75a0bd76d84a5381d5b2 Mon Sep 17 00:00:00 2001 From: Kyle Kloberdanz Date: Tue, 28 Jul 2026 11:30:47 -0500 Subject: [PATCH 031/347] Quarantine corrupt events (#67638) This implements a mechanism that will move events that fail to deserialize from the audit_queue into the corrupt_events table. This will ensure that events that fail to deserialize don't "clog" the main queue. These events do not belong in the dead-letter table because if they fail to deserialize, then there is no point in retrying them. Between process restarts, we will attempt to re-send corrupt_events in case the failed deserialization was due to a version mismatch from a Teleport downgrade and upgrade. --- lib/events/auditqueue/metrics.go | 27 +++ lib/events/auditqueue/sqlite.go | 295 ++++++++++++++++++++++++--- lib/events/auditqueue/sqlite_disk.go | 23 ++- lib/events/auditqueue/sqlite_test.go | 283 ++++++++++++++++++++++++- 4 files changed, 597 insertions(+), 31 deletions(-) diff --git a/lib/events/auditqueue/metrics.go b/lib/events/auditqueue/metrics.go index 37cd77209a88..dab107d5ea7f 100644 --- a/lib/events/auditqueue/metrics.go +++ b/lib/events/auditqueue/metrics.go @@ -97,6 +97,30 @@ var eventsDelivered = prometheus.NewCounter( }, ) +var corruptEvents = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_corrupt_events_total", + Help: "Total number of audit events quarantined after failing to deserialize.", + }, +) + +var corruptRecovered = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_corrupt_events_recovered_total", + Help: "Total number of quarantined audit events that later deserialized and were re-queued for delivery.", + }, +) + +var corruptExpired = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_corrupt_events_expired_total", + Help: "Total number of corrupt audit events permanently dropped after exceeding the retention TTL.", + }, +) + var prometheusCollectors = []prometheus.Collector{ batchSize, orphansAdopted, @@ -107,4 +131,7 @@ var prometheusCollectors = []prometheus.Collector{ deadLetterExpired, eventsEnqueued, eventsDelivered, + corruptEvents, + corruptRecovered, + corruptExpired, } diff --git a/lib/events/auditqueue/sqlite.go b/lib/events/auditqueue/sqlite.go index b64f4181568b..cd83a387665f 100644 --- a/lib/events/auditqueue/sqlite.go +++ b/lib/events/auditqueue/sqlite.go @@ -43,6 +43,7 @@ const ( sqlitePageSize = 4096 // Bytes auditQueueTable = "audit_queue" auditDeadLetterTable = "audit_dead_letter" + corruptEventsTable = "corrupt_events" defaultMaxAttempts = 10 defaultDeadLetterSweepInterval = 10 * time.Minute defaultDeadLetterTTL = 30 * 24 * time.Hour // 30 days @@ -96,8 +97,7 @@ CREATE TABLE IF NOT EXISTS teleport_info ( -- We need AUTOINCREMENT here to ensure the recoveryWatermark has a -- monotonically incrementing id. We need to ensure that the 'id' is never --- re-used for this table. Other tables do not have this requirement. --- See: https://sqlite.org/autoinc.html +-- re-used for this table. CREATE TABLE IF NOT EXISTS corrupt_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, payload BLOB NOT NULL, @@ -139,6 +139,12 @@ type sqliteQueue struct { deadLetterSweepInterval time.Duration deadLetterTTL time.Duration synchronous SynchronousMode + + // recoveryWatermark is the highest corrupt_events id that + // recoverCorruptEvents has already examined in this process. It only ever + // climbs, so each pass skips rows it has already tried. + recoveryWatermark int64 + // deadLetterKick wakes the dead-letter sweeper ahead of its timer. It has // capacity 1 so pending kicks coalesce into a single sweep. deadLetterKick chan struct{} @@ -596,6 +602,8 @@ func (q *sqliteQueue) deadLetterSweepLoop(ctx context.Context, handler Handler) } q.sweepDeadLetter(ctx, handler) q.expireDeadLetter() + q.recoverCorruptEvents() + q.expireCorruptEvents() timer.Reset(q.deadLetterSweepInterval) } } @@ -668,7 +676,28 @@ func (q *sqliteQueue) fetchDeadLetterRange(afterID, maxID int64, limit int) ([]I if err != nil { return nil, trace.Wrap(err) } - return scanItems(rows) + items, corrupt, err := scanItems(rows) + if err != nil { + return nil, trace.Wrap(err) + } + handleCorrupt(q.ctx, q.db, auditDeadLetterTable, corrupt) + return items, nil +} + +// fetchDeadLetter reads up to `limit` oldest items from the audit_dead_letter +// table, quarantining any rows whose payload fails to deserialize. +func (q *sqliteQueue) fetchDeadLetter(limit int) ([]Item, error) { + rows, err := q.db.QueryContext(q.ctx, + "SELECT id, payload FROM audit_dead_letter ORDER BY id ASC LIMIT ?", limit) + if err != nil { + return nil, trace.Wrap(err) + } + items, corrupt, err := scanItems(rows) + if err != nil { + return nil, trace.Wrap(err) + } + handleCorrupt(q.ctx, q.db, auditDeadLetterTable, corrupt) + return items, nil } // ackDeadLetter deletes successfully re-delivered items from audit_dead_letter. @@ -709,6 +738,143 @@ func (q *sqliteQueue) expireDeadLetter() { } } +func (q *sqliteQueue) expireCorruptEvents() { + cutoff := time.Now().Add(-q.deadLetterTTL).Unix() + res, err := q.db.ExecContext(q.ctx, + "DELETE FROM corrupt_events WHERE failed_at < ?", cutoff) + if err != nil { + if q.ctx.Err() == nil { + slog.ErrorContext(q.ctx, + "Failed to expire corrupt audit events.", + "error", err, + ) + } + return + } + n, err := res.RowsAffected() + if err != nil || n == 0 { + return + } + corruptExpired.Add(float64(n)) + slog.WarnContext(q.ctx, + "Permanently dropped corrupt audit events that exceeded the retention TTL.", + "count", n, + "ttl", q.deadLetterTTL, + ) +} + +func (q *sqliteQueue) recoverCorruptEvents() { + var total int + for { + if q.ctx.Err() != nil { + return + } + batch, err := fetchCorruptForRecovery(q.ctx, q.db, q.recoveryWatermark, dequeueBatchSize) + if err != nil { + if q.ctx.Err() == nil { + slog.ErrorContext(q.ctx, + "Failed to read corrupt audit events for recovery.", + "error", err, + ) + } + return + } + if len(batch) == 0 { + break + } + + var recovered []recoveredEvent + for _, r := range batch { + var oneOf apievents.OneOf + if err := oneOf.Unmarshal(r.payload); err != nil { + continue + } + if _, err := apievents.FromOneOf(oneOf); err != nil { + continue + } + // The event deserializes, so it is no longer corrupt and can be + // re-sent. + recovered = append(recovered, r) + } + if len(recovered) > 0 { + if err := q.reinjectRecovered(recovered); err != nil { + if q.ctx.Err() == nil { + slog.ErrorContext(q.ctx, + "Failed to re-queue recovered audit events.", + "error", err, + "count", len(recovered), + ) + } + // Leave the watermark unmoved so this batch is retried next pass + // rather than skipped until the next process restart. + return + } + corruptRecovered.Add(float64(len(recovered))) + total += len(recovered) + } + // Advance only after the batch is fully handled. + q.recoveryWatermark = batch[len(batch)-1].id + } + if total > 0 { + slog.InfoContext(q.ctx, + "Recovered previously-corrupt audit events and re-queued them for delivery.", + "count", total, + ) + } +} + +type recoveredEvent struct { + id int64 + payload []byte +} + +func fetchCorruptForRecovery(ctx context.Context, db *sql.DB, afterID int64, limit int) ([]recoveredEvent, error) { + rows, err := db.QueryContext(ctx, + "SELECT id, payload FROM corrupt_events WHERE id > ? ORDER BY id ASC LIMIT ?", afterID, limit) + if err != nil { + return nil, trace.Wrap(err) + } + defer rows.Close() + var out []recoveredEvent + for rows.Next() { + var r recoveredEvent + if err := rows.Scan(&r.id, &r.payload); err != nil { + return nil, trace.Wrap(err) + } + out = append(out, r) + } + return out, trace.Wrap(rows.Err()) +} + +// reinjectRecovered atomically moves recovered events back into audit_queue and +// removes them from corrupt_events. +func (q *sqliteQueue) reinjectRecovered(events []recoveredEvent) error { + tx, err := q.db.BeginTx(q.ctx, nil) + if err != nil { + return trace.Wrap(err) + } + defer tx.Rollback() + + insertStmt, err := tx.PrepareContext(q.ctx, "INSERT INTO audit_queue (payload) VALUES (?)") + if err != nil { + return trace.Wrap(err) + } + defer insertStmt.Close() + + ids := make([]int64, 0, len(events)) + for _, e := range events { + if _, err := insertStmt.ExecContext(q.ctx, e.payload); err != nil { + return trace.Wrap(err) + } + ids = append(ids, e.id) + } + + if err := deleteIDsFromTable(q.ctx, tx, corruptEventsTable, ids); err != nil { + return trace.Wrap(err) + } + return trace.Wrap(tx.Commit()) +} + func (q *sqliteQueue) fetch(limit int) ([]Item, error) { return fetchDB(q.ctx, q.db, limit) } @@ -723,41 +889,116 @@ func (q *sqliteQueue) ack(items []Item) error { return trace.Wrap(err) } -// fetchDB reads up to `limit` oldest items from the table `audit_queue`. -func fetchDB(ctx context.Context, db *sql.DB, limit int) ([]Item, error) { - if limit <= 0 { - return nil, nil - } - rows, err := db.QueryContext(ctx, - "SELECT id, payload FROM audit_queue ORDER BY id ASC LIMIT ?", limit) - if err != nil { - return nil, trace.Wrap(err) - } - return scanItems(rows) +// corruptRow is a queue row whose payload failed to deserialize. Such rows are +// moved to the corrupt_events table so they do not clog the queue. +type corruptRow struct { + id int64 + payload []byte + err error } -func scanItems(rows *sql.Rows) ([]Item, error) { +func scanItems(rows *sql.Rows) (items []Item, corrupt []corruptRow, err error) { defer rows.Close() - var items []Item for rows.Next() { var ( id int64 payload []byte ) if err := rows.Scan(&id, &payload); err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) } + var oneOf apievents.OneOf if err := oneOf.Unmarshal(payload); err != nil { - return nil, trace.Wrap(err) + corrupt = append(corrupt, corruptRow{id: id, payload: payload, err: err}) + continue } event, err := apievents.FromOneOf(oneOf) if err != nil { - return nil, trace.Wrap(err) + corrupt = append(corrupt, corruptRow{id: id, payload: payload, err: err}) + continue } items = append(items, Item{id: id, Event: event}) } - return items, trace.Wrap(rows.Err()) + return items, corrupt, trace.Wrap(rows.Err()) +} + +func quarantineCorrupt(ctx context.Context, db *sql.DB, sourceTable string, corrupt []corruptRow) error { + if len(corrupt) == 0 { + return nil + } + switch sourceTable { + case auditQueueTable, auditDeadLetterTable: + default: + return trace.BadParameter("unknown table %q", sourceTable) + } + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return trace.Wrap(err) + } + defer tx.Rollback() + + insertStmt, err := tx.PrepareContext(ctx, + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, ?, ?, ?)") + if err != nil { + return trace.Wrap(err) + } + defer insertStmt.Close() + + ids := make([]int64, 0, len(corrupt)) + failedAt := time.Now().Unix() + for _, c := range corrupt { + if _, err := insertStmt.ExecContext(ctx, c.payload, c.err.Error(), sourceTable, failedAt); err != nil { + return trace.Wrap(err) + } + ids = append(ids, c.id) + } + + if err := deleteIDsFromTable(ctx, tx, sourceTable, ids); err != nil { + return trace.Wrap(err) + } + return trace.Wrap(tx.Commit()) +} + +func handleCorrupt(ctx context.Context, db *sql.DB, sourceTable string, corrupt []corruptRow) { + if len(corrupt) == 0 { + return + } + if err := quarantineCorrupt(ctx, db, sourceTable, corrupt); err != nil { + slog.ErrorContext(ctx, + "Failed to quarantine corrupt audit events.", + "error", err, + "source_table", sourceTable, + "count", len(corrupt), + ) + return + } + corruptEvents.Add(float64(len(corrupt))) + slog.WarnContext(ctx, + "Quarantined corrupt audit events that failed to deserialize.", + "source_table", sourceTable, + "count", len(corrupt), + "first_error", corrupt[0].err.Error(), + ) +} + +// fetchDB reads up to `limit` oldest items from the table `audit_queue`. +func fetchDB(ctx context.Context, db *sql.DB, limit int) ([]Item, error) { + if limit <= 0 { + return nil, nil + } + rows, err := db.QueryContext(ctx, + "SELECT id, payload FROM audit_queue ORDER BY id ASC LIMIT ?", limit) + if err != nil { + return nil, trace.Wrap(err) + } + items, corrupt, err := scanItems(rows) + if err != nil { + return nil, trace.Wrap(err) + } + handleCorrupt(ctx, db, auditQueueTable, corrupt) + return items, nil } func placeholders(n int) string { @@ -767,21 +1008,27 @@ func placeholders(n int) string { return strings.Repeat("?,", n-1) + "?" } -func deleteIDsFromTable(ctx context.Context, db *sql.DB, table string, ids []int64) error { +// execer is satisfied by both *sql.DB and *sql.Tx, so deleteIDsFromTable can +// run standalone or inside a caller's transaction. +type execer interface { + ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) +} + +func deleteIDsFromTable(ctx context.Context, e execer, table string, ids []int64) error { if len(ids) == 0 { return nil } switch table { - case auditQueueTable, auditDeadLetterTable: + case auditQueueTable, auditDeadLetterTable, corruptEventsTable: default: return trace.BadParameter("unknown table %q", table) } - query := "DELETE FROM " + table + " WHERE id IN (" + placeholders(len(ids)) + ")" args := make([]any, len(ids)) for i, id := range ids { args[i] = id } - _, err := db.ExecContext(ctx, query, args...) + _, err := e.ExecContext(ctx, + "DELETE FROM "+table+" WHERE id IN ("+placeholders(len(ids))+")", args...) return trace.Wrap(err) } diff --git a/lib/events/auditqueue/sqlite_disk.go b/lib/events/auditqueue/sqlite_disk.go index f7fba834262e..3ae365844ff1 100644 --- a/lib/events/auditqueue/sqlite_disk.go +++ b/lib/events/auditqueue/sqlite_disk.go @@ -540,7 +540,10 @@ func (q *sqliteQueue) migrateOrphanDB(ctx context.Context, db *sql.DB, name stri if err := q.migrateOrphanQueue(ctx, db, name); err != nil { return trace.Wrap(err) } - return trace.Wrap(q.migrateOrphanDeadLetter(ctx, db, name)) + if err := q.migrateOrphanDeadLetter(ctx, db, name); err != nil { + return trace.Wrap(err) + } + return trace.Wrap(q.migrateOrphanCorruptEvents(ctx, db, name)) } func (q *sqliteQueue) migrateOrphanQueue(ctx context.Context, orphan *sql.DB, name string) error { @@ -614,9 +617,10 @@ func (q *sqliteQueue) readOrphanWatermark(ctx context.Context, key string) (int6 func (q *sqliteQueue) clearOrphanWatermarks(ctx context.Context, name string) { if _, err := q.db.ExecContext(ctx, - "DELETE FROM teleport_info WHERE key IN (?, ?)", + "DELETE FROM teleport_info WHERE key IN (?, ?, ?)", orphanWatermarkKey(name, auditQueueTable), orphanWatermarkKey(name, auditDeadLetterTable), + orphanWatermarkKey(name, corruptEventsTable), ); err != nil { slog.ErrorContext(q.ctx, "Failed to clear orphan migration watermarks.", @@ -682,6 +686,13 @@ func (q *sqliteQueue) insertMigratedBatch(ctx context.Context, insertSQL string, return trace.Wrap(tx.Commit()) } +func (q *sqliteQueue) migrateOrphanCorruptEvents(ctx context.Context, orphan *sql.DB, name string) error { + return q.migrateOrphanTable(ctx, orphan, name, corruptEventsTable, + "SELECT id, payload, error, source, failed_at FROM corrupt_events WHERE id > ? ORDER BY id ASC LIMIT ?", + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, ?, ?, ?)", + ) +} + func (q *sqliteQueue) Close() error { var errs []error q.closeOnce.Do(func() { @@ -727,11 +738,13 @@ func (q *sqliteQueue) Close() error { return trace.NewAggregate(errs...) } +const isEmptyQuery = `SELECT EXISTS(SELECT 1 FROM audit_queue) + OR EXISTS(SELECT 1 FROM audit_dead_letter) + OR EXISTS(SELECT 1 FROM corrupt_events)` + func isQueueEmpty(db *sql.DB) (bool, error) { var hasRows int - err := db.QueryRow( - "SELECT EXISTS(SELECT 1 FROM audit_queue) OR EXISTS(SELECT 1 FROM audit_dead_letter)", - ).Scan(&hasRows) + err := db.QueryRow(isEmptyQuery).Scan(&hasRows) if err != nil { return false, trace.Wrap(err) } diff --git a/lib/events/auditqueue/sqlite_test.go b/lib/events/auditqueue/sqlite_test.go index 54022d7c64f5..4553a42ee249 100644 --- a/lib/events/auditqueue/sqlite_test.go +++ b/lib/events/auditqueue/sqlite_test.go @@ -34,6 +34,7 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport" + apievents "github.com/gravitational/teleport/api/types/events" ) const queueDir = "test-queue" @@ -71,7 +72,8 @@ func fetchDeadLetter(ctx context.Context, db *sql.DB, limit int) ([]Item, error) if err != nil { return nil, err } - return scanItems(rows) + items, _, err := scanItems(rows) + return items, err } func TestEnqueueDequeue_FIFO(t *testing.T) { @@ -867,7 +869,7 @@ func TestIsSQLiteFullError(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "test.db") - db, err := sql.Open("sqlite", getDSN(dbPath, 10*sqlitePageSize, SynchronousNormal)) + db, err := sql.Open("sqlite", getDSN(dbPath, 50*sqlitePageSize, SynchronousNormal)) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() }) @@ -1171,6 +1173,283 @@ func TestDeadLetterTTL_ExpiresOldRows(t *testing.T) { require.Empty(t, dlItems, "row should have been deleted by expireDeadLetter") } +var corruptPayload = []byte{0xff, 0xff, 0xff} + +func countRows(t *testing.T, q *sqliteQueue, table string) int { + t.Helper() + var n int + require.NoError(t, q.db.QueryRow("SELECT COUNT(*) FROM "+table).Scan(&n)) + return n +} + +func TestFetch_QuarantinesCorruptEvent(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + _, err := q.db.Exec("INSERT INTO audit_queue (payload) VALUES (?)", corruptPayload) + require.NoError(t, err) + for i := int64(0); i < 3; i++ { + require.NoError(t, q.Enqueue(newTestEvent(i))) + } + + require.Equal(t, 1, countRows(t, q, "audit_queue WHERE payload = x'ffffff'"), + "corrupt row should be removed from audit_queue") + + items, err := q.fetch(10) + require.NoError(t, err) + require.Len(t, items, 3, "the three good events should still be returned") + for i, item := range items { + require.Equal(t, int64(i), item.Event.GetIndex()) + } + + require.Zero(t, countRows(t, q, "audit_queue WHERE payload = x'ffffff'"), + "corrupt row should be removed from audit_queue") + + var ( + payload []byte + errMsg string + source string + ) + require.NoError(t, q.db.QueryRow( + "SELECT payload, error, source FROM corrupt_events").Scan(&payload, &errMsg, &source)) + require.Equal(t, corruptPayload, payload) + require.NotEmpty(t, errMsg, "the deserialization error should be recorded") + require.Equal(t, auditQueueTable, source) +} + +func TestFetch_CorruptDoesNotBlockQueue(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + _, err := q.db.Exec("INSERT INTO audit_queue (payload) VALUES (?)", corruptPayload) + require.NoError(t, err) + for i := range int64(3) { + require.NoError(t, q.Enqueue(newTestEvent(i))) + } + + var delivered []int64 + for { + items, err := q.fetch(10) + require.NoError(t, err) + if len(items) == 0 { + break + } + for _, item := range items { + delivered = append(delivered, item.Event.GetIndex()) + } + require.NoError(t, q.ack(items)) + } + + require.Equal(t, []int64{0, 1, 2}, delivered, "all good events should drain") + require.Zero(t, countRows(t, q, auditQueueTable), "queue should be fully drained") + require.Equal(t, 1, countRows(t, q, "corrupt_events")) +} + +func TestFetchDeadLetter_QuarantinesCorrupt(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + _, err := q.db.Exec( + "INSERT INTO audit_dead_letter (payload, failed_at) VALUES (?, ?)", + corruptPayload, time.Now().Unix()) + require.NoError(t, err) + + items, err := q.fetchDeadLetter(10) + require.NoError(t, err) + require.Empty(t, items) + + require.Zero(t, countRows(t, q, auditDeadLetterTable), + "corrupt row should be removed from audit_dead_letter") + + var source string + require.NoError(t, q.db.QueryRow("SELECT source FROM corrupt_events").Scan(&source)) + require.Equal(t, auditDeadLetterTable, source) +} + +func TestExpireCorruptEvents(t *testing.T) { + t.Parallel() + + q, err := newSQLiteQueue(Config{ + Path: filepath.Join(t.TempDir(), queueDir), + DeadLetterTTL: time.Hour, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = q.Close() }) + + old := time.Now().Add(-2 * time.Hour).Unix() + recent := time.Now().Unix() + _, err = q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'boom', 'audit_queue', ?), (?, 'boom', 'audit_queue', ?)", + corruptPayload, old, corruptPayload, recent) + require.NoError(t, err) + + q.expireCorruptEvents() + + require.Equal(t, 1, countRows(t, q, "corrupt_events"), "only the recent corrupt row should remain") +} + +func TestRecoverCorruptEvents(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + // A genuinely corrupt payload that will never deserialize. + _, err := q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'boom', 'audit_queue', ?)", + corruptPayload, time.Now().Unix()) + require.NoError(t, err) + + // A valid payload that now deserializes, as if written by a newer binary + // and quarantined before an upgrade made it readable. + oneOf, err := apievents.ToOneOf(newTestEvent(7)) + require.NoError(t, err) + validPayload, err := oneOf.Marshal() + require.NoError(t, err) + _, err = q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'was unknown event type', 'audit_queue', ?)", + validPayload, time.Now().Unix()) + require.NoError(t, err) + + q.recoverCorruptEvents() + + // The recoverable event is back in audit_queue and deliverable. + items, err := q.fetch(10) + require.NoError(t, err) + require.Len(t, items, 1) + require.Equal(t, int64(7), items[0].Event.GetIndex()) + + // The genuinely corrupt row is retained for a future attempt. + require.Equal(t, 1, countRows(t, q, "corrupt_events")) +} + +func TestRecoverCorruptEvents_PagesLargeBacklog(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + const validCount = dequeueBatchSize*2 + 10 // spans multiple recovery pages + const corruptCount = 5 + + insert := func(payload []byte) { + _, err := q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'x', 'audit_queue', 0)", + payload) + require.NoError(t, err) + } + + for i := 0; i < validCount; i++ { + oneOf, err := apievents.ToOneOf(newTestEvent(int64(i))) + require.NoError(t, err) + payload, err := oneOf.Marshal() + require.NoError(t, err) + insert(payload) + // Interleave corrupt rows so the cursor must page past them. + if i < corruptCount { + insert(corruptPayload) + } + } + + q.recoverCorruptEvents() + + require.Equal(t, validCount, countRows(t, q, "audit_queue"), + "all recoverable events should be re-queued across pages") + require.Equal(t, corruptCount, countRows(t, q, "corrupt_events"), + "only the genuinely corrupt rows should remain") +} + +func TestRecoverCorruptEvents_WatermarkAdvances(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + // An un-recoverable row examined by the first pass. + _, err := q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'boom', 'audit_queue', 0)", + corruptPayload) + require.NoError(t, err) + + q.recoverCorruptEvents() + require.Positive(t, q.recoveryWatermark, "watermark should advance past the examined row") + require.Zero(t, countRows(t, q, "audit_queue")) + require.Equal(t, 1, countRows(t, q, "corrupt_events")) + + // A recoverable row inserted after the first pass gets a higher id, so it + // lands above the watermark and is picked up on the next pass. + oneOf, err := apievents.ToOneOf(newTestEvent(7)) + require.NoError(t, err) + validPayload, err := oneOf.Marshal() + require.NoError(t, err) + _, err = q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'was unknown', 'audit_queue', 0)", + validPayload) + require.NoError(t, err) + + q.recoverCorruptEvents() + + items, err := q.fetch(10) + require.NoError(t, err) + require.Len(t, items, 1) + require.Equal(t, int64(7), items[0].Event.GetIndex()) + require.Equal(t, 1, countRows(t, q, "corrupt_events"), + "the un-recoverable row should remain, skipped by the watermark") +} + +func TestOrphanAdoption_MigratesCorruptEvents(t *testing.T) { + t.Parallel() + parent := t.TempDir() + ctx := t.Context() + + aPath := filepath.Join(parent, "a") + a, err := newSQLiteQueue(Config{Path: aPath}) + require.NoError(t, err) + + failedAt := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).Unix() + _, err = a.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'boom', 'audit_queue', ?)", + corruptPayload, failedAt) + require.NoError(t, err) + require.NoError(t, a.Close()) + + _, err = os.Stat(aPath) + require.NoError(t, err, "expected A's directory to remain after Close due to non-empty corrupt_events") + + b, err := newSQLiteQueue(Config{ + Path: filepath.Join(parent, "b"), + OrphanScanInterval: 50 * time.Millisecond, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = b.Close() }) + + bRunCtx, cancelB := context.WithCancel(ctx) + t.Cleanup(cancelB) + bRunErr := make(chan error, 1) + go func() { + bRunErr <- b.Run(bRunCtx, func(_ context.Context, items []Item) []Item { return items }) + }() + + require.Eventually(t, func() bool { + return countRows(t, b, "corrupt_events") == 1 + }, 5*time.Second, 50*time.Millisecond, "expected B to migrate A's corrupt row") + + var ( + payload []byte + errMsg string + source string + gotFaildAt int64 + ) + require.NoError(t, b.db.QueryRow( + "SELECT payload, error, source, failed_at FROM corrupt_events").Scan(&payload, &errMsg, &source, &gotFaildAt)) + require.Equal(t, corruptPayload, payload) + require.Equal(t, "boom", errMsg) + require.Equal(t, "audit_queue", source) + require.Equal(t, failedAt, gotFaildAt, "failed_at should be preserved across migration") + + require.Eventually(t, func() bool { + _, err := os.Stat(aPath) + return os.IsNotExist(err) + }, 5*time.Second, 50*time.Millisecond, "expected A's directory to be removed after migration") + + cancelB() + require.ErrorIs(t, <-bRunErr, context.Canceled) +} + func TestItemsNotIn(t *testing.T) { t.Parallel() From 07aab410891e37967985a85f7895ceddbacc499f Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 10:40:53 -0600 Subject: [PATCH 032/347] tsh recordings export: support Linux desktop recordings (#69073) --- lib/srv/desktop/rdp/decoder/Cargo.toml | 3 +++ tool/tsh/common/recording_export.go | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/srv/desktop/rdp/decoder/Cargo.toml b/lib/srv/desktop/rdp/decoder/Cargo.toml index dc8e68abb0b8..f25505b7f62a 100644 --- a/lib/srv/desktop/rdp/decoder/Cargo.toml +++ b/lib/srv/desktop/rdp/decoder/Cargo.toml @@ -14,6 +14,9 @@ ironrdp-core.workspace = true ironrdp-graphics.workspace = true ironrdp-pdu.workspace = true ironrdp-session.workspace = true +# Linux desktop sessions are encoded with the QOIZ codec, +# so the decoder must be able to decode them. +ironrdp-session.features = ["qoiz"] [features] image-resize = ["dep:fast_image_resize"] diff --git a/tool/tsh/common/recording_export.go b/tool/tsh/common/recording_export.go index 5e2b07731e1c..2e179a78fcf4 100644 --- a/tool/tsh/common/recording_export.go +++ b/tool/tsh/common/recording_export.go @@ -252,13 +252,17 @@ loop: d.progress.Add(1) switch evt := evt.(type) { - case *apievents.WindowsDesktopSessionStart: + case *apievents.WindowsDesktopSessionStart, *apievents.LinuxDesktopSessionStart: case *apievents.WindowsDesktopSessionEnd: if !evt.StartTime.IsZero() && !evt.EndTime.IsZero() { endMillis = max(endMillis, evt.EndTime.Sub(evt.StartTime).Milliseconds()) } break loop - + case *apievents.LinuxDesktopSessionEnd: + if !evt.StartTime.IsZero() && !evt.EndTime.IsZero() { + endMillis = max(endMillis, evt.EndTime.Sub(evt.StartTime).Milliseconds()) + } + break loop case *apievents.DesktopRecording: screenData, err := decoder.UpdateScreen(evt) if err != nil { From 32354078f0b3460668b8a7692fcb0bea3a1b510c Mon Sep 17 00:00:00 2001 From: Chris Thach Date: Tue, 28 Jul 2026 12:54:16 -0400 Subject: [PATCH 033/347] Add const for TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA. (#68433) * Add const for TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA. Signed-off-by: Chris Thach * Move to constants.go per new convention. Signed-off-by: Chris Thach * Rename to EnvVarUnstableForceInBandMFA. Signed-off-by: Chris Thach --------- Signed-off-by: Chris Thach --- constants.go | 7 +++++++ lib/services/role.go | 4 ++-- lib/srv/authhandlers.go | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/constants.go b/constants.go index cd5468415086..5d2f2a0bf5c6 100644 --- a/constants.go +++ b/constants.go @@ -1082,4 +1082,11 @@ const ( // entity_descriptor_url check preventing following redirects via HTTP originating from // HTTPS route. EnvVarUnstableDisableSAMLRedirectDowngradeCheck = "TELEPORT_UNSTABLE_DISABLE_SAML_REDIRECT_DOWNGRADE_CHECK" + + // EnvVarUnstableForceInBandMFA is the environment variable that, when set to "yes", disables the legacy out-of-band + // MFA flow and forces all connections to use in-band MFA. When unset or empty, both flows are supported during the + // transition period. + // + // TODO(cthach): Remove in v20.0 when the legacy out-of-band MFA flow is removed and in-band MFA is the default. + EnvVarUnstableForceInBandMFA = "TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA" ) diff --git a/lib/services/role.go b/lib/services/role.go index 54f6793672ba..de4fc9518905 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -2990,7 +2990,7 @@ func (set RoleSet) checkAccess( // When TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA is set to "yes", only in-band MFA is allowed and enforced. // // TODO(cthach): Remove in v20.0 when the legacy out-of-band MFA flow is removed. - if state.MFARequired == MFARequiredAlways && (os.Getenv("TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA") == "yes" || !state.MFAVerified) { + if state.MFARequired == MFARequiredAlways && (os.Getenv(teleport.EnvVarUnstableForceInBandMFA) == "yes" || !state.MFAVerified) { // If the caller doesn't want preconditions returned, deny access early to avoid unnecessary work. if !state.ReturnPreconditions { logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, cluster requires per-session MFA") @@ -3072,7 +3072,7 @@ func (set RoleSet) checkAccess( // // TODO(cthach): Remove in v20.0 when the legacy out-of-band MFA flow is removed. bypassMFAChecks := state.MFARequired == MFARequiredNever || - (state.MFAVerified && (os.Getenv("TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA") != "yes" || !state.ReturnPreconditions)) + (state.MFAVerified && (os.Getenv(teleport.EnvVarUnstableForceInBandMFA) != "yes" || !state.ReturnPreconditions)) // TODO(codingllama): Consider making EnableDeviceVerification opt-out instead // of opt-in. diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index d4a97d54963f..eefd00684354 100644 --- a/lib/srv/authhandlers.go +++ b/lib/srv/authhandlers.go @@ -803,7 +803,7 @@ func requiresInBandMFA(id *sshca.Identity, conn ssh.ConnMetadata) (bool, error) } var ( - forceInBandMFA = os.Getenv("TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA") == "yes" + forceInBandMFA = os.Getenv(teleport.EnvVarUnstableForceInBandMFA) == "yes" isLegacyClient = !inBandMFASupported isRegularSSHCert = id.MFAVerified == "" isPerSessionMFACert = !isRegularSSHCert @@ -1282,7 +1282,7 @@ func (a *ahLoginChecker) evaluateSSHAccess(ident *sshca.Identity, ca types.CertA osUser == teleport.SSHSessionJoinPrincipal && moderation.RoleSupportsModeratedSessions(accessChecker.Roles()) && (state.MFARequired == services.MFARequiredNever || - (os.Getenv("TELEPORT_UNSTABLE_FORCE_IN_BAND_MFA") != "yes" && state.MFAVerified)) + (os.Getenv(teleport.EnvVarUnstableForceInBandMFA) != "yes" && state.MFAVerified)) // Collect preconditions that must be met before the session can start. var preconds []*decisionpb.Precondition From 2cd466bd7c8f2380e2caa4c254cfc9bd02809e4b Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 10:59:43 -0600 Subject: [PATCH 034/347] Bound input length for AWS endpoint parsers (#68790) This addresses a few more oss-fuzz timeouts that occur when parsing really large inputs on shared infrastructure. --- api/utils/aws/endpoint.go | 43 +++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/api/utils/aws/endpoint.go b/api/utils/aws/endpoint.go index 1dfb5f1b5605..de08c8cd7780 100644 --- a/api/utils/aws/endpoint.go +++ b/api/utils/aws/endpoint.go @@ -26,6 +26,8 @@ import ( "github.com/gravitational/trace" ) +const maxEndpointLength = 4096 + // IsAWSEndpoint returns true if the input URI is an AWS endpoint under the // amazonaws.com domains. It deliberately excludes api.aws endpoints (see // IsAWSAPIEndpoint) because callers like the DynamoDB/OpenSearch endpoint @@ -143,8 +145,12 @@ func (d RDSEndpointDetails) IsProxy() bool { // ParseRDSEndpoint extracts the identifier and region from the provided RDS // endpoint. -func ParseRDSEndpoint(endpoint string) (d *RDSEndpointDetails, err error) { +func ParseRDSEndpoint(endpoint string) (*RDSEndpointDetails, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } if strings.ContainsRune(endpoint, ':') { + var err error endpoint, _, err = net.SplitHostPort(endpoint) if err != nil { return nil, trace.Wrap(err) @@ -278,6 +284,10 @@ func parseRDSWithoutSuffixes(endpoint string, parts []string, region string) (*R // ParseRedshiftEndpoint extracts cluster ID and region from the provided // Redshift endpoint. func ParseRedshiftEndpoint(endpoint string) (clusterID, region string, err error) { + if len(endpoint) > maxEndpointLength { + return "", "", trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + if strings.ContainsRune(endpoint, ':') { endpoint, _, err = net.SplitHostPort(endpoint) if err != nil { @@ -332,8 +342,13 @@ type RedshiftServerlessEndpointDetails struct { // ParseRedshiftServerlessEndpoint extracts name, AWS Account ID, and region // from the provided Redshift Serverless endpoint. -func ParseRedshiftServerlessEndpoint(endpoint string) (details *RedshiftServerlessEndpointDetails, err error) { +func ParseRedshiftServerlessEndpoint(endpoint string) (*RedshiftServerlessEndpointDetails, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + if strings.ContainsRune(endpoint, ':') { + var err error endpoint, _, err = net.SplitHostPort(endpoint) if err != nil { return nil, trace.Wrap(err) @@ -434,6 +449,10 @@ const ( // // https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/GettingStarted.ConnectToCacheNode.html func ParseElastiCacheEndpoint(endpoint string) (*RedisEndpointInfo, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + endpoint, err := removeSchemaAndPort(endpoint) if err != nil { return nil, trace.Wrap(err) @@ -529,9 +548,9 @@ func ParseElastiCacheEndpoint(endpoint string) (*RedisEndpointInfo, error) { } // Remove "-ro" from reader endpoint. - if strings.HasSuffix(parts[0], "-ro") { + if before, ok := strings.CutSuffix(parts[0], "-ro"); ok { return &RedisEndpointInfo{ - ID: strings.TrimSuffix(parts[0], "-ro"), + ID: before, Region: region, TransitEncryptionEnabled: false, EndpointType: ElastiCacheReaderEndpoint, @@ -595,6 +614,10 @@ func trimElastiCacheShardAndNodeID(input string) string { // ElastiCacheServerless Redis endpoint, which should be in the form // .serverless..cache.amazonaws.com: func ParseElastiCacheServerlessEndpoint(endpoint string) (*RedisEndpointInfo, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + endpoint, err := removeSchemaAndPort(endpoint) if err != nil { return nil, trace.Wrap(err) @@ -648,6 +671,10 @@ func ParseElastiCacheServerlessEndpoint(endpoint string) (*RedisEndpointInfo, er // // https://docs.aws.amazon.com/memorydb/latest/devguide/endpoints.html func ParseMemoryDBEndpoint(endpoint string) (*RedisEndpointInfo, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + endpoint, err := removeSchemaAndPort(endpoint) if err != nil { return nil, trace.Wrap(err) @@ -828,6 +855,10 @@ type DynamoDBEndpointInfo struct { // ParseDynamoDBEndpoint parses and extract info from the provided DynamoDB endpoint. func ParseDynamoDBEndpoint(endpoint string) (*DynamoDBEndpointInfo, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + endpoint = strings.ToLower(endpoint) parts, partition, err := extractAWSEndpointParts(endpoint) if err != nil { @@ -878,6 +909,10 @@ type OpenSearchEndpointInfo struct { // ParseOpensearchEndpoint parses and extract info from the provided OpenSearch endpoint. func ParseOpensearchEndpoint(endpoint string) (*OpenSearchEndpointInfo, error) { + if len(endpoint) > maxEndpointLength { + return nil, trace.BadParameter("invalid endpoint exceeds maximum length of %d", maxEndpointLength) + } + endpoint = strings.ToLower(endpoint) parts, partition, err := extractAWSEndpointParts(endpoint) if err != nil { From 27f64e0cb047ff1252ed54b247d0008dbbf3b6c7 Mon Sep 17 00:00:00 2001 From: williamong-tel Date: Tue, 28 Jul 2026 10:21:31 -0700 Subject: [PATCH 035/347] Ensure IAC and tctl cannot create scoped apps (#69076) Update Makefile --- integrations/operator/Makefile | 3 ++- lib/auth/auth.go | 9 +++++++++ lib/auth/auth_with_roles.go | 11 +++++++++++ lib/auth/auth_with_roles_test.go | 15 +++++++++++++++ lib/services/app.go | 12 ++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/integrations/operator/Makefile b/integrations/operator/Makefile index b8f360160aa4..ad74cdd8352f 100644 --- a/integrations/operator/Makefile +++ b/integrations/operator/Makefile @@ -264,7 +264,7 @@ endif kubectl create namespace test --dry-run=client -o yaml | kubectl apply -f - $(if $(ENTERPRISE), kubectl -n test create secret generic license --from-file=/var/lib/teleport/license.pem --dry-run=client -o yaml | kubectl replace --force -f -) - + # deploy a teleport cluster with the operator # To add feature flags, append --set "extraEnv[N].name=..." lines below. KUBECONFIG=$(KUBECONFIG) helm upgrade \ @@ -281,4 +281,5 @@ endif $(if $(ENTERPRISE),--set "enterprise=true") \ $(if $(and $(ENTERPRISE),$(LOCAL_BUILD)),--set "enterpriseImage=$(TELEPORT_IMAGE)") + rm $$KUBECONFIG diff --git a/lib/auth/auth.go b/lib/auth/auth.go index c31df854d26a..63d2b8f8fb71 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -7636,6 +7636,9 @@ func (a *Server) IterateResources(ctx context.Context, req proto.ListResourcesRe // CreateApp creates a new application resource. func (a *Server) CreateApp(ctx context.Context, app types.Application) error { + if err := services.EnsureNotScopedApp(app); err != nil { + return trace.Wrap(err) + } if err := services.ValidateApp(app, a); err != nil { return trace.Wrap(err) } @@ -7665,6 +7668,12 @@ func (a *Server) CreateApp(ctx context.Context, app types.Application) error { // UpdateApp updates an existing application resource. func (a *Server) UpdateApp(ctx context.Context, app types.Application) error { + // TODO (williamo/scopes): While a blanket deny is fine for update for now, + // when scoped dynamic app registration is supported, we must check whether + // the updated scope differs or not, and reject if different. + if err := services.EnsureNotScopedApp(app); err != nil { + return trace.Wrap(err) + } if err := services.ValidateApp(app, a); err != nil { return trace.Wrap(err) } diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index c5b2c13915b3..efe9c87e61cf 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -7401,6 +7401,10 @@ func (a *ServerWithRoles) CreateApp(ctx context.Context, app types.Application) if err := a.authorizeAction(types.KindApp, types.VerbCreate); err != nil { return trace.Wrap(err) } + // Reject scoped apps before the rest of validation + if err := services.EnsureNotScopedApp(app); err != nil { + return trace.Wrap(err) + } // Validate before the label-matching access check so callers with // the correct role see input errors rather than access-denied from // a label mismatch on a malformed app. @@ -7427,6 +7431,13 @@ func (a *ServerWithRoles) UpdateApp(ctx context.Context, app types.Application) if err := a.authorizeAction(types.KindApp, types.VerbUpdate); err != nil { return trace.Wrap(err) } + + // TODO (williamo/scopes): While a blanket deny is fine for update for now, + // when scoped dynamic app registration is supported, we must check whether + // the updated scope differs or not, and reject if different. + if err := services.EnsureNotScopedApp(app); err != nil { + return trace.Wrap(err) + } // Validate before the GetApp existence check so a malformed name // returns the input error rather than "doesn't exist". if err := services.ValidateApp(app, a.authServer); err != nil { diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index e00ba8844c38..d044e7674848 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -4415,6 +4415,21 @@ func TestApps(t *testing.T) { cmpopts.IgnoreFields(types.Metadata{}, "Revision"), )) + // Clients can't create/update scoped apps right now. + // TODO (williamo/scopes) - delete this check when dynamic scoped app registration is supported. + scopedApp := adminApp.Copy() + scopedApp.SetName("scoped") + scopedApp.Scope = "/prod" + err = adminClt.CreateApp(ctx, scopedApp) + require.True(t, trace.IsBadParameter(err), "expected bad parameter, got %v", err) + require.ErrorContains(t, err, "dynamic registration of scoped applications is not supported") + + scopedAdminApp := adminApp.Copy() + scopedAdminApp.Scope = "/prod" + err = adminClt.UpdateApp(ctx, scopedAdminApp) + require.True(t, trace.IsBadParameter(err), "expected bad parameter, got %v", err) + require.ErrorContains(t, err, "dynamic registration of scoped applications is not supported") + // Clients can't create/update apps for the beams service. adminApp.GetMetadata().Labels["teleport.internal/beams/app-type"] = "llm" err = adminClt.UpdateApp(ctx, adminApp) diff --git a/lib/services/app.go b/lib/services/app.go index af0a6625200e..7d5df9834656 100644 --- a/lib/services/app.go +++ b/lib/services/app.go @@ -100,6 +100,18 @@ type ApplicationsInternal interface { ) ([]backend.ConditionalAction, error) } +// TODO(williamo/scopes): remove once dynamic scoped app registration is +// supported. +func EnsureNotScopedApp(app types.Application) error { + if app == nil { + return trace.BadParameter("nil application") + } + if scope := app.GetScope(); scope != "" { + return trace.BadParameter("application %q cannot be created with scope %q: dynamic registration of scoped applications is not supported, remove the scope attribute", app.GetName(), scope) + } + return nil +} + // ValidateApp checks an Application's name, public_addr, and // required_apps. func ValidateApp(app types.Application, proxyGetter ProxyGetter) error { From d6ed11122335c094e732a52f69287af70d559434 Mon Sep 17 00:00:00 2001 From: Gavin Frazar Date: Tue, 28 Jul 2026 11:04:16 -0700 Subject: [PATCH 036/347] Add Terraform ECS module example (#68915) * Add an example of AWS ECS Terraform module usage * trim proxy addr scheme --- .../aws/aws_ecs_task_definition.tf | 7 +- .../aws/examples/db-service/README.md | 47 +++++++++++ .../aws/examples/db-service/data.tf | 1 + .../aws/examples/db-service/docs_description | 1 + .../aws/examples/db-service/docs_title | 1 + .../aws/examples/db-service/main.tf | 84 +++++++++++++++++++ .../aws/examples/db-service/outputs.tf | 3 + .../aws/examples/db-service/providers.tf | 14 ++++ .../aws/examples/db-service/variables.tf | 4 + .../aws/examples/db-service/versions.tf | 18 ++++ 10 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/README.md create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/data.tf create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_description create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_title create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/main.tf create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/outputs.tf create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/providers.tf create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/variables.tf create mode 100644 integrations/terraform-modules/teleport/container-service/aws/examples/db-service/versions.tf diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf index a65a397f3beb..a7d8b340bd41 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf @@ -3,7 +3,12 @@ ################################################################################ locals { - managed_updates_proxy_addr = try(var.teleport_config.teleport.proxy_server, "") + managed_updates_proxy_addr = try( + replace( + trimspace(var.teleport_config.teleport.proxy_server), + "/^[^:]*:[/]{2}/", # trim any URL scheme + ""), + "") managed_updates_version = ( length(data.http.managed_updates) == 1 ? jsondecode(data.http.managed_updates[0].response_body).auto_update.agent_version diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/README.md b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/README.md new file mode 100644 index 000000000000..d01ce4255d57 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/README.md @@ -0,0 +1,47 @@ +## Deploy Teleport Database Service to ECS + +This example deploys the Teleport Database Service to AWS ECS and joins it to an example Teleport cluster using an IAM join token. + + +## Requirements + +| Name | Version | +| ---- | ------- | +| terraform | >= 1.5.7 | +| aws | ~> 6.0 | +| http | ~> 3.0 | +| teleport | ~> 18.5 | + +## Providers + +| Name | Version | +| ---- | ------- | +| aws | ~> 6.0 | +| teleport | ~> 18.5 | + +## Modules + +| Name | Source | Version | +| ---- | ------ | ------- | +| teleport\_database\_service | ../.. | n/a | +| vpc | terraform-aws-modules/vpc/aws | 6.6.0 | + +## Resources + +| Name | Type | +| ---- | ---- | +| teleport_provision_token.iam | resource | +| [aws_availability_zones.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| teleport\_proxy\_addr | The address of the Teleport proxy service in host:port form. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| teleport\_database\_service | n/a | + \ No newline at end of file diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/data.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/data.tf new file mode 100644 index 000000000000..098ab9b9e491 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/data.tf @@ -0,0 +1 @@ +data "aws_availability_zones" "this" {} diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_description b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_description new file mode 100644 index 000000000000..28f8bb5a04c4 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_description @@ -0,0 +1 @@ +Deploy Teleport Database Service to ECS diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_title b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_title new file mode 100644 index 000000000000..ccfa1c0ad63e --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/docs_title @@ -0,0 +1 @@ +Teleport Database Service Example diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/main.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/main.tf new file mode 100644 index 000000000000..2ce31f2a43c7 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/main.tf @@ -0,0 +1,84 @@ +locals { + namespace = "example" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "6.6.0" + + azs = slice(data.aws_availability_zones.this.names, 0, 3) + cidr = "10.0.0.0/16" + name = "${local.namespace}-vpc" + + public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] +} + +module "teleport_database_service" { + source = "../.." + + # Enable managed updates + managed_updates_enabled = true + managed_updates_group = "default" + + apply_aws_tags = { "example" = "true" } + assign_public_ip = true # must be true when using public subnets + ecs_cluster_name = "${local.namespace}-cluster" + ecs_service_name = "${local.namespace}-svc" + ecs_service_subnets = module.vpc.public_subnets + ecs_task_desired_count = 1 + ecs_task_name = "${local.namespace}-task" + environment_vars = { EXAMPLE_VAR = "EXAMPLE_VALUE" } + vpc_id = module.vpc.vpc_id + + teleport_config = { + version = "v3" + teleport = { + join_params = { + token_name = "${local.namespace}-iam" + method = "iam" + } + proxy_server = var.teleport_proxy_addr + log = { + severity = "DEBUG" + } + } + auth_service = { + enabled = "no" + } + proxy_service = { + enabled = "no" + } + ssh_service = { + enabled = "no" + } + discovery_service = { + enabled = "no" + } + db_service = { + enabled = "yes" + resources = [ + { + labels = { + "env" = "example" + } + } + ] + } + } +} + +resource "teleport_provision_token" "iam" { + metadata = { + name = "${local.namespace}-iam" + description = "Allow the Teleport ECS agent to join the cluster using AWS IAM credentials." + } + spec = { + allow = [{ + aws_arn = module.teleport_database_service.teleport_provision_token_allow_aws_arn + }] + join_method = "iam" + roles = ["Db"] + } + version = "v2" +} diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/outputs.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/outputs.tf new file mode 100644 index 000000000000..8b8cf75ee3b0 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/outputs.tf @@ -0,0 +1,3 @@ +output "teleport_database_service" { + value = module.teleport_database_service +} diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/providers.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/providers.tf new file mode 100644 index 000000000000..44d116f3e8f7 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/providers.tf @@ -0,0 +1,14 @@ +provider "aws" { + region = "us-east-1" + + default_tags { + tags = { + env = "example" + } + } +} + +provider "teleport" { + addr = var.teleport_proxy_addr + profile_name = replace(var.teleport_proxy_addr, "/:[0-9]+.*/", "") +} diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/variables.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/variables.tf new file mode 100644 index 000000000000..aaae7f1e3049 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/variables.tf @@ -0,0 +1,4 @@ +variable "teleport_proxy_addr" { + description = "The address of the Teleport proxy service in host:port form." + type = string +} diff --git a/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/versions.tf b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/versions.tf new file mode 100644 index 000000000000..fd544bd1c2df --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/examples/db-service/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + http = { + source = "hashicorp/http" + version = "~> 3.0" + } + teleport = { + source = "terraform.releases.teleport.dev/gravitational/teleport" + version = "~> 18.5" + } + } +} From 90f4d2d85a5c196a64e737109f138eb1489b76c7 Mon Sep 17 00:00:00 2001 From: Alan Parra <12500300+codingllama@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:04:53 -0300 Subject: [PATCH 037/347] Update CA override docs with async-CSR collection (#69071) --- .../management/security/ca-overrides.mdx | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/docs/pages/zero-trust-access/management/security/ca-overrides.mdx b/docs/pages/zero-trust-access/management/security/ca-overrides.mdx index e725cce1c133..a9480401fc38 100644 --- a/docs/pages/zero-trust-access/management/security/ca-overrides.mdx +++ b/docs/pages/zero-trust-access/management/security/ca-overrides.mdx @@ -88,33 +88,12 @@ You may customize the certificate subject: $ tctl auth create-override-csr --type=windows --subject='OU=My Organization Unit,CN=My Windows CA' ``` -### Multi-Auth Service clusters with per-instance HSM keys - + In clusters with multiple Auth Service instances and a separate HSM key per -instance, run `tctl` locally against each Auth Service instance. Each Auth -Service instance generates CSRs for the keys it can access. - -For example: - -```code -# Access the first Auth instance. -# Modify the command below as appropriate. -$ tsh ssh user@Auth1 - -# Generate the CSRs. -# Modify as desired. -$ tctl auth create-override-csr --type=windows - -# Second Auth instance. -$ tsh ssh user@Auth2 -$ tctl auth create-override-csr --type=windows - -# Repeat for each remaining Auth Service instance. -``` - -When writing the override resource, make sure the final -`spec.certificate_overrides` contains one entry for each certificate issued from -those CSRs. See Step 4. +instance, make sure all Auth Service instances are online and healthy before +issuing the `create-override-csr` command. Teleport will automatically collect +CSRs from all online Auth Service instances. + ## Step 2/5. Issue override certificates using your external CA From bf162d6c83ce3c875b5dde1d84e56cb422e5980b Mon Sep 17 00:00:00 2001 From: Kyle Kloberdanz Date: Tue, 28 Jul 2026 14:09:14 -0500 Subject: [PATCH 038/347] On Auth: don't enqueue events that originate from agents (#67696) This implements a part of the cloud considerations that require the Auth server does not Enqueue events that Agents originate, but it must Enqueue events that the Auth server originates. --- lib/auth/audit_fallback_grpc_test.go | 159 ++++++++++++++++++++ lib/auth/grpcserver.go | 4 + lib/events/emitter.go | 18 +-- lib/events/emitter_test.go | 8 +- lib/events/fallback_emitter.go | 189 +++++++++++++++++++++++ lib/events/fallback_emitter_test.go | 216 +++++++++++++++++++++++++++ lib/service/service.go | 73 ++++++--- 7 files changed, 634 insertions(+), 33 deletions(-) create mode 100644 lib/auth/audit_fallback_grpc_test.go create mode 100644 lib/events/fallback_emitter.go create mode 100644 lib/events/fallback_emitter_test.go diff --git a/lib/auth/audit_fallback_grpc_test.go b/lib/auth/audit_fallback_grpc_test.go new file mode 100644 index 000000000000..8f5cd3596d53 --- /dev/null +++ b/lib/auth/audit_fallback_grpc_test.go @@ -0,0 +1,159 @@ +/* + * Teleport + * Copyright (C) 2026 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 auth_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/auth/authtest" + "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/events/auditqueue" +) + +// toggleBackend is a controllable audit backend that records delivered events +// and can be switched to fail, standing in for an SQS outage. +type toggleBackend struct { + mu sync.Mutex + fail bool // Simulate backend failures. + got []apievents.AuditEvent // All events that we received. +} + +func (b *toggleBackend) setFail(fail bool) { + b.mu.Lock() + defer b.mu.Unlock() + b.fail = fail +} + +func (b *toggleBackend) count() int { + b.mu.Lock() + defer b.mu.Unlock() + return len(b.got) +} + +func (b *toggleBackend) EmitAuditEvent(_ context.Context, event apievents.AuditEvent) error { + b.mu.Lock() + defer b.mu.Unlock() + if b.fail { + return trace.ConnectionProblem(nil, "backend unavailable") + } + b.got = append(b.got, event) + return nil +} + +func newAuthFallback(t *testing.T, backend *toggleBackend) *events.FallbackEmitter { + t.Helper() + fe, err := events.NewFallbackEmitter(events.FallbackEmitterConfig{ + Primary: backend, + DataDir: t.TempDir(), + EnableAuditQueue: true, + AuditQueueCfg: auditqueue.Config{ + MaxAttempts: 3, + DeadLetterSweepInterval: 50 * time.Millisecond, + }, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, fe.Close()) }) + return fe +} + +func userLoginEvent() apievents.AuditEvent { + return &apievents.UserLogin{ + Metadata: apievents.Metadata{ + Type: events.UserLoginEvent, + Code: events.UserLocalLoginCode, + }, + Status: apievents.Status{Success: true}, + } +} + +func TestEmitAuditEvent_ForwardedNotAdopted_GRPC(t *testing.T) { + ctx := context.Background() + backend := &toggleBackend{} + backend.setFail(true) + + srv := newTestTLSServer(t) + srv.Auth().SetEmitter(newAuthFallback(t, backend)) + + client, err := srv.NewClient(authtest.TestServerID(types.RoleNode, "test-node")) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, client.Close()) }) + + require.Error(t, client.EmitAuditEvent(ctx, userLoginEvent()), + "forwarded event delivery error must propagate to the client") + + require.NoError(t, srv.Auth().EmitAuditEvent(ctx, userLoginEvent()), + "auth server must queue its own event on backend failure") + + backend.setFail(false) + require.Eventually(t, func() bool { + return backend.count() >= 1 + }, 5*time.Second, 50*time.Millisecond, + "auth server should re-deliver its own queued event after recovery") +} + +func TestEmitAuditEvent_AgentRetriesEndToEnd_GRPC(t *testing.T) { + ctx := context.Background() + backend := &toggleBackend{} + backend.setFail(true) + + srv := newTestTLSServer(t) + srv.Auth().SetEmitter(newAuthFallback(t, backend)) + + client, err := srv.NewClient(authtest.TestServerID(types.RoleNode, "test-node")) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, client.Close()) }) + + agent, err := events.NewCheckingAsyncEmitter( + events.CheckingEmitterConfig{}, + events.AsyncEmitterConfig{ + Inner: client, + DataDir: t.TempDir(), + EnableAuditQueue: true, + AuditQueueCfg: auditqueue.Config{ + MaxAttempts: 3, + DeadLetterSweepInterval: 50 * time.Millisecond, + }, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }, + ) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, agent.Close()) }) + + require.NoError(t, agent.EmitAuditEvent(ctx, userLoginEvent())) + + require.Never(t, func() bool { + return backend.count() > 0 + }, 500*time.Millisecond, 50*time.Millisecond, + "event must not be delivered while the backend is down") + + backend.setFail(false) + require.Eventually(t, func() bool { + return backend.count() >= 1 + }, 5*time.Second, 50*time.Millisecond, + "agent should re-deliver the forwarded event through gRPC after recovery") +} diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 3a7235ca38b7..1b739cd72831 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -295,6 +295,10 @@ func (g *GRPCServer) EmitAuditEvent(ctx context.Context, req *apievents.OneOf) ( if err != nil { return nil, trace.Wrap(err) } + // Mark this event as forwarded from a remote instance so the auth server's + // fallback queue does not take ownership of it on a delivery failure. The + // originating instance retries from its own queue. + ctx = events.WithForwardedEmit(ctx) err = auth.EmitAuditEvent(ctx, event) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/events/emitter.go b/lib/events/emitter.go index 90bcf6570db5..7e77d20077f7 100644 --- a/lib/events/emitter.go +++ b/lib/events/emitter.go @@ -56,9 +56,9 @@ type AsyncEmitterConfig struct { // DataDir is the Teleport data directory. This is required for sqlite // backed queues. DataDir string - // EnableSQLiteQueue enables the SQLite-backed audit queue. When false, + // EnableAuditQueue enables the audit queue subsystem. When false, // the legacy in-memory channel is used. - EnableSQLiteQueue bool + EnableAuditQueue bool // AuditQueueCfg holds the options from the Teleport yaml config. AuditQueueCfg auditqueue.Config // AuditQueueBackends is the ordered list of backends to try on startup. @@ -85,17 +85,17 @@ func NewAsyncEmitter(cfg AsyncEmitterConfig) (*AsyncEmitter, error) { } var queue auditqueue.Queue - if cfg.EnableSQLiteQueue { + if cfg.EnableAuditQueue { var err error - queue, err = makeQueue(cfg) + queue, err = makeQueue(cfg.DataDir, cfg.AuditQueueCfg, cfg.AuditQueueBackends) if err != nil { return nil, trace.Wrap(err) } } if queue == nil { - slog.InfoContext(context.TODO(), "Using default in-memory audit event channel. SQLite-backed audit queue is disabled.") + slog.InfoContext(context.TODO(), "Using default in-memory audit event channel. Audit queue is disabled.") } else { - slog.InfoContext(context.TODO(), "SQLite-backed audit queue is enabled.") + slog.InfoContext(context.TODO(), "Audit queue is enabled.") } ctx, cancel := context.WithCancel(context.Background()) @@ -119,11 +119,9 @@ func NewAsyncEmitter(cfg AsyncEmitterConfig) (*AsyncEmitter, error) { return a, nil } -func makeQueue(cfg AsyncEmitterConfig) (auditqueue.Queue, error) { - queueCfg := cfg.AuditQueueCfg - queueCfg.Path = filepath.Join(cfg.DataDir, auditQueueDir, uuid.NewString()) +func makeQueue(dataDir string, queueCfg auditqueue.Config, backends []auditqueue.Kind) (auditqueue.Queue, error) { + queueCfg.Path = filepath.Join(dataDir, auditQueueDir, uuid.NewString()) - backends := cfg.AuditQueueBackends if len(backends) == 0 { backends = []auditqueue.Kind{auditqueue.KindSQLiteDisk} } diff --git a/lib/events/emitter_test.go b/lib/events/emitter_test.go index 203ea7739f4f..89cc44bdf301 100644 --- a/lib/events/emitter_test.go +++ b/lib/events/emitter_test.go @@ -319,7 +319,7 @@ func TestAsyncEmitterAuditQueueBackends(t *testing.T) { t.Run("in-memory backend", func(t *testing.T) { emitter, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ Inner: eventstest.NewChannelEmitter(10), - EnableSQLiteQueue: true, + EnableAuditQueue: true, AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, }) require.NoError(t, err) @@ -336,7 +336,7 @@ func TestAsyncEmitterAuditQueueBackends(t *testing.T) { t.Run("fallback to in-memory", func(t *testing.T) { emitter, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ Inner: eventstest.NewChannelEmitter(10), - EnableSQLiteQueue: true, + EnableAuditQueue: true, AuditQueueBackends: []auditqueue.Kind{"invalid_backend", auditqueue.KindSQLiteMemory}, }) require.NoError(t, err) @@ -346,7 +346,7 @@ func TestAsyncEmitterAuditQueueBackends(t *testing.T) { t.Run("all backends fail", func(t *testing.T) { _, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ Inner: eventstest.NewChannelEmitter(10), - EnableSQLiteQueue: true, + EnableAuditQueue: true, AuditQueueBackends: []auditqueue.Kind{"invalid_backend"}, }) require.Error(t, err) @@ -356,7 +356,7 @@ func TestAsyncEmitterAuditQueueBackends(t *testing.T) { inner := eventstest.NewChannelEmitter(1) emitter, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ Inner: inner, - EnableSQLiteQueue: true, + EnableAuditQueue: true, AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, }) require.NoError(t, err) diff --git a/lib/events/fallback_emitter.go b/lib/events/fallback_emitter.go new file mode 100644 index 000000000000..c77f1f93292a --- /dev/null +++ b/lib/events/fallback_emitter.go @@ -0,0 +1,189 @@ +/* + * Teleport + * Copyright (C) 2026 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 events + +import ( + "context" + "log/slog" + "sync" + + "github.com/gravitational/trace" + "github.com/prometheus/client_golang/prometheus" + + "github.com/gravitational/teleport" + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/events/auditqueue" + "github.com/gravitational/teleport/lib/observability/metrics" +) + +// auditFallbackQueuedEvents counts locally-originated audit events diverted to +// the fallback queue after the primary backend rejected them. +var auditFallbackQueuedEvents = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_fallback_queued_events_total", + Help: "Total number of locally-originated audit events diverted to the fallback queue after the primary audit backend rejected them.", + }, +) + +type forwardedEmitKey struct{} + +// WithForwardedEmit marks ctx as carrying an audit event forwarded from an +// agent. A FallbackEmitter does not queue such events when delivery fails. The +// originating instance owns their durability and will retry from its own queue, +// so the delivery error is returned to it instead. +func WithForwardedEmit(ctx context.Context) context.Context { + return context.WithValue(ctx, forwardedEmitKey{}, struct{}{}) +} + +// isForwardedEmit reports whether ctx was marked by WithForwardedEmit. +func isForwardedEmit(ctx context.Context) bool { + return ctx.Value(forwardedEmitKey{}) != nil +} + +// FallbackEmitterConfig configures a FallbackEmitter. +type FallbackEmitterConfig struct { + // Primary is the audit backend that events are emitted to. + Primary apievents.Emitter + // DataDir is the Teleport data directory, used for the fallback queue. + DataDir string + // EnableAuditQueue enables the fallback queue. When false, the + // FallbackEmitter simply delegates to Primary with no fallback behavior. + EnableAuditQueue bool + // AuditQueueCfg holds the queue options from the Teleport yaml config. + AuditQueueCfg auditqueue.Config + // AuditQueueBackends is the ordered list of queue backends to try. + AuditQueueBackends []auditqueue.Kind +} + +// CheckAndSetDefaults checks and sets default values. +func (c *FallbackEmitterConfig) CheckAndSetDefaults() error { + if c.Primary == nil { + return trace.BadParameter("missing parameter Primary") + } + return nil +} + +// FallbackEmitter emits audit events directly to a primary backend. When the +// primary backend fails to accept a locally-originated event, the event is +// enqueued to the Audit Queue so it can be retried later. This behavior was +// requested during drafting the RFD found below: +// +// See: https://github.com/gravitational/teleport.e/blob/rfd/0254-sqlite-audit-log-event-queue/rfd/0254-sqlite-audit-log-event-queue.md#auth-server--kubernetes-and-cloud-considerations +type FallbackEmitter struct { + cfg FallbackEmitterConfig + queue auditqueue.Queue + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup +} + +// NewFallbackEmitter returns a FallbackEmitter wrapping cfg.Primary. +func NewFallbackEmitter(cfg FallbackEmitterConfig) (*FallbackEmitter, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + if err := metrics.RegisterPrometheusCollectors(auditFallbackQueuedEvents); err != nil { + return nil, trace.Wrap(err) + } + + var queue auditqueue.Queue + if cfg.EnableAuditQueue { + var err error + queue, err = makeQueue(cfg.DataDir, cfg.AuditQueueCfg, cfg.AuditQueueBackends) + if err != nil { + return nil, trace.Wrap(err) + } + } + + ctx, cancel := context.WithCancel(context.Background()) + f := &FallbackEmitter{ + cfg: cfg, + queue: queue, + ctx: ctx, + cancel: cancel, + } + if queue != nil { + slog.InfoContext(ctx, "Audit fallback queue is enabled.") + f.wg.Go(func() { + if err := queue.Run(ctx, f.deliver); err != nil && ctx.Err() == nil { + slog.ErrorContext(ctx, "Audit fallback queue Run returned error.", "error", err) + } + }) + } + return f, nil +} + +// EmitAuditEvent emits the event to the primary backend. On failure, a +// locally-originated event is enqueued to the fallback queue and a nil error is +// returned. +func (f *FallbackEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error { + err := f.cfg.Primary.EmitAuditEvent(ctx, event) + if err == nil { + return nil + } + if f.queue == nil || isForwardedEmit(ctx) { + return trace.Wrap(err) + } + + slog.WarnContext(ctx, + "Failed to emit audit event to the audit backend, falling back to the local queue.", + "event_type", event.GetType(), + "event_code", event.GetCode(), + "error", err, + ) + if qerr := f.queue.Enqueue(event); qerr != nil { + slog.ErrorContext(ctx, + "Failed to enqueue audit event to the local fallback queue.", + "event_type", event.GetType(), + "event_code", event.GetCode(), + "error", qerr, + ) + return trace.Wrap(qerr) + } + auditFallbackQueuedEvents.Inc() + return nil +} + +// deliver is the queue.Run handler. It emits queued events to the primary +// backend and returns the subset that were successfully delivered. +func (f *FallbackEmitter) deliver(ctx context.Context, items []auditqueue.Item) []auditqueue.Item { + var delivered []auditqueue.Item + for _, item := range items { + if ctx.Err() != nil { + return delivered + } + if err := f.cfg.Primary.EmitAuditEvent(ctx, item.Event); err != nil { + slog.ErrorContext(ctx, "Failed to re-emit queued audit event.", "error", err) + continue + } + delivered = append(delivered, item) + } + return delivered +} + +// Close shuts down the background consumer and releases the queue. +func (f *FallbackEmitter) Close() error { + f.cancel() + f.wg.Wait() + if f.queue != nil { + return trace.Wrap(f.queue.Close()) + } + return nil +} diff --git a/lib/events/fallback_emitter_test.go b/lib/events/fallback_emitter_test.go new file mode 100644 index 000000000000..125c20c4db2d --- /dev/null +++ b/lib/events/fallback_emitter_test.go @@ -0,0 +1,216 @@ +/* + * Teleport + * Copyright (C) 2026 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 events_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/events/auditqueue" +) + +// fakePrimary mocks an audit backend and can be toggled to simulate failures. +type fakePrimary struct { + mu sync.Mutex + fail bool + got []apievents.AuditEvent +} + +func (f *fakePrimary) setFail(fail bool) { + f.mu.Lock() + defer f.mu.Unlock() + f.fail = fail +} + +func (f *fakePrimary) count() int { + f.mu.Lock() + defer f.mu.Unlock() + return len(f.got) +} + +func (f *fakePrimary) EmitAuditEvent(_ context.Context, event apievents.AuditEvent) error { + f.mu.Lock() + defer f.mu.Unlock() + if f.fail { + return trace.ConnectionProblem(nil, "backend unavailable") + } + f.got = append(f.got, event) + return nil +} + +func newFallbackTestEmitter(t *testing.T, primary apievents.Emitter, enableQueue bool) *events.FallbackEmitter { + t.Helper() + e, err := events.NewFallbackEmitter(events.FallbackEmitterConfig{ + Primary: primary, + DataDir: t.TempDir(), + EnableAuditQueue: enableQueue, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, e.Close()) }) + return e +} + +func testEvent() apievents.AuditEvent { + return &apievents.UserLogin{ + Metadata: apievents.Metadata{ + Type: events.UserLoginEvent, + Code: events.UserLocalLoginCode, + }, + } +} + +func TestFallbackEmitter_PrimarySucceeds(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + e := newFallbackTestEmitter(t, primary, true) + + require.NoError(t, e.EmitAuditEvent(t.Context(), testEvent())) + require.Equal(t, 1, primary.count()) +} + +func TestFallbackEmitter_QueuesAndRecovers(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setFail(true) + e := newFallbackTestEmitter(t, primary, true) + + // Backend is down. The event is queued, not lost, and emit reports success. + require.NoError(t, e.EmitAuditEvent(t.Context(), testEvent())) + require.Equal(t, 0, primary.count()) + + // Backend recovers. The background consumer re-delivers the queued event. + primary.setFail(false) + require.Eventually(t, func() bool { + return primary.count() == 1 + }, 5*time.Second, 50*time.Millisecond, "queued event should be re-delivered after recovery") +} + +func TestFallbackEmitter_ForwardedNotQueued(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setFail(true) + e := newFallbackTestEmitter(t, primary, true) + + ctx := events.WithForwardedEmit(t.Context()) + require.Error(t, e.EmitAuditEvent(ctx, testEvent()), "forwarded event delivery error must propagate") + + // Even after the backend recovers, the forwarded event must never appear, + // because it was never queued. + primary.setFail(false) + require.Never(t, func() bool { + return primary.count() > 0 + }, time.Second, 50*time.Millisecond, "forwarded event must not be queued/re-delivered") +} + +func TestFallbackEmitter_QueueDisabled(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setFail(true) + e := newFallbackTestEmitter(t, primary, false) + + require.Error(t, e.EmitAuditEvent(t.Context(), testEvent())) + + primary.setFail(false) + require.NoError(t, e.EmitAuditEvent(t.Context(), testEvent())) + require.Equal(t, 1, primary.count()) +} + +type authBoundary struct { + fallback apievents.Emitter +} + +func (a authBoundary) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error { + return a.fallback.EmitAuditEvent(events.WithForwardedEmit(ctx), event) +} + +func newQueuedAsyncEmitter(t *testing.T, inner apievents.Emitter) *events.AsyncEmitter { + t.Helper() + a, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ + Inner: inner, + DataDir: t.TempDir(), + EnableAuditQueue: true, + // A short dead-letter sweep keeps recovery fast. If an event exhausts + // its retries during the outage, the sweeper re-delivers it promptly + // once the backend is healthy again. + AuditQueueCfg: auditqueue.Config{ + MaxAttempts: 3, + DeadLetterSweepInterval: 50 * time.Millisecond, + }, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + return a +} + +func TestForwardedEvent_RetriedByAgent(t *testing.T) { + t.Parallel() + backend := &fakePrimary{} + backend.setFail(true) + + authFallback := newFallbackTestEmitter(t, backend, true) + agent := newQueuedAsyncEmitter(t, authBoundary{fallback: authFallback}) + t.Cleanup(func() { require.NoError(t, agent.Close()) }) + + require.NoError(t, agent.EmitAuditEvent(t.Context(), testEvent())) + + // Backend is down. The event is not delivered, but it is not lost. The + // agent retries from its own queue. + require.Never(t, func() bool { + return backend.count() > 0 + }, 500*time.Millisecond, 50*time.Millisecond, "event must not be delivered while the backend is down") + + // Backend recovers. The agent's retry loop delivers the event end-to-end. + backend.setFail(false) + require.Eventually(t, func() bool { + return backend.count() >= 1 + }, 5*time.Second, 50*time.Millisecond, "agent should re-deliver the forwarded event after recovery") +} + +func TestForwardedEvent_NotAdoptedByAuth(t *testing.T) { + t.Parallel() + backend := &fakePrimary{} + backend.setFail(true) + + authFallback := newFallbackTestEmitter(t, backend, true) + agent := newQueuedAsyncEmitter(t, authBoundary{fallback: authFallback}) + + require.NoError(t, agent.EmitAuditEvent(t.Context(), testEvent())) + require.Never(t, func() bool { + return backend.count() > 0 + }, 500*time.Millisecond, 50*time.Millisecond, "event must not be delivered while the backend is down") + + // Stop the only queue that owns the forwarded event while the backend is + // still down. + require.NoError(t, agent.Close()) + + // The auth server never queued the forwarded event, so recovery delivers + // nothing. It was the agent's responsibility and its queue is gone. + backend.setFail(false) + require.Never(t, func() bool { + return backend.count() > 0 + }, time.Second, 50*time.Millisecond, "auth server must not have adopted the forwarded event") +} diff --git a/lib/service/service.go b/lib/service/service.go index b94252c437eb..26c02f43a576 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2442,6 +2442,21 @@ func (process *TeleportProcess) initAuthService() error { } } + // When the audit queue is enabled, wrap the audit backend so that audit + // events this auth server originates are queued to disk if the backend + // rejects them, and re-delivered later. Events forwarded from + // agents/proxies are not queued here (see GRPCServer.EmitAuditEvent). Their + // originating instance retries them from its own queue. + var fallbackEmitter *events.FallbackEmitter + if isAuditQueueEnabled() { + fe, err := process.newAuthFallbackEmitter(emitter) + if err != nil { + return trace.Wrap(err) + } + fallbackEmitter = fe + emitter = fe + } + checkingEmitter, err := events.NewCheckingEmitter(events.CheckingEmitterConfig{ Inner: events.NewMultiEmitter(events.NewLoggingEmitter(process.GetClusterFeatures().Cloud), emitter), Clock: process.Clock, @@ -3070,6 +3085,9 @@ func (process *TeleportProcess) initAuthService() error { // execute this when process is asked to exit: process.OnExit("auth.shutdown", func(payload any) { + if fallbackEmitter != nil { + warnOnErr(process.ExitContext(), fallbackEmitter.Close(), logger) + } // The listeners have to be closed here, because if shutdown // was called before the start of the http server, // the http server would have not started tracking the listeners @@ -3471,7 +3489,7 @@ func (process *TeleportProcess) proxyPublicAddr() utils.NetAddr { return process.Config.Proxy.PublicAddrs[0] } -func isSQLiteQueueEnabled() bool { +func isAuditQueueEnabled() bool { enabled, _ := strconv.ParseBool(os.Getenv("TELEPORT_UNSTABLE_AUDIT_LOG_RELIABILITY")) return enabled } @@ -3479,11 +3497,6 @@ func isSQLiteQueueEnabled() bool { // NewAsyncEmitter wraps client and returns emitter that never blocks, logs some events and checks values. // It is caller's responsibility to call Close on the emitter once done. func (process *TeleportProcess) NewAsyncEmitter(clt apievents.Emitter) (*events.CheckingAsyncEmitter, error) { - var backends []auditqueue.Kind - for _, backend := range process.Config.AuditQueue.Backends { - backends = append(backends, auditqueue.Kind(backend)) - } - // Wrap the AsyncEmitter in a CheckingEmitter to ensure event fields are // properly set before inserting events into the queue. return events.NewCheckingAsyncEmitter( @@ -3491,23 +3504,45 @@ func (process *TeleportProcess) NewAsyncEmitter(clt apievents.Emitter) (*events. Clock: process.Clock, }, events.AsyncEmitterConfig{ - Inner: events.NewMultiEmitter(events.NewLoggingEmitter(process.GetClusterFeatures().Cloud), clt), - DataDir: process.Config.DataDir, - EnableSQLiteQueue: isSQLiteQueueEnabled(), - AuditQueueCfg: auditqueue.Config{ - SoftLimit: process.Config.AuditQueue.SoftLimit, - MaxBytes: process.Config.AuditQueue.MaxBytes, - MaxAttempts: process.Config.AuditQueue.MaxAttempts, - DeadLetterTTL: process.Config.AuditQueue.DeadLetterTTL, - DeadLetterSweepInterval: process.Config.AuditQueue.DeadLetterSweepInterval, - OrphanScanInterval: process.Config.AuditQueue.OrphanScanInterval, - Synchronous: process.Config.AuditQueue.Synchronous, - }, - AuditQueueBackends: backends, + Inner: events.NewMultiEmitter(events.NewLoggingEmitter(process.GetClusterFeatures().Cloud), clt), + DataDir: process.Config.DataDir, + EnableAuditQueue: isAuditQueueEnabled(), + AuditQueueCfg: process.auditQueueConfig(), + AuditQueueBackends: process.auditQueueBackends(), }, ) } +func (process *TeleportProcess) newAuthFallbackEmitter(primary apievents.Emitter) (*events.FallbackEmitter, error) { + return events.NewFallbackEmitter(events.FallbackEmitterConfig{ + Primary: primary, + DataDir: process.Config.DataDir, + EnableAuditQueue: isAuditQueueEnabled(), + AuditQueueCfg: process.auditQueueConfig(), + AuditQueueBackends: process.auditQueueBackends(), + }) +} + +func (process *TeleportProcess) auditQueueConfig() auditqueue.Config { + return auditqueue.Config{ + SoftLimit: process.Config.AuditQueue.SoftLimit, + MaxBytes: process.Config.AuditQueue.MaxBytes, + MaxAttempts: process.Config.AuditQueue.MaxAttempts, + DeadLetterTTL: process.Config.AuditQueue.DeadLetterTTL, + DeadLetterSweepInterval: process.Config.AuditQueue.DeadLetterSweepInterval, + OrphanScanInterval: process.Config.AuditQueue.OrphanScanInterval, + Synchronous: process.Config.AuditQueue.Synchronous, + } +} + +func (process *TeleportProcess) auditQueueBackends() []auditqueue.Kind { + var backends []auditqueue.Kind + for _, backend := range process.Config.AuditQueue.Backends { + backends = append(backends, auditqueue.Kind(backend)) + } + return backends +} + // ServerTLSConfig returns a new server-side [*tls.Config] that presents the // connector's credentials as its certificate. The returned tls.Config doesn't // request or trust any client certificates, so the caller is responsible for From 438944c5a3f720b788d660ee41cbc75e6bd4295d Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Tue, 28 Jul 2026 12:11:34 -0700 Subject: [PATCH 039/347] Validate MFA challenge scopes provided by the client (#69028) * Have Auth server validate that the MFA challenge scope is a known value. * Fix mfa Ceremony converting all bad parameter errors to ErrMFANotSupported rather than just specific unsupported cases (RoleAdmin). --- api/mfa/ceremony.go | 17 +++++++--- api/mfa/ceremony_test.go | 48 ++++++++++++++++++++++++++++ api/mfa/mfa.go | 31 ++++++++++++++++++ api/mfa/mfa_test.go | 41 ++++++++++++++++++++++++ lib/auth/auth.go | 4 +++ lib/auth/auth_login_test.go | 64 +++++++++++++++++++++++++++++++++++++ lib/auth/auth_with_roles.go | 5 +-- 7 files changed, 204 insertions(+), 6 deletions(-) diff --git a/api/mfa/ceremony.go b/api/mfa/ceremony.go index 5673901c8bc0..016cf47bf84d 100644 --- a/api/mfa/ceremony.go +++ b/api/mfa/ceremony.go @@ -18,6 +18,7 @@ package mfa import ( "context" + "errors" "log/slog" "slices" @@ -60,6 +61,14 @@ type CreateSessionChallengeFunc func(ctx context.Context, req *mfav2.CreateSessi // ValidateSessionChallengeFunc is a function that validates a session-bound MFA challenge. type ValidateSessionChallengeFunc func(ctx context.Context, req *mfav2.ValidateSessionChallengeRequest, opts ...grpc.CallOption) (*mfav2.ValidateSessionChallengeResponse, error) +// isMFANotSupportedForClient reports whether err is the Auth server's response +// indicating that the caller is not an end user and therefore cannot perform an +// MFA ceremony. +func isMFANotSupportedForClient(err error) bool { + return errors.Is(err, &ErrMFANotSupportedContextUser) || + errors.Is(err, &ErrMFANotSupportedMFARequiredCheck) +} + // Run the MFA ceremony. // // req may be nil if ceremony.CreateAuthenticateChallenge does not require it, e.g. in @@ -99,10 +108,10 @@ func (c *Ceremony) Run(ctx context.Context, req *proto.CreateAuthenticateChallen chal, err := c.CreateAuthenticateChallenge(ctx, req) if err != nil { - // CreateAuthenticateChallenge returns a bad parameter error when the client - // user is not a Teleport user - for example, the AdminRole. Treat this as an MFA - // not supported error so the client knows when it can be ignored. - if trace.IsBadParameter(err) { + // CreateAuthenticateChallenge returns a "NotSupported" error when the client + // user is not a Teleport user - for example, the AdminRole. Treat this as an + // MFA not supported error so the client knows when it can be ignored. + if isMFANotSupportedForClient(err) { return nil, &ErrMFANotSupported } return nil, trace.Wrap(err) diff --git a/api/mfa/ceremony_test.go b/api/mfa/ceremony_test.go index 9bbd1d1a3cf5..ee2c46eb0d48 100644 --- a/api/mfa/ceremony_test.go +++ b/api/mfa/ceremony_test.go @@ -116,6 +116,54 @@ func TestMFACeremony(t *testing.T) { assert.ErrorContains(t, err, "prompt mfa failure") assert.Nil(t, mr) }, + }, { + name: "OK ErrMFANotSupportedContextUser maps to ErrMFANotSupported", + ceremony: &mfa.Ceremony{ + CreateAuthenticateChallenge: func(ctx context.Context, req *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error) { + return nil, trace.Wrap(&mfa.ErrMFANotSupportedContextUser) + }, + PromptConstructor: func(opts ...mfa.PromptOpt) mfa.Prompt { + return mfa.PromptFunc(func(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + return nil, trace.BadParameter("prompt should not be reached") + }) + }, + }, + assertCeremonyResponse: func(t *testing.T, mr *proto.MFAAuthenticateResponse, err error, i ...interface{}) { + assert.ErrorIs(t, err, &mfa.ErrMFANotSupported) + assert.Nil(t, mr) + }, + }, { + name: "OK ErrMFANotSupportedMFARequiredCheck maps to ErrMFANotSupported", + ceremony: &mfa.Ceremony{ + CreateAuthenticateChallenge: func(ctx context.Context, req *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error) { + return nil, trace.Wrap(&mfa.ErrMFANotSupportedMFARequiredCheck) + }, + PromptConstructor: func(opts ...mfa.PromptOpt) mfa.Prompt { + return mfa.PromptFunc(func(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + return nil, trace.BadParameter("prompt should not be reached") + }) + }, + }, + assertCeremonyResponse: func(t *testing.T, mr *proto.MFAAuthenticateResponse, err error, i ...interface{}) { + assert.ErrorIs(t, err, &mfa.ErrMFANotSupported) + assert.Nil(t, mr) + }, + }, { + name: "OK unrelated bad parameter is surfaced", + ceremony: &mfa.Ceremony{ + CreateAuthenticateChallenge: func(ctx context.Context, req *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error) { + return nil, trace.BadParameter("stored session lacks challenge extensions") + }, + PromptConstructor: func(opts ...mfa.PromptOpt) mfa.Prompt { + return mfa.PromptFunc(func(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + return nil, trace.BadParameter("prompt should not be reached") + }) + }, + }, + assertCeremonyResponse: func(t *testing.T, mr *proto.MFAAuthenticateResponse, err error, i ...interface{}) { + assert.ErrorContains(t, err, "stored session lacks challenge extensions") + assert.Nil(t, mr) + }, }, } { t.Run(tt.name, func(t *testing.T) { diff --git a/api/mfa/mfa.go b/api/mfa/mfa.go index 368435fb169d..2ee08b56371a 100644 --- a/api/mfa/mfa.go +++ b/api/mfa/mfa.go @@ -28,6 +28,7 @@ import ( "google.golang.org/grpc/metadata" "github.com/gravitational/teleport/api/client/proto" + mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" ) // ResponseMetadataKey is the context metadata key for an MFA response in a gRPC request. @@ -47,13 +48,43 @@ var ( // the client user. ErrMFANotSupported = trace.BadParameterError{Message: "re-authentication with MFA is not supported for this client"} + // ErrMFANotSupportedContextUser is returned by the Auth server's + // CreateAuthenticateChallenge when a caller that is not an end user (for + // example, the built-in admin identity) requests a challenge for itself via + // ContextUser. Such callers cannot perform an MFA ceremony. + // + // The message must not change: MFA ceremonies detect this condition by message + // so it remains recognizable across version skew, including against older Auth + // servers that returned it as a bare bad-parameter error. + ErrMFANotSupportedContextUser = trace.BadParameterError{Message: "only end users are allowed to issue authentication challenges using ContextUser"} + + // ErrMFANotSupportedMFARequiredCheck is returned by the Auth server's + // CreateAuthenticateChallenge when a caller that is not an end user supplies an + // MFARequiredCheck. Such callers cannot perform an MFA ceremony. + // + // The message must not change, for the same backwards-compatibility reason as + // [ErrMFANotSupportedContextUser]. + ErrMFANotSupportedMFARequiredCheck = trace.BadParameterError{Message: "only end users are allowed to supply MFARequiredCheck"} + // ErrExpiredReusableMFAResponse is returned by Auth APIs like // GenerateUserCerts when an expired reusable MFA response is provided. ErrExpiredReusableMFAResponse = trace.AccessDeniedError{ Message: "Reusable MFA response validation failed and possibly expired", } + + // ErrUnknownChallengeScope is returned if the requested challenge scope is + // unknown, usually indicating an out of date auth server. + ErrUnknownChallengeScope = trace.BadParameterError{Message: "challenge scope unknown; auth server may be out of date"} ) +// ValidateChallengeScope checks that the given challenge is valid. +func ValidateChallengeScope(scope mfav1.ChallengeScope) error { + if _, ok := mfav1.ChallengeScope_name[int32(scope)]; !ok { + return trace.Wrap(&ErrUnknownChallengeScope) + } + return nil +} + // WithCredentials can be called on a GRPC client request to attach // MFA credentials to the GRPC metadata for requests that require MFA, // like admin-level requests. diff --git a/api/mfa/mfa_test.go b/api/mfa/mfa_test.go index 522596a66ee2..360576624e44 100644 --- a/api/mfa/mfa_test.go +++ b/api/mfa/mfa_test.go @@ -28,6 +28,7 @@ import ( "google.golang.org/grpc/credentials" "github.com/gravitational/teleport/api/client/proto" + mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/testhelpers/mtls" "github.com/gravitational/teleport/api/utils/grpc/interceptors" @@ -106,3 +107,43 @@ func TestMFAPerRPCCredentials(t *testing.T) { _, err = client.Ping(context.Background(), &proto.PingRequest{}, mfa.WithCredentials(mfaTestResp)) assert.NoError(t, err) } + +func TestValidateChallengeScope(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + name string + scope mfav1.ChallengeScope + assertErr require.ErrorAssertionFunc + }{ + { + name: "unspecified is known", + scope: mfav1.ChallengeScope_CHALLENGE_SCOPE_UNSPECIFIED, + assertErr: require.NoError, + }, + { + name: "defined scope", + scope: mfav1.ChallengeScope_CHALLENGE_SCOPE_USER_SESSION, + assertErr: require.NoError, + }, + { + name: "out of range scope", + scope: mfav1.ChallengeScope(99), + assertErr: func(t require.TestingT, err error, args ...any) { + require.ErrorIs(t, err, &mfa.ErrUnknownChallengeScope) + }, + }, + { + name: "negative scope", + scope: mfav1.ChallengeScope(-1), + assertErr: func(t require.TestingT, err error, args ...any) { + require.ErrorIs(t, err, &mfa.ErrUnknownChallengeScope) + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tt.assertErr(t, mfa.ValidateChallengeScope(tt.scope)) + }) + } +} diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 63d2b8f8fb71..e9d791bb4a5d 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -4541,6 +4541,10 @@ func (a *Server) CreateAuthenticateChallenge(ctx context.Context, req *proto.Cre } } + if err := mfa.ValidateChallengeScope(challengeExtensions.Scope); err != nil { + return nil, trace.Wrap(err) + } + switch req.GetRequest().(type) { case *proto.CreateAuthenticateChallengeRequest_UserCredentials: username = req.GetUserCredentials().GetUsername() diff --git a/lib/auth/auth_login_test.go b/lib/auth/auth_login_test.go index 683a6baf9035..3f261b749dc9 100644 --- a/lib/auth/auth_login_test.go +++ b/lib/auth/auth_login_test.go @@ -41,6 +41,7 @@ import ( mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1" + "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/utils/sshutils" @@ -676,6 +677,69 @@ func TestCreateAuthenticateChallenge_failedLoginAudit(t *testing.T) { }) } +// TestCreateAuthenticateChallenge_errors verifies that CreateAuthenticateChallenge +// returns the expected sentinel errors and that they survive the gRPC round trip, +// so mfa.(*Ceremony).Run can detect them on the client. +func TestCreateAuthenticateChallenge_errors(t *testing.T) { + t.Parallel() + ctx := context.Background() + srv := newTestTLSServer(t) + + // An end user gets past the "only end users" gate, exercising errors that + // occur deeper in challenge creation. + u, err := createUserWithSecondFactors(srv) + require.NoError(t, err) + endUserClient, err := srv.NewClient(authtest.TestUser(u.username)) + require.NoError(t, err) + + // A built-in identity (here, the admin role) is not an end user and cannot + // perform an MFA ceremony. + builtinClient, err := srv.NewClient(authtest.TestBuiltin(types.RoleAdmin)) + require.NoError(t, err) + + for _, tt := range []struct { + name string + client *authclient.Client + req *proto.CreateAuthenticateChallengeRequest + wantErr error + }{ + { + name: "unknown challenge scope", + client: endUserClient, + req: &proto.CreateAuthenticateChallengeRequest{ + ChallengeExtensions: &mfav1.ChallengeExtensions{ + Scope: mfav1.ChallengeScope(99), // out of range, unknown scope + }, + }, + wantErr: &mfa.ErrUnknownChallengeScope, + }, + { + name: "non-end-user ContextUser challenge", + client: builtinClient, + req: &proto.CreateAuthenticateChallengeRequest{}, + wantErr: &mfa.ErrMFANotSupportedContextUser, + }, + { + name: "non-end-user MFARequiredCheck", + client: builtinClient, + // A non-default request type skips the ContextUser gate above, so the + // MFARequiredCheck gate is what rejects the non-end-user caller. + req: &proto.CreateAuthenticateChallengeRequest{ + Request: &proto.CreateAuthenticateChallengeRequest_Passwordless{Passwordless: &proto.Passwordless{}}, + MFARequiredCheck: &proto.IsMFARequiredRequest{}, + }, + wantErr: &mfa.ErrMFANotSupportedMFARequiredCheck, + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + // Requests go over gRPC to ensure the error shapes survive the round trip. + _, err := tt.client.CreateAuthenticateChallenge(ctx, tt.req) + require.ErrorIs(t, err, tt.wantErr) + }) + } +} + func TestCreateRegisterChallenge(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index efe9c87e61cf..f7464b529c8f 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -51,6 +51,7 @@ import ( scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1" "github.com/gravitational/teleport/api/internalutils/stream" "github.com/gravitational/teleport/api/metadata" + "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" apiutils "github.com/gravitational/teleport/api/utils" @@ -8296,7 +8297,7 @@ func (a *ServerWithRoles) CreateAuthenticateChallenge(ctx context.Context, req * case *proto.CreateAuthenticateChallengeRequest_Passwordless: default: // nil or *proto.CreateAuthenticateChallengeRequest_ContextUser: if !isLocalOrRemoteUser { - return nil, trace.BadParameter("only end users are allowed to issue authentication challenges using ContextUser") + return nil, trace.Wrap(&mfa.ErrMFANotSupportedContextUser) } } @@ -8308,7 +8309,7 @@ func (a *ServerWithRoles) CreateAuthenticateChallenge(ctx context.Context, req * if req.MFARequiredCheck != nil { // Return a nicer error message. if !isLocalOrRemoteUser { - return nil, trace.BadParameter("only end users are allowed to supply MFARequiredCheck") + return nil, trace.Wrap(&mfa.ErrMFANotSupportedMFARequiredCheck) } mfaRequiredResp, err := a.IsMFARequired(ctx, req.MFARequiredCheck) From 037a727962aa1918fd3b6e8971ea6359b369b0c2 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 13:59:42 -0600 Subject: [PATCH 040/347] web: simplify index.html CSP calculation (#69037) Determining the appropriate Content-Security-Policy used to depend on a number of factors, including whether or not the cluster is running in Teleport Cloud and whether stripe.js is in use. In order to avoid running this logic on every request, we used to cache computed CSPs for reuse. Today, there's only two possible CSPs for index.html - the default one, and a custom CSP for use in routes that require WASM. Compute both of these variants at init time and eliminate the unnecessary caching layer. This also solves an issue - the CSP cache had no eviction and was keyed on request path, so it contained multiple copies of the same CSP and would grow over time but never shrink. --- lib/httplib/httpheaders.go | 166 +++++++++++++----------------------- lib/httplib/httplib_test.go | 47 ++++++---- lib/web/apiserver.go | 2 +- 3 files changed, 93 insertions(+), 122 deletions(-) diff --git a/lib/httplib/httpheaders.go b/lib/httplib/httpheaders.go index 131c614176b0..b7e605ddc150 100644 --- a/lib/httplib/httpheaders.go +++ b/lib/httplib/httpheaders.go @@ -26,63 +26,31 @@ import ( "regexp" "sort" "strings" - "sync" "time" "github.com/gravitational/teleport/api/utils" ) -// Mutex protected cache for memoizing functions which construct the CSP header string. -// This is necessary because the CSP header is constructed on every request to the web UI, -// so caching here has a significant performance impact. -type cspCache struct { - sync.RWMutex - entries map[string]string -} - -func (c *cspCache) get(key string) (string, bool) { - c.RLock() - defer c.RUnlock() - - val, ok := c.entries[key] - return val, ok -} - -func (c *cspCache) set(key, value string) { - c.Lock() - defer c.Unlock() - - c.entries[key] = value -} - -func newCSPCache() *cspCache { - return &cspCache{ - entries: make(map[string]string), - } -} - -// CSPMap holds a map of Content Security Policy. +// CSPMap holds a map of Content Security Policy directives. type CSPMap map[string][]string -var defaultContentSecurityPolicy = CSPMap{ - "default-src": {"'self'"}, - "script-src": {"'self'"}, - // specify CSP directives not covered by `default-src` - "base-uri": {"'self'"}, - "form-action": {"'self'"}, - "frame-ancestors": {"'none'"}, - // additional default restrictions - "object-src": {"'none'"}, - "img-src": {"'self'", "data:", "blob:"}, - "style-src": {"'self'", "'unsafe-inline'"}, -} - -var defaultFontSrc = CSPMap{"font-src": {"'self'", "data:"}} -var defaultConnectSrc = CSPMap{"connect-src": {"'self'", "wss:"}} - -var wasmSecurityPolicy = CSPMap{ - "script-src": {"'self'", "'wasm-unsafe-eval'"}, -} +var ( + defaultContentSecurityPolicy = CSPMap{ + "default-src": {"'self'"}, + "script-src": {"'self'"}, + // specify CSP directives not covered by `default-src` + "base-uri": {"'self'"}, + "form-action": {"'self'"}, + "frame-ancestors": {"'none'"}, + // additional default restrictions + "object-src": {"'none'"}, + "img-src": {"'self'", "data:", "blob:"}, + "style-src": {"'self'", "'unsafe-inline'"}, + } + defaultFontSrc = CSPMap{"font-src": {"'self'", "data:"}} + defaultConnectSrc = CSPMap{"connect-src": {"'self'", "wss:"}} + wasmSecurityPolicy = CSPMap{"script-src": {"'self'", "'wasm-unsafe-eval'"}} +) // combineCSPMaps combines multiple CSP maps into a single map. // When multiple of the input CSPMap have the same key, their @@ -179,76 +147,62 @@ func getIndexContentSecurityPolicy(withWasm bool) CSPMap { return combineCSPMaps(cspMaps...) } -// desktopSessionRe is a regex that matches /web/cluster/:clusterId/desktops/:desktopName/:username -// which is a route to a desktop session that uses WASM. -var desktopSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/desktops/[^/]+/[^/]+$`) +var ( + // desktopSessionRe is a regex that matches /web/cluster/:clusterId/desktops/:desktopName/:username + // which is a route to a Windows desktop session that uses WASM. + desktopSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/desktops/[^/]+/[^/]+$`) + + // linuxDesktopSessionRe is a regex that matches /web/cluster/:clusterId/linux_desktops/:desktopName/:username + // which is a route to a Linux desktop session that uses WASM. + linuxDesktopSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/linux_desktops/[^/]+/[^/]+$`) -// linuxDesktopSessionRe is a regex that matches /web/cluster/:clusterId/linux_desktops/:desktopName/:username -// which is a route to a Linux desktop session that uses WASM. -var linuxDesktopSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/linux_desktops/[^/]+/[^/]+$`) + // recordingRe is a regex for the recordings endpoint /web/cluster/:clusterId/session/:sid + // which is a route to a desktop recording that uses WASM. + recordingRe = regexp.MustCompile(`^/web/cluster/[^/]+/session/[^/]+$`) -// regex for the recordings endpoint /web/cluster/:clusterId/session/:sid -// which is a route to a desktop recording that uses WASM. -var recordingRe = regexp.MustCompile(`^/web/cluster/[^/]+/session/[^/]+$`) + // sshSessionRe is a regex for the ssh terminal endpoint /web/cluster/:clusterId/console/node/:sid/:login + // which is a route to a SSH session that uses WASM. + // (Note: the WASM dependency comes from xterm.js extensions) + sshSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/console/node/[^/]+/[^/]+$`) +) -// regex for the ssh terminal endpoint /web/cluster/:clusterId/console/node/:sid/:login -// which is a route to a ssh session that uses WASM. -var sshSessionRe = regexp.MustCompile(`^/web/cluster/[^/]+/console/node/[^/]+/[^/]+$`) +const ( + cspHeaderName = "Content-Security-Policy" + contentTypeHeaderName = "Content-Type" +) -var indexCSPStringCache *cspCache = newCSPCache() +var ( + indexCSPWithWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(true)) + indexCSPWithoutWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(false)) +) func getIndexContentSecurityPolicyString(urlPath string) string { - // Check for result with this urlPath in cache - if cspString, ok := indexCSPStringCache.get(urlPath); ok { - return cspString + if wasm := desktopSessionRe.MatchString(urlPath) || + linuxDesktopSessionRe.MatchString(urlPath) || + recordingRe.MatchString(urlPath) || + sshSessionRe.MatchString(urlPath); wasm { + return indexCSPWithWASM } - - // Nothing found in cache, calculate regex and result - withWasm := desktopSessionRe.MatchString(urlPath) || recordingRe.MatchString(urlPath) || sshSessionRe.MatchString(urlPath) || linuxDesktopSessionRe.MatchString(urlPath) - cspString := GetContentSecurityPolicyString( - getIndexContentSecurityPolicy(withWasm), - ) - // Add result to cache - indexCSPStringCache.set(urlPath, cspString) - - return cspString + return indexCSPWithoutWASM } -// SetIndexContentSecurityPolicy sets the Content-Security-Policy header for main index.html page +// SetIndexContentSecurityPolicy sets the Content-Security-Policy header for main index.html page. func SetIndexContentSecurityPolicy(h http.Header, urlPath string) { - cspString := getIndexContentSecurityPolicyString(urlPath) - h.Set("Content-Security-Policy", cspString) -} - -var redirectCSPStringCache *cspCache = newCSPCache() - -func getRedirectPageContentSecurityPolicyString(scriptSrc string) string { - if cspString, ok := redirectCSPStringCache.get(scriptSrc); ok { - return cspString - } - - cspString := GetContentSecurityPolicyString( - defaultContentSecurityPolicy, - CSPMap{ - "script-src": {"'" + scriptSrc + "'"}, - }, - ) - redirectCSPStringCache.set(scriptSrc, cspString) - - return cspString + h.Set(cspHeaderName, getIndexContentSecurityPolicyString(urlPath)) } +// SetRedirectPageContentSecurityPolicy sets the Content-Security-Policy header +// for when Teleport serves a redirect. func SetRedirectPageContentSecurityPolicy(h http.Header, scriptSrc string) { - cspString := getRedirectPageContentSecurityPolicyString(scriptSrc) - h.Set("Content-Security-Policy", cspString) -} - -// SetWebConfigHeaders sets headers for webConfig.js -func SetWebConfigHeaders(h http.Header) { - h.Set("Content-Type", "application/javascript") + h.Set( + cspHeaderName, + GetContentSecurityPolicyString( + defaultContentSecurityPolicy, + CSPMap{"script-src": {"'" + scriptSrc + "'"}}, + )) } -// SetScriptHeaders sets headers for the teleport install script +// SetScriptHeaders sets headers for Teleport install scripts. func SetScriptHeaders(h http.Header) { - h.Set("Content-Type", "text/x-shellscript") + h.Set(contentTypeHeaderName, "text/x-shellscript") } diff --git a/lib/httplib/httplib_test.go b/lib/httplib/httplib_test.go index 75602aeb0fcf..d9c7b342bc40 100644 --- a/lib/httplib/httplib_test.go +++ b/lib/httplib/httplib_test.go @@ -273,12 +273,12 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { for _, tt := range []struct { name string urlPath string - expectedCspVals map[string]string + expectedCSPVals map[string]string }{ { name: "default (no wasm)", urlPath: "/web/index.js", - expectedCspVals: map[string]string{ + expectedCSPVals: map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -294,7 +294,7 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { { name: "for cloud based usage, EUB product (no wasm)", urlPath: "/web/index.js", - expectedCspVals: map[string]string{ + expectedCSPVals: map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -309,7 +309,23 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { { name: "for desktop session (with wasm)", urlPath: "/web/cluster/:clusterId/desktops/:desktopName/:username", - expectedCspVals: map[string]string{ + expectedCSPVals: map[string]string{ + "default-src": "'self'", + "base-uri": "'self'", + "form-action": "'self'", + "frame-ancestors": "'none'", + "object-src": "'none'", + "script-src": "'self' 'wasm-unsafe-eval'", + "style-src": "'self' 'unsafe-inline'", + "img-src": "'self' data: blob:", + "font-src": "'self' data:", + "connect-src": "'self' wss:", + }, + }, + { + name: "for Linux desktop session (with wasm)", + urlPath: "/web/cluster/:clusterId/linux_desktops/:desktopName/:username", + expectedCSPVals: map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -325,7 +341,7 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { { name: "for web ssh session (with wasm)", urlPath: "/web/cluster/:clusterId/console/node/:sessionId/:username", - expectedCspVals: map[string]string{ + expectedCSPVals: map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -341,7 +357,7 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { { name: "for cloud based usage & desktop session, with wasm", urlPath: "/web/cluster/:clusterId/desktops/:desktopName/:username", - expectedCspVals: map[string]string{ + expectedCSPVals: map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -358,10 +374,10 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { t.Run(tt.name, func(t *testing.T) { h := make(http.Header) SetIndexContentSecurityPolicy(h, tt.urlPath) - actualCsp := h.Get("Content-Security-Policy") - for k, v := range tt.expectedCspVals { - expectedCspSubString := fmt.Sprintf("%s %s;", k, v) - require.Contains(t, actualCsp, expectedCspSubString) + actualCSP := h.Get("Content-Security-Policy") + for k, v := range tt.expectedCSPVals { + expectedCSPSubstring := fmt.Sprintf("%s %s;", k, v) + require.Contains(t, actualCSP, expectedCSPSubstring) } }) } @@ -372,7 +388,7 @@ func TestSetRedirectPageContentSecurityPolicy(t *testing.T) { scriptSrc := "nonce-123456789abcdefg" - expectedCspVals := map[string]string{ + expectedCSPVals := map[string]string{ "default-src": "'self'", "base-uri": "'self'", "form-action": "'self'", @@ -385,10 +401,11 @@ func TestSetRedirectPageContentSecurityPolicy(t *testing.T) { h := make(http.Header) SetRedirectPageContentSecurityPolicy(h, scriptSrc) - actualCsp := h.Get("Content-Security-Policy") - for k, v := range expectedCspVals { - expectedCspSubString := fmt.Sprintf("%s %s;", k, v) - require.Contains(t, actualCsp, expectedCspSubString) + + actualCSP := h.Get("Content-Security-Policy") + for k, v := range expectedCSPVals { + expectedCSPSubString := fmt.Sprintf("%s %s;", k, v) + require.Contains(t, actualCSP, expectedCSPSubString) } } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index f316e06d2570..8815b6e16235 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -2018,7 +2018,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht // getWebConfig returns configuration for the web application. func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprouter.Params) (any, error) { - httplib.SetWebConfigHeaders(w.Header()) + w.Header().Set("Content-Type", "application/javascript") clusterFeatures := h.GetClusterFeatures() automaticUpgradesEnabled := clusterFeatures.GetAutomaticUpgrades() From 726b51810a35d22b30afc1db86e2d8a1cc32dd0a Mon Sep 17 00:00:00 2001 From: Michael Patterson-Muir Date: Tue, 28 Jul 2026 21:01:06 +0100 Subject: [PATCH 041/347] Add `tctl discovery status` troubleshooting includes for AWS and Azure (#68954) * Add `tctl discovery status` troubleshooting includes for AWS and Azure * Add resource-specific context to `tctl discovery status` includes * Update docs/pages/includes/discovery/tctl-discovery-status.mdx Co-authored-by: Paul Gottschling * Update docs/pages/includes/discovery/tctl-discovery-status.mdx Co-authored-by: Paul Gottschling * Refine wording and formatting in `tctl discovery status` include for clarity --------- Co-authored-by: Paul Gottschling --- .../auto-discovery/databases/aws.mdx | 2 + .../eks-discovery/eks-discovery-terraform.mdx | 2 + .../azure-vm-troubleshooting.mdx | 2 + .../auto-discovery/ec2-troubleshooting.mdx | 2 + .../discovery/tctl-discovery-status.mdx | 38 +++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 docs/pages/includes/discovery/tctl-discovery-status.mdx diff --git a/docs/pages/enroll-resources/auto-discovery/databases/aws.mdx b/docs/pages/enroll-resources/auto-discovery/databases/aws.mdx index d28d1668e8bd..9f5ee3a99464 100644 --- a/docs/pages/enroll-resources/auto-discovery/databases/aws.mdx +++ b/docs/pages/enroll-resources/auto-discovery/databases/aws.mdx @@ -313,6 +313,8 @@ for information about database user provisioning and configuration. ## Troubleshooting +(!docs/pages/includes/discovery/tctl-discovery-status.mdx provider="AWS" cloud="aws" resource="AWS RDS"!) + (!docs/pages/includes/discovery/discovery-service-troubleshooting.mdx resourceKind="database" tctlResource="db" cloud="AWS"!) (!docs/pages/includes/discovery/database-service-troubleshooting.mdx!) diff --git a/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-terraform.mdx b/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-terraform.mdx index 0869cd82b4db..8ecbadb3b41c 100644 --- a/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-terraform.mdx +++ b/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-terraform.mdx @@ -350,6 +350,8 @@ in the newly-configured regions. ## Troubleshooting +(!docs/pages/includes/discovery/tctl-discovery-status.mdx provider="AWS" cloud="aws" resource="AWS EKS"!) + (!docs/pages/includes/discovery/discovery-service-troubleshooting.mdx resourceKind="Kubernetes cluster" tctlResource="kube_cluster" cloud="AWS"!) (!docs/pages/includes/discovery/kubernetes-service-troubleshooting.mdx!) diff --git a/docs/pages/includes/auto-discovery/azure-vm-troubleshooting.mdx b/docs/pages/includes/auto-discovery/azure-vm-troubleshooting.mdx index f8b160ee8bc0..773b4a438a0f 100644 --- a/docs/pages/includes/auto-discovery/azure-vm-troubleshooting.mdx +++ b/docs/pages/includes/auto-discovery/azure-vm-troubleshooting.mdx @@ -1,4 +1,6 @@ {/* this comment is here to make the include below render */} +(!docs/pages/includes/discovery/tctl-discovery-status.mdx provider="Azure" cloud="azure" resource="Azure VM"!) + (!docs/pages/includes/auto-discovery/tctl-discovery-nodes-azure.mdx!) ### Teleport reports no error but VM does not join diff --git a/docs/pages/includes/auto-discovery/ec2-troubleshooting.mdx b/docs/pages/includes/auto-discovery/ec2-troubleshooting.mdx index 1adbc3134ee9..2d1b0f397b1a 100644 --- a/docs/pages/includes/auto-discovery/ec2-troubleshooting.mdx +++ b/docs/pages/includes/auto-discovery/ec2-troubleshooting.mdx @@ -1,4 +1,6 @@ {/* this comment is here to make the include below render */} +(!docs/pages/includes/discovery/tctl-discovery-status.mdx provider="AWS" cloud="aws" resource="AWS EC2"!) + (!docs/pages/includes/auto-discovery/tctl-discovery-nodes-aws.mdx!) If Installs are showing failed or instances are failing to appear check the diff --git a/docs/pages/includes/discovery/tctl-discovery-status.mdx b/docs/pages/includes/discovery/tctl-discovery-status.mdx new file mode 100644 index 000000000000..68fabf9be318 --- /dev/null +++ b/docs/pages/includes/discovery/tctl-discovery-status.mdx @@ -0,0 +1,38 @@ +{{ provider="AWS" cloud="aws" resource="AWS EC2" }} + +### Inspect {{ provider }} discovery status + +Run `tctl discovery status` to check the state of dynamic Discovery Service +configurations, when each Discovery Service instance last reported, and the latest +{{ provider }} enrollment totals: + +```code +$ tctl discovery status --cloud={{ cloud }} +``` + +The following example shows a healthy configuration that discovered and enrolled two resources: + +```text +Discovery config example-discovery: + Discovery group: production + Status: healthy + Last run: 1 minute ago + + Service (00000000-0000-0000-0000-000000000000): + Poll interval: 5 minutes + Last update: 1 minute ago + example-integration: + {{ resource }}: + Previous sync: 1 minute ago (took 12s) + Result: 2 found, 2 enrolled, 0 failed +``` + +A persistent `error` or `not reporting yet` status, a message that no Discovery +Service instances are running, or a nonzero `failed` count indicates a discovery +or enrollment problem. + +The command reports dynamic `discovery_config` resources. It does not include +static discovery matchers configured in `teleport.yaml`. + +See [`tctl discovery status`](../../reference/cli/tctl.mdx#tctl-discovery-status) +for the full flag reference. From abffb9fa281882ad58de7f4e5271b957c4599327 Mon Sep 17 00:00:00 2001 From: Kyle Kloberdanz Date: Tue, 28 Jul 2026 15:43:28 -0500 Subject: [PATCH 042/347] Attempt to drain the audit queue on shutdown. (#67818) This PR attempts to drain the queue during a graceful shutdown. It adds the method Drain() to the Queue interface, which blocks while the queue still has events in it. --- lib/events/auditqueue/auditqueue.go | 6 + lib/events/auditqueue/drain_test.go | 263 +++++++++++++++++++++++++ lib/events/auditqueue/sqlite.go | 145 ++++++++++++-- lib/events/auditqueue/sqlite_disk.go | 13 +- lib/events/auditqueue/sqlite_memory.go | 6 + lib/events/auditqueue/sqlite_test.go | 4 +- lib/events/emitter.go | 37 +++- lib/events/emitter_test.go | 23 +++ lib/events/fallback_emitter.go | 14 ++ lib/events/fallback_emitter_test.go | 44 +++++ lib/service/db.go | 4 +- lib/service/discovery.go | 4 +- lib/service/kubernetes.go | 4 +- lib/service/relay.go | 2 + lib/service/service.go | 33 +++- 15 files changed, 563 insertions(+), 39 deletions(-) create mode 100644 lib/events/auditqueue/drain_test.go diff --git a/lib/events/auditqueue/auditqueue.go b/lib/events/auditqueue/auditqueue.go index f12c03607e24..c923b84b37f5 100644 --- a/lib/events/auditqueue/auditqueue.go +++ b/lib/events/auditqueue/auditqueue.go @@ -112,6 +112,12 @@ type Queue interface { // Run is the single consumer that drains the queue and forwards the audit // log events to `handler`. Run(ctx context.Context, handler Handler) error + // Drain blocks until the queue is drained. It makes a best-effort attempt + // to forward all pending events to the consumer before shutdown. It stops + // adopting new orphans and waits for both the main queue and the + // dead-letter queue to empty or for ctx to be done, whichever comes first. + // Callers must still call Close. + Drain(ctx context.Context) error // Close releases resources held by the queue. Close() error } diff --git a/lib/events/auditqueue/drain_test.go b/lib/events/auditqueue/drain_test.go new file mode 100644 index 000000000000..b32c523e353f --- /dev/null +++ b/lib/events/auditqueue/drain_test.go @@ -0,0 +1,263 @@ +/* + * Teleport + * Copyright (C) 2026 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 auditqueue + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestDrain_DrainsMainQueue(t *testing.T) { + t.Parallel() + + for _, kind := range allKinds { + t.Run(string(kind), func(t *testing.T) { + t.Parallel() + ctx := t.Context() + q := newTestQueue(t, kind) + + const n = 25 + for i := int64(0); i < n; i++ { + require.NoError(t, q.Enqueue(newTestEvent(i))) + } + + runCtx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + + release := make(chan struct{}) + entered := make(chan struct{}, 1) + var delivered atomic.Int64 + handler := func(_ context.Context, items []Item) []Item { + select { + case entered <- struct{}{}: + default: + } + <-release + delivered.Add(int64(len(items))) + return items + } + runErr := make(chan error, 1) + go func() { runErr <- q.Run(runCtx, handler) }() + + <-entered + + drainCtx, drainCancel := context.WithTimeout(ctx, testDefaultTimeout) + t.Cleanup(drainCancel) + + drainDone := make(chan error, 1) + go func() { drainDone <- q.Drain(drainCtx) }() + + select { + case <-drainDone: + t.Fatal("Drain returned before the queue was drained") + case <-time.After(100 * time.Millisecond): + } + + close(release) + select { + case err := <-drainDone: + require.NoError(t, err) + case <-time.After(testDefaultTimeout): + t.Fatal("Drain did not return after the queue drained") + } + require.Equal(t, int64(n), delivered.Load()) + + cancel() + require.ErrorIs(t, <-runErr, context.Canceled) + }) + } +} + +func TestDrain_FlushesDeadLetter(t *testing.T) { + t.Parallel() + + for _, kind := range allKinds { + t.Run(string(kind), func(t *testing.T) { + t.Parallel() + ctx := t.Context() + q := newTestQueueWithConfig(t, kind, Config{ + MaxAttempts: 1, + DeadLetterSweepInterval: time.Hour, // only Drain may trigger sweeps + }) + + require.NoError(t, q.Enqueue(newTestEvent(0))) + + var failing atomic.Bool + failing.Store(true) + var delivered atomic.Int64 + handler := func(_ context.Context, items []Item) []Item { + if failing.Load() { + return nil + } + delivered.Add(int64(len(items))) + return items + } + + runCtx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + runErr := make(chan error, 1) + go func() { runErr <- q.Run(runCtx, handler) }() + + // Wait for the event to exhaust its attempts and land in the + // dead-letter queue, emptying the main queue. + sq := underlyingQueue(t, q) + require.Eventually(t, func() bool { + dl, err := fetchDeadLetter(ctx, sq.db, 10) + return err == nil && len(dl) == 1 + }, testDefaultTimeout, 10*time.Millisecond) + + drainCtx, drainCancel := context.WithTimeout(ctx, testDefaultTimeout) + t.Cleanup(drainCancel) + + drainDone := make(chan error, 1) + go func() { drainDone <- q.Drain(drainCtx) }() + + select { + case <-drainDone: + t.Fatal("Drain returned while dead-letter events were pending") + case <-time.After(100 * time.Millisecond): + } + + failing.Store(false) + select { + case err := <-drainDone: + require.NoError(t, err) + case <-time.After(testDefaultTimeout): + t.Fatal("Drain did not return after the dead-letter queue drained") + } + require.Equal(t, int64(1), delivered.Load()) + + cancel() + require.ErrorIs(t, <-runErr, context.Canceled) + }) + } +} + +func TestDrainKickPolicy(t *testing.T) { + t.Parallel() + + // ticksUntilKick advances the policy until shouldKick reports true and + // returns how many ticks that took. + ticksUntilKick := func(t *testing.T, p *drainKickPolicy, deadLetterCount int64, maxTicks int) int { + t.Helper() + for i := 0; i <= maxTicks; i++ { + if p.shouldKick(deadLetterCount) { + return i + } + p.tick() + } + t.Fatalf("no kick after %d ticks", maxTicks) + return 0 + } + + t.Run("no kick before first tick", func(t *testing.T) { + t.Parallel() + p := newDrainKickPolicy() + require.False(t, p.shouldKick(10), "must not kick immediately after the initial synchronous sweep") + p.tick() + require.True(t, p.shouldKick(10)) + }) + + t.Run("backoff doubles without progress", func(t *testing.T) { + t.Parallel() + p := newDrainKickPolicy() + ticksUntilKick(t, p, 10, 1) + require.Equal(t, 2, ticksUntilKick(t, p, 10, 10)) + require.Equal(t, 4, ticksUntilKick(t, p, 10, 10)) + require.Equal(t, 8, ticksUntilKick(t, p, 10, 10)) + }) + + t.Run("backoff caps at maxDrainKickBackoff", func(t *testing.T) { + t.Parallel() + p := newDrainKickPolicy() + maxTicks := int(maxDrainKickBackoff/pollInterval) + 1 + for range 20 { + ticksUntilKick(t, p, 10, maxTicks) + } + require.Equal(t, int(maxDrainKickBackoff/pollInterval), ticksUntilKick(t, p, 10, maxTicks)) + }) + + t.Run("progress kicks immediately and resets backoff", func(t *testing.T) { + t.Parallel() + p := newDrainKickPolicy() + ticksUntilKick(t, p, 10, 1) + ticksUntilKick(t, p, 10, 10) + ticksUntilKick(t, p, 10, 10) + require.True(t, p.shouldKick(9), "shrinking dead-letter queue must kick immediately") + require.Equal(t, 1, ticksUntilKick(t, p, 9, 10), "backoff must reset after progress") + }) + + t.Run("growing count is not progress", func(t *testing.T) { + t.Parallel() + p := newDrainKickPolicy() + ticksUntilKick(t, p, 10, 1) + require.False(t, p.shouldKick(15), "new dead-letter promotions must not bypass the backoff") + require.Equal(t, 2, ticksUntilKick(t, p, 15, 10)) + }) +} + +func TestDrain_RespectsContextDeadline(t *testing.T) { + t.Parallel() + + for _, kind := range allKinds { + t.Run(string(kind), func(t *testing.T) { + t.Parallel() + ctx := t.Context() + q := newTestQueue(t, kind) + + const n = 5 + for i := int64(0); i < n; i++ { + require.NoError(t, q.Enqueue(newTestEvent(i))) + } + + drainCtx, drainCancel := context.WithTimeout(ctx, 100*time.Millisecond) + t.Cleanup(drainCancel) + + drainDone := make(chan error, 1) + go func() { drainDone <- q.Drain(drainCtx) }() + select { + case err := <-drainDone: + require.ErrorIs(t, err, context.DeadlineExceeded) + case <-time.After(testDefaultTimeout): + t.Fatal("Drain did not return after its deadline expired") + } + + runCtx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + var delivered atomic.Int64 + handler := func(_ context.Context, items []Item) []Item { + delivered.Add(int64(len(items))) + return items + } + runErr := make(chan error, 1) + go func() { runErr <- q.Run(runCtx, handler) }() + + require.Eventually(t, func() bool { + return delivered.Load() == int64(n) + }, testDefaultTimeout, 10*time.Millisecond) + + cancel() + require.ErrorIs(t, <-runErr, context.Canceled) + }) + } +} diff --git a/lib/events/auditqueue/sqlite.go b/lib/events/auditqueue/sqlite.go index cd83a387665f..da219dba7058 100644 --- a/lib/events/auditqueue/sqlite.go +++ b/lib/events/auditqueue/sqlite.go @@ -47,6 +47,7 @@ const ( defaultMaxAttempts = 10 defaultDeadLetterSweepInterval = 10 * time.Minute defaultDeadLetterTTL = 30 * 24 * time.Hour // 30 days + maxDrainKickBackoff = 30 * time.Second // We've run benchmarks and found a batch size of 25 to be a good middle // ground between insertion performance and memory overhead of @@ -117,6 +118,10 @@ type writeRequest struct { resp chan error } +type sweepRequest struct { + done chan struct{} +} + type sqliteQueue struct { db *sql.DB path string @@ -130,6 +135,9 @@ type sqliteQueue struct { cancel context.CancelFunc wg sync.WaitGroup closeOnce sync.Once + drainOnce sync.Once + drainCh chan struct{} + sweepCh chan sweepRequest parentDir string selfStat os.FileInfo unlock func() error @@ -192,6 +200,8 @@ func newBaseQueue(db *sql.DB, cfg Config) (*sqliteQueue, error) { q := &sqliteQueue{ db: db, toBeWritten: make(chan writeRequest), + drainCh: make(chan struct{}), + sweepCh: make(chan sweepRequest), maxBatch: defaultMaxBatch, ctx: ctx, cancel: cancel, @@ -471,6 +481,102 @@ func (q *sqliteQueue) ackWithRetry(ctx context.Context, items []Item) error { } } +// Drain exits when both the main audit queue and the dead-letter queue are +// empty. It allows one to await the draining of the queue on shutdown. Run is +// executed in the background and will continue to drain the queue. Corrupt +// events are excluded. They cannot be delivered, so waiting on them would +// stall every shutdown until the drain deadline. +func (q *sqliteQueue) Drain(ctx context.Context) error { + q.drainOnce.Do(func() { close(q.drainCh) }) + + // Trigger and wait for a dead letter sweep. + req := sweepRequest{done: make(chan struct{})} + select { + case q.sweepCh <- req: + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + case <-q.ctx.Done(): + return trace.Wrap(q.ctx.Err()) + } + select { + case <-req.done: + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + case <-q.ctx.Done(): + return trace.Wrap(q.ctx.Err()) + } + + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + policy := newDrainKickPolicy() + var lastErr error + for { + mainEmpty, deadLetterCount, err := drainQueueState(ctx, q.db) + lastErr = err + if err != nil { + slog.ErrorContext(ctx, + "Failed to check whether audit queue is empty while draining.", + "error", err, + ) + } else if mainEmpty && deadLetterCount == 0 { + return nil + } else if mainEmpty && policy.shouldKick(deadLetterCount) { + q.kickDeadLetterSweep() + } + + select { + case <-ctx.Done(): + return trace.NewAggregate(ctx.Err(), lastErr) + case <-q.ctx.Done(): + return trace.NewAggregate(q.ctx.Err(), lastErr) + case <-ticker.C: + } + policy.tick() + } +} + +type drainKickPolicy struct { + backoff time.Duration + sinceLastKick time.Duration + lastCount int64 +} + +func newDrainKickPolicy() *drainKickPolicy { + return &drainKickPolicy{ + backoff: pollInterval, + lastCount: -1, + } +} + +func (p *drainKickPolicy) shouldKick(deadLetterCount int64) bool { + progressed := p.lastCount >= 0 && deadLetterCount < p.lastCount + p.lastCount = deadLetterCount + if progressed { + p.backoff = pollInterval + } else if p.sinceLastKick < p.backoff { + return false + } else { + p.backoff = min(p.backoff*2, maxDrainKickBackoff) + } + p.sinceLastKick = 0 + return true +} + +func (p *drainKickPolicy) tick() { + p.sinceLastKick += pollInterval +} + +const drainQueueStateQuery = `SELECT + NOT EXISTS(SELECT 1 FROM audit_queue), + (SELECT COUNT(*) FROM audit_dead_letter)` + +func drainQueueState(ctx context.Context, db *sql.DB) (mainEmpty bool, deadLetterCount int64, _ error) { + if err := db.QueryRowContext(ctx, drainQueueStateQuery).Scan(&mainEmpty, &deadLetterCount); err != nil { + return false, 0, trace.Wrap(err) + } + return mainEmpty, deadLetterCount, nil +} + func (q *sqliteQueue) handleDeliveryFailures(ctx context.Context, items []Item, successfullyDelivered []Item) { failed := itemsNotIn(items, successfullyDelivered) if len(failed) == 0 { @@ -597,14 +703,20 @@ func (q *sqliteQueue) deadLetterSweepLoop(ctx context.Context, handler Handler) return case <-q.ctx.Done(): return + case req := <-q.sweepCh: + // Trigger a sweep on-demand. + q.runSweeps(ctx, handler) + close(req.done) + timer.Reset(q.deadLetterSweepInterval) case <-timer.C: + // Trigger a sweep on a timer. + q.runSweeps(ctx, handler) + timer.Reset(q.deadLetterSweepInterval) case <-q.deadLetterKick: + // Trigger a sweep when delivery recovers after failing. + q.runSweeps(ctx, handler) + timer.Reset(q.deadLetterSweepInterval) } - q.sweepDeadLetter(ctx, handler) - q.expireDeadLetter() - q.recoverCorruptEvents() - q.expireCorruptEvents() - timer.Reset(q.deadLetterSweepInterval) } } @@ -617,6 +729,13 @@ func (q *sqliteQueue) kickDeadLetterSweep() { } } +func (q *sqliteQueue) runSweeps(ctx context.Context, handler Handler) { + q.sweepDeadLetter(ctx, handler) + q.expireDeadLetter() + q.recoverCorruptEvents() + q.expireCorruptEvents() +} + func (q *sqliteQueue) sweepDeadLetter(ctx context.Context, handler Handler) { maxID, err := q.maxDeadLetterID() if err != nil { @@ -684,22 +803,6 @@ func (q *sqliteQueue) fetchDeadLetterRange(afterID, maxID int64, limit int) ([]I return items, nil } -// fetchDeadLetter reads up to `limit` oldest items from the audit_dead_letter -// table, quarantining any rows whose payload fails to deserialize. -func (q *sqliteQueue) fetchDeadLetter(limit int) ([]Item, error) { - rows, err := q.db.QueryContext(q.ctx, - "SELECT id, payload FROM audit_dead_letter ORDER BY id ASC LIMIT ?", limit) - if err != nil { - return nil, trace.Wrap(err) - } - items, corrupt, err := scanItems(rows) - if err != nil { - return nil, trace.Wrap(err) - } - handleCorrupt(q.ctx, q.db, auditDeadLetterTable, corrupt) - return items, nil -} - // ackDeadLetter deletes successfully re-delivered items from audit_dead_letter. func (q *sqliteQueue) ackDeadLetter(items []Item) error { return deleteByIDs(q.ctx, q.db, auditDeadLetterTable, items) diff --git a/lib/events/auditqueue/sqlite_disk.go b/lib/events/auditqueue/sqlite_disk.go index 3ae365844ff1..6b84f704003e 100644 --- a/lib/events/auditqueue/sqlite_disk.go +++ b/lib/events/auditqueue/sqlite_disk.go @@ -307,7 +307,7 @@ func (q *sqliteQueue) Run(ctx context.Context, handler Handler) error { func (q *sqliteQueue) orphanScanLoop(ctx context.Context) { ticker := time.NewTicker(q.orphanScanInterval) defer ticker.Stop() - for { + for !q.isDraining() { q.sweepStaleTmp() q.adoptOrphans(ctx) @@ -316,11 +316,22 @@ func (q *sqliteQueue) orphanScanLoop(ctx context.Context) { return case <-q.ctx.Done(): return + case <-q.drainCh: + return case <-ticker.C: } } } +func (q *sqliteQueue) isDraining() bool { + select { + case <-q.drainCh: + return true + default: + return false + } +} + // sweepStaleTmp cleans up any audit queue directories that have been orphaned // during their initialization phase. This should be rare, but we still want to // account for it. diff --git a/lib/events/auditqueue/sqlite_memory.go b/lib/events/auditqueue/sqlite_memory.go index 8aa0a8e46551..56bb835fcfe1 100644 --- a/lib/events/auditqueue/sqlite_memory.go +++ b/lib/events/auditqueue/sqlite_memory.go @@ -131,6 +131,12 @@ func (m *sqliteInMemoryQueue) Run(ctx context.Context, handler Handler) error { return m.inner.runPollLoop(ctx, handler) } +// Drain makes a best-effort attempt to flush pending events upstream before +// shutdown. See Queue.Drain. +func (m *sqliteInMemoryQueue) Drain(ctx context.Context) error { + return m.inner.Drain(ctx) +} + // Close shuts down the in-memory queue. func (m *sqliteInMemoryQueue) Close() error { var err error diff --git a/lib/events/auditqueue/sqlite_test.go b/lib/events/auditqueue/sqlite_test.go index 4553a42ee249..16e0ffdff39d 100644 --- a/lib/events/auditqueue/sqlite_test.go +++ b/lib/events/auditqueue/sqlite_test.go @@ -1254,7 +1254,9 @@ func TestFetchDeadLetter_QuarantinesCorrupt(t *testing.T) { corruptPayload, time.Now().Unix()) require.NoError(t, err) - items, err := q.fetchDeadLetter(10) + maxID, err := q.maxDeadLetterID() + require.NoError(t, err) + items, err := q.fetchDeadLetterRange(0, maxID, 10) require.NoError(t, err) require.Empty(t, items) diff --git a/lib/events/emitter.go b/lib/events/emitter.go index 7e77d20077f7..7e3f358cb1aa 100644 --- a/lib/events/emitter.go +++ b/lib/events/emitter.go @@ -169,6 +169,22 @@ func (a *AsyncEmitter) Close() error { return nil } +// Shutdown makes a best effort attempt to flush pending audit events to the +// inner emitter before closing. +func (a *AsyncEmitter) Shutdown(ctx context.Context) error { + if a.queue != nil { + if err := a.queue.Drain(ctx); err != nil { + slog.WarnContext(ctx, + "Audit queue drain returned an error during graceful shutdown.", + "error", err, + ) + } + return trace.Wrap(a.Close()) + } + + return trace.Wrap(a.Close()) +} + func (a *AsyncEmitter) forward() { for { select { @@ -198,16 +214,27 @@ func (a *AsyncEmitter) deliver(ctx context.Context, items []auditqueue.Item) []a // interface. I suspect that having first-class batching will have a greater // improvement on performance over parallelism alone. It will also have less // overhead than parallelism over multiple events. + var failed int + var firstErr error for _, item := range items { if ctx.Err() != nil { - return successfullyDelivered + break } if err := a.cfg.Inner.EmitAuditEvent(ctx, item.Event); err != nil { - slog.ErrorContext(ctx, "Failed to emit audit event.", "error", err) + failed++ + if firstErr == nil { + firstErr = err + } continue } successfullyDelivered = append(successfullyDelivered, item) } + if failed > 0 && ctx.Err() == nil { + slog.ErrorContext(ctx, "Failed to emit audit events.", + "count", failed, + "error", firstErr, + ) + } return successfullyDelivered } @@ -334,6 +361,12 @@ func (c *CheckingAsyncEmitter) Close() error { return c.asyncEmitter.Close() } +// Shutdown attempts to drain the underlying AsyncEmitter before closing. +// See AsyncEmitter.Shutdown. +func (c *CheckingAsyncEmitter) Shutdown(ctx context.Context) error { + return c.asyncEmitter.Shutdown(ctx) +} + // checkAndSetEventFields updates passed event fields with additional information // common for all event types such as unique IDs, timestamps, codes, etc. // diff --git a/lib/events/emitter_test.go b/lib/events/emitter_test.go index 89cc44bdf301..ddd181a5fe31 100644 --- a/lib/events/emitter_test.go +++ b/lib/events/emitter_test.go @@ -378,3 +378,26 @@ func TestAsyncEmitterAuditQueueBackends(t *testing.T) { } }) } + +func TestAsyncEmitterShutdownDrainsQueue(t *testing.T) { + ctx := t.Context() + counter := eventstest.NewCountingEmitter() + emitter, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ + Inner: counter, + EnableAuditQueue: true, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + + evts := eventstest.GenerateTestSession(eventstest.SessionParams{PrintEvents: 20}) + for _, e := range evts { + require.NoError(t, emitter.EmitAuditEvent(ctx, e)) + } + + shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + t.Cleanup(cancel) + require.NoError(t, emitter.Shutdown(shutdownCtx)) + + require.EqualValues(t, len(evts), counter.Count(), + "Shutdown should drain every enqueued event to the inner emitter") +} diff --git a/lib/events/fallback_emitter.go b/lib/events/fallback_emitter.go index c77f1f93292a..85d6162aec29 100644 --- a/lib/events/fallback_emitter.go +++ b/lib/events/fallback_emitter.go @@ -187,3 +187,17 @@ func (f *FallbackEmitter) Close() error { } return nil } + +// Shutdown makes a best effort attempt to flush pending audit events to the +// inner emitter before closing. +func (f *FallbackEmitter) Shutdown(ctx context.Context) error { + if f.queue != nil { + if err := f.queue.Drain(ctx); err != nil { + slog.WarnContext(ctx, + "Audit fallback queue drain returned an error during graceful shutdown.", + "error", err, + ) + } + } + return trace.Wrap(f.Close()) +} diff --git a/lib/events/fallback_emitter_test.go b/lib/events/fallback_emitter_test.go index 125c20c4db2d..a8dd8532df77 100644 --- a/lib/events/fallback_emitter_test.go +++ b/lib/events/fallback_emitter_test.go @@ -139,6 +139,50 @@ func TestFallbackEmitter_QueueDisabled(t *testing.T) { require.Equal(t, 1, primary.count()) } +func TestFallbackEmitter_ShutdownDrains(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setFail(true) + + e, err := events.NewFallbackEmitter(events.FallbackEmitterConfig{ + Primary: primary, + DataDir: t.TempDir(), + EnableAuditQueue: true, + AuditQueueCfg: auditqueue.Config{MaxAttempts: 1_000_000}, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = e.Close() }) + + const n = 5 + for range n { + require.NoError(t, e.EmitAuditEvent(t.Context(), testEvent())) + } + require.Equal(t, 0, primary.count(), "events should be queued while the backend is down") + + primary.setFail(false) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + t.Cleanup(cancel) + require.NoError(t, e.Shutdown(ctx)) + require.Equal(t, n, primary.count(), "Shutdown should flush all queued events to the primary") +} + +func TestFallbackEmitter_CloseDoesNotDrain(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setFail(true) + e := newFallbackTestEmitter(t, primary, true) + + require.NoError(t, e.EmitAuditEvent(t.Context(), testEvent())) + require.Equal(t, 0, primary.count()) + + require.NoError(t, e.Close()) + primary.setFail(false) + require.Never(t, func() bool { + return primary.count() > 0 + }, time.Second, 50*time.Millisecond, "Close must not drain queued events") +} + type authBoundary struct { fallback apievents.Emitter } diff --git a/lib/service/db.go b/lib/service/db.go index 7de6bf9d85ba..2110b787aca2 100644 --- a/lib/service/db.go +++ b/lib/service/db.go @@ -227,12 +227,10 @@ func (process *TeleportProcess) initDatabaseService() (retErr error) { warnOnErr(process.ExitContext(), dbService.Shutdown(payloadContext(payload)), logger) } } - if asyncEmitter != nil { - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) - } if agentPool != nil { agentPool.Stop() } + shutdownEmitter(process, asyncEmitter, payload, logger) warnOnErr(process.ExitContext(), conn.Close(), logger) logger.InfoContext(process.ExitContext(), "Exited.") }) diff --git a/lib/service/discovery.go b/lib/service/discovery.go index 9005e6f0295f..45bc83db6082 100644 --- a/lib/service/discovery.go +++ b/lib/service/discovery.go @@ -103,9 +103,7 @@ func (process *TeleportProcess) initDiscoveryService() error { if discoveryService != nil { discoveryService.Stop() } - if asyncEmitter != nil { - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) - } + shutdownEmitter(process, asyncEmitter, payload, logger) warnOnErr(process.ExitContext(), conn.Close(), logger) logger.InfoContext(process.ExitContext(), "Exited.") }) diff --git a/lib/service/kubernetes.go b/lib/service/kubernetes.go index 1415a4d03ddf..a438611ada87 100644 --- a/lib/service/kubernetes.go +++ b/lib/service/kubernetes.go @@ -362,9 +362,7 @@ func (process *TeleportProcess) initKubernetesService(logger *slog.Logger, conn if relayTunnelClient != nil { relayTunnelClient.Close() } - if asyncEmitter != nil { - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) - } + shutdownEmitter(process, asyncEmitter, payload, logger) if healthCheckManager != nil { warnOnErr(process.ExitContext(), healthCheckManager.Close(), logger) } diff --git a/lib/service/relay.go b/lib/service/relay.go index 8d5a2de5dc7d..19e99887e6b5 100644 --- a/lib/service/relay.go +++ b/lib/service/relay.go @@ -473,6 +473,8 @@ func (process *TeleportProcess) runRelayService() error { }) warnOnErr(egCtx, eg.Wait(), log) + shutdownEmitter(process, asyncEmitter, exitEvent.Payload, log) + warnOnErr(ctx, hb.Close(), log) warnOnErr(ctx, conn.Close(), log) diff --git a/lib/service/service.go b/lib/service/service.go index 26c02f43a576..28c25ebf8168 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -3118,6 +3118,9 @@ func (process *TeleportProcess) initAuthService() error { } } } + if fallbackEmitter != nil { + shutdownEmitter(process, fallbackEmitter, payload, logger) + } logger.InfoContext(process.ExitContext(), "Exited.") }) @@ -3967,6 +3970,8 @@ func (process *TeleportProcess) initSSH() error { relayTunnelClient.Close() } + shutdownEmitter(process, asyncEmitter, event.Payload, logger) + logger.InfoContext(process.ExitContext(), "Exited.") return nil }) @@ -5458,7 +5463,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { _ = peerQUICTransport.Close() _ = peerQUICTransport.Conn.Close() } - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) + shutdownEmitter(process, asyncEmitter, payload, logger) warnOnErr(process.ExitContext(), conn.Close(), logger) logger.InfoContext(process.ExitContext(), "Exited") }) @@ -7273,11 +7278,8 @@ func (process *TeleportProcess) initApps() { logger.InfoContext(process.ExitContext(), "Shutting down gracefully.") warnOnErr(process.ExitContext(), appServer.Shutdown(payloadContext(payload)), logger) } - if asyncEmitter != nil { - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) - } agentPool.Stop() - warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger) + shutdownEmitter(process, asyncEmitter, payload, logger) warnOnErr(process.ExitContext(), conn.Close(), logger) logger.InfoContext(process.ExitContext(), "Exited.") }) @@ -7291,6 +7293,27 @@ func (process *TeleportProcess) initApps() { }) } +// drainableEmitter is an audit emitter whose queue can be drained on shutdown. +type drainableEmitter interface { + Shutdown(context.Context) error + Close() error +} + +const emitterDrainTimeout = time.Hour + +// shutdownEmitter drains the emitter's queue to the audit backend when payload +// carries a graceful shutdown context, otherwise it closes the emitter +// immediately. +func shutdownEmitter(process *TeleportProcess, emitter drainableEmitter, payload any, logger *slog.Logger) { + if payload == nil { + warnOnErr(process.ExitContext(), emitter.Close(), logger) + return + } + drainCtx, cancel := context.WithTimeout(payloadContext(payload), emitterDrainTimeout) + defer cancel() + warnOnErr(process.ExitContext(), emitter.Shutdown(drainCtx), logger) +} + // TODO(williamo/scopes): Update this validate function when we add support for these features. func validateScopedAppRegistration(isScoped bool, appName string, publicAddr, cloud string, mcp *types.MCP, llm *types.LLM, requiredApps []string, aws *servicecfg.AppAWS) error { if !isScoped { From e091f5c679fa3ba79cc48e469b3b11a0163528f1 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 28 Jul 2026 21:38:51 -0600 Subject: [PATCH 043/347] Deflake TestIntegrations/DiscoveryRecovers (#69109) Our integration tests pre-bind ports and inject them into the Teleport process as file descriptors so that the subprocess doesn't have to bind itself. Several tests were defeating this mechanism by tagging the proxy's SSH port with the wrong type (node SSH instead of proxy SSH). As a result, the pre-bound FD was closed and a new ephemeral port is bound from scratch. This creates a small window where some other parallel test can claim the port that was intended for this test. You can see this in the output of the failed runs: Closing imported but unused descriptor. type=node addr=localhost:39923 Setting up Proxy listeners Service is creating new listener. type=proxy:ssh addr=localhost:39923 Teleport process has exited with error listen tcp 127.0.0.1:39923: bind: address already in use TestIntegrations/Discovery recovers starts 8 proxies, so it hits the race most frequently, but the following tests are also impacted: - TestIntegrations/Discovery - TestIntegrations/DiscoveryNode - TestIntegrations/ReverseTunnelCollapse - TestKube/ExecWithNoAuth Closes #61486 --- integration/integration_test.go | 8 ++++---- integration/kube_integration_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index 5ccb0a68db28..05111b1b2650 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -3943,7 +3943,7 @@ func testDiscoveryRecovers(t *testing.T, suite *integrationTestSuite) { Name: name, DisableWebService: true, } - newConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerNodeSSH, &newConfig.FileDescriptors) + newConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxySSH, &newConfig.FileDescriptors) newConfig.WebAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyWeb, &newConfig.FileDescriptors) newConfig.ReverseTunnelAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyTunnel, &newConfig.FileDescriptors) reverseTunnelAddr = newConfig.ReverseTunnelAddr @@ -4074,7 +4074,7 @@ func testDiscovery(t *testing.T, suite *integrationTestSuite) { Name: "cluster-main-proxy", DisableWebService: true, } - proxyConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerNodeSSH, &proxyConfig.FileDescriptors) + proxyConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxySSH, &proxyConfig.FileDescriptors) proxyConfig.WebAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyWeb, &proxyConfig.FileDescriptors) proxyConfig.ReverseTunnelAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyTunnel, &proxyConfig.FileDescriptors) @@ -4206,7 +4206,7 @@ func testReverseTunnelCollapse(t *testing.T, suite *integrationTestSuite) { DisableWebInterface: true, DisableALPNSNIListener: true, } - proxyConfig.SSHAddr = helpers.NewListener(t, service.ListenerNodeSSH, &proxyConfig.FileDescriptors) + proxyConfig.SSHAddr = helpers.NewListener(t, service.ListenerProxySSH, &proxyConfig.FileDescriptors) proxyConfig.WebAddr = helpers.NewListener(t, service.ListenerProxyWeb, &proxyConfig.FileDescriptors) proxyConfig.ReverseTunnelAddr = helpers.NewListener(t, service.ListenerProxyTunnel, &proxyConfig.FileDescriptors) @@ -4355,7 +4355,7 @@ func testDiscoveryNode(t *testing.T, suite *integrationTestSuite) { Name: "cluster-main-proxy", DisableWebService: true, } - proxyConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerNodeSSH, &proxyConfig.FileDescriptors) + proxyConfig.SSHAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxySSH, &proxyConfig.FileDescriptors) proxyConfig.WebAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyWeb, &proxyConfig.FileDescriptors) proxyConfig.ReverseTunnelAddr = helpers.NewListenerOn(t, main.Hostname, service.ListenerProxyTunnel, &proxyConfig.FileDescriptors) diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index bc47bc501351..7af815f01f83 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -3000,7 +3000,7 @@ func testExecNoAuth(t *testing.T, suite *KubeSuite) { DisableWebService: true, DisableALPNSNIListener: true, } - proxyConfig.SSHAddr = helpers.NewListenerOn(t, teleport.Hostname, service.ListenerNodeSSH, &proxyConfig.FileDescriptors) + proxyConfig.SSHAddr = helpers.NewListenerOn(t, teleport.Hostname, service.ListenerProxySSH, &proxyConfig.FileDescriptors) proxyConfig.WebAddr = helpers.NewListenerOn(t, teleport.Hostname, service.ListenerProxyWeb, &proxyConfig.FileDescriptors) proxyConfig.KubeAddr = helpers.NewListenerOn(t, teleport.Hostname, service.ListenerProxyKube, &proxyConfig.FileDescriptors) proxyConfig.ReverseTunnelAddr = helpers.NewListenerOn(t, teleport.Hostname, service.ListenerProxyTunnel, &proxyConfig.FileDescriptors) From 4c016a49476af2f126f2cc7c3a7b8ce378a3b696 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Wed, 29 Jul 2026 10:30:51 +0100 Subject: [PATCH 044/347] Update scopes documentation to cover Scoped SPIFFE (#69057) * Update scopes doc to cover scoped spiffe * slightly tweak docs * Address paul's feedback --- .../rbac-get-started/scopes.mdx | 140 +++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx index d871abee7b62..22233b75a420 100644 --- a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx +++ b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx @@ -94,6 +94,8 @@ be assumed unsupported in scoped mode. grant scoped roles to groups of users. - Scoped Bots for Machine and Workload Identity (MWI), supporting both identity output and SSH access. +- Scoped Workload Identity resources for issuing SPIFFE credentials to + workloads within a scope. ## Basic usage @@ -709,7 +711,7 @@ sub_kind: dynamic spec: user: my-scope-admin assignments: - - role: staging-scope-mwi-admin + - role: /staging::staging-scope-mwi-admin scope: /staging ``` @@ -738,7 +740,7 @@ sub_kind: dynamic spec: bot: /staging::my-scoped-bot assignments: - - role: staging-ssh-access + - role: /staging::staging-ssh-access scope: /staging ``` @@ -785,3 +787,137 @@ services: Once `tbot` is running, the credentials it produces can be used to access resources within the scope using `tsh` or `tctl` with the identity file, or with `ssh` using the OpenSSH configuration files generated by `tbot`. + +### Scoped SPIFFE + +The standard `workload_identity` resource supports the `scope` field. When +set, the Workload Identity is considered scoped. Scoped Workload Identities +can be created, read, updated, and deleted by scope admins through scoped +roles and scoped role assignments, and can be used by scoped Bots to issue +SPIFFE credentials to workloads. A scoped Workload Identity cannot be +defined in the root scope `/`. See the +[Workload Identity resource reference](../../reference/machine-workload-identity/workload-identity/workload-identity-resource.mdx) +for the available fields. + +#### SPIFFE ID structure + +A scoped Workload Identity must specify a SPIFFE ID that encodes the scope +in which it is defined. The path of the SPIFFE ID consists of three +sections: + +- The scope section: segments that match the scope in which the Workload + Identity is defined. +- The separator segment: `/_/`. +- The administratively-defined section: one or more segments freely chosen + by the administrator. + +For example, a Workload Identity defined in `/staging` may specify the +SPIFFE ID `/staging/_/payments/api`. Workloads are then issued SVIDs with +the following SPIFFE ID: + +```text +spiffe://example.teleport.sh/staging/_/payments/api +``` + +The following rules apply to the SPIFFE ID of a scoped Workload Identity: + +- The scope section must exactly match the scope in which the Workload + Identity is defined. It cannot be an ancestor or descendant of that + scope. This means the scope responsible for a SPIFFE ID can always be + determined from the ID alone, and admins of one scope cannot issue SPIFFE + IDs that appear to belong to another scope. +- The scope section must be immediately followed by the separator segment. +- The administratively-defined section must contain at least one segment, + and must not itself contain the separator segment, so that a scoped + SPIFFE ID contains exactly one separator. +- Templating within the SPIFFE ID is supported. These rules are enforced + against the rendered SPIFFE ID at issuance time, so template attributes + cannot be used to escape the scope. + +#### Granting issuance to a scoped Bot + +Issuance is controlled by the `workload_identity` section of a scoped role, +which selects the Workload Identities that the role permits issuance with +by label. Issuance additionally requires the `readnosecrets` verb (and, +when issuing by label selector, `list`) for the `workload_identity` +resource in the role's `rules`. + +#### Configuring `tbot` in scoped mode + +In scoped mode, `tbot` supports the `workload-identity-x509`, +`workload-identity-jwt`, and `workload-identity-api` services. When +selecting a scoped Workload Identity by name, use its scope-qualified name, +for example `/staging::staging-payments-api`. + +### Scoped SPIFFE example + +The following example builds on the scoped MWI example above, granting the +scoped Bot `my-scoped-bot` the ability to issue SPIFFE SVIDs. It assumes the +scope administrator's role also grants write access to the `workload_identity` resource. + +As the scope administrator, create a scoped Workload Identity: + +```yaml +kind: workload_identity +version: v1 +metadata: + name: staging-payments-api + labels: + app: payments +scope: /staging +spec: + spiffe: + id: /staging/_/payments/api +``` + +Create a scoped role permitting issuance with Workload Identities labeled +`app: payments`, and assign it to the Bot: + +```yaml +kind: scoped_role +version: v1 +metadata: + name: staging-spiffe-issuer +scope: /staging +spec: + assignable_scopes: + - /staging + workload_identity: + labels: + - name: app + values: [payments] + rules: + - resources: [workload_identity] + verbs: [list, readnosecrets] +--- +kind: scoped_role_assignment +version: v1 +metadata: + name: 9c41d3be-72af-4d9e-8e5a-1f3b6c0d47e1 +scope: /staging +sub_kind: dynamic +spec: + bot: /staging::my-scoped-bot + assignments: + - role: /staging::staging-spiffe-issuer + scope: /staging +``` + +Add a Workload Identity service to the `tbot` configuration from the +previous example, selecting the Workload Identity by its scope-qualified +name: + +```yaml +services: + - type: workload-identity-api + listen: unix:///opt/machine-id/workload.sock + selector: + name: /staging::staging-payments-api +``` + +Workloads connecting to the SPIFFE Workload API socket are issued SVIDs +with the following SPIFFE ID: + +```text +spiffe://example.teleport.sh/staging/_/payments/api +``` From 431fcf308cabb33d92df107c1c5faf2caa14831b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Smoli=C5=84ski?= Date: Wed, 29 Jul 2026 12:49:36 +0200 Subject: [PATCH 045/347] GenericReconciler: distinguish between sequential and parallel reconciliation flows (#66314) --- lib/services/reconciler.go | 62 ++++++++++++++++++++++++--------- lib/services/reconciler_test.go | 44 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/lib/services/reconciler.go b/lib/services/reconciler.go index ecdfff61dd14..3c7b8d8ed578 100644 --- a/lib/services/reconciler.go +++ b/lib/services/reconciler.go @@ -304,14 +304,55 @@ func (r *GenericReconciler[K, T]) Reconcile(ctx context.Context) error { start := time.Now() + var errs []error + if r.cfg.Concurrency > 1 { + errs = r.reconcileConcurrent(ctx, currentResources, newResources) + } else { + errs = r.reconcileSequential(ctx, currentResources, newResources) + } + + if r.stats.hasChanges() { + r.logger.InfoContext(ctx, "Reconciliation completed", + "kind", r.resourceKind(currentResources, newResources), + "took", time.Since(start).String(), + "stats", r.stats, + ) + } + + // TODO(zmb3): with a large number of resources, this can return a lengthy + // error message that is difficult to parse + return trace.NewAggregate(errs...) +} + +// reconcileSequential processes resources inline without goroutines. +// This is used when Concurrency == 1 (the default) to avoid unnecessary +// goroutine creation overhead. +func (r *GenericReconciler[K, T]) reconcileSequential(ctx context.Context, currentResources, newResources map[K]T) []error { + var errs []error + // Process already registered resources to see if any of them were removed. + for key, current := range currentResources { + if err := r.processRegisteredResource(ctx, newResources, key, current); err != nil { + errs = append(errs, trace.Wrap(err)) + } + } + // Add new resources if there are any or refresh those that were updated. + for key, newResource := range newResources { + if err := r.processNewResource(ctx, currentResources, key, newResource); err != nil { + errs = append(errs, trace.Wrap(err)) + } + } + return errs +} + +// reconcileConcurrent processes resources using an errgroup with the configured +// concurrency limit. This is used when Concurrency > 1. +func (r *GenericReconciler[K, T]) reconcileConcurrent(ctx context.Context, currentResources, newResources map[K]T) []error { var g errgroup.Group g.SetLimit(r.cfg.Concurrency) - var ( mu sync.Mutex errs []error ) - // Process already registered resources to see if any of them were removed. for key, current := range currentResources { g.Go(func() error { @@ -323,7 +364,6 @@ func (r *GenericReconciler[K, T]) Reconcile(ctx context.Context) error { return nil }) } - // Add new resources if there are any or refresh those that were updated. for key, newResource := range newResources { g.Go(func() error { @@ -335,21 +375,9 @@ func (r *GenericReconciler[K, T]) Reconcile(ctx context.Context) error { return nil }) } - - // Error are collected separately. + // Errors are collected separately. _ = g.Wait() - - if r.stats.hasChanges() { - r.logger.InfoContext(ctx, "Reconciliation completed", - "kind", r.resourceKind(currentResources, newResources), - "took", time.Since(start), - "stats", r.stats, - ) - } - - // TODO(zmb3): with a large number of resources, this can return a lengthy - // error message that is difficult to parse - return trace.NewAggregate(errs...) + return errs } // resourceKind extracts the resource kind from the first available resource. diff --git a/lib/services/reconciler_test.go b/lib/services/reconciler_test.go index 6ce790870a67..683ae6666975 100644 --- a/lib/services/reconciler_test.go +++ b/lib/services/reconciler_test.go @@ -21,6 +21,7 @@ package services import ( "context" "fmt" + "log/slog" "maps" "sync" "testing" @@ -529,3 +530,46 @@ func (r testResource) GetName() string { func (r testResource) GetKind() string { return "testResource" } + +// BenchmarkReconciler benchmarks reconciliation with varying resource counts +// and concurrency levels to measure overhead differences between sequential +// and concurrent modes. +func BenchmarkReconciler(b *testing.B) { + for _, resourceCount := range []int{100, 1000, 100000} { + b.Run(fmt.Sprintf("%d", resourceCount), func(b *testing.B) { + benchmarkReconciler(b, resourceCount, 1) + }) + } +} + +func benchmarkReconciler(b *testing.B, resourceCount, concurrency int) { + b.Helper() + b.ReportAllocs() + + labels := map[string]string{"env": "prod"} + currentResources := make(map[int]testResource, resourceCount) + for i := range resourceCount { + currentResources[i] = makeDynamicResource(fmt.Sprintf("res%d", i), maps.Clone(labels)) + } + r, err := NewGenericReconciler(GenericReconcilerConfig[int, testResource]{ + Logger: slog.New(slog.DiscardHandler), + Matcher: func(tr testResource) bool { return true }, + CompareResources: func(tr1, tr2 testResource) int { return 0 }, + GetCurrentResources: func() map[int]testResource { return currentResources }, + GetNewResources: func() map[int]testResource { return currentResources }, + OnCreate: func(ctx context.Context, tr testResource) error { return nil }, + OnUpdate: func(ctx context.Context, tr, old testResource) error { return nil }, + OnDelete: func(ctx context.Context, tr testResource) error { return nil }, + Concurrency: concurrency, + }) + if err != nil { + b.Fatal(err) + } + ctx := context.Background() + b.ResetTimer() + for range b.N { + if err := r.Reconcile(ctx); err != nil { + b.Fatal(err) + } + } +} From 36f493d587d1d78e7604b890c4aa7fff31ce77e9 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:57:42 -0400 Subject: [PATCH 046/347] [e2e] Allow e2e tests to declare a custom Teleport config (#68769) * add support for e2e tests to declare a custom teleport config * CR * require configs to be in test.describe block --- e2e/README.md | 23 ++ e2e/helpers/test.ts | 9 + e2e/runner/go.mod | 2 +- e2e/runner/main.go | 10 + e2e/runner/playwright.go | 195 ++++++++++------- e2e/runner/scan.go | 204 ++++++++++++++++-- e2e/runner/scan_test.go | 138 +++++++++++- e2e/runner/teleport_config.go | 70 ++++++ .../web/authenticated/customConfig.spec.ts | 44 ++++ 9 files changed, 607 insertions(+), 88 deletions(-) create mode 100644 e2e/runner/teleport_config.go create mode 100644 e2e/tests/web/authenticated/customConfig.spec.ts diff --git a/e2e/README.md b/e2e/README.md index 509b3a906797..fc09b3cffe81 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -172,6 +172,29 @@ the same `user`/`users`. Concretely: `e2e/.auth/-.json`. Tests pick up that state via Playwright's `storageState`, so they start already authenticated without running the UI login flow. +### Teleport config + +If a test requires a Teleport config different from the base config that e2e tests use by default, it can declare one +with `test.use({ teleport: { config: {...} } })` inside a `test.describe()` block. +Values are evaluated as a JS object literal, so they must be static (no imports or function calls). + +```ts +test.describe('custom license', () => { + test.use({ + teleport: { + config: { + auth_service: { + license_file: '${E2E_DIR}/testdata/licenses/custom-license.pem', + }, + }, + }, + }); +}); +``` + +Note that the e2e runner will only restart Teleport with the different config and not re-initialize it from fresh, so the data directory +will remains the same for all tests which means the cluster's identity like cluster name, CA, bootstrapped users, and roles will carry over unchanged. + ### Session Recordings The runner automatically seeds session recordings into Teleport's data directory at startup so the Web UI's diff --git a/e2e/helpers/test.ts b/e2e/helpers/test.ts index f1b40420403e..3eb5b7f8abc5 100644 --- a/e2e/helpers/test.ts +++ b/e2e/helpers/test.ts @@ -58,6 +58,13 @@ export interface UserDefinition { loginAs?: boolean; } +/** + * TeleportOption lets a test declare a custom Teleport cluster config to use. + **/ +export interface TeleportOption { + config: Record; +} + const e2eDir = process.env.E2E_DIR ?? join(dirname(fileURLToPath(import.meta.url)), '..'); const authDir = join(e2eDir, '.auth'); @@ -75,6 +82,7 @@ interface E2EFixtures { recordings: string[]; user: UserDefinition; users: UserDefinition[]; + teleport: TeleportOption; username: string; loginAs: (index: number) => Promise; recordingIds: Record; @@ -95,6 +103,7 @@ export const test = base.extend({ recordings: [[], { option: true }], user: [undefined as unknown as UserDefinition, { option: true }], users: [[], { option: true }], + teleport: [undefined as unknown as TeleportOption, { option: true }], username: async ({ user, users }, use, testInfo) => { const mapping = tryLoadUserMapping() ?? {}; diff --git a/e2e/runner/go.mod b/e2e/runner/go.mod index e689479d1f94..fb8ac2007008 100644 --- a/e2e/runner/go.mod +++ b/e2e/runner/go.mod @@ -12,6 +12,7 @@ require ( github.com/lmittmann/tint v1.1.3 github.com/moby/moby/api v1.54.2 github.com/moby/moby/client v0.4.1 + github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.54.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.22.0 @@ -235,7 +236,6 @@ require ( github.com/sirupsen/logrus v1.9.4 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/stretchr/objx v0.5.3 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/e2e/runner/main.go b/e2e/runner/main.go index fbce7e935287..5929200abc76 100644 --- a/e2e/runner/main.go +++ b/e2e/runner/main.go @@ -146,6 +146,11 @@ type e2eConfig struct { instances []*testInstance connectInstance *testInstance + + // teleportConfigs are the unique custom Teleport configs declared by tests. + teleportConfigs []uniqueTeleportConfig + // defaultTestFiles are the selectors for the tests that run against the base config. + defaultTestFiles []string } // run sets up the test environment (ports, certs, credentials, teleport instance) @@ -291,6 +296,11 @@ func run(flags *e2eFlags, mode runMode, e2eDir string, isCI bool) error { } slog.Debug("discovered bootstrap users", "count", len(scannedUsers)) + config.teleportConfigs, config.defaultTestFiles, err = scanTeleportConfigs(targets) + if err != nil { + return fmt.Errorf("failed to scan teleport configs: %w", err) + } + bootstrap, err := buildBootstrapState(e2eDir, scannedUsers) if err != nil { return fmt.Errorf("failed to build bootstrap state: %w", err) diff --git a/e2e/runner/playwright.go b/e2e/runner/playwright.go index 9038e682b972..971145373d97 100644 --- a/e2e/runner/playwright.go +++ b/e2e/runner/playwright.go @@ -29,6 +29,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" "golang.org/x/sync/errgroup" ) @@ -37,6 +38,8 @@ type playwrightRunner struct { config *e2eConfig } +var baseProjects = []string{"authenticated", "unauthenticated"} + func (p *playwrightRunner) startURL(inst *testInstance) string { if p.config.teleportURL != "" { return p.config.teleportURL + "/web" @@ -95,8 +98,6 @@ func (p *playwrightRunner) test(ctx context.Context, debug bool) error { return fmt.Errorf("cleaning blob-reports directory: %w", err) } - baseProjects := []string{"authenticated", "unauthenticated"} - var extraArgs []string if p.config.updateSnapshots { extraArgs = append(extraArgs, "--update-snapshots") @@ -108,83 +109,13 @@ func (p *playwrightRunner) test(ctx context.Context, debug bool) error { for _, inst := range p.config.instances { g.Go(func() error { - if err := inst.start(ctx); err != nil { - return err - } - defer inst.stop() - - env, err := p.startEnv(inst) - if err != nil { - return fmt.Errorf("building env for %s: %w", inst.browser, err) - } - if debug { - env = append(env, "PWDEBUG=1") - } - - env = append(env, "PLAYWRIGHT_BLOB_OUTPUT_FILE="+filepath.Join(blobBaseDir, inst.browser+".zip")) - - args := []string{"exec", "playwright", "test", p.configFlag()} - args = append(args, extraArgs...) - args = append(args, "--reporter=blob,"+filepath.Join(p.config.sharedDir, "scripts", "dot-progress-reporter.ts")) - // Avoid `.playwright-artifacts-` collisions across parallel pnpm runs. - args = append(args, "--output=test-results/"+inst.browser) - - for _, proj := range baseProjects { - args = append(args, "--project="+inst.browser+":"+proj) - } - - if len(p.config.testFiles) > 0 { - args = append(args, p.config.testFiles...) - } - - if len(p.config.testFiles) > 0 { - inst.log.Info("running e2e tests", "files", p.config.testFiles) - } else { - inst.log.Info("running e2e tests", "projects", baseProjects) - } - - if err := p.pnpm(ctx, args, env); err != nil { - return fmt.Errorf("playwright tests failed for %s: %w", inst.browser, err) - } - return nil + return p.runInstance(ctx, inst, blobBaseDir, debug, extraArgs) }) } if ci := p.config.connectInstance; ci != nil { g.Go(func() error { - if err := ci.start(ctx); err != nil { - return err - } - defer ci.stop() - - env, err := p.startEnv(ci) - if err != nil { - return fmt.Errorf("building env for connect: %w", err) - } - if debug { - env = append(env, "PWDEBUG=1") - } - - env = append(env, "PLAYWRIGHT_BLOB_OUTPUT_FILE="+filepath.Join(blobBaseDir, "connect.zip")) - - args := []string{"exec", "playwright", "test", p.configFlag()} - args = append(args, extraArgs...) - args = append(args, "--reporter=blob,"+filepath.Join(p.config.sharedDir, "scripts", "dot-progress-reporter.ts"), "--project=connect", "--output=test-results/connect") - - if len(p.config.testFiles) > 0 { - args = append(args, p.config.testFiles...) - } - - if len(p.config.testFiles) > 0 { - ci.log.Info("running e2e tests", "files", p.config.testFiles) - } else { - ci.log.Info("running e2e tests", "projects", []string{"connect"}) - } - - if err := p.pnpm(ctx, args, env); err != nil { - return fmt.Errorf("playwright tests failed for connect: %w", err) - } - return nil + return p.runInstance(ctx, ci, blobBaseDir, debug, extraArgs) }) } @@ -213,6 +144,122 @@ func (p *playwrightRunner) test(ctx context.Context, debug bool) error { return testErr } +func (p *playwrightRunner) runInstance(ctx context.Context, inst *testInstance, blobBaseDir string, debug bool, extraArgs []string) error { + if err := inst.start(ctx); err != nil { + return err + } + defer inst.stop() + + hasConfigs := len(p.config.teleportConfigs) > 0 + defaultFiles := filesForProject(inst, p.config.testFiles) + if hasConfigs { + defaultFiles = filesForProject(inst, p.config.defaultTestFiles) + } + + // Skip the default pass if this instance has nothing to run against the base config + if !hasConfigs || len(defaultFiles) > 0 { + blobPath := filepath.Join(blobBaseDir, inst.browser+".zip") + if err := p.runInstanceTests(ctx, inst, defaultFiles, blobPath, debug, extraArgs); err != nil { + return err + } + } + + baseConfigPath := inst.teleportConfigPath + for i, cfg := range p.config.teleportConfigs { + files := filesForProject(inst, cfg.files) + if len(files) == 0 { + continue // no tests for this instance's project + } + if err := p.runTeleportConfig(ctx, inst, baseConfigPath, cfg, files, i, blobBaseDir, debug, extraArgs); err != nil { + return err + } + } + return nil +} + +// filesForProject picks the connect specs or browser specs for a testInstance +func filesForProject(inst *testInstance, files []string) []string { + if len(files) == 0 { + return files + } + var out []string + for _, f := range files { + isConnect := strings.HasPrefix(filepath.ToSlash(f), "tests/connect/") + if isConnect == (inst.browser == "connect") { + out = append(out, f) + } + } + return out +} + +// runInstanceTests runs Playwright against a running Teleport instance for the +// given test files. If `files` is empty, all tests are run. +func (p *playwrightRunner) runInstanceTests(ctx context.Context, inst *testInstance, files []string, blobPath string, debug bool, extraArgs []string) error { + env, err := p.startEnv(inst) + if err != nil { + return fmt.Errorf("building env for %s: %w", inst.browser, err) + } + if debug { + env = append(env, "PWDEBUG=1") + } + env = append(env, "PLAYWRIGHT_BLOB_OUTPUT_FILE="+blobPath) + + args := []string{"exec", "playwright", "test", p.configFlag()} + args = append(args, extraArgs...) + args = append(args, "--reporter=blob,"+filepath.Join(p.config.sharedDir, "scripts", "dot-progress-reporter.ts")) + // Avoid `.playwright-artifacts-` collisions across parallel pnpm runs. + args = append(args, "--output=test-results/"+inst.browser) + if inst.browser == "connect" { + args = append(args, "--project=connect") + } else { + for _, proj := range baseProjects { + args = append(args, "--project="+inst.browser+":"+proj) + } + } + args = append(args, files...) + + if len(files) > 0 { + inst.log.Info("running e2e tests", "files", files) + } else { + inst.log.Info("running e2e tests", "projects", baseProjects) + } + + if err := p.pnpm(ctx, args, env); err != nil { + return fmt.Errorf("playwright tests failed for %s: %w", inst.browser, err) + } + return nil +} + +// runTeleportConfig re-inits the instance's Teleport with a test-declared config. +func (p *playwrightRunner) runTeleportConfig(ctx context.Context, inst *testInstance, baseConfigPath string, cfg uniqueTeleportConfig, files []string, idx int, blobBaseDir string, debug bool, extraArgs []string) error { + inst.log.Info("re-initializing teleport with a test-declared config", "files", files) + + inst.teleport.stop() + + mergedPath := filepath.Join(p.config.e2eDir, "config", fmt.Sprintf("%s-teleport-config-%d.yaml", inst.browser, idx)) + if err := mergeTeleportConfig(baseConfigPath, mergedPath, p.config.e2eDir, cfg.raw); err != nil { + return fmt.Errorf("merging teleport config for %s: %w", inst.browser, err) + } + inst.teleport.configPath = mergedPath + inst.teleportConfigPath = mergedPath + + if err := inst.teleport.start(ctx); err != nil { + return fmt.Errorf("re-initializing teleport for %s: %w", inst.browser, err) + } + if err := inst.teleport.waitReady(ctx, 30*time.Second); err != nil { + return fmt.Errorf("teleport for %s not ready after config change: %w", inst.browser, err) + } + + if inst.node != nil { + if err := inst.node.waitJoined(ctx, 30*time.Second); err != nil { + return fmt.Errorf("node for %s failed to rejoin: %w", inst.browser, err) + } + } + + blobPath := filepath.Join(blobBaseDir, fmt.Sprintf("%s-config-%d.zip", inst.browser, idx)) + return p.runInstanceTests(ctx, inst, files, blobPath, debug, extraArgs) +} + func (p *playwrightRunner) ui(ctx context.Context) error { slog.Info("starting playwright in UI mode") diff --git a/e2e/runner/scan.go b/e2e/runner/scan.go index faf29a16c60a..fabf5f5a18cb 100644 --- a/e2e/runner/scan.go +++ b/e2e/runner/scan.go @@ -29,6 +29,7 @@ import ( "slices" "strconv" "strings" + "unicode" "github.com/gravitational/teleport/e2e/runner/fixtures" ) @@ -39,6 +40,9 @@ var ( fixtureRefRe = regexp.MustCompile(`['"]([^'"]+)['"]`) helperImportRe = regexp.MustCompile(`from\s+['"]@gravitational/e2e/helpers/(\w+)['"]`) roleFileRe = regexp.MustCompile(`\bfile:\s*['"]@gravitational/e2e/roles/([^'"]+)['"]`) + describeTitleRe = regexp.MustCompile(`\.describe\(\s*['"]([^'"]*)['"]`) + testDefRe = regexp.MustCompile(`\btest(?:\.(?:only|skip|fixme))?\(\s*['"]`) + teleportKeyRe = regexp.MustCompile(`\bteleport\s*:`) // The "key" regexes below require a word boundary so identifiers like // `super_user:` or `myRoles:` don't match as `user:` / `roles:`. @@ -282,13 +286,11 @@ func parseHelperImports(path string) []string { } func scanFile(path string, targetLine int) []*fixtures.Fixture { - data, err := os.ReadFile(path) - if err != nil { + cleaned, ok := readCleaned(path) + if !ok { return nil } - lines := strings.Split(string(data), "\n") - cleaned := stripComments(lines) blocks := parseBlocks(cleaned) content := strings.Join(cleaned, "\n") @@ -311,6 +313,16 @@ func scanFile(path string, targetLine int) []*fixtures.Fixture { return result } +// readCleaned reads a spec file and returns its lines with comments stripped. +func readCleaned(path string) ([]string, bool) { + data, err := os.ReadFile(path) + if err != nil { + slog.Warn("scan: could not read file", "path", path, "error", err) + return nil, false + } + return stripComments(strings.Split(string(data), "\n")), true +} + func stripComments(lines []string) []string { cleaned := make([]string, len(lines)) inBlock := false @@ -577,17 +589,11 @@ func defaultUsers() []scannedUser { // are rejected if they declare user/users/recordings since runtime would // merge them into every importing spec. func scanFileUsers(path string, targetLine int, sourceFile string) ([]scannedUser, error) { - data, err := os.ReadFile(path) - if err != nil { - // Helper module paths are guessed from import names (see - // resolveTargetsWithHelpers), so the file may not exist. Warn - // but continue rather than failing the whole scan. - slog.Warn("scan: could not read file", "path", path, "error", err) + cleaned, ok := readCleaned(path) + if !ok { return nil, nil } - lines := strings.Split(string(data), "\n") - cleaned := stripComments(lines) blocks := parseBlocks(cleaned) content := strings.Join(cleaned, "\n") @@ -1109,3 +1115,177 @@ func sortRoles(roles []scannedRole) { return strings.Compare(a.file, b.file) }) } + +// uniqueTeleportConfig is a distinct custom Teleport config declared by one or more +// test files, and the test files that declare it. +type uniqueTeleportConfig struct { + // raw is the config object's raw JS text. + raw string + // files are the test files that declared this config. + files []string +} + +// scanTeleportConfigs finds the unique custom Teleport configs declared by tests +// and the base-config selectors (tests that run without one). +func scanTeleportConfigs(targets []scanTarget) ([]uniqueTeleportConfig, []string, error) { + byKey := make(map[string]*uniqueTeleportConfig) + var order []string + var defaults []string + + for _, t := range targets { + cleaned, ok := readCleaned(t.path) + if !ok { + continue + } + content := strings.Join(cleaned, "\n") + + // A config declared in a helper has no describe to scope it to so it should be rejected + if t.sourceFile == "" { + for _, call := range findTestUseCalls(content) { + if open, _ := findKeyValueAtDepth(content[call.start:call.end], "teleport", '{', '}', 1); open >= 0 { + return nil, nil, fmt.Errorf( + "%s:%d: helper modules cannot declare teleport: in test.use()", + t.path, 1+strings.Count(content[:call.start], "\n")) + } + } + continue + } + + configs, err := extractTeleportConfigs(content, parseBlocks(cleaned), t.path) + if err != nil { + return nil, nil, err + } + + for _, sc := range configs { + key := normalizeConfigText(sc.raw) + u, ok := byKey[key] + if !ok { + u = &uniqueTeleportConfig{raw: sc.raw} + byKey[key] = u + order = append(order, key) + } + u.files = append(u.files, fileSelector(t.sourceFile, sc.line)) + } + + defaults = append(defaults, defaultSelectors(t.sourceFile, content, configs, t.line)...) + } + + result := make([]uniqueTeleportConfig, 0, len(order)) + for _, k := range order { + result = append(result, *byKey[k]) + } + return result, defaults, nil +} + +// defaultSelectors returns the selectors for this file's tests that run against +// the base config which is either the whole file if it declares no config, or each test +// defined outside a config-scoped describe. +func defaultSelectors(sourceFile, content string, configs []scopedTeleportConfig, line int) []string { + if len(configs) == 0 { + return []string{fileSelector(sourceFile, line)} + } + + var out []string + for _, loc := range testDefRe.FindAllStringIndex(content, -1) { + if inConfiguredBlock(loc[0], configs) { + continue + } + out = append(out, fileSelector(sourceFile, 1+strings.Count(content[:loc[0]], "\n"))) + } + return out +} + +func inConfiguredBlock(pos int, configs []scopedTeleportConfig) bool { + for _, c := range configs { + if pos > c.startByte && pos < c.endByte { + return true + } + } + return false +} + +// scopedTeleportConfig is a declared config and the describe block it's scoped to. +type scopedTeleportConfig struct { + raw string + line int + startByte, endByte int +} + +// extractTeleportConfigs returns every declared config with the describe it is +// scoped to. +func extractTeleportConfigs(content string, blocks []blockRange, path string) ([]scopedTeleportConfig, error) { + var out []scopedTeleportConfig + + for _, call := range findTestUseCalls(content) { + body := content[call.start:call.end] + + teleportOpen, teleportClose := findKeyValueAtDepth(body, "teleport", '{', '}', 1) + if teleportOpen < 0 { + if teleportKeyRe.MatchString(body) { + return nil, fmt.Errorf("%s: the teleport option in test.use() must be an inline object", path) + } + continue + } + + teleportBody := body[teleportOpen:teleportClose] + configOpen, configClose := findKeyValueAtDepth(teleportBody, "config", '{', '}', 1) + if configOpen < 0 { + return nil, fmt.Errorf("%s: test.use({ teleport }) must contain a `config` object", path) + } + + b := smallestEnclosingBlock(call.start, blocks) + if b == nil || describeTitle(content, b.startByte) == "" { + return nil, fmt.Errorf("%s: a teleport config must be declared inside a named test.describe block", path) + } + + sc := scopedTeleportConfig{ + raw: teleportBody[configOpen:configClose], + line: b.start, + startByte: b.startByte, + endByte: b.endByte, + } + for _, existing := range out { + if existing.line == sc.line && normalizeConfigText(existing.raw) != normalizeConfigText(sc.raw) { + return nil, fmt.Errorf("%s: conflicting teleport configs declared in the same describe", path) + } + } + out = append(out, sc) + } + + return out, nil +} + +// describeTitle returns the title of the innermost test.describe enclosing the +// block that opens at blockStart, or "" if it isn't a named describe +func describeTitle(content string, blockStart int) string { + ms := describeTitleRe.FindAllStringSubmatch(content[:blockStart], -1) + if len(ms) == 0 { + return "" + } + return ms[len(ms)-1][1] +} + +// normalizeConfigText gets rid of whitespace. +func normalizeConfigText(s string) string { + var b strings.Builder + space := false + for _, r := range s { + if unicode.IsSpace(r) { + space = true + continue + } + if space && b.Len() > 0 { + b.WriteByte(' ') + } + space = false + b.WriteRune(r) + } + return b.String() +} + +func fileSelector(sourceFile string, line int) string { + if line > 0 { + return fmt.Sprintf("%s:%d", sourceFile, line) + } + return sourceFile +} diff --git a/e2e/runner/scan_test.go b/e2e/runner/scan_test.go index 5da2fd1a0eed..c670d353f4ce 100644 --- a/e2e/runner/scan_test.go +++ b/e2e/runner/scan_test.go @@ -26,6 +26,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/e2e/runner/fixtures" ) @@ -564,7 +566,7 @@ func TestScanRecordings(t *testing.T) { }, { name: "no recordings", - content: `test.use({ user: { roles: ['access'] } });`, + content: `test.use({ user: { roles: ['access'] } });`, wantUsers: 1, wantRecordings: nil, wantLoginAs: true, @@ -1215,3 +1217,137 @@ func fixtureNames(ff []*fixtures.Fixture) []string { } return names } + +func TestExtractTeleportConfigs(t *testing.T) { + tests := []struct { + name string + content string + want []scopedTeleportConfig + wantErr bool + }{ + { + name: "no declaration", + content: `test.use({ user: { roles: ['access'] } });`, + want: nil, + }, + { + name: "describe-scoped config carries its line", + content: `test.describe('grp', () => { + test.use({ teleport: { config: { a: 1 } } }); + test('x', () => {}); +});`, + want: []scopedTeleportConfig{{raw: `{ a: 1 }`, line: 1}}, + }, + { + name: "two describes with different configs", + content: `test.describe('one', () => { + test.use({ teleport: { config: { a: 1 } } }); +}); +test.describe('two', () => { + test.use({ teleport: { config: { a: 2 } } }); +});`, + want: []scopedTeleportConfig{ + {raw: `{ a: 1 }`, line: 1}, + {raw: `{ a: 2 }`, line: 4}, + }, + }, + { + name: "file-level config errors", + content: `test.use({ teleport: { config: { a: 1 } } });`, + wantErr: true, + }, + { + name: "conflicting configs in one describe errors", + content: `test.describe('grp', () => { + test.use({ teleport: { config: { a: 1 } } }); + test.use({ teleport: { config: { a: 2 } } }); +});`, + wantErr: true, + }, + { + name: "teleport without config errors", + content: `test.use({ teleport: { foo: 1 } });`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + blocks := parseBlocks(strings.Split(tt.content, "\n")) + got, err := extractTeleportConfigs(tt.content, blocks, "test.spec.ts") + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Len(t, got, len(tt.want)) + for i := range tt.want { + require.Equal(t, tt.want[i].raw, got[i].raw) + require.Equal(t, tt.want[i].line, got[i].line) + } + }) + } +} + +func TestDefaultSelectors(t *testing.T) { + content := `test.describe('configured', () => { + test.use({ teleport: { config: { a: 1 } } }); + test('in scope', () => {}); +}); +test('bare', () => {}); +test.describe('other', () => { + test('nested', () => {}); +});` + + configs, err := extractTeleportConfigs(content, parseBlocks(strings.Split(content, "\n")), "x.spec.ts") + require.NoError(t, err) + + got := defaultSelectors("tests/x.spec.ts", content, configs, 0) + require.Equal(t, []string{"tests/x.spec.ts:5", "tests/x.spec.ts:7"}, got) + + // A file with no config just runs as a whole + require.Equal(t, []string{"tests/x.spec.ts"}, defaultSelectors("tests/x.spec.ts", content, nil, 0)) +} + +func TestNormalizeConfigText(t *testing.T) { + // Differently-formatted but identical configs normalize the same (so they can be deduped). + a := normalizeConfigText("{ auth_service: { license_file: 'x.pem' } }") + b := normalizeConfigText("{\n auth_service: {\n license_file: 'x.pem'\n }\n}") + require.Equal(t, a, b) + + c := normalizeConfigText("{ auth_service: { license_file: 'y.pem' } }") + require.NotEqual(t, a, c) +} + +func TestScanTeleportConfigsRejectsHelperConfig(t *testing.T) { + dir := t.TempDir() + writeFile(t, dir, "helper.ts", ` +test.use({ + teleport: { + config: { proxy_service: { ssh_public_addr: ['example.com:3023'] } }, + }, +}); +`) + + _, _, err := scanTeleportConfigs([]scanTarget{{path: filepath.Join(dir, "helper.ts")}}) + require.Error(t, err) + require.Contains(t, err.Error(), "helper modules cannot declare teleport:") +} + +func TestScanTeleportConfigsAllowsHelperWithoutConfig(t *testing.T) { + dir := t.TempDir() + writeFile(t, dir, "helper.ts", `test.use({ fixtures: ['connect'] });`) + + _, _, err := scanTeleportConfigs([]scanTarget{{path: filepath.Join(dir, "helper.ts")}}) + require.NoError(t, err) +} + +func TestExtractTeleportConfigsRejectsNonInlineOption(t *testing.T) { + content := `test.describe('d', () => { + test.use({ teleport: customTeleport }); + test('t', async () => {}); +});` + _, err := extractTeleportConfigs(content, parseBlocks(strings.Split(content, "\n")), "spec.ts") + require.Error(t, err) + require.Contains(t, err.Error(), "must be an inline object") +} diff --git a/e2e/runner/teleport_config.go b/e2e/runner/teleport_config.go new file mode 100644 index 000000000000..a6e2f30a3415 --- /dev/null +++ b/e2e/runner/teleport_config.go @@ -0,0 +1,70 @@ +/** + * Teleport + * Copyright (C) 2026 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 main + +import ( + "fmt" + "os" + "strings" + + "gopkg.in/yaml.v3" +) + +// mergeTeleportConfig deep-merges a custom config into the base Teleport config at basePath and writes the +// result to outPath. +func mergeTeleportConfig(basePath, outPath, e2eDir, raw string) error { + raw = strings.ReplaceAll(raw, "${E2E_DIR}", e2eDir) + + var override map[string]any + if err := yaml.Unmarshal([]byte(raw), &override); err != nil { + return fmt.Errorf("parsing declared teleport config %q: %w", raw, err) + } + + baseData, err := os.ReadFile(basePath) + if err != nil { + return fmt.Errorf("reading base config %s: %w", basePath, err) + } + base := map[string]any{} + if err := yaml.Unmarshal(baseData, &base); err != nil { + return fmt.Errorf("parsing base config %s: %w", basePath, err) + } + + deepMerge(base, override) + + merged, err := yaml.Marshal(base) + if err != nil { + return fmt.Errorf("marshaling merged config: %w", err) + } + if err := os.WriteFile(outPath, merged, 0o644); err != nil { + return fmt.Errorf("writing merged config %s: %w", outPath, err) + } + return nil +} + +func deepMerge(dst, src map[string]any) { + for k, sv := range src { + if svMap, ok := sv.(map[string]any); ok { + if dvMap, ok := dst[k].(map[string]any); ok { + deepMerge(dvMap, svMap) + continue + } + } + dst[k] = sv + } +} diff --git a/e2e/tests/web/authenticated/customConfig.spec.ts b/e2e/tests/web/authenticated/customConfig.spec.ts new file mode 100644 index 000000000000..6de2f62a1a8a --- /dev/null +++ b/e2e/tests/web/authenticated/customConfig.spec.ts @@ -0,0 +1,44 @@ +/** + * Teleport + * Copyright (C) 2026 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 { expect, test } from '@gravitational/e2e/helpers/test'; + +// Verify that an e2e test can be run with a custom config +test.describe('custom config', () => { + test.use({ + teleport: { + config: { + proxy_service: { + ssh_public_addr: ['e2e-custom-config.example.com:3023'], + }, + }, + }, + }); + + test('runs against a test-declared custom Teleport config', async ({ + page, + }) => { + const response = await page.request.get('/webapi/ping'); + expect(response.ok()).toBeTruthy(); + + const ping = await response.json(); + expect(ping.proxy.ssh.ssh_public_addr).toBe( + 'e2e-custom-config.example.com:3023' + ); + }); +}); From 6d5fe8493400a009316f5cb58bf46e45fe49f9ae Mon Sep 17 00:00:00 2001 From: nixpig <143995476+nixpig@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:57:55 +0100 Subject: [PATCH 047/347] Export GeneratePureULS for use in e (#68993) * Export GeneratePureULS for use in e * Update doc comments --- lib/auth/user_login_state.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lib/auth/user_login_state.go diff --git a/lib/auth/user_login_state.go b/lib/auth/user_login_state.go new file mode 100644 index 000000000000..3a2ea0d8a913 --- /dev/null +++ b/lib/auth/user_login_state.go @@ -0,0 +1,30 @@ +// Teleport +// Copyright (C) 2026 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 auth + +import ( + "context" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/userloginstate" +) + +// GeneratePureULS is used to determine login state for the user. +// It emits no usage events and ignores any existing user login state in the backend. +func (a *Server) GeneratePureULS(ctx context.Context, user types.User) (*userloginstate.UserLoginState, error) { + return a.ulsGenerator.GeneratePureULS(ctx, user) +} From dd55b4113a740f0c9689905de583f8021f59172e Mon Sep 17 00:00:00 2001 From: Alan Parra <12500300+codingllama@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:26:00 -0300 Subject: [PATCH 048/347] Tweak the required/documented CreateCSR permissions (#68978) * Tweak the required/documented CreateCSR permissions * make grpc --- api/gen/proto/go/teleport/subca/v1/subca_service_grpc.pb.go | 6 ++++-- api/proto/teleport/subca/v1/subca_service.proto | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/api/gen/proto/go/teleport/subca/v1/subca_service_grpc.pb.go b/api/gen/proto/go/teleport/subca/v1/subca_service_grpc.pb.go index 3020496e8f37..a8200df9a1bf 100644 --- a/api/gen/proto/go/teleport/subca/v1/subca_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/subca/v1/subca_service_grpc.pb.go @@ -83,7 +83,8 @@ type SubCAServiceClient interface { // instead it will use local keys. In this case, each Auth server must be // contacted independently to create their corresponding CSRs. // - // CreateCSR requires cert_authority_override:create+update permissions. + // CreateCSR requires the cert_authority_override:create permission, which + // reflects its powers to write PendingCSRRequest resources to the backend. CreateCSR(ctx context.Context, in *CreateCSRRequest, opts ...grpc.CallOption) (*CreateCSRResponse, error) // CreateCertAuthorityOverride creates a CertAuthorityOverride resource. // @@ -292,7 +293,8 @@ type SubCAServiceServer interface { // instead it will use local keys. In this case, each Auth server must be // contacted independently to create their corresponding CSRs. // - // CreateCSR requires cert_authority_override:create+update permissions. + // CreateCSR requires the cert_authority_override:create permission, which + // reflects its powers to write PendingCSRRequest resources to the backend. CreateCSR(context.Context, *CreateCSRRequest) (*CreateCSRResponse, error) // CreateCertAuthorityOverride creates a CertAuthorityOverride resource. // diff --git a/api/proto/teleport/subca/v1/subca_service.proto b/api/proto/teleport/subca/v1/subca_service.proto index d33a55751487..9cf6a1379b90 100644 --- a/api/proto/teleport/subca/v1/subca_service.proto +++ b/api/proto/teleport/subca/v1/subca_service.proto @@ -61,7 +61,8 @@ service SubCAService { // instead it will use local keys. In this case, each Auth server must be // contacted independently to create their corresponding CSRs. // - // CreateCSR requires cert_authority_override:create+update permissions. + // CreateCSR requires the cert_authority_override:create permission, which + // reflects its powers to write PendingCSRRequest resources to the backend. rpc CreateCSR(CreateCSRRequest) returns (CreateCSRResponse); // CreateCertAuthorityOverride creates a CertAuthorityOverride resource. From da651a4a74d5bde84d9ecc1ef3268219814cb0a6 Mon Sep 17 00:00:00 2001 From: Nibras Ohin Date: Wed, 29 Jul 2026 13:05:40 -0400 Subject: [PATCH 049/347] chore: added keyboard accessiblity support for menu item component (#69075) * chore: added keyboard accessiblity support for menu item component * chore: added focus trap management for modal --- web/packages/design/src/Menu/Menu.tsx | 17 +++++- web/packages/design/src/Modal/Modal.test.tsx | 39 +------------- web/packages/design/src/Modal/Modal.tsx | 33 ++++++++++-- web/packages/design/src/utils/testing.tsx | 42 +++++++++++++++ .../components/MenuAction/MenuAction.test.tsx | 53 +++++++++++++++++-- .../MenuAction/MenuActionButton.tsx | 2 + .../components/MenuAction/MenuActionIcon.tsx | 2 + 7 files changed, 143 insertions(+), 45 deletions(-) diff --git a/web/packages/design/src/Menu/Menu.tsx b/web/packages/design/src/Menu/Menu.tsx index 54e177d40d7b..e2eb5513d2fa 100644 --- a/web/packages/design/src/Menu/Menu.tsx +++ b/web/packages/design/src/Menu/Menu.tsx @@ -32,6 +32,7 @@ export default function Menu( onEntering?: (el: HTMLElement) => void; menuListCss?: CSSProp; menuListProps?: ComponentPropsWithoutRef; + autoFocus?: boolean; }> & Pick< PopoverProps, @@ -43,6 +44,7 @@ export default function Menu( | 'anchorOrigin' | 'transformOrigin' | 'updatePositionOnChildResize' + | 'trapFocus' > ) { const { @@ -51,6 +53,7 @@ export default function Menu( menuListCss, menuListProps, onEntering, + autoFocus, anchorOrigin = { vertical: 'bottom', horizontal: 'right' }, transformOrigin = { vertical: 'top', horizontal: 'right' }, ...other @@ -81,11 +84,15 @@ export default function Menu( menuList.style.width = `calc(100% + ${size})`; } + if (autoFocus) { + menuList.focus(); + } + if (onEntering) { onEntering(element); } }, - [onEntering] + [autoFocus, onEntering] ); return ( @@ -98,7 +105,12 @@ export default function Menu( getContentAnchorEl={defaultGetContentAnchorEl} {...other} > - + {children} @@ -111,6 +123,7 @@ export const MenuList = styled.div.attrs({ role: 'menu' })` box-shadow: ${props => props.theme.boxShadow[0]}; box-sizing: border-box; max-height: calc(100% - 96px); + outline: none; overflow: hidden; overflow-y: auto; position: relative; diff --git a/web/packages/design/src/Modal/Modal.test.tsx b/web/packages/design/src/Modal/Modal.test.tsx index 57e63f14c6a0..091e5f1eae89 100644 --- a/web/packages/design/src/Modal/Modal.test.tsx +++ b/web/packages/design/src/Modal/Modal.test.tsx @@ -20,7 +20,7 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useState } from 'react'; -import { fireEvent, render } from 'design/utils/testing'; +import { fireEvent, mockOffsetParent, render } from 'design/utils/testing'; import Modal, { ModalProps } from './Modal'; @@ -177,42 +177,7 @@ test('closing dialog when attachCustomKeyEventHandler is set only hides it with }); describe('trapFocus', () => { - let originalOffsetParent: PropertyDescriptor | undefined; - - beforeAll(() => { - // JSDOM doesn't implement layout, so offsetParent is always null. Mock it to return the - // parent element (non-null) for elements that aren't hidden via display:none on themselves or - // an ancestor. - originalOffsetParent = Object.getOwnPropertyDescriptor( - HTMLElement.prototype, - 'offsetParent' - ); - Object.defineProperty(HTMLElement.prototype, 'offsetParent', { - get(this: HTMLElement) { - // Walk up the ancestor chain — in real browsers, offsetParent is null when any - // ancestor has display:none. - let el: HTMLElement | null = this; - while (el) { - if (el.style.display === 'none') { - return null; - } - el = el.parentElement; // eslint-disable-line testing-library/no-node-access - } - return this.parentElement; // eslint-disable-line testing-library/no-node-access - }, - configurable: true, - }); - }); - - afterAll(() => { - if (originalOffsetParent) { - Object.defineProperty( - HTMLElement.prototype, - 'offsetParent', - originalOffsetParent - ); - } - }); + mockOffsetParent(); // In the following describe group, the focused element is removed during a re-render, leaving // lastModalFocus as a stale detached reference. Explicit keys ensure React unmounts the Removable diff --git a/web/packages/design/src/Modal/Modal.tsx b/web/packages/design/src/Modal/Modal.tsx index bdd9c08f28b8..d8a40764da66 100644 --- a/web/packages/design/src/Modal/Modal.tsx +++ b/web/packages/design/src/Modal/Modal.tsx @@ -105,12 +105,19 @@ export type ModalProps = { trapFocus?: boolean; }; +/** + * Modal instances with an active focus trap, in the order the traps were enabled. Only the + * topmost trap responds to focus and Tab events. + */ +const focusTrapStack: Modal[] = []; + export default class Modal extends React.Component { lastFocus: HTMLElement | undefined; lastModalFocus: HTMLElement | undefined; modalEl: HTMLDivElement | null = null; mounted = false; lastTabKeyDirection: 'forward' | 'backward' | null = null; + wasOpen = false; componentDidMount() { this.mounted = true; @@ -123,7 +130,6 @@ export default class Modal extends React.Component { if (prevProps.open && !this.props.open) { this.handleClose(); } else if (!prevProps.open && this.props.open) { - this.lastFocus = document.activeElement as HTMLElement; this.handleOpen(); } else if ( this.props.open && @@ -233,17 +239,24 @@ export default class Modal extends React.Component { if (activeEl && this.modalEl?.contains(activeEl)) { this.lastModalFocus = activeEl; } + if (!focusTrapStack.includes(this)) { + focusTrapStack.push(this); + } document.addEventListener('focusin', this.handleFocusTrapFocusIn); }; disableFocusTrap = () => { + const index = focusTrapStack.indexOf(this); + if (index !== -1) { + focusTrapStack.splice(index, 1); + } document.removeEventListener('focusin', this.handleFocusTrapFocusIn); this.lastModalFocus = undefined; this.lastTabKeyDirection = null; }; handleFocusTrapFocusIn = (event: FocusEvent) => { - if (!this.modalEl) { + if (!this.modalEl || focusTrapStack.at(-1) !== this) { return; } @@ -304,7 +317,12 @@ export default class Modal extends React.Component { }; handleFocusTrapTab = (event: KeyboardEvent) => { - if (!this.props.trapFocus || !this.modalEl || event.defaultPrevented) { + if ( + !this.props.trapFocus || + !this.modalEl || + event.defaultPrevented || + focusTrapStack.at(-1) !== this + ) { return; } @@ -378,6 +396,15 @@ export default class Modal extends React.Component { keepInDOMAfterClose, } = this.props; + // Capture the element to restore focus to before the children render. Waiting until + // componentDidUpdate would be too late: layout effects of the children run first, so anything + // that moves focus inside the modal on mount would be captured instead of the element that was + // focused before the modal opened. + if (open && !this.wasOpen) { + this.lastFocus = document.activeElement as HTMLElement; + } + this.wasOpen = open; + if (!open && !keepInDOMAfterClose) { return null; } diff --git a/web/packages/design/src/utils/testing.tsx b/web/packages/design/src/utils/testing.tsx index 0f75c2950d2e..ed6249c5235e 100644 --- a/web/packages/design/src/utils/testing.tsx +++ b/web/packages/design/src/utils/testing.tsx @@ -184,6 +184,48 @@ export function enableMswServer() { afterAll(() => server.close()); } +/** + * Mocks HTMLElement.prototype.offsetParent for the current test suite. Call + * this at the top level of a test file (or inside a describe block) that + * exercises code filtering out hidden elements by offsetParent, e.g. the focus + * trap in Modal. + */ +export function mockOffsetParent() { + let originalOffsetParent: PropertyDescriptor | undefined; + + beforeAll(() => { + originalOffsetParent = Object.getOwnPropertyDescriptor( + HTMLElement.prototype, + 'offsetParent' + ); + Object.defineProperty(HTMLElement.prototype, 'offsetParent', { + get(this: HTMLElement) { + // Walk up the ancestor chain — in real browsers, offsetParent is null when any + // ancestor has display: none. + let el: HTMLElement | null = this; + while (el) { + if (el.style.display === 'none') { + return null; + } + el = el.parentElement; + } + return this.parentElement; + }, + configurable: true, + }); + }); + + afterAll(() => { + if (originalOffsetParent) { + Object.defineProperty( + HTMLElement.prototype, + 'offsetParent', + originalOffsetParent + ); + } + }); +} + export { act, screen, diff --git a/web/packages/shared/components/MenuAction/MenuAction.test.tsx b/web/packages/shared/components/MenuAction/MenuAction.test.tsx index cb74598dafb2..1b8a24466b7c 100644 --- a/web/packages/shared/components/MenuAction/MenuAction.test.tsx +++ b/web/packages/shared/components/MenuAction/MenuAction.test.tsx @@ -16,9 +16,13 @@ * along with this program. If not, see . */ -import { screen } from '@testing-library/react'; - -import { fireEvent, render } from 'design/utils/testing'; +import { + fireEvent, + mockOffsetParent, + render, + screen, + userEvent, +} from 'design/utils/testing'; import { MenuIcon, MenuItem } from '.'; @@ -62,3 +66,46 @@ test('menuActionProps is respected', () => { render(); expect(screen.getByTestId('button')).toHaveStyle(menuListCss.style); }); + +describe('keyboard navigation', () => { + mockOffsetParent(); + + const openMenu = async (user: ReturnType) => { + render( + + Edit + Delete + + ); + await user.click(screen.getByRole('button', { name: 'Options' })); + }; + + test('tab moves focus through menu items and wraps around', async () => { + const user = userEvent.setup(); + await openMenu(user); + expect(screen.getByRole('menu')).toHaveFocus(); + + await user.tab(); + expect(screen.getByRole('menuitem', { name: 'Edit' })).toHaveFocus(); + + await user.tab(); + expect(screen.getByRole('menuitem', { name: 'Delete' })).toHaveFocus(); + + // Focus wraps around at the end of the menu. + await user.tab(); + expect(screen.getByRole('menuitem', { name: 'Edit' })).toHaveFocus(); + + await user.tab({ shift: true }); + expect(screen.getByRole('menuitem', { name: 'Delete' })).toHaveFocus(); + }); + + test('escape closes the menu and restores focus to the trigger', async () => { + const user = userEvent.setup(); + await openMenu(user); + expect(screen.getByRole('menu')).toBeInTheDocument(); + + await user.keyboard('{Escape}'); + expect(screen.queryByRole('menu')).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Options' })).toHaveFocus(); + }); +}); diff --git a/web/packages/shared/components/MenuAction/MenuActionButton.tsx b/web/packages/shared/components/MenuAction/MenuActionButton.tsx index ebd363a7dee9..4b1607e70bf6 100644 --- a/web/packages/shared/components/MenuAction/MenuActionButton.tsx +++ b/web/packages/shared/components/MenuAction/MenuActionButton.tsx @@ -116,6 +116,8 @@ class InnerMenuActionIcon extends React.Component< anchorEl={this.anchorEl} open={open} onClose={this.onClose} + autoFocus + trapFocus transformOrigin={{ vertical: 'top', horizontal: 'right', diff --git a/web/packages/shared/components/MenuAction/MenuActionIcon.tsx b/web/packages/shared/components/MenuAction/MenuActionIcon.tsx index e5f718a503cf..725cbc8eff71 100644 --- a/web/packages/shared/components/MenuAction/MenuActionIcon.tsx +++ b/web/packages/shared/components/MenuAction/MenuActionIcon.tsx @@ -75,6 +75,8 @@ export default class MenuActionIcon extends React.Component< anchorEl={this.anchorEl} open={open} onClose={this.onClose} + autoFocus + trapFocus anchorOrigin={{ vertical: 'bottom', horizontal: 'center', From 5dacf73a105dd1f2d15fe91fc10c91df4693362d Mon Sep 17 00:00:00 2001 From: Alan Parra <12500300+codingllama@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:08:53 -0300 Subject: [PATCH 050/347] chore: Bump github.com/klauspost/compress to v1.18.7 (#69146) * chore: Bump github.com/klauspost/compress to v1.18.7 * tidy --- build.assets/tooling/go.mod | 2 +- build.assets/tooling/go.sum | 4 ++-- e2e/runner/go.mod | 2 +- e2e/runner/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integrations/event-handler/go.mod | 2 +- integrations/event-handler/go.sum | 4 ++-- integrations/terraform-mwi/go.mod | 2 +- integrations/terraform-mwi/go.sum | 4 ++-- integrations/terraform/go.mod | 2 +- integrations/terraform/go.sum | 4 ++-- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index 0409073a638c..5f6399484461 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -194,7 +194,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/kisielk/gotool v1.0.0 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index f11e509dd174..9514fb25d232 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -585,8 +585,8 @@ github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXw github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/e2e/runner/go.mod b/e2e/runner/go.mod index fb8ac2007008..b9bae1b985b7 100644 --- a/e2e/runner/go.mod +++ b/e2e/runner/go.mod @@ -185,7 +185,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/e2e/runner/go.sum b/e2e/runner/go.sum index ecb32fd81e91..8d616c16f7e5 100644 --- a/e2e/runner/go.sum +++ b/e2e/runner/go.sum @@ -584,8 +584,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/go.mod b/go.mod index cd0afe7ad6ba..e63f6f3b6112 100644 --- a/go.mod +++ b/go.mod @@ -181,7 +181,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 - github.com/klauspost/compress v1.18.6 + github.com/klauspost/compress v1.18.7 github.com/mailgun/mailgun-go/v4 v4.23.0 github.com/mark3labs/mcp-go v0.48.0 github.com/mattn/go-shellwords v1.0.13 diff --git a/go.sum b/go.sum index 07a8b2ec4294..4ea183561bcf 100644 --- a/go.sum +++ b/go.sum @@ -950,8 +950,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 9933ffee1029..bffa34989d23 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -239,7 +239,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index 2adf11aa8448..edd167d9becf 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -645,8 +645,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/integrations/terraform-mwi/go.mod b/integrations/terraform-mwi/go.mod index cec41d9c361e..e911b906e29f 100644 --- a/integrations/terraform-mwi/go.mod +++ b/integrations/terraform-mwi/go.mod @@ -313,7 +313,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/integrations/terraform-mwi/go.sum b/integrations/terraform-mwi/go.sum index d114a0f26c30..6643691e022e 100644 --- a/integrations/terraform-mwi/go.sum +++ b/integrations/terraform-mwi/go.sum @@ -923,8 +923,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index fd2e760f51f6..1c6d0dba983e 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -315,7 +315,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index bfeee8e0ffa2..523c99cf4b6b 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -1052,8 +1052,8 @@ github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From a1b7a53be011748162ba99eaac7feb3c3a23cd2d Mon Sep 17 00:00:00 2001 From: Gustavo Medori Date: Wed, 29 Jul 2026 11:25:04 -0600 Subject: [PATCH 051/347] iOS: Device Trust credential storage (#68777) * Initial secure storage implementation * Added base infrastructure for Device Trust Credential Demo * Add the concept of a "location" for storing device trust credentials (2 options, "app" or "demo") * Add view + view model that demos the lifecycle of the device trust credential * Improve the names of various device trust operations to better reflect their actual purpose * Improve the user-facing copy of the secure enclave demo * Improve doc comment of `.emptyChallenge` error --- .../Verify/App Lifecycle/Dependencies.swift | 3 + .../DeviceTrustCredentialClient.swift | 119 ++++++++ .../DeviceTrustCredentialKeychain.swift | 174 +++++++++++ .../SecureEnclaveCredentialStore.swift | 255 ++++++++++++++++ .../Verify/Features/Debugging/DebugView.swift | 21 +- .../Features/Debugging/DebugViewModel.swift | 12 +- .../DeviceTrustCredentialView.swift | 262 +++++++++++++++++ .../DeviceTrustCredentialViewModel.swift | 278 ++++++++++++++++++ .../Debugging/Feature Demos/FeatureDemo.swift | 24 ++ mobile/Verify/Verify/Info.plist | 2 + 10 files changed, 1148 insertions(+), 2 deletions(-) create mode 100644 mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialClient.swift create mode 100644 mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift create mode 100644 mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift create mode 100644 mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialView.swift create mode 100644 mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialViewModel.swift create mode 100644 mobile/Verify/Verify/Features/Debugging/Feature Demos/FeatureDemo.swift diff --git a/mobile/Verify/Verify/App Lifecycle/Dependencies.swift b/mobile/Verify/Verify/App Lifecycle/Dependencies.swift index 159214dd731c..4070c67f78ac 100644 --- a/mobile/Verify/Verify/App Lifecycle/Dependencies.swift +++ b/mobile/Verify/Verify/App Lifecycle/Dependencies.swift @@ -21,4 +21,7 @@ import DependenciesMacros extension DependencyValues { @DependencyEntry(liveValue: EnrollClient.liveValue) nonisolated var enrollClient = EnrollClient() + + @DependencyEntry(liveValue: DeviceTrustCredentialClient.liveValue) + nonisolated var deviceTrustCredentialClient = DeviceTrustCredentialClient() } diff --git a/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialClient.swift b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialClient.swift new file mode 100644 index 000000000000..28a13654d5c2 --- /dev/null +++ b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialClient.swift @@ -0,0 +1,119 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Dependencies +import DependenciesMacros +import Foundation + +/// Provides the Device Trust credential operations needed by enrollment and authentication ceremonies. +/// +/// Feature tests should override this dependency directly. They do not need to imitate Keychain or Secure Enclave. +@DependencyClient +struct DeviceTrustCredentialClient { + /// Enrollment operation: loads the existing credential or creates one only when it is genuinely absent. + var loadOrCreate: @Sendable () throws -> DeviceTrustCredential + + /// Authentication operation: loads the existing credential without ever creating a replacement. + var load: @Sendable () throws -> DeviceTrustCredential + + /// Signs the raw Teleport challenge after a user-presence check and returns an ASN.1 DER ECDSA signature. + /// + /// Callers should treat ``DeviceTrustCredentialError/signingAuthorizationCancelled`` as a no-op. All other errors + /// describe an operation that failed and should follow the feature's normal error handling. + var signChallenge: @Sendable ( + _ challenge: Data, + _ purpose: DeviceTrustChallengePurpose, + ) async throws -> Data +} + +// MARK: - Live Implementation + +extension DeviceTrustCredentialClient { + static let liveValue = value(location: .app) + + static func value(location: DeviceTrustCredentialKeychain.Location) -> Self { + let store = SecureEnclaveCredentialStore( + keychain: DeviceTrustCredentialKeychain(location: location), + ) + return Self( + loadOrCreate: { + try store.loadOrCreate() + }, + load: { + try store.loadExistingCredential() + }, + signChallenge: { challenge, purpose in + try await store.signChallenge(challenge, purpose: purpose) + }, + ) + } +} + +// MARK: - Supporting Types + +/// The public portion of a Device Trust credential. +/// +/// The private key is deliberately absent. It remains in the Secure Enclave and can only be used through +/// ``DeviceTrustCredentialClient/signChallenge``. +struct DeviceTrustCredential: Equatable { + /// An opaque identifier generated when the credential is first created. + let id: String + + /// The P-256 public key encoded as PKIX ASN.1 DER for Teleport's enrollment protocol. + let publicKeyDER: Data +} + +/// Identifies why Teleport needs a signature so the system user-presence prompt can explain the request. +enum DeviceTrustChallengePurpose { + case enrollment + case authentication +} + +/// Stable failures that callers can turn into user-facing recovery actions. +/// +/// Apple framework errors are mapped here so `CFError`, `NSError`, and other non-Sendable values do not escape the +/// dependency boundary. +enum DeviceTrustCredentialError: Error, Equatable { + /// The current device cannot create or use Secure Enclave keys. + case secureEnclaveUnavailable + + /// Authentication asked for a credential, but this app has not created one. + case notFound + + /// Keychain contained a record that could not be decoded or restored by this Secure Enclave. + case invalidStoredCredential + + /// Caller of `signChallenge` supplied no bytes to sign. + case emptyChallenge + + /// The Secure Enclave could not create a new signing key. + case keyCreationFailed + + /// Security.framework could not construct the access-control policy used by the key. + case accessControlCreationFailed + + /// The user or system cancelled the presence prompt. + case signingAuthorizationCancelled + + /// The user-presence check failed without being cancelled. + case signingAuthorizationFailed + + /// CryptoKit could not sign the challenge with the stored Secure Enclave key. + case signingFailed + + /// Security.framework returned an error while accessing Keychain. + case keychain(status: Int32) +} diff --git a/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift new file mode 100644 index 000000000000..498501a51f87 --- /dev/null +++ b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift @@ -0,0 +1,174 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Foundation +import OSLog +import Security + +/// The versioned value stored as one Keychain item. +/// +/// Keeping the credential ID and encrypted key representation in a single record ensures they are created and loaded +/// together. `privateKeyRepresentation` is an opaque, encrypted Secure Enclave handle—not raw private-key bytes. +struct StoredDeviceTrustCredential: Codable { + fileprivate static let currentVersion = 1 + + fileprivate let version: Int + let id: String + let privateKeyRepresentation: Data + + init(id: String, privateKeyRepresentation: Data) { + self.version = Self.currentVersion + self.id = id + self.privateKeyRepresentation = privateKeyRepresentation + } +} + +/// Persists the Device Trust credential record in the Verify app's default Keychain access group. +/// +/// This type knows nothing about signing or key creation. Its entire responsibility is to atomically load or insert +/// the opaque record used by ``SecureEnclaveCredentialStore``. +struct DeviceTrustCredentialKeychain { + /// A fixed Keychain identity for a Device Trust credential. + /// + /// The private initializer prevents callers from inventing new storage locations while still allowing the app and + /// debug demo to use independent credentials. + struct Location { + fileprivate let service: String + fileprivate let account: String + + static let app = Self( + service: "com.gravitational.teleport.verify.device-trust", + account: "credential.v1", + ) + + #if DEBUG + static let demo = Self( + service: "com.gravitational.teleport.verify.device-trust.demo", + account: "credential.demo.v1", + ) + #endif + + private init(service: String, account: String) { + self.service = service + self.account = account + } + } + + private let location: Location + private let logger = Logger.forType(DeviceTrustCredentialKeychain.self) + + init(location: Location) { + self.location = location + } + + /// Loads and validates the existing versioned record. + func load() throws -> StoredDeviceTrustCredential { + var query = baseQuery + query[kSecReturnData] = true + query[kSecMatchLimit] = kSecMatchLimitOne + + var item: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &item) + switch status { + case errSecSuccess: + guard let data = item as? Data else { + throw DeviceTrustCredentialError.invalidStoredCredential + } + + return try decode(data) + + case errSecItemNotFound: + throw DeviceTrustCredentialError.notFound + + default: + logger.error("Could not load Device Trust credential from Keychain: \(status)") + throw DeviceTrustCredentialError.keychain(status: status) + } + } + + /// Adds a new record without replacing an existing credential. + /// + /// `errSecDuplicateItem` is intentionally returned to the caller, which reloads the record that won a concurrent + /// creation race. + func insert(_ storedCredential: StoredDeviceTrustCredential) throws { + let data: Data + do { + let encoder = PropertyListEncoder() + encoder.outputFormat = .binary + data = try encoder.encode(storedCredential) + } catch { + throw DeviceTrustCredentialError.keyCreationFailed + } + + var attributes = baseQuery + // Match the Secure Enclave key's lifetime: local to this device and invalid once its passcode is removed. + attributes[kSecAttrAccessible] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly + attributes[kSecAttrSynchronizable] = false + attributes[kSecValueData] = data + + let status = SecItemAdd(attributes as CFDictionary, nil) + guard status == errSecSuccess else { + logger.error("Could not store Device Trust credential in Keychain: \(status)") + throw DeviceTrustCredentialError.keychain(status: status) + } + } + + #if DEBUG + /// Deletes this Keychain record so debug-only callers can repeat credential creation. + func delete() throws { + let status = SecItemDelete(baseQuery as CFDictionary) + switch status { + case errSecSuccess, errSecItemNotFound: + return + + default: + logger.error("Could not delete Device Trust credential from Keychain: \(status)") + throw DeviceTrustCredentialError.keychain(status: status) + } + } + #endif + + private func decode(_ data: Data) throws -> StoredDeviceTrustCredential { + do { + let storedCredential = try PropertyListDecoder().decode(StoredDeviceTrustCredential.self, from: data) + guard + storedCredential.version == StoredDeviceTrustCredential.currentVersion, + UUID(uuidString: storedCredential.id) != nil, + !storedCredential.privateKeyRepresentation.isEmpty + else { + throw DeviceTrustCredentialError.invalidStoredCredential + } + + return storedCredential + } catch let error as DeviceTrustCredentialError { + throw error + } catch { + logger.error("Could not decode the stored Device Trust credential") + throw DeviceTrustCredentialError.invalidStoredCredential + } + } + + /// The selected service/account pair identifies exactly one Device Trust credential. + /// + /// No access group is specified, so Security.framework uses the Verify app's default signed access group. + private var baseQuery: [CFString: Any] { + [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: location.service, + kSecAttrAccount: location.account, + ] + } +} diff --git a/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift new file mode 100644 index 000000000000..542dc66312fe --- /dev/null +++ b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift @@ -0,0 +1,255 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import CryptoKit +import Foundation +import LocalAuthentication +import OSLog +import Security + +/// Implements Device Trust using two pieces of device-local storage. +/// +/// The Secure Enclave owns the actual private key. Keychain stores only CryptoKit's encrypted representation of that +/// key together with Teleport's opaque credential ID. The representation cannot be used to recover the private-key +/// bytes and only the Secure Enclave that created it can restore the key. +final class SecureEnclaveCredentialStore: Sendable { + private let keychain: DeviceTrustCredentialKeychain + private let logger = Logger.forType(SecureEnclaveCredentialStore.self) + + init(keychain: DeviceTrustCredentialKeychain) { + self.keychain = keychain + } +} + +// MARK: - Credential Operations + +extension SecureEnclaveCredentialStore { + /// Loads the configured location's existing credential or creates one when no record exists. + /// + /// A corrupt record or unexpected Keychain error is never treated as absence. Silently replacing a credential in + /// those cases would leave Teleport enrolled with a public key that the app can no longer use. + func loadOrCreate() throws -> DeviceTrustCredential { + try requireSecureEnclave() + + do { + let credential = try loadCredentialFromStorage() + logger.debug("Loaded existing Device Trust credential") + return credential + } catch DeviceTrustCredentialError.notFound { + // Absence is the only condition under which enrollment may create a credential. + } + + let signingKey = try createSigningKey() + let storedCredential = StoredDeviceTrustCredential( + id: UUID().uuidString.lowercased(), + privateKeyRepresentation: signingKey.dataRepresentation, + ) + + do { + // This is intentionally an add, not an update: an existing identity must never be overwritten. + try keychain.insert(storedCredential) + } catch DeviceTrustCredentialError.keychain(status: errSecDuplicateItem) { + // Another creator won the race. Discard this new handle and use the credential that became canonical. + logger.debug("Another caller stored a Device Trust credential; loading it") + return try loadCredentialFromStorage() + } + + logger.debug("Created Device Trust credential") + return makePublicCredential(from: storedCredential, signingKey: signingKey) + } + + /// Loads an existing credential for authentication without creating or rotating key material. + func loadExistingCredential() throws -> DeviceTrustCredential { + try requireSecureEnclave() + return try loadCredentialFromStorage() + } + + /// Uses the existing private key to sign a raw Teleport challenge after confirming user presence. + func signChallenge( + _ challenge: Data, + purpose: DeviceTrustChallengePurpose, + ) async throws -> Data { + guard !challenge.isEmpty else { + throw DeviceTrustCredentialError.emptyChallenge + } + + try requireSecureEnclave() + + // Load the record before prompting so a missing or inaccessible credential remains a Keychain error. + let storedCredential = try keychain.load() + let context = LAContext() + context.localizedReason = purpose.signingPromptReason + let signingKey = try restoreSigningKey( + from: storedCredential, + authenticationContext: context, + ) + + try await authorizeSigningWithUserPresence(using: context, for: purpose) + defer { context.invalidate() } + + do { + // The Data overload hashes the challenge with SHA-256 once. DER is the format Teleport's Go verifier + // expects. + return try signingKey.signature(for: challenge).derRepresentation + } catch { + logger.error("Could not sign the Device Trust challenge") + throw DeviceTrustCredentialError.signingFailed + } + } +} + +// MARK: - Credential Loading and Creation + +extension SecureEnclaveCredentialStore { + private func requireSecureEnclave() throws { + guard SecureEnclave.isAvailable else { + throw DeviceTrustCredentialError.secureEnclaveUnavailable + } + } + + private func loadCredentialFromStorage() throws -> DeviceTrustCredential { + let storedCredential = try keychain.load() + let signingKey = try restoreSigningKey(from: storedCredential) + return makePublicCredential(from: storedCredential, signingKey: signingKey) + } + + /// Creates a P-256 signing key whose private operations require local user presence. + private func createSigningKey() throws -> SecureEnclave.P256.Signing.PrivateKey { + let accessControl = try makeSigningKeyAccessControl() + + do { + return try SecureEnclave.P256.Signing.PrivateKey(accessControl: accessControl) + } catch { + logger.error("Could not create a Secure Enclave Device Trust key") + throw DeviceTrustCredentialError.keyCreationFailed + } + } + + /// Restores CryptoKit's handle to the existing Secure Enclave key. + /// + /// Restoring a handle is not the same as exporting the private key. If restoration fails, the record remains in + /// place so callers can require deliberate re-enrollment rather than silently changing device identity. + private func restoreSigningKey( + from storedCredential: StoredDeviceTrustCredential, + authenticationContext: LAContext? = nil, + ) throws -> SecureEnclave.P256.Signing.PrivateKey { + do { + return try SecureEnclave.P256.Signing.PrivateKey( + dataRepresentation: storedCredential.privateKeyRepresentation, + authenticationContext: authenticationContext, + ) + } catch { + logger.error("Could not restore the stored Device Trust credential") + throw DeviceTrustCredentialError.invalidStoredCredential + } + } + + private func makePublicCredential( + from storedCredential: StoredDeviceTrustCredential, + signingKey: SecureEnclave.P256.Signing.PrivateKey, + ) -> DeviceTrustCredential { + DeviceTrustCredential( + id: storedCredential.id, + publicKeyDER: signingKey.publicKey.derRepresentation, + ) + } +} + +// MARK: - Signing Authorization + +extension SecureEnclaveCredentialStore { + /// Evaluates the key's user-presence policy through LocalAuthentication before asking CryptoKit to sign. + /// + /// `LAContext` reports cancellation as a direct `LAError`, so callers can ignore it without relying on the + /// undocumented error wrappers produced by CryptoKit. The same authorized context is reused for signing. + private func authorizeSigningWithUserPresence( + using context: LAContext, + for purpose: DeviceTrustChallengePurpose, + ) async throws { + let accessControl = try makeSigningKeyAccessControl() + + let authorized: Bool + do { + authorized = try await context.evaluateAccessControl( + accessControl, + operation: .useKeySign, + localizedReason: purpose.signingPromptReason, + ) + } catch let error as LAError { + switch error.code { + case .appCancel, .systemCancel, .userCancel: + throw DeviceTrustCredentialError.signingAuthorizationCancelled + + default: + throw DeviceTrustCredentialError.signingAuthorizationFailed + } + } catch is CancellationError { + throw DeviceTrustCredentialError.signingAuthorizationCancelled + } catch { + throw DeviceTrustCredentialError.signingAuthorizationFailed + } + + guard authorized else { + throw DeviceTrustCredentialError.signingAuthorizationFailed + } + + // Only the explicit LocalAuthentication call may present UI. CryptoKit must reuse this authorization or fail. + context.interactionNotAllowed = true + } +} + +// MARK: - Key Access Policy + +extension SecureEnclaveCredentialStore { + /// Builds the policy enforced by Security.framework whenever the private key signs. + /// + /// - `WhenPasscodeSetThisDeviceOnly` prevents migration and invalidates the credential if the passcode is removed. + /// - `privateKeyUsage` applies the policy to private-key operations rather than key creation or public-key reads. + /// - `userPresence` accepts Face ID, Touch ID, or the device passcode. + private func makeSigningKeyAccessControl() throws -> SecAccessControl { + var error: Unmanaged? = nil + guard + let accessControl = SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, + [.privateKeyUsage, .userPresence], + &error, + ) + else { + if let error { + logger.error("Could not create the Device Trust access-control policy: \(error.takeRetainedValue())") + } + + throw DeviceTrustCredentialError.accessControlCreationFailed + } + + return accessControl + } +} + +// MARK: - Signing Prompt Copy + +extension DeviceTrustChallengePurpose { + fileprivate var signingPromptReason: String { + switch self { + case .enrollment: + "Confirm your presence to enroll this device with Teleport." + + case .authentication: + "Confirm your presence to verify this device with Teleport." + } + } +} diff --git a/mobile/Verify/Verify/Features/Debugging/DebugView.swift b/mobile/Verify/Verify/Features/Debugging/DebugView.swift index 2f2998a55605..43ca8dc04cb9 100644 --- a/mobile/Verify/Verify/Features/Debugging/DebugView.swift +++ b/mobile/Verify/Verify/Features/Debugging/DebugView.swift @@ -17,16 +17,35 @@ #if DEBUG import SwiftUI + import SwiftUINavigation /// DebugView serves as the entrypoint for in-development ideas, UI component catalogs, and any other bits that we /// want to be able to see when testing on a device, but which we don't want to ship to customers. /// /// - Note: DebugView and all its associated components should always be wrapped in `#if DEBUG` checks. struct DebugView: View { + @Bindable var viewModel: DebugViewModel var body: some View { - Text("Placeholder for DebugView") + NavigationStack { + List { + Group { + Section("Feature Demos") { + Button("Secure Enclave Storage", systemImage: "lock") { + viewModel.destination = .deviceTrustCredentialDemo(.init()) + } + } + } + } + .navigationTitle("Debug") + + // MARK: - Navigation + + .navigationDestination(item: $viewModel.destination.deviceTrustCredentialDemo) { viewModel in + FeatureDemo.DeviceTrustCredentialView(viewModel: viewModel) + } + } } } diff --git a/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift b/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift index a4012f6bf321..03f59943bdb6 100644 --- a/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift +++ b/mobile/Verify/Verify/Features/Debugging/DebugViewModel.swift @@ -17,9 +17,19 @@ #if DEBUG import Observation + import SwiftNavigation @Observable - final class DebugViewModel {} + final class DebugViewModel { + @CasePathable + enum Destination { + // MARK: - Feature Demos + + case deviceTrustCredentialDemo(FeatureDemo.DeviceTrustCredentialViewModel) + } + + var destination: Destination? = nil + } // MARK: - SheetPresentable diff --git a/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialView.swift b/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialView.swift new file mode 100644 index 000000000000..dde37364b323 --- /dev/null +++ b/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialView.swift @@ -0,0 +1,262 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +#if DEBUG + + import SwiftUI + + extension FeatureDemo { + struct DeviceTrustCredentialView: View { + @Bindable + var viewModel: DeviceTrustCredentialViewModel + + var body: some View { + Form { + descriptionSection + deviceSection + actionsSection + statusSection + storedCredentialSection + challengeAndSignatureSection + } + .navigationTitle("Device Trust Credential") + .navigationBarTitleDisplayMode(.inline) + } + } + } + + // MARK: - Sections + + extension FeatureDemo.DeviceTrustCredentialView { + var descriptionSection: some View { + Section { + Text( + """ + Creates or loads a separate demo credential, reloads it from Keychain, requests user presence \ + to authorize signing, signs a random challenge, and verifies the signature locally. + """, + ) + .font(.callout) + .foregroundStyle(.secondary) + } + } + + var deviceSection: some View { + Section("Device") { + HStack { + Text("Secure Enclave") + Label( + viewModel.secureEnclaveAvailable + ? "Available" + : "Unavailable", + systemImage: viewModel.secureEnclaveAvailable + ? "checkmark.circle.fill" + : "xmark.circle.fill", + ) + .labelIconToTitleSpacing(.small) + .foregroundStyle(viewModel.secureEnclaveAvailable ? Color.success : Color.danger) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + } + + var actionsSection: some View { + Section { + runRoundTripButton + loadCredentialButton + resetCredentialButton + } header: { + Text("Actions") + } footer: { + Text( + "This demo uses a separate Keychain item and never reads or modifies the app's Device Trust credential.", + ) + } + } + + var statusSection: some View { + Section("Status") { + statusView + } + } + + @ViewBuilder + var storedCredentialSection: some View { + if let credentialID = viewModel.credentialID { + Section("Stored Demo Credential") { + detail("Credential ID", value: credentialID) + + if let fingerprint = viewModel.publicKeyFingerprint { + detail("Public Key Fingerprint (SHA-256)", value: fingerprint) + } + + if let publicKey = viewModel.publicKeyDERBase64 { + detail("Public Key DER (Base64)", value: publicKey) + } + + if let matched = viewModel.credentialReloadMatched { + result("Credential persisted correctly", succeeded: matched) + } + } + } + } + + @ViewBuilder + var challengeAndSignatureSection: some View { + if let challenge = viewModel.challengeHex { + Section("Challenge and Signature") { + detail("Random 32-byte challenge", value: challenge) + + if let signature = viewModel.signatureDERBase64 { + detail("ECDSA Signature (DER, Base64-encoded)", value: signature) + } + + if let verified = viewModel.signatureVerified { + result("Signature verified with reloaded public key", succeeded: verified) + } + } + } + } + } + + // MARK: - Actions + + extension FeatureDemo.DeviceTrustCredentialView { + var runRoundTripButton: some View { + Button { + Task { await viewModel.runRoundTrip() } + } label: { + Label("Create, Sign, and Verify", systemImage: "checkmark.shield") + } + .disabled(viewModel.isRunning || !viewModel.secureEnclaveAvailable) + } + + var loadCredentialButton: some View { + Button { + viewModel.loadExistingCredential() + } label: { + Label("Load Demo Credential", systemImage: "key.viewfinder") + } + .disabled(viewModel.isRunning || !viewModel.secureEnclaveAvailable) + } + + var resetCredentialButton: some View { + Button { + viewModel.resetDemoCredential() + } label: { + Label { + Text("Delete Demo Credential") + } icon: { + Image(systemName: "trash") + .foregroundStyle(.danger) + } + .tint(.danger) + } + .disabled(viewModel.isRunning) + } + } + + // MARK: - Status + + extension FeatureDemo.DeviceTrustCredentialView { + @ViewBuilder + var statusView: some View { + switch viewModel.status { + case .idle: + status( + "Ready", + message: "Create, sign, and verify on a physical device.", + systemImage: "circle.dotted", + color: .secondary, + ) + + case let .loading(message): + HStack(spacing: 12) { + ProgressView() + Text(message) + } + + case let .success(message): + status( + "Success", + message: message, + systemImage: "checkmark.circle.fill", + color: .success, + ) + + case .cancelled: + status( + "Cancelled", + message: "No signature was produced.", + systemImage: "xmark.circle", + color: .secondary, + ) + + case let .failure(message): + status( + "Failed", + message: message, + systemImage: "exclamationmark.triangle.fill", + color: .danger, + ) + } + } + + private func status( + _ title: String, + message: String, + systemImage: String, + color: Color, + ) -> some View { + HStack(alignment: .top, spacing: 12) { + Image(systemName: systemImage) + .foregroundStyle(color) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .fontWeight(.semibold) + Text(message) + .font(.callout) + .foregroundStyle(.secondary) + } + } + } + } + + // MARK: - Result Rows + + extension FeatureDemo.DeviceTrustCredentialView { + private func detail(_ title: String, value: String) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.caption) + .foregroundStyle(.secondary) + Text(value) + .font(.system(.caption, design: .monospaced)) + .textSelection(.enabled) + } + } + + private func result(_ title: String, succeeded: Bool) -> some View { + Label( + title, + systemImage: succeeded ? "checkmark.circle.fill" : "xmark.circle.fill", + ) + .foregroundStyle(succeeded ? Color.success : Color.danger) + } + } + +#endif diff --git a/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialViewModel.swift b/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialViewModel.swift new file mode 100644 index 000000000000..c5d520b15a4e --- /dev/null +++ b/mobile/Verify/Verify/Features/Debugging/Feature Demos/Device Trust Credential Demo/DeviceTrustCredentialViewModel.swift @@ -0,0 +1,278 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +#if DEBUG + + import CryptoKit + import Foundation + import Observation + import Security + + extension FeatureDemo { + @Observable @MainActor + final class DeviceTrustCredentialViewModel { + private(set) var status: DeviceTrustCredentialDemoStatus = .idle + private(set) var credentialID: String? = nil + private(set) var publicKeyFingerprint: String? = nil + private(set) var publicKeyDERBase64: String? = nil + private(set) var credentialReloadMatched: Bool? = nil + private(set) var challengeHex: String? = nil + private(set) var signatureDERBase64: String? = nil + private(set) var signatureVerified: Bool? = nil + + @ObservationIgnored + private let credentialClient: DeviceTrustCredentialClient + + init() { + self.credentialClient = DeviceTrustCredentialClient.value(location: .demo) + } + + var secureEnclaveAvailable: Bool { + SecureEnclave.isAvailable + } + + var isRunning: Bool { + if case .loading = status { + true + } else { + false + } + } + } + } + + // MARK: - User Actions + + extension FeatureDemo.DeviceTrustCredentialViewModel { + /// Exercises persistence, Secure Enclave signing, and verification using the public key loaded from storage. + func runRoundTrip() async { + guard !isRunning else { + return + } + + resetResults() + + do { + status = .loading("Creating or loading the demo credential…") + let initialCredential = try credentialClient.loadOrCreate() + show(initialCredential) + + status = .loading("Loading the credential back from Keychain…") + let reloadedCredential = try credentialClient.load() + credentialReloadMatched = initialCredential == reloadedCredential + + guard credentialReloadMatched == true else { + throw DeviceTrustCredentialDemoError.credentialChangedDuringReload + } + + let challenge = Self.makeRandomChallenge() + challengeHex = Self.hex(challenge) + + status = .loading("Requesting user presence to authorize signing…") + let signature = try await credentialClient.signChallenge(challenge, .authentication) + signatureDERBase64 = signature.base64EncodedString() + + status = .loading("Verifying the signature with the reloaded public key…") + let verified = try Self.verify( + signature: signature, + for: challenge, + using: reloadedCredential.publicKeyDER, + ) + signatureVerified = verified + + guard verified else { + throw DeviceTrustCredentialDemoError.signatureRejected + } + + status = .success( + "The Secure Enclave key signed the challenge, and the reloaded public key verified the signature.", + ) + } catch DeviceTrustCredentialError.signingAuthorizationCancelled { + // Cancellation is expected control flow. Nothing is sent and no failure is shown. + status = .cancelled + } catch { + status = .failure(Self.message(for: error)) + } + } + + /// Loads the existing demo credential without creating one or showing a user-presence prompt. + func loadExistingCredential() { + guard !isRunning else { + return + } + + resetResults() + status = .loading("Loading the existing demo credential from Keychain…") + + do { + let credential = try credentialClient.load() + show(credential) + status = .success("Loaded the demo credential without requesting signing authorization.") + } catch { + status = .failure(Self.message(for: error)) + } + } + + /// Deletes only the debug demo's Keychain record, bypassing the production credential client. + func resetDemoCredential() { + guard !isRunning else { + return + } + + resetResults() + status = .loading("Deleting the demo credential from Keychain…") + + do { + // Keep the storage location explicit here so this debug escape hatch cannot affect the app credential. + try DeviceTrustCredentialKeychain(location: .demo).delete() + status = .success("Deleted the demo credential. The app's Device Trust credential was not touched.") + } catch { + status = .failure(Self.message(for: error)) + } + } + } + + // MARK: - Presented Results + + extension FeatureDemo.DeviceTrustCredentialViewModel { + private func resetResults() { + credentialID = nil + publicKeyFingerprint = nil + publicKeyDERBase64 = nil + credentialReloadMatched = nil + challengeHex = nil + signatureDERBase64 = nil + signatureVerified = nil + } + + private func show(_ credential: DeviceTrustCredential) { + credentialID = credential.id + publicKeyFingerprint = Self.hex( + Data(SHA256.hash(data: credential.publicKeyDER)), + separator: ":", + ) + publicKeyDERBase64 = credential.publicKeyDER.base64EncodedString() + } + } + + // MARK: - Challenge and Signature + + extension FeatureDemo.DeviceTrustCredentialViewModel { + private static func makeRandomChallenge() -> Data { + var generator = SystemRandomNumberGenerator() + return Data( + (0 ..< 32).map { _ in + UInt8.random(in: UInt8.min ... UInt8.max, using: &generator) + }, + ) + } + + private static func verify( + signature: Data, + for challenge: Data, + using publicKeyDER: Data, + ) throws -> Bool { + let publicKey = try P256.Signing.PublicKey(derRepresentation: publicKeyDER) + let signature = try P256.Signing.ECDSASignature(derRepresentation: signature) + return publicKey.isValidSignature(signature, for: challenge) + } + + private static func hex(_ data: Data, separator: String = "") -> String { + data.map { String(format: "%02X", $0) }.joined(separator: separator) + } + } + + // MARK: - Error Presentation + + extension FeatureDemo.DeviceTrustCredentialViewModel { + private static func message(for error: any Error) -> String { + guard let error = error as? DeviceTrustCredentialError else { + return error.localizedDescription + } + + switch error { + case .secureEnclaveUnavailable: + return "The Secure Enclave is unavailable. Run this demo on a supported physical device." + + case .notFound: + return "No demo credential exists yet. Create, sign, and verify to create one." + + case .invalidStoredCredential: + return "Keychain contains a credential that this Secure Enclave cannot restore." + + case .emptyChallenge: + return "The signing API received an empty challenge." + + case .keyCreationFailed: + return "CryptoKit could not create the Secure Enclave signing key." + + case .accessControlCreationFailed: + return "Security.framework could not create the key's access-control policy." + + case .signingAuthorizationCancelled, .signingAuthorizationFailed, .signingFailed: + return operationMessage(for: error) + + case let .keychain(status): + let detail = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown Keychain error" + return "Keychain returned OSStatus \(status): \(detail)" + } + } + + private static func operationMessage(for error: DeviceTrustCredentialError) -> String { + switch error { + case .signingAuthorizationCancelled: + "Signing authorization was cancelled." + + case .signingAuthorizationFailed: + "LocalAuthentication could not confirm user presence." + + case .signingFailed: + "CryptoKit could not sign with the stored Secure Enclave key." + + default: + preconditionFailure("Only signing authorization and signing errors belong here") + } + } + } + + // MARK: - Supporting Types + + extension FeatureDemo.DeviceTrustCredentialViewModel { + enum DeviceTrustCredentialDemoStatus { + case idle + case loading(String) + case success(String) + case cancelled + case failure(String) + } + + private enum DeviceTrustCredentialDemoError: LocalizedError { + case credentialChangedDuringReload + case signatureRejected + + var errorDescription: String? { + switch self { + case .credentialChangedDuringReload: + "The credential loaded from Keychain did not match the credential returned by loadOrCreate." + + case .signatureRejected: + "CryptoKit rejected the signature produced by the Secure Enclave." + } + } + } + } + +#endif diff --git a/mobile/Verify/Verify/Features/Debugging/Feature Demos/FeatureDemo.swift b/mobile/Verify/Verify/Features/Debugging/Feature Demos/FeatureDemo.swift new file mode 100644 index 000000000000..ca611c3365ae --- /dev/null +++ b/mobile/Verify/Verify/Features/Debugging/Feature Demos/FeatureDemo.swift @@ -0,0 +1,24 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +#if DEBUG + + import Foundation + + /// An namespace for all feature demos we'd like to include inside of `DebugView`. + enum FeatureDemo {} + +#endif diff --git a/mobile/Verify/Verify/Info.plist b/mobile/Verify/Verify/Info.plist index 4bbd38d5abec..9e0f895cb287 100644 --- a/mobile/Verify/Verify/Info.plist +++ b/mobile/Verify/Verify/Info.plist @@ -4,6 +4,8 @@ NSCameraUsageDescription Teleport Verify uses the built-in QR code scanner to receive enrollment data from the Teleport Web UI + NSFaceIDUsageDescription + Teleport Verify uses Face ID to confirm your presence when proving this device's identity CFBundleURLTypes From aabcf0bfc39ccf5e44996aa8fd930f3ac774947b Mon Sep 17 00:00:00 2001 From: Edoardo Spadolini Date: Wed, 29 Jul 2026 21:52:07 +0200 Subject: [PATCH 052/347] Don't fetch the role list twice for Kubernetes connection locks (#68959) * Add MaxKubernetesConnections to the AccessChecker interface * Don't fetch the role list again in the Kube forwarder * Move tracing span to acquireConnectionLock * Add TODO for scoped identities --- lib/kube/proxy/forwarder.go | 42 ++++++++++++++++++-------------- lib/kube/proxy/forwarder_test.go | 7 ++++++ lib/services/access_checker.go | 6 +++++ lib/services/role.go | 4 +-- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index 6e0874bbee53..c38896ad1841 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -659,23 +659,19 @@ func (f *Forwarder) withAuthStd(handler handlerWithAuthFuncStd) http.HandlerFunc // acquireConnectionLockWithIdentity acquires a connection lock under a given identity. func (f *Forwarder) acquireConnectionLockWithIdentity(ctx context.Context, identity *authContext) error { - ctx, span := f.cfg.tracer.Start( - ctx, - "kube.Forwarder/acquireConnectionLockWithIdentity", - oteltrace.WithSpanKind(oteltrace.SpanKindServer), - oteltrace.WithAttributes( - semconv.RPCServiceKey.String(f.cfg.KubeServiceType), - semconv.RPCSystemKey.String("kube"), - ), - ) - defer span.End() - user := identity.Identity.GetIdentity().Username - roles, err := getRolesByName(ctx, f, identity.Identity.GetIdentity().Groups) - if err != nil { - return trace.Wrap(err) + unscopedContext, isUnscoped := identity.UnscopedContext() + if !isUnscoped { + // TODO(espadolini) TODO(eriktate): scoped identities don't currently + // support max_kubernetes_connections, this should be updated when they + // do + return nil } - - if err := f.acquireConnectionLock(ctx, user, roles); err != nil { + maxConnections := unscopedContext.Checker.MaxKubernetesConnections() + if maxConnections == 0 { + return nil + } + user := unscopedContext.Identity.GetIdentity().Username + if err := f.acquireConnectionLock(ctx, user, maxConnections); err != nil { return trace.Wrap(err) } @@ -1681,8 +1677,18 @@ func wsProxy(ctx context.Context, log *slog.Logger, wsSource *gwebsocket.Conn, w // acquireConnectionLock acquires a semaphore used to limit connections to the Kubernetes agent. // The semaphore is releasted when the request is returned/connection is closed. // Returns an error if a semaphore could not be acquired. -func (f *Forwarder) acquireConnectionLock(ctx context.Context, user string, roles services.RoleSet) error { - maxConnections := roles.MaxKubernetesConnections() +func (f *Forwarder) acquireConnectionLock(ctx context.Context, user string, maxConnections int64) error { + ctx, span := f.cfg.tracer.Start( + ctx, + "kube.Forwarder/acquireConnectionLock", + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + oteltrace.WithAttributes( + semconv.RPCServiceKey.String(f.cfg.KubeServiceType), + semconv.RPCSystemKey.String("kube"), + ), + ) + defer span.End() + if maxConnections == 0 { return nil } diff --git a/lib/kube/proxy/forwarder_test.go b/lib/kube/proxy/forwarder_test.go index 73b4a51393e4..61b6671755fd 100644 --- a/lib/kube/proxy/forwarder_test.go +++ b/lib/kube/proxy/forwarder_test.go @@ -2004,6 +2004,13 @@ func TestKubernetesConnectionLimit(t *testing.T) { Username: user.GetName(), Groups: []string{testCase.role.GetName()}, }), + Checker: services.NewAccessCheckerWithRoleSet( + &services.AccessInfo{ + Roles: []string{testCase.role.GetName()}, + }, + "clustername", + services.NewRoleSet(testCase.role), + ), }), } diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index 8aaf21f6129a..15c2bc91ad2a 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -309,6 +309,12 @@ type AccessChecker interface { // DelegationSessionID returns the ID of the current Delegation Session. DelegationSessionID() string + + // MaxKubernetesConnections returns the maximum number of concurrent + // Kubernetes connections allowed. If MaxKubernetesConnections is zero then + // no maximum was defined and the number of concurrent connections is + // unconstrained. + MaxKubernetesConnections() int64 } // AccessInfo hold information about an identity necessary to check whether that diff --git a/lib/services/role.go b/lib/services/role.go index de4fc9518905..bf4331dc35f2 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -1599,9 +1599,7 @@ func (set RoleSet) MaxSessions() int64 { return ms } -// MaxConnections returns the maximum number of concurrent Kubernetes connections -// allowed. If MaxConnections is zero then no maximum was defined -// and the number of concurrent connections is unconstrained. +// MaxKubernetesConnections implements [AccessChecker]. func (set RoleSet) MaxKubernetesConnections() int64 { var mcs int64 for _, role := range set { From b37bd29de83307d79ebce733038b401d1b316dcb Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Wed, 29 Jul 2026 17:23:46 -0300 Subject: [PATCH 053/347] Initial in-memory LLM tokens limiter (#69116) * feat(llm): in-memory tokens limiter * chore(llm): code review suggestions * refactor(llm): code review suggestions --- lib/srv/app/llm/anthropic/anthropic.go | 74 ++- lib/srv/app/llm/anthropic/anthropic_test.go | 66 ++- lib/srv/app/llm/errors/errors.go | 4 +- lib/srv/app/llm/handler.go | 84 ++- lib/srv/app/llm/handler_test.go | 124 ++-- lib/srv/app/llm/limiter/inmemory.go | 163 ++++++ lib/srv/app/llm/limiter/inmemory_test.go | 597 ++++++++++++++++++++ lib/srv/app/llm/limiter/limiter.go | 79 +++ lib/srv/app/llm/openai/errors.go | 2 +- lib/srv/app/llm/openai/openai.go | 80 ++- lib/srv/app/llm/openai/openai_test.go | 208 ++++++- lib/srv/app/llm/openai/types.go | 52 +- lib/srv/app/llm/openai/types_test.go | 24 + lib/srv/app/llm/request/request.go | 23 + lib/srv/app/llm/testing/testing.go | 40 ++ 15 files changed, 1496 insertions(+), 124 deletions(-) create mode 100644 lib/srv/app/llm/limiter/inmemory.go create mode 100644 lib/srv/app/llm/limiter/inmemory_test.go create mode 100644 lib/srv/app/llm/limiter/limiter.go diff --git a/lib/srv/app/llm/anthropic/anthropic.go b/lib/srv/app/llm/anthropic/anthropic.go index 97e234a84b00..c7573cad3065 100644 --- a/lib/srv/app/llm/anthropic/anthropic.go +++ b/lib/srv/app/llm/anthropic/anthropic.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/srv/app/llm/bedrock" llmerrors "github.com/gravitational/teleport/lib/srv/app/llm/errors" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" "github.com/gravitational/teleport/lib/srv/app/llm/models" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" "github.com/gravitational/teleport/lib/utils" @@ -37,16 +38,20 @@ import ( // NewRequest creates a new provider request based on the downstream request, // and inference endpoint configuration. -func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, error) { +func NewRequest(cfg *llmrequest.Config) (*llmrequest.Request, error) { var ( - info = &RequestInfo{} + info = &RequestInfo{} + res = &llmrequest.Request{ + Info: info, + SettleFunc: llmlimiter.EmptySettleFunc, + } providerPath string providerMethod string providerHeaders = http.Header{} ) if err := cfg.CheckAndSetDefaults(); err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } llm := cfg.App.GetLLM() @@ -65,7 +70,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, case "/messages": if cfg.DownstreamRequest.Method != http.MethodPost { // We're ok with returning 404 back to clients instead 405 status. - return nil, info, trace.NotFound("messages API supports only POST requests") + return res, trace.NotFound("messages API supports only POST requests") } // Messages API endpoint supported. // @@ -86,7 +91,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, providerHeaders.Set("anthropic-version", "2023-06-01") // Currently, the only version supported. providerHeaders.Set("content-type", "application/json") default: - return nil, info, trace.NotFound("unsupported endpoint") + return res, trace.NotFound("unsupported endpoint") } case types.LLMProviderAWSBedrock: cfg.ProviderURL = bedrock.BuildAnthropicURL(cfg.Logger, cfg.App) @@ -94,7 +99,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, case "/messages": if cfg.DownstreamRequest.Method != http.MethodPost { // We're ok with returning 404 back to clients instead 405 status. - return nil, info, trace.NotFound("messages API supports only POST requests") + return res, trace.NotFound("messages API supports only POST requests") } // Messages API endpoint supported. // @@ -109,10 +114,10 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, providerHeaders.Set("anthropic-version", "2023-06-01") providerHeaders.Set("content-type", "application/json") default: - return nil, info, trace.NotFound("unsupported endpoint") + return res, trace.NotFound("unsupported endpoint") } default: - return nil, info, trace.NotImplemented("provider %q is not supported", llm.Provider) + return res, trace.NotImplemented("provider %q is not supported", llm.Provider) } body, err := utils.ReadAtMost(cfg.DownstreamRequest.Body, teleport.MaxHTTPRequestSize) @@ -120,31 +125,50 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, cfg.Logger.WarnContext(cfg.DownstreamRequest.Context(), "failed to close downstream body", "error", closeErr) } if err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } defer cfg.DownstreamRequest.Body.Close() var req messagesAPIRequest if err := utils.FastUnmarshal(body, &req); err != nil { - return nil, info, trace.BadParameter("unable to parse request body") + return res, trace.BadParameter("unable to parse request body") } info.requestedModel = req.Model info.stream = req.Stream providerModel, found := models.ConvertName(llm.Models, llm.FallbackModel, req.Model) if !found { - return nil, info, trace.BadParameter("requested model %q is not supported", req.Model) + return res, trace.BadParameter("requested model %q is not supported", req.Model) } info.providerModel = providerModel req.Model = providerModel + if req.MaxTokens > maxOutputTokens { + return res, trace.BadParameter("max_tokens exceeds max value of %d", maxOutputTokens) + } + if req.MaxTokens > maxNonStreamingTokens && !info.IsStream() { - return nil, info, trace.BadParameter("max_tokens must be %d or less for non-streaming requests", maxNonStreamingTokens) + return res, trace.BadParameter("max_tokens must be %d or less for non-streaming requests", maxNonStreamingTokens) + } + + // Messages API always require the max output to be available, and setting + // it to zero is also valid (used for prompt caching). So here we don't need + // to rely on making assumptions on the values provided. + _, settleFunc, err := cfg.Reserve(cfg.DownstreamRequest.Context(), llmlimiter.ReserveRequest{ + App: cfg.App, + Usage: &llmlimiter.Usage{ + // TODO(gabrielcorado): add reservation for input tokens. + OutputTokens: uint(req.MaxTokens), + }, + }) + if err != nil { + return res, trace.Wrap(err) } + res.SettleFunc = settleFunc providerBody, err := utils.FastMarshal(req) if err != nil { - return nil, info, trace.ConnectionProblem(err, "failed to generate provider request") + return res, trace.ConnectionProblem(err, "failed to generate provider request") } info.requestSize = len(providerBody) @@ -159,7 +183,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, bytes.NewBuffer(providerBody), ) if err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } providerReq.Header = providerHeaders @@ -169,10 +193,11 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, // details, so it is only logged, and clients receive a generic // internal error message. cfg.Logger.ErrorContext(providerReq.Context(), "failed to sign provider request", "error", err) - return nil, info, llmerrors.ErrConfig + return res, llmerrors.ErrConfig } } - return providerReq, info, nil + res.HTTPRequest = providerReq + return res, nil } // WriteError writes an error in Anthropic format. @@ -213,7 +238,7 @@ func newErrorMessage(err error) *errorEnvelope { r.Error.Type = errorTypeInvalidRequestError case errors.Is(err, llmerrors.ErrUnauthorized): r.Error.Type = errorTypeAuthenticationError - case errors.Is(err, llmerrors.ErrRejected): + case errors.Is(err, llmerrors.ErrRejected), errors.Is(err, llmerrors.ErrLimitExceeded): r.Error.Type = errorTypeRateLimitError case errors.Is(err, llmerrors.ErrUnsupported), trace.IsNotFound(err): r.Error.Type = errorTypeNotFoundError @@ -257,4 +282,19 @@ const ( // The result give us around 21k tokens. This value covers all non-legacy // models. maxNonStreamingTokens = 21_000 + + // maxOutputTokens is the max value that can be used on the provider to + // avoid using the entire limit all at once. + // + // Anthropic supports different models, here we picked the latest "model + // class": `4.6`+ (that covers Opus, Sonnet, and Mythos/Fable). + // + // Reference: https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison + // + // Note: This can cause older models to reject the requests. This is an + // acceptable risk. + // + // TODO(gabrielcorado): take this information from the LLM configuration so + // users can configure it on a per-model basis. + maxOutputTokens = 128_000 // 128k tokens. ) diff --git a/lib/srv/app/llm/anthropic/anthropic_test.go b/lib/srv/app/llm/anthropic/anthropic_test.go index 2a5d5d4c472c..85ef5b211590 100644 --- a/lib/srv/app/llm/anthropic/anthropic_test.go +++ b/lib/srv/app/llm/anthropic/anthropic_test.go @@ -28,26 +28,22 @@ import ( "strings" "testing" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" + llmtesting "github.com/gravitational/teleport/lib/srv/app/llm/testing" ) func TestNewRequest(t *testing.T) { apiKey := "random-api-key" - signAWSRequest := func(_ context.Context, _ types.Application, req *http.Request, reqBody []byte) error { - hash := sha256.New() - hash.Write(reqBody) - req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(hash.Sum(nil))) - req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=test") - return nil - } - for name, tc := range map[string]struct { llm types.Application + reserveFunc llmrequest.ReserveFunc request func() *http.Request signAWSRequest func(context.Context, types.Application, *http.Request, []byte) error expectedError require.ErrorAssertionFunc @@ -59,6 +55,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -87,6 +84,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -110,6 +108,7 @@ func TestNewRequest(t *testing.T) { {ProviderName: "claude-opus-4-8", Name: "claude-sonnet-4-20250514"}, }, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -140,6 +139,7 @@ func TestNewRequest(t *testing.T) { }, FallbackModel: "claude-sonnet-4-20250514", }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -169,6 +169,7 @@ func TestNewRequest(t *testing.T) { {ProviderName: "claude-opus-4-8", Name: "claude-sonnet-4-20250514"}, }, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -190,6 +191,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -211,6 +213,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -234,6 +237,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -251,6 +255,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodGet, @@ -273,7 +278,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-west-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -311,6 +317,7 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-west-2", }), + reserveFunc: llmtesting.NoopReserveFunc, signAWSRequest: func(ctx context.Context, a types.Application, r *http.Request, b []byte) error { return errors.New("signing failed") }, @@ -337,6 +344,7 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-west-2", }), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -356,7 +364,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-west-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -376,7 +385,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-west-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodGet, @@ -389,19 +399,44 @@ func TestNewRequest(t *testing.T) { expectedRequest: require.Nil, expectedInfo: require.NotNil, }, + "max output limit exceeded": { + llm: newApp(t, &types.LLM{ + Format: types.LLMFormatAnthropic, + Provider: types.LLMProviderAnthropic, + }, nil /* appAWS */), + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{}, trace.LimitExceeded("limit exceeded")), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/messages", + strings.NewReader(`{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"Hello"}]}`), + ) + return r + }, + expectedError: require.Error, + expectedRequest: require.Nil, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "claude-sonnet-4-20250514", info.RequestedModel()) + require.Equal(tt, "claude-sonnet-4-20250514", info.ProviderModel()) + }, + }, } { t.Run(name, func(t *testing.T) { - req, info, err := NewRequest(&llmrequest.Config{ + res, err := NewRequest(&llmrequest.Config{ App: tc.llm, DownstreamRequest: tc.request(), GetAPIKeyFunc: func() string { return apiKey }, SignBedrockRequest: tc.signAWSRequest, + Reserve: tc.reserveFunc, }) + require.NotNil(t, res) + require.NotNil(t, res.SettleFunc) tc.expectedError(t, err) - tc.expectedRequest(t, req) - tc.expectedInfo(t, info) + tc.expectedRequest(t, res.HTTPRequest) + tc.expectedInfo(t, res.Info) }) } } @@ -462,10 +497,11 @@ func BenchmarkNewRequest(b *testing.B) { for b.Loop() { r.Body = io.NopCloser(bytes.NewReader(bc.body)) - if _, _, err := NewRequest(&llmrequest.Config{ + if _, err := NewRequest(&llmrequest.Config{ App: app, DownstreamRequest: r, GetAPIKeyFunc: func() string { return "" }, + Reserve: llmtesting.NoopReserveFunc, }); err != nil { b.Fatal(err) } diff --git a/lib/srv/app/llm/errors/errors.go b/lib/srv/app/llm/errors/errors.go index ed82474a5e93..26885b74b541 100644 --- a/lib/srv/app/llm/errors/errors.go +++ b/lib/srv/app/llm/errors/errors.go @@ -45,6 +45,8 @@ var ( ErrConfig = errors.New("unable to serve request due to an app configuration error. Contact your Teleport administrator") // ErrInternal returned when there is a Teleport processing error (nothing to do with the inference provider). ErrInternal = errors.New("unable to serve the request due to an internal error. Contact your Teleport administrator") + // ErrLimitExceeded returned when Teleport rejects the request due to limit exceeded. + ErrLimitExceeded = errors.New("tokens quota exceeded. Contact your Teleport administrator") // ErrUnknown returned when the handler could not identify the error. ErrUnknown = errors.New("the inference provider returned an unexpected error. Contact your Teleport administrator") ) @@ -90,7 +92,7 @@ func StatusCodeFromErr(err error) int { return http.StatusBadRequest case errors.Is(err, ErrUnauthorized): return http.StatusUnauthorized - case errors.Is(err, ErrRejected): + case errors.Is(err, ErrRejected), errors.Is(err, ErrLimitExceeded): return http.StatusTooManyRequests case errors.Is(err, ErrUnsupported): return http.StatusNotFound diff --git a/lib/srv/app/llm/handler.go b/lib/srv/app/llm/handler.go index 94d8ff601d35..be436955bf25 100644 --- a/lib/srv/app/llm/handler.go +++ b/lib/srv/app/llm/handler.go @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/lib/srv/app/llm/anthropic" "github.com/gravitational/teleport/lib/srv/app/llm/bedrock" llmerrors "github.com/gravitational/teleport/lib/srv/app/llm/errors" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" "github.com/gravitational/teleport/lib/srv/app/llm/openai" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" ) @@ -65,6 +66,8 @@ type HandlerConfig struct { // Transport is the transport used to issue requests to the upstream // LLM provider. Transport *http.Transport + // Limiter is the tokens limiter applied to the requests. + Limiter Limiter } // CheckAndSetDefaults validates required dependencies and sets defaults. @@ -89,6 +92,31 @@ func (c *HandlerConfig) CheckAndSetDefaults() error { } c.Transport = tr } + if c.Limiter == nil { + // Attempt to initialize the in-memory limiter which acts on the entire + // server instance. + inputRaw := os.Getenv(inputTokensQuotaEnvVar) + outputRaw := os.Getenv(outputTokensQuotaEnvVar) + if inputRaw != "" || outputRaw != "" { + if inputRaw == "" || outputRaw == "" { + return trace.BadParameter("both %q and %q environment variables must be set to enable the in-memory limiter", inputTokensQuotaEnvVar, outputTokensQuotaEnvVar) + } + + input, err := strconv.ParseUint(inputRaw, 10, 64) + if err != nil { + return trace.Wrap(err, "failed to parse input limit set on %q environment variable", inputTokensQuotaEnvVar) + } + output, err := strconv.ParseUint(outputRaw, 10, 64) + if err != nil { + return trace.Wrap(err, "failed to parse output limit set on %q environment variable", outputTokensQuotaEnvVar) + } + c.Limiter = llmlimiter.NewInMemory(uint(input), uint(output)) + c.Log.InfoContext(context.Background(), "using in-memory limiter", "input", input, "output", output) + } else { + // No settings, default to no limiter. + c.Limiter = &noopLimiter{} + } + } return nil } @@ -223,7 +251,7 @@ func (h *Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error { case types.LLMFormatAnthropic: h.handleRequest( sessionCtx, w, r, - func(app types.Application, r *http.Request) (*http.Request, llmrequest.RequestInfo, error) { + func(app types.Application, r *http.Request) (*llmrequest.Request, error) { return anthropic.NewRequest(&llmrequest.Config{ App: app, DownstreamRequest: r, @@ -232,6 +260,7 @@ func (h *Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error { return h.anthropicAPIKey }, SignBedrockRequest: h.signBedrockRequest, + Reserve: h.cfg.Limiter.Reserve, }) }, func(l *slog.Logger, _ llmrequest.RequestInfo, w http.ResponseWriter) (UpstreamRecorder, error) { @@ -242,7 +271,7 @@ func (h *Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error { case types.LLMFormatOpenAI: h.handleRequest( sessionCtx, w, r, - func(app types.Application, r *http.Request) (*http.Request, llmrequest.RequestInfo, error) { + func(app types.Application, r *http.Request) (*llmrequest.Request, error) { return openai.NewRequest(&llmrequest.Config{ App: app, DownstreamRequest: r, @@ -251,6 +280,7 @@ func (h *Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error { return h.openAIAPIKey }, SignBedrockRequest: h.signBedrockRequest, + Reserve: h.cfg.Limiter.Reserve, }) }, func(l *slog.Logger, info llmrequest.RequestInfo, w http.ResponseWriter) (UpstreamRecorder, error) { @@ -278,7 +308,7 @@ func (h *Handler) signBedrockRequest(ctx context.Context, app types.Application, type WriteErrorFunc func(http.ResponseWriter, error) error // NewUpstreamRequestFunc function used to create a new upstream request. -type NewUpstreamRequestFunc func(app types.Application, r *http.Request) (*http.Request, llmrequest.RequestInfo, error) +type NewUpstreamRequestFunc func(app types.Application, r *http.Request) (*llmrequest.Request, error) // NewUpstreamRecoderFunc function used to initialize a upstream response // recorder. @@ -319,9 +349,10 @@ func (h *Handler) handleRequest( ) var ( - err error - info llmrequest.RequestInfo - rec UpstreamRecorder + err error + info llmrequest.RequestInfo + rec UpstreamRecorder + settleFunc llmlimiter.SettleFunc = llmlimiter.EmptySettleFunc ) defer func() { @@ -335,13 +366,25 @@ func (h *Handler) handleRequest( resp.InputTokenCount = rec.InputTokensCount() resp.OutputTokenCount = rec.OutputTokensCount() } + if settleFunc != nil { + settleFunc(h.closeContext, llmlimiter.Usage{ + InputTokens: uint(resp.InputTokenCount), + OutputTokens: uint(resp.OutputTokenCount), + }) + } if err := sessionCtx.Audit.OnLLMRequest(h.closeContext, sessionCtx, r, req, resp); err != nil { log.ErrorContext(h.closeContext, "failed to emit audit event", "error", err) } }() - var req *http.Request - req, info, err = newRequestFunc(sessionCtx.App, r) + providerReq, err := newRequestFunc(sessionCtx.App, r) + // Even in cases of error the request could contain the info and settle + // we need to still perform. So we do the assignment before dealing with + // the error. + if providerReq != nil { + info = providerReq.Info + settleFunc = providerReq.SettleFunc + } if err != nil { log.ErrorContext(r.Context(), "failed to rewrite request", "error", err) if werr := writeErrorFunc(w, err); werr != nil { @@ -389,7 +432,7 @@ func (h *Handler) handleRequest( } start := time.Now() - fwd.ServeHTTP(rec, req) + fwd.ServeHTTP(rec, providerReq.HTTPRequest) if err := rec.Close(); err != nil { log.ErrorContext(h.closeContext, "failed to close llm recorder", "error", err) } @@ -424,6 +467,22 @@ func (h *Handler) handleRequest( ).Add(float64(rec.OutputTokensCount())) } +// Limiter defines the interface of tokens limiter. +type Limiter interface { + Reserve(context.Context, llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) +} + +// noopLimiter implements a noop tokens limiter. +type noopLimiter struct{} + +// Reserve implements [Limiter]. +func (n *noopLimiter) Reserve(_ context.Context, req llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) { + return llmlimiter.ReserveInfo{ + // This should skip all APIs from limits. + NotApplicable: true, + }, llmlimiter.EmptySettleFunc, nil +} + const ( // anthropicAddressEnvVarName is the Anthropic's default environment // variable used to set base API address. @@ -441,4 +500,11 @@ const ( // openAIAPIKeyEnvVarName is the OpenAI's environment variable used to // set API keys. openAIAPIKeyEnvVarName = "OPENAI_API_KEY" + + // inputTokensQuotaEnvVar is the name of the environment variable used to + // set the input tokens quota. + inputTokensQuotaEnvVar = "TELEPORT_BEAMS_BETA_INPUT_TOKENS_QUOTA" + // outputTokensQuotaEnvVar is the name of the environment variable used to + // set the output tokens quota. + outputTokensQuotaEnvVar = "TELEPORT_BEAMS_BETA_OUTPUT_TOKENS_QUOTA" ) diff --git a/lib/srv/app/llm/handler_test.go b/lib/srv/app/llm/handler_test.go index 9cf9f94ac6a9..a0599933fae0 100644 --- a/lib/srv/app/llm/handler_test.go +++ b/lib/srv/app/llm/handler_test.go @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/srv/app/common" llmerrors "github.com/gravitational/teleport/lib/srv/app/llm/errors" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" ) @@ -52,7 +53,7 @@ func TestHandleRequest(t *testing.T) { for name, tc := range map[string]struct { transport *http.Transport - newRequestFunc func(http.ResponseWriter, *httptest.Server) NewUpstreamRequestFunc + newRequestFunc func(http.ResponseWriter, *httptest.Server, llmlimiter.SettleFunc) NewUpstreamRequestFunc modifyRecorderFunc func(*mockUpstreamRecorder) writeErrorFunc WriteErrorFunc upstreamBody string @@ -61,16 +62,20 @@ func TestHandleRequest(t *testing.T) { expectedResponse require.ValueAssertionFunc }{ "success request": { - newRequestFunc: func(w http.ResponseWriter, s *httptest.Server) NewUpstreamRequestFunc { - return func(_ types.Application, _ *http.Request) (*http.Request, llmrequest.RequestInfo, error) { + newRequestFunc: func(w http.ResponseWriter, s *httptest.Server, settleFunc llmlimiter.SettleFunc) NewUpstreamRequestFunc { + return func(_ types.Application, _ *http.Request) (*llmrequest.Request, error) { req, err := http.NewRequest(http.MethodPost, s.URL, nil) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, trace.Wrap(err) } - return req, &mockRequestInfo{ - requestedModel: "requested", - providerModel: "provider", + return &llmrequest.Request{ + HTTPRequest: req, + Info: &mockRequestInfo{ + requestedModel: "requested", + providerModel: "provider", + }, + SettleFunc: settleFunc, }, nil } }, @@ -96,9 +101,11 @@ func TestHandleRequest(t *testing.T) { expectedResponse: expectString("REPLY"), }, "new request error": { - newRequestFunc: func(w http.ResponseWriter, s *httptest.Server) NewUpstreamRequestFunc { - return func(_ types.Application, _ *http.Request) (*http.Request, llmrequest.RequestInfo, error) { - return nil, nil, trace.BadParameter("invalid request") + newRequestFunc: func(w http.ResponseWriter, s *httptest.Server, settleFunc llmlimiter.SettleFunc) NewUpstreamRequestFunc { + return func(_ types.Application, _ *http.Request) (*llmrequest.Request, error) { + return &llmrequest.Request{ + SettleFunc: settleFunc, + }, trace.BadParameter("invalid request") } }, writeErrorFunc: func(w http.ResponseWriter, err error) error { @@ -118,11 +125,14 @@ func TestHandleRequest(t *testing.T) { expectedResponse: expectString("invalid request"), }, "new request error with partial info": { - newRequestFunc: func(w http.ResponseWriter, s *httptest.Server) NewUpstreamRequestFunc { - return func(_ types.Application, _ *http.Request) (*http.Request, llmrequest.RequestInfo, error) { - return nil, &mockRequestInfo{ - requestedModel: "requested", - providerModel: "provider", + newRequestFunc: func(w http.ResponseWriter, s *httptest.Server, settleFunc llmlimiter.SettleFunc) NewUpstreamRequestFunc { + return func(_ types.Application, _ *http.Request) (*llmrequest.Request, error) { + return &llmrequest.Request{ + Info: &mockRequestInfo{ + requestedModel: "requested", + providerModel: "provider", + }, + SettleFunc: settleFunc, }, trace.BadParameter("invalid request") } }, @@ -143,16 +153,20 @@ func TestHandleRequest(t *testing.T) { expectedResponse: expectString("invalid request"), }, "successful request with recorder error": { - newRequestFunc: func(w http.ResponseWriter, s *httptest.Server) NewUpstreamRequestFunc { - return func(_ types.Application, _ *http.Request) (*http.Request, llmrequest.RequestInfo, error) { + newRequestFunc: func(w http.ResponseWriter, s *httptest.Server, settleFunc llmlimiter.SettleFunc) NewUpstreamRequestFunc { + return func(_ types.Application, _ *http.Request) (*llmrequest.Request, error) { req, err := http.NewRequest(http.MethodPost, s.URL, nil) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, trace.Wrap(err) } - return req, &mockRequestInfo{ - requestedModel: "requested", - providerModel: "provider", + return &llmrequest.Request{ + HTTPRequest: req, + Info: &mockRequestInfo{ + requestedModel: "requested", + providerModel: "provider", + }, + SettleFunc: settleFunc, }, nil } }, @@ -177,16 +191,20 @@ func TestHandleRequest(t *testing.T) { }, // This case covers scenarios where upstream is not reachable. "upstream forward error": { - newRequestFunc: func(w http.ResponseWriter, s *httptest.Server) NewUpstreamRequestFunc { - return func(_ types.Application, _ *http.Request) (*http.Request, llmrequest.RequestInfo, error) { + newRequestFunc: func(w http.ResponseWriter, s *httptest.Server, settleFunc llmlimiter.SettleFunc) NewUpstreamRequestFunc { + return func(_ types.Application, _ *http.Request) (*llmrequest.Request, error) { req, err := http.NewRequest(http.MethodPost, s.URL, nil) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, trace.Wrap(err) } - return req, &mockRequestInfo{ - requestedModel: "requested", - providerModel: "provider", + return &llmrequest.Request{ + HTTPRequest: req, + Info: &mockRequestInfo{ + requestedModel: "requested", + providerModel: "provider", + }, + SettleFunc: settleFunc, }, nil } }, @@ -233,7 +251,9 @@ func TestHandleRequest(t *testing.T) { } }) - h := newTestHandler(t, tc.transport) + const maxOutputTokens = 128 + limiter := newMockLimiter(llmlimiter.ReserveInfo{OutputTokens: maxOutputTokens}) + h := newTestHandler(t, tc.transport, limiter) app := newTestApp(t, &types.LLM{Format: types.LLMFormatAnthropic, Provider: types.LLMProviderAnthropic}) sessionCtx := &common.SessionContext{App: app, Audit: audit} req := newTestSessionRequest( @@ -245,14 +265,23 @@ func TestHandleRequest(t *testing.T) { sessionCtx, ) + // Reservations are made by the provider requests, which are mocked + // here, so the settle function is provided to them. + _, settleFunc, err := limiter.Reserve(t.Context(), llmlimiter.ReserveRequest{ + App: app, + Usage: &llmlimiter.Usage{OutputTokens: maxOutputTokens}, + }) + require.NoError(t, err) + + var rec *mockUpstreamRecorder w := httptest.NewRecorder() h.handleRequest( sessionCtx, w, req, - tc.newRequestFunc(w, mockServer), + tc.newRequestFunc(w, mockServer, settleFunc), func(_ *slog.Logger, _ llmrequest.RequestInfo, w http.ResponseWriter) (UpstreamRecorder, error) { - rec := &mockUpstreamRecorder{ResponseWriter: w} + rec = &mockUpstreamRecorder{ResponseWriter: w} tc.modifyRecorderFunc(rec) return rec, nil }, @@ -262,6 +291,16 @@ func TestHandleRequest(t *testing.T) { require.NotNil(t, auditEvent) tc.expectAuditEvent(t, auditEvent) tc.expectedResponse(t, w.Body.String()) + + var expectedUsage llmlimiter.Usage + if rec != nil { + expectedUsage = llmlimiter.Usage{ + InputTokens: uint(rec.InputTokensCount()), + OutputTokens: uint(rec.OutputTokensCount()), + } + } + require.True(t, limiter.settleCalled, "expected reservation settle to be called") + require.Equal(t, expectedUsage, limiter.settleUsage) }) } } @@ -314,7 +353,7 @@ func TestHandleError(t *testing.T) { } { t.Run(name, func(t *testing.T) { var auditEvent *apievents.AppSessionLLMRequest - h := newTestHandler(t, nil) + h := newTestHandler(t, nil /* transport */, nil /* limiter */) req := newTestSessionRequest( t, http.MethodPost, @@ -400,6 +439,26 @@ type streamRecorder struct { func (s *streamRecorder) Write(p []byte) (int, error) { return len(p), nil } +func newMockLimiter(info llmlimiter.ReserveInfo) *mockLimiter { + return &mockLimiter{reserveInfo: info} +} + +// mockLimiter serves a single use limiter that records the calls. +type mockLimiter struct { + reserveInfo llmlimiter.ReserveInfo + + settleUsage llmlimiter.Usage + settleCalled bool +} + +// Reserve implements [Limiter]. +func (m *mockLimiter) Reserve(_ context.Context, _ llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) { + return m.reserveInfo, func(_ context.Context, settleUsage llmlimiter.Usage) { + m.settleCalled = true + m.settleUsage = settleUsage + }, nil +} + // newTestAudit creates a common.Audit that calls onRecord for each recorded event. func newTestAudit(t *testing.T, onRecord func(apievents.PreparedSessionEvent)) common.Audit { t.Helper() @@ -442,11 +501,12 @@ func newTestSessionRequest(t *testing.T, method, addr string, path string, body return common.WithSessionContext(req, sessionCtx) } -func newTestHandler(t *testing.T, tr *http.Transport) *Handler { +func newTestHandler(t *testing.T, tr *http.Transport, limiter Limiter) *Handler { t.Helper() h, err := NewHandler(t.Context(), HandlerConfig{ Log: slog.Default(), Transport: tr, + Limiter: limiter, }) require.NoError(t, err) return h diff --git a/lib/srv/app/llm/limiter/inmemory.go b/lib/srv/app/llm/limiter/inmemory.go new file mode 100644 index 000000000000..7e34fe834e97 --- /dev/null +++ b/lib/srv/app/llm/limiter/inmemory.go @@ -0,0 +1,163 @@ +// Teleport +// Copyright (C) 2026 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 limiter + +import ( + "context" + "math" + "sync" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" + llmerrors "github.com/gravitational/teleport/lib/srv/app/llm/errors" +) + +// InMemory provides tokens limiter in memory. +// +// This should act as initial implementation and behavior reference for future +// implementations like distributed solutions. +type InMemory struct { + mu sync.Mutex + storage map[appKey]Usage + + inputTokensLimit uint + outputTokensLimit uint +} + +// NewInMemory creates a new instance of the [InMemory] limiter. +func NewInMemory(input, output uint) *InMemory { + return &InMemory{ + inputTokensLimit: input, + outputTokensLimit: output, + storage: make(map[appKey]Usage), + } +} + +// Reserve reserves LLM usage. +func (i *InMemory) Reserve(ctx context.Context, req ReserveRequest) (ReserveInfo, SettleFunc, error) { + if err := req.CheckAndSetDefaults(); err != nil { + return ReserveInfo{}, EmptySettleFunc, trace.Wrap(err) + } + + key := newAppKey(req.App) + i.mu.Lock() + defer i.mu.Unlock() + curr := i.storage[key] + + // Consumption goes past the limits when a settlement reports more usage + // than reserved, leaving nothing to reserve. + if curr.InputTokens > i.inputTokensLimit || curr.OutputTokens > i.outputTokensLimit { + return ReserveInfo{}, EmptySettleFunc, llmerrors.ErrLimitExceeded + } + + inputLeft := i.inputTokensLimit - curr.InputTokens + outputLeft := i.outputTokensLimit - curr.OutputTokens + + if req.MaxUsage != nil { + req.Usage = &Usage{} + req.Usage.OutputTokens = min( + outputLeft, + req.MaxUsage.OutputTokens, + ) + + // There is still a case where the usage is zero, and that is if there + // is no limit left. + if req.Usage.OutputTokens == 0 { + return ReserveInfo{}, EmptySettleFunc, llmerrors.ErrLimitExceeded + } + } + + // Checked against the tokens left because summing the usage with the + // current consumption can overflow and bypass the limits. + if req.Usage.InputTokens > inputLeft || req.Usage.OutputTokens > outputLeft { + return ReserveInfo{}, EmptySettleFunc, llmerrors.ErrLimitExceeded + } + + curr.InputTokens += req.Usage.InputTokens + curr.OutputTokens += req.Usage.OutputTokens + i.storage[key] = curr + + var settleOnce sync.Once + + // TODO(gabrielcorado): cancel reservations using context. + return ReserveInfo{OutputTokens: req.Usage.OutputTokens}, func(ctx context.Context, u Usage) { + settleOnce.Do(func() { + i.mu.Lock() + defer i.mu.Unlock() + curr := i.storage[key] + + // It is most common to have consumed less tokens than it was reserved + // so we need to adjust the value. + // + // If the usage is greater than the requested, we should increase the + // value stored instead of decreasing. This is already handled by + // settleTokens. + // + // We wrap those into a check if the reported usage is greater than 0 + // in case the provider or recorder weren't able to extract usage + // information. For those cases we want to keep the reservation values. + if u.InputTokens > 0 { + curr.InputTokens = settleTokens(curr.InputTokens, req.Usage.InputTokens, u.InputTokens) + } + if u.OutputTokens > 0 { + curr.OutputTokens = settleTokens(curr.OutputTokens, req.Usage.OutputTokens, u.OutputTokens) + } + + i.storage[key] = curr + }) + }, nil +} + +// Consumption retrieves the current app limit consumption. +func (i *InMemory) Consumption(app types.Application) Usage { + i.mu.Lock() + defer i.mu.Unlock() + return i.storage[newAppKey(app)] +} + +// settleTokens replaces the tokens held by a reservation with the usage +// reported on settlement. The reported usage is provider-controlled, so the +// adjustment saturates instead of wrapping around, which would reset the +// consumption back to a small value. +func settleTokens(consumed, reserved, reported uint) uint { + if reported >= reserved { + if total := consumed + (reported - reserved); total >= consumed { + return total + } + return math.MaxUint + } + + // Giving back more than consumed means the reservation was settled twice. + if diff := reserved - reported; diff <= consumed { + return consumed - diff + } + return 0 +} + +type appKey struct { + name string + llmFormat string + llmProvider string +} + +func newAppKey(app types.Application) appKey { + // Safe to assume LLM will always be present on this app since this is + // specific for LLM access. + llm := app.GetLLM() + return appKey{name: app.GetName(), llmFormat: llm.Format, llmProvider: llm.Provider} +} diff --git a/lib/srv/app/llm/limiter/inmemory_test.go b/lib/srv/app/llm/limiter/inmemory_test.go new file mode 100644 index 000000000000..993912043099 --- /dev/null +++ b/lib/srv/app/llm/limiter/inmemory_test.go @@ -0,0 +1,597 @@ +// Teleport +// Copyright (C) 2026 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 limiter + +import ( + "fmt" + "math" + "testing" + "unicode" + + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "github.com/gravitational/teleport/api/types" +) + +// TestProperty_OnlyOneUsageRequestType given a reserve request, ensures that +// only one usage type can be used. +func TestInMemoryOnlyOneUsageRequestType(t *testing.T) { + l := NewInMemory(0, 0) + _, _, err := l.Reserve(t.Context(), ReserveRequest{ + Usage: &Usage{ + InputTokens: 32, + OutputTokens: 32, + }, + MaxUsage: &Usage{ + InputTokens: 32, + OutputTokens: 32, + }, + }) + require.Error(t, err) +} + +// TestProperty_InputOutputIsolated given a limiter, input and output limits +// are isolated and do not interfere on each other. +func TestProperty_InputOutputIsolated(t *testing.T) { + t.Run("input", rapid.MakeCheck(func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + baseLimits := rapid.UintRange(32, 128).Draw(t, "base_limits") + // Make the output limits larger so requests will exceed input limits + // first. + l := NewInMemory(baseLimits, baseLimits*2) + + nReservations := rapid.IntRange(1, int(baseLimits)).Draw(t, "n_reservations") + for _, usage := range usageGenerator(nReservations, baseLimits, baseLimits).Draw(t, "reservations") { + _ = reserveAndSettle(t, l, app, usage) + } + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &Usage{ + InputTokens: rapid.Just(uint(1)).Draw(t, "input_usage"), + OutputTokens: rapid.UintRange(1, baseLimits-1).Draw(t, "output_usage"), + }}) + require.Error(t, err) + require.NotNil(t, settleFunc) + })) + + t.Run("output", rapid.MakeCheck(func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + baseLimits := rapid.UintRange(32, 128).Draw(t, "base_limits") + // Make the input limits larger so requests will exceed input limits + // first. + l := NewInMemory(baseLimits*2, baseLimits) + + nReservations := rapid.IntRange(1, int(baseLimits)).Draw(t, "n_reservations") + for _, usage := range usageGenerator(nReservations, baseLimits, baseLimits).Draw(t, "reservations") { + _ = reserveAndSettle(t, l, app, usage) + } + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &Usage{ + InputTokens: rapid.UintRange(1, baseLimits-1).Draw(t, "input_usage"), + OutputTokens: rapid.Just(uint(1)).Draw(t, "output_usage"), + }}) + require.Error(t, err) + require.NotNil(t, settleFunc) + })) +} + +// TestProperty_AppsIsolated given a list of apps being served, their limits are +// isolated and do not affect each other. +func TestProperty_AppsIsolated(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + maxInput, maxOutput := genLimits.Draw(t, "max_input"), genLimits.Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + apps := rapid.SliceOfDistinct(appGenerator(), func(app types.Application) string { return app.GetName() }).Draw(t, "apps") + randomApps := rapid.Permutation(apps) + + // For each app we'll make requests to consume the limit, leaving no + // room. + for _, app := range randomApps.Draw(t, "apps_reservations_permutation") { + // Draw usage but not enough to exceed the app limit. + var ( + totalInput uint + totalOutput uint + ) + for _, usage := range usageGenerator(rapid.IntRange(1, 10).Draw(t, "n_app_reservations"), maxInput, maxOutput).Draw(t, "app_usage") { + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &usage}) + require.NoError(t, err) + require.NotNil(t, settleFunc) + settleFunc(t.Context(), usage) + totalInput += usage.InputTokens + totalOutput += usage.OutputTokens + } + + require.Equal(t, totalInput, l.Consumption(app).InputTokens) + require.Equal(t, totalOutput, l.Consumption(app).OutputTokens) + } + + // Now we'll do the last reservation that will return limit exceeded + // for all apps. + for _, app := range randomApps.Draw(t, "apps_reservations_permutation_failure") { + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &Usage{InputTokens: 1, OutputTokens: 1}}) + require.Error(t, err) + require.NotNil(t, settleFunc) + } + + }) +} + +// TestProperty_ReserveInfo given a reservation with max usage, it returns the +// reserved output tokens. +func TestProperty_MaxUsageReportsReserveInfo(t *testing.T) { + for name, tc := range map[string]struct { + maxOutput *rapid.Generator[uint] + reserveReq func(maxOutput uint) *rapid.Generator[ReserveRequest] + expectErr require.ErrorAssertionFunc + expectInfo func(tt require.TestingT, maxOutput uint, req ReserveRequest, info ReserveInfo) + }{ + "requested value with limits": { + maxOutput: genLimits, + reserveReq: func(maxOutput uint) *rapid.Generator[ReserveRequest] { + return rapid.Custom(func(t *rapid.T) ReserveRequest { + return ReserveRequest{ + App: appGenerator().Draw(t, "app"), + MaxUsage: &Usage{ + OutputTokens: rapid.UintMin(maxOutput).Draw(t, "max_provider_output"), + }, + } + }) + }, + expectErr: require.NoError, + expectInfo: func(tt require.TestingT, maxOutput uint, req ReserveRequest, info ReserveInfo) { + require.Equal(tt, maxOutput, info.OutputTokens) + }, + }, + "provider defaults": { + maxOutput: genLimits, + reserveReq: func(maxOutput uint) *rapid.Generator[ReserveRequest] { + return rapid.Custom(func(t *rapid.T) ReserveRequest { + return ReserveRequest{ + App: appGenerator().Draw(t, "app"), + MaxUsage: &Usage{ + OutputTokens: rapid.UintRange(1, maxOutput).Draw(t, "max_provider_output"), + }, + } + }) + }, + expectErr: require.NoError, + expectInfo: func(tt require.TestingT, _ uint, req ReserveRequest, info ReserveInfo) { + require.Equal(tt, req.MaxUsage.OutputTokens, info.OutputTokens) + }, + }, + "not enough limit available": { + maxOutput: rapid.Just(uint(0)), + reserveReq: func(maxOutput uint) *rapid.Generator[ReserveRequest] { + return rapid.Custom(func(t *rapid.T) ReserveRequest { + return ReserveRequest{ + App: appGenerator().Draw(t, "app"), + MaxUsage: &Usage{ + OutputTokens: rapid.Uint().Draw(t, "max_provider_output"), + }, + } + }) + }, + expectErr: require.Error, + }, + } { + t.Run(name, rapid.MakeCheck(func(t *rapid.T) { + maxOutput := tc.maxOutput.Draw(t, "max_output") + l := NewInMemory(genLimits.Draw(t, "max_input"), maxOutput) + + req := tc.reserveReq(maxOutput).Draw(t, "reserve_req") + info, _, err := l.Reserve(t.Context(), req) + tc.expectErr(t, err) + if tc.expectInfo != nil { + tc.expectInfo(t, maxOutput, req, info) + } + })) + } +} + +// TestProperty_EmptyUsageSettlementDoesNothing given a reservation that is +// settled with empty usage, it should not change the consumed values. +func TestProperty_EmptyUsageSettlementReturnToOriginalState(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + maxInput, maxOutput := genLimits.Draw(t, "max_input"), genLimits.Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + initialConsumptionState := l.Consumption(app) + + _, settleFunc, err := l.Reserve( + t.Context(), + ReserveRequest{ + App: app, + Usage: &Usage{ + InputTokens: rapid.UintMax(maxInput-1).Draw(t, "reserve_input"), + OutputTokens: rapid.UintRange(1, maxOutput-1).Draw(t, "reserve_output"), + }, + }, + ) + require.NoError(t, err) + require.NotNil(t, settleFunc) + + afterReservation := l.Consumption(app) + require.GreaterOrEqual(t, afterReservation.InputTokens, initialConsumptionState.InputTokens) + require.Greater(t, afterReservation.OutputTokens, initialConsumptionState.OutputTokens) + + settleFunc(t.Context(), Usage{}) + + require.Equal(t, afterReservation.InputTokens, l.Consumption(app).InputTokens) + require.Equal(t, afterReservation.OutputTokens, l.Consumption(app).OutputTokens) + }) +} + +// TestProperty_ReservationsAndSettlementsCountForLimit given reservations and +// settlements, both contribute to the app limits. +func TestProperty_ReservationsAndSettlementsCountForLimit(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + maxInput, maxOutput := genLimits.Draw(t, "max_input"), genLimits.Draw(t, "max_output") + halfInput := (maxInput - 2) / 2 + halfOutput := (maxOutput - 2) / 2 + l := NewInMemory(maxInput, maxOutput) + + var ( + totalInput uint + totalOutput uint + ) + + nReservations := rapid.IntRange(1, 10).Draw(t, "n_reservations") + for _, usage := range usageGenerator(nReservations, halfInput, halfOutput).Draw(t, "reservations") { + _, _, err := l.Reserve(t.Context(), ReserveRequest{ + App: app, + Usage: &usage, + }) + require.NoError(t, err) + totalInput += usage.InputTokens + totalOutput += usage.OutputTokens + } + + require.Equal(t, totalInput, l.Consumption(app).InputTokens) + require.Equal(t, totalOutput, l.Consumption(app).OutputTokens) + + nSettled := rapid.IntRange(1, 10).Draw(t, "n_settled") + for _, usage := range usageGenerator(nSettled, halfInput, halfOutput).Draw(t, "reservations_to_be_settled") { + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{ + App: app, + Usage: &usage, + }) + require.NoError(t, err) + settleFunc(t.Context(), usage) + totalInput += usage.InputTokens + totalOutput += usage.OutputTokens + } + + require.Equal(t, totalInput, l.Consumption(app).InputTokens) + require.Equal(t, totalOutput, l.Consumption(app).OutputTokens) + + // Final reservation will top the limits, anything from now on should + // be rejected. + // + // Reserve the settleFunc for later to ensure settling is not affected + // after the limit is exceeded. + validUsage := Usage{ + InputTokens: rapid.Just(maxInput-l.Consumption(app).InputTokens).Draw(t, "valid_reserve_input"), + OutputTokens: rapid.Just(maxOutput-l.Consumption(app).OutputTokens).Draw(t, "valid_reserve_output"), + } + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{ + App: app, + Usage: &validUsage, + }) + require.NoError(t, err) + require.NotNil(t, settleFunc) + + // Any reserve now should fail. + _, _, err = l.Reserve(t.Context(), ReserveRequest{ + App: app, + Usage: &Usage{ + InputTokens: rapid.Just(uint(1)).Draw(t, "final_reserve_input"), + OutputTokens: rapid.Just(uint(1)).Draw(t, "final_reserve_output"), + }, + }) + require.Error(t, err) + + settleFunc(t.Context(), validUsage) + }) +} + +// TestProperty_SettlementAdjustReservationValues given a settlement, it adjust +// the limit values based on the reported usage. +func TestProperty_SettlementAdjustReservationValues(t *testing.T) { + minReservation := func(min uint) func(t *rapid.T, maxInput, maxOutput uint) Usage { + return func(t *rapid.T, maxInput, maxOutput uint) Usage { + return Usage{ + InputTokens: rapid.UintRange(min, maxInput-1).Draw(t, "reserve_input"), + OutputTokens: rapid.UintRange(min, maxOutput-1).Draw(t, "reserve_output"), + } + } + } + + for name, tc := range map[string]struct { + reserve func(t *rapid.T, maxInput uint, maxOutput uint) Usage + usage func(t *rapid.T, reservedInput uint, reservedOutput uint) Usage + expectInputTokens require.ComparisonAssertionFunc + expectOutputTokens require.ComparisonAssertionFunc + }{ + "exact token consumption": { + reserve: minReservation(1), + usage: func(t *rapid.T, reservedInput, reservedOutput uint) Usage { + return Usage{ + InputTokens: rapid.Just(reservedInput).Draw(t, "settle_input"), + OutputTokens: rapid.Just(reservedOutput).Draw(t, "settle_output"), + } + }, + expectInputTokens: require.Equal, + expectOutputTokens: require.Equal, + }, + "consumed more": { + reserve: minReservation(1), + usage: func(t *rapid.T, reservedInput, reservedOutput uint) Usage { + return Usage{ + InputTokens: rapid.UintMin(reservedInput+1).Draw(t, "settle_input"), + OutputTokens: rapid.UintMin(reservedOutput+1).Draw(t, "settle_output"), + } + }, + expectInputTokens: require.Greater, + expectOutputTokens: require.Greater, + }, + "consumed less": { + // Create some room so that usage reports less than the value + // reserved. + reserve: minReservation(10), + usage: func(t *rapid.T, reservedInput, reservedOutput uint) Usage { + return Usage{ + InputTokens: rapid.UintRange(1, reservedInput-1).Draw(t, "settle_input"), + OutputTokens: rapid.UintRange(1, reservedOutput-1).Draw(t, "settle_output"), + } + }, + expectInputTokens: require.Less, + expectOutputTokens: require.Less, + }, + "empty settlement usage": { + reserve: minReservation(1), + usage: func(t *rapid.T, reservedInput, reservedOutput uint) Usage { + return Usage{ + InputTokens: rapid.Just(uint(0)).Draw(t, "settle_input"), + OutputTokens: rapid.Just(uint(0)).Draw(t, "settle_output"), + } + }, + expectInputTokens: require.Equal, + expectOutputTokens: require.Equal, + }, + "zero input reservation": { + reserve: func(t *rapid.T, maxInput, maxOutput uint) Usage { + return Usage{ + InputTokens: rapid.Just(uint(0)).Draw(t, "reserve_input"), + OutputTokens: rapid.Just(uint(1)).Draw(t, "reserve_output"), + } + }, + usage: func(t *rapid.T, reservedInput, reservedOutput uint) Usage { + return Usage{ + InputTokens: rapid.UintMin(1).Draw(t, "settle_input"), + OutputTokens: rapid.Just(uint(1)).Draw(t, "settle_output"), + } + }, + expectInputTokens: require.Greater, + expectOutputTokens: require.Equal, + }, + } { + t.Run(name, rapid.MakeCheck(func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + maxInput, maxOutput := genLimits.Draw(t, "max_input"), genLimits.Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + + reservedUsage := tc.reserve(t, maxInput, maxOutput) + _, settleFunc, err := l.Reserve( + t.Context(), + ReserveRequest{ + App: app, + Usage: &reservedUsage, + }, + ) + require.NoError(t, err) + require.NotNil(t, settleFunc) + + require.Equal(t, reservedUsage.InputTokens, l.Consumption(app).InputTokens) + require.Equal(t, reservedUsage.OutputTokens, l.Consumption(app).OutputTokens) + + settleFunc(t.Context(), tc.usage(t, reservedUsage.InputTokens, reservedUsage.OutputTokens)) + + tc.expectInputTokens(t, l.Consumption(app).InputTokens, reservedUsage.InputTokens) + tc.expectOutputTokens(t, l.Consumption(app).OutputTokens, reservedUsage.OutputTokens) + })) + } +} + +// TestProperty_LargeReservationsDoNotBypassLimits given reservations that +// exceed the internal max value, subsequent requests are still rejected. +func TestProperty_LargeReservationsDoNotBypassLimits(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + // Use limits that are close to the max unit value. + maxInput, maxOutput := rapid.UintRange(math.MaxUint-10, math.MaxUint-1).Draw(t, "max_input"), rapid.UintRange(math.MaxUint-10, math.MaxUint-1).Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + + // Initial reservation will almost consume the entire limits. + _ = reserveAndSettle(t, l, app, Usage{ + InputTokens: rapid.UintRange(maxInput-10, maxInput-1).Draw(t, "initial_input"), + OutputTokens: rapid.UintRange(maxOutput-10, maxOutput-1).Draw(t, "initial_output"), + }) + + initialConsumption := l.Consumption(app) + + // Now requests that will push the value into wrapping. + _, _, err := l.Reserve(t.Context(), ReserveRequest{ + App: app, + Usage: &Usage{ + InputTokens: rapid.UintMin(100).Draw(t, "large_input"), + OutputTokens: rapid.UintMin(100).Draw(t, "large_output"), + }, + }) + // Those will exceed the limits. + require.Error(t, err) + + // Just to ensure the limits are not affected. + require.Equal(t, initialConsumption.InputTokens, l.Consumption(app).InputTokens) + require.Equal(t, initialConsumption.OutputTokens, l.Consumption(app).OutputTokens) + + }) +} + +// TestProperty_LargeSettlementsDoNotBypassLimits given settlements reporting +// usage far above what was reserved, the consumption saturates instead of +// wrapping around and resetting the app limits. +func TestProperty_LargeSettlementsDoNotBypassLimits(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + // Use limits that are close to the max uint value. + maxInput, maxOutput := rapid.UintRange(math.MaxUint-10, math.MaxUint).Draw(t, "max_input"), rapid.UintRange(math.MaxUint-10, math.MaxUint).Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + + // Initial reservation will almost consume the entire limits. + _ = reserveAndSettle(t, l, app, Usage{ + InputTokens: maxInput - 10, + OutputTokens: maxOutput - 10, + }) + + // Small reservation that later reports much more usage than reserved. + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &Usage{ + InputTokens: rapid.UintRange(1, 10).Draw(t, "reserve_input"), + OutputTokens: rapid.UintRange(1, 10).Draw(t, "reserve_output"), + }}) + require.NoError(t, err) + require.NotNil(t, settleFunc) + settleFunc(t.Context(), Usage{ + InputTokens: rapid.UintMin(math.MaxUint/2).Draw(t, "settle_input"), + OutputTokens: rapid.UintMin(math.MaxUint/2).Draw(t, "settle_output"), + }) + + // The over-reported usage must not wrap the consumption back to a low + // value. + require.GreaterOrEqual(t, l.Consumption(app).InputTokens, maxInput) + require.GreaterOrEqual(t, l.Consumption(app).OutputTokens, maxOutput) + + // With the limits consumed, any reservation is rejected. + _, _, err = l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &Usage{ + InputTokens: rapid.Just(uint(1)).Draw(t, "final_reserve_input"), + OutputTokens: rapid.Just(uint(1)).Draw(t, "final_reserve_output"), + }}) + require.Error(t, err) + }) +} + +// TestProperty_SettlementsShouldOnlyApplyOnce given a settlement, calling it +// multiple times should not affect the limits usage. +func TestProperty_SettlementsShouldOnlyApplyOnce(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + app := appGenerator().Draw(t, "app") + maxInput, maxOutput := genLimits.Draw(t, "max_input"), genLimits.Draw(t, "max_output") + l := NewInMemory(maxInput, maxOutput) + + usage := Usage{ + InputTokens: rapid.UintRange(1, maxInput-1).Draw(t, "reserve_input"), + OutputTokens: rapid.UintRange(1, maxOutput-1).Draw(t, "reserve_output"), + } + _, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &usage}) + require.NoError(t, err) + require.NotNil(t, settleFunc) + + consumption := l.Consumption(app) + + for range rapid.IntRange(2, 5).Draw(t, "n_settlements") { + settleFunc(t.Context(), usage) + require.Equal(t, consumption.InputTokens, l.Consumption(app).InputTokens) + require.Equal(t, consumption.OutputTokens, l.Consumption(app).OutputTokens) + } + }) +} + +var genLimits = rapid.Custom(func(t *rapid.T) uint { + return rapid.UintMin(32).Draw(t, "") +}) + +// usageGenerator returns a [Usage] generator that is able to generate a list +// of usage that sum to the inputTotal and outputTotal. +func usageGenerator(n int, inputTotal uint, outputTotal uint) *rapid.Generator[[]Usage] { + if n == 1 { + return rapid.Just([]Usage{{ + InputTokens: inputTotal, + OutputTokens: outputTotal, + }}) + } + + return rapid.Custom(func(t *rapid.T) []Usage { + inputs := drawParts(t, n, inputTotal, "input") + outputs := drawParts(t, n, outputTotal, "output") + + usages := make([]Usage, n) + for i := range usages { + usages[i] = Usage{ + InputTokens: inputs[i], + OutputTokens: outputs[i], + } + } + + return usages + }) +} + +// appGenerator returns a [types.Application] generator. +func appGenerator() *rapid.Generator[types.Application] { + return rapid.Custom(func(t *rapid.T) types.Application { + app, err := types.NewAppV3(types.Metadata{ + Name: rapid.StringOfN(rapid.RuneFrom(nil, unicode.ASCII_Hex_Digit), 6, 253, -1).Draw(t, "name"), + }, types.AppSpecV3{ + LLM: &types.LLM{ + Format: rapid.OneOf( + rapid.Just(types.LLMFormatAnthropic), + rapid.Just(types.LLMFormatOpenAI), + ).Draw(t, "llm_format"), + Provider: types.LLMProviderAWSBedrock, + }, + }) + require.NoError(t, err) + return app + }) +} + +func reserveAndSettle(t *rapid.T, l *InMemory, app types.Application, usage Usage) ReserveInfo { + t.Helper() + + info, settleFunc, err := l.Reserve(t.Context(), ReserveRequest{App: app, Usage: &usage}) + require.NoError(t, err) + require.NotNil(t, settleFunc) + settleFunc(t.Context(), usage) + return info +} + +func drawParts(t *rapid.T, n int, total uint, label string) []uint { + t.Helper() + if n <= 0 || total < uint(n) { + t.Fatalf("total must be at least n, and n must be positive") + } + + parts := make([]uint, n) + remaining := total - uint(n) + + for i := range n - 1 { + part := rapid.UintRange(0, remaining).Draw(t, fmt.Sprintf("%s-%d", label, i)) + parts[i] = 1 + part + remaining -= part + } + + parts[n-1] = 1 + remaining + return parts +} diff --git a/lib/srv/app/llm/limiter/limiter.go b/lib/srv/app/llm/limiter/limiter.go new file mode 100644 index 000000000000..a4dc60f519c1 --- /dev/null +++ b/lib/srv/app/llm/limiter/limiter.go @@ -0,0 +1,79 @@ +// Teleport +// Copyright (C) 2026 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 limiter + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" +) + +// ReserveRequest is a request to reserve tokens out of the application limits. +type ReserveRequest struct { + // App is the application that is serving the request. + App types.Application + // Usage is the exact amount to be reserved. + // Mutually exclusive with MaxUsage. + Usage *Usage + // MaxUsage asks for the available limit amount with max value defined by + // this property. The values exact reserved values will be returned by + // the reserve function. + MaxUsage *Usage +} + +func (r ReserveRequest) CheckAndSetDefaults() error { + switch { + case r.App == nil: + return trace.BadParameter("must provide App") + case r.Usage != nil && r.MaxUsage != nil: + return trace.BadParameter("can only provide Usage or MaxUsage, not both") + case r.Usage == nil && r.MaxUsage == nil: + return trace.BadParameter("must provide Usage or MaxUsage") + default: + return nil + } +} + +// Usage is an amount of LLM tokens. It is used both to describe reservations +// and the effective consumption reported by providers. +type Usage struct { + // InputTokens is the number of tokens sent to the provider. + InputTokens uint + // OutputTokens is the number of tokens generated by the provider. + OutputTokens uint +} + +// ReserveInfo is the reservation info. +type ReserveInfo struct { + // NotApplicable indicates the request is exempt from limiting. Callers can + // still expect to receive a non-nil settle function, for example + // [EmptySettleFunc]. + NotApplicable bool + // OutputTokens is the max output tokens the provider can generate that + // won't exceed the limit. + OutputTokens uint +} + +// SettleFunc settles a reservation with the usage reported for the request. +// Settling an empty [Usage] cancels the reservation. +type SettleFunc func(context.Context, Usage) + +// EmptySettleFunc is a no-op [SettleFunc], returned by limiters when there is +// nothing to settle. +func EmptySettleFunc(context.Context, Usage) {} diff --git a/lib/srv/app/llm/openai/errors.go b/lib/srv/app/llm/openai/errors.go index a8b886b4c4b4..e5f1533bc5b4 100644 --- a/lib/srv/app/llm/openai/errors.go +++ b/lib/srv/app/llm/openai/errors.go @@ -107,7 +107,7 @@ func newErrorMessage(err error) errorMessage { Message: err.Error(), } switch { - case errors.Is(err, llmerrors.ErrRejected): + case errors.Is(err, llmerrors.ErrRejected), errors.Is(err, llmerrors.ErrLimitExceeded): msg.Type = errorTypeRateLimitExceeded case errors.Is(err, llmerrors.ErrBadRequest), errors.Is(err, llmerrors.ErrUnauthorized), diff --git a/lib/srv/app/llm/openai/openai.go b/lib/srv/app/llm/openai/openai.go index 9325aea09d4a..fce8f19718eb 100644 --- a/lib/srv/app/llm/openai/openai.go +++ b/lib/srv/app/llm/openai/openai.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/srv/app/llm/bedrock" llmerrors "github.com/gravitational/teleport/lib/srv/app/llm/errors" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" "github.com/gravitational/teleport/lib/srv/app/llm/models" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" "github.com/gravitational/teleport/lib/utils" @@ -37,9 +38,13 @@ import ( // NewRequest creates a new provider request based on the downstream request, // and inference endpoint configuration. -func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, error) { +func NewRequest(cfg *llmrequest.Config) (*llmrequest.Request, error) { var ( - info = &RequestInfo{} + info = &RequestInfo{} + res = &llmrequest.Request{ + Info: info, + SettleFunc: llmlimiter.EmptySettleFunc, + } providerPath string providerMethod string providerHeaders = http.Header{} @@ -47,7 +52,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, ) if err := cfg.CheckAndSetDefaults(); err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } // Default URL address. @@ -72,7 +77,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, case "/responses": if cfg.DownstreamRequest.Method != http.MethodPost { // We're ok with returning 404 back to clients instead 405 status. - return nil, info, trace.NotFound("responses API supports only POST requests") + return res, trace.NotFound("responses API supports only POST requests") } // Teleport and Bedrock don't support WebSocket mode. @@ -80,7 +85,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, // https://developers.openai.com/api/docs/guides/websocket-mode // https://developers.openai.com/api/docs/guides/amazon-bedrock#responses-api-feature-availability if websocket.IsWebSocketUpgrade(cfg.DownstreamRequest) { - return nil, info, trace.NotFound("websocket mode is not supported") + return res, trace.NotFound("websocket mode is not supported") } info.endpointType = endpointTypeResponses @@ -90,7 +95,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, case "/chat/completions": if cfg.DownstreamRequest.Method != http.MethodPost { // We're ok with returning 404 back to clients instead 405 status. - return nil, info, trace.NotFound("chat completions API supports only POST requests") + return res, trace.NotFound("chat completions API supports only POST requests") } info.endpointType = endpointTypeChatCompletions @@ -98,7 +103,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, providerMethod = http.MethodPost req = &chatCompletionsAPIRequest{} default: - return nil, info, trace.NotFound("unsupported endpoint") + return res, trace.NotFound("unsupported endpoint") } switch llm.Provider { @@ -110,7 +115,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, case types.LLMProviderAWSBedrock: cfg.ProviderURL = bedrock.BuildOpenAIURL(cfg.Logger, cfg.App, info.endpointType == endpointTypeResponses) default: - return nil, info, trace.NotImplemented("provider %q is not supported", llm.Provider) + return res, trace.NotImplemented("provider %q is not supported", llm.Provider) } body, err := utils.ReadAtMost(cfg.DownstreamRequest.Body, teleport.MaxHTTPRequestSize) @@ -118,11 +123,11 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, cfg.Logger.WarnContext(cfg.DownstreamRequest.Context(), "failed to close downstream body", "error", closeErr) } if err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } if err := utils.FastUnmarshal(body, &req); err != nil { - return nil, info, trace.BadParameter("unable to parse request body") + return res, trace.BadParameter("unable to parse request body") } // Clients can send `null` JSON values, and since we're decoding into an @@ -130,11 +135,11 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, // interface causes it to be `nil`. This condition guards against this case // avoiding a nil pointer exception. if req == nil { - return nil, info, trace.BadParameter("invalid request body") + return res, trace.BadParameter("invalid request body") } if err := req.Validate(); err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } info.requestedModel = req.GetModel() @@ -142,16 +147,39 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, providerModel, found := models.ConvertName(llm.Models, llm.FallbackModel, req.GetModel()) if !found { - return nil, info, trace.BadParameter("requested model %q is not supported", req.GetModel()) + return res, trace.BadParameter("requested model %q is not supported", req.GetModel()) } info.providerModel = providerModel req.SetModel(providerModel) req.EnableReportUsage() req.DisableDataRetention() + // OpenAI endpoints do not require max output tokens to be specified. + reserveReq := llmlimiter.ReserveRequest{App: cfg.App} + switch requestOutput := req.GetOutputTokens(); requestOutput { + case nil: + // Not specified: We ask the limiter to return the value to be used on + // the provider request. + // TODO(gabrielcorado): add reservation for input tokens. + reserveReq.MaxUsage = &llmlimiter.Usage{OutputTokens: maxOutputTokens} + default: + // Specified: Request exact amount. + // TODO(gabrielcorado): add reservation for input tokens. + reserveReq.Usage = &llmlimiter.Usage{OutputTokens: uint(*requestOutput)} + } + reserveInfo, settleFunc, err := cfg.Reserve(cfg.DownstreamRequest.Context(), reserveReq) + if err != nil { + return res, trace.Wrap(err) + } + res.SettleFunc = settleFunc + + if !reserveInfo.NotApplicable { + req.SetOutputTokens(int(reserveInfo.OutputTokens)) + } + providerBody, err := utils.FastMarshal(req) if err != nil { - return nil, info, trace.ConnectionProblem(err, "failed to generate provider request") + return res, trace.ConnectionProblem(err, "failed to generate provider request") } info.requestSize = len(providerBody) @@ -165,7 +193,7 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, bytes.NewBuffer(providerBody), ) if err != nil { - return nil, info, trace.Wrap(err) + return res, trace.Wrap(err) } providerReq.Header = providerHeaders @@ -175,9 +203,27 @@ func NewRequest(cfg *llmrequest.Config) (*http.Request, llmrequest.RequestInfo, // details, so it is only logged, and clients receive a generic // internal error message. cfg.Logger.ErrorContext(providerReq.Context(), "failed to sign provider request", "error", err) - return nil, info, llmerrors.ErrConfig + return res, llmerrors.ErrConfig } } - return providerReq, info, nil + res.HTTPRequest = providerReq + return res, nil } + +const ( + // maxOutputTokens is the max value that can be used on the provider to + // avoid using the entire limit all at once. + // + // OpenAI supports different models, here we picked the latest "model class" + // from them: `gpt-5`+, using as reference `gpt-5.6-sol`. + // + // Reference: https://developers.openai.com/api/docs/models/gpt-5.6-sol + // + // Note: This can cause older models to reject the requests. This is an + // acceptable risk. + // + // TODO(gabrielcorado): take this information from the LLM configuration so + // users can configure it on a per-model basis. + maxOutputTokens = 128_000 // 128k tokens. +) diff --git a/lib/srv/app/llm/openai/openai_test.go b/lib/srv/app/llm/openai/openai_test.go index dd7dd46ce2ec..318045963fc0 100644 --- a/lib/srv/app/llm/openai/openai_test.go +++ b/lib/srv/app/llm/openai/openai_test.go @@ -28,26 +28,22 @@ import ( "strings" "testing" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" + llmtesting "github.com/gravitational/teleport/lib/srv/app/llm/testing" ) func TestNewRequest(t *testing.T) { apiKey := "random-api-key" - signAWSRequest := func(_ context.Context, _ types.Application, req *http.Request, reqBody []byte) error { - hash := sha256.New() - hash.Write(reqBody) - req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(hash.Sum(nil))) - req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=test") - return nil - } - for name, tc := range map[string]struct { app types.Application + reserveFunc llmrequest.ReserveFunc request func() *http.Request signAWSRequest func(context.Context, types.Application, *http.Request, []byte) error expectedError require.ErrorAssertionFunc @@ -59,6 +55,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -86,6 +83,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -109,6 +107,7 @@ func TestNewRequest(t *testing.T) { {ProviderName: "gpt-5", Name: "gpt-4o"}, }, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -139,6 +138,7 @@ func TestNewRequest(t *testing.T) { }, FallbackModel: "gpt-4o", }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -168,6 +168,7 @@ func TestNewRequest(t *testing.T) { {ProviderName: "gpt-5", Name: "gpt-4o"}, }, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -189,6 +190,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -212,6 +214,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -229,6 +232,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodGet, @@ -246,6 +250,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -265,6 +270,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -282,6 +288,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -299,6 +306,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -316,6 +324,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -347,6 +356,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -364,6 +374,7 @@ func TestNewRequest(t *testing.T) { Format: types.LLMFormatOpenAI, Provider: types.LLMProviderOpenAI, }, nil /* appAWS */), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodGet, @@ -386,7 +397,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -425,7 +437,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -461,6 +474,7 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), + reserveFunc: llmtesting.NoopReserveFunc, signAWSRequest: func(ctx context.Context, a types.Application, r *http.Request, b []byte) error { return errors.New("signing failed") }, @@ -487,6 +501,7 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), + reserveFunc: llmtesting.NoopReserveFunc, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -506,7 +521,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodPost, @@ -526,7 +542,8 @@ func TestNewRequest(t *testing.T) { }, &types.AppAWS{ Region: "us-east-2", }), - signAWSRequest: signAWSRequest, + reserveFunc: llmtesting.NoopReserveFunc, + signAWSRequest: llmtesting.SignAWSRequest, request: func() *http.Request { r, _ := http.NewRequest( http.MethodGet, @@ -539,19 +556,177 @@ func TestNewRequest(t *testing.T) { expectedRequest: require.Nil, expectedInfo: require.NotNil, }, + "max output limit exceeded": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{}, trace.LimitExceeded("limit exceeded")), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/responses", + strings.NewReader(`{"model":"gpt-5","input":"Hello"}`), + ) + return r + }, + expectedError: require.Error, + expectedRequest: require.Nil, + expectedInfo: require.NotNil, + }, + "responses unset max output set to limit": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{OutputTokens: 123}, nil), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/responses", + strings.NewReader(`{"model":"gpt-5","input":"Hello"}`), + ) + return r + }, + expectedError: require.NoError, + expectedRequest: func(tt require.TestingT, i1 any, i2 ...any) { + req, _ := i1.(*http.Request) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.JSONEq(tt, `{"model":"gpt-5","input":"Hello","background":false,"store":false,"max_output_tokens":123}`, string(body)) + }, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "gpt-5", info.RequestedModel()) + require.Equal(tt, "gpt-5", info.ProviderModel()) + }, + }, + "responses limit reserved": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + // Reserve must return the same value as the request. + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{OutputTokens: 100}, nil), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/responses", + strings.NewReader(`{"model":"gpt-5","input":"Hello","max_output_tokens":100}`), + ) + return r + }, + expectedError: require.NoError, + expectedRequest: func(tt require.TestingT, i1 any, i2 ...any) { + req, _ := i1.(*http.Request) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.JSONEq(tt, `{"model":"gpt-5","input":"Hello","background":false,"store":false,"max_output_tokens":100}`, string(body)) + }, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "gpt-5", info.RequestedModel()) + require.Equal(tt, "gpt-5", info.ProviderModel()) + }, + }, + "chat completions unset max output set to limit": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{OutputTokens: 123}, nil), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/chat/completions", + strings.NewReader(`{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}`), + ) + return r + }, + expectedError: require.NoError, + expectedRequest: func(tt require.TestingT, i1 any, i2 ...any) { + req, _ := i1.(*http.Request) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.JSONEq(tt, `{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}],"stream":false,"store":false,"max_completion_tokens":123}`, string(body)) + }, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "gpt-5", info.RequestedModel()) + require.Equal(tt, "gpt-5", info.ProviderModel()) + }, + }, + "chat completions limits reserved": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + // Reserve must return the same value as the request. + reserveFunc: llmtesting.ReserveFunc(llmlimiter.ReserveInfo{OutputTokens: 100}, nil), + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/chat/completions", + strings.NewReader(`{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}],"max_completion_tokens":100}`), + ) + return r + }, + expectedError: require.NoError, + expectedRequest: func(tt require.TestingT, i1 any, i2 ...any) { + req, _ := i1.(*http.Request) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.JSONEq(tt, `{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}],"stream":false,"store":false,"max_completion_tokens":100}`, string(body)) + }, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "gpt-5", info.RequestedModel()) + require.Equal(tt, "gpt-5", info.ProviderModel()) + }, + }, + "not applicable limiters do not modify original request": { + app: newApp(t, &types.LLM{ + Format: types.LLMFormatOpenAI, + Provider: types.LLMProviderOpenAI, + }, nil /* appAWS */), + reserveFunc: llmtesting.NotApplicableReserveFunc, + request: func() *http.Request { + r, _ := http.NewRequest( + http.MethodPost, + "/chat/completions", + strings.NewReader(`{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}`), + ) + return r + }, + expectedError: require.NoError, + expectedRequest: func(tt require.TestingT, i1 any, i2 ...any) { + req, _ := i1.(*http.Request) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.JSONEq(tt, `{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}],"stream":false,"store":false}`, string(body)) + }, + expectedInfo: func(tt require.TestingT, i1 any, i2 ...any) { + info, _ := i1.(*RequestInfo) + require.Equal(tt, "gpt-5", info.RequestedModel()) + require.Equal(tt, "gpt-5", info.ProviderModel()) + }, + }, } { t.Run(name, func(t *testing.T) { - req, info, err := NewRequest(&llmrequest.Config{ + res, err := NewRequest(&llmrequest.Config{ App: tc.app, DownstreamRequest: tc.request(), GetAPIKeyFunc: func() string { return apiKey }, SignBedrockRequest: tc.signAWSRequest, + Reserve: tc.reserveFunc, }) + require.NotNil(t, res) + require.NotNil(t, res.SettleFunc) tc.expectedError(t, err) - tc.expectedRequest(t, req) - tc.expectedInfo(t, info) + tc.expectedRequest(t, res.HTTPRequest) + tc.expectedInfo(t, res.Info) }) } } @@ -612,10 +787,11 @@ func BenchmarkNewRequest(b *testing.B) { for b.Loop() { r.Body = io.NopCloser(bytes.NewReader(bc.body)) - if _, _, err := NewRequest(&llmrequest.Config{ + if _, err := NewRequest(&llmrequest.Config{ App: app, DownstreamRequest: r, GetAPIKeyFunc: func() string { return "" }, + Reserve: llmtesting.NoopReserveFunc, }); err != nil { b.Fatal(err) } diff --git a/lib/srv/app/llm/openai/types.go b/lib/srv/app/llm/openai/types.go index 3ef30b10369b..f923815c1e30 100644 --- a/lib/srv/app/llm/openai/types.go +++ b/lib/srv/app/llm/openai/types.go @@ -57,6 +57,8 @@ type apiRequest interface { EnableReportUsage() DisableDataRetention() Validate() error + GetOutputTokens() *int + SetOutputTokens(int) } // responsesAPIRequest contains part of the fields from responses API request @@ -64,10 +66,11 @@ type apiRequest interface { // // https://developers.openai.com/api/reference/resources/responses/methods/create type responsesAPIRequest struct { - Model string `json:"model"` - Stream bool `json:"stream"` - Background bool `json:"background"` - Store bool `json:"store"` + Model string `json:"model"` + Stream bool `json:"stream"` + Background bool `json:"background"` + Store bool `json:"store"` + MaxOutputTokens *int `json:"max_output_tokens"` raw map[string]json.RawMessage `json:"-"` } @@ -78,10 +81,12 @@ func (r *responsesAPIRequest) DisableDataRetention() { } // Nothing to do, usage is always reported on responses API. -func (r *responsesAPIRequest) EnableReportUsage() {} -func (r *responsesAPIRequest) GetModel() string { return r.Model } -func (r *responsesAPIRequest) GetStream() bool { return r.Stream } -func (r *responsesAPIRequest) SetModel(model string) { r.Model = model } +func (r *responsesAPIRequest) EnableReportUsage() {} +func (r *responsesAPIRequest) GetModel() string { return r.Model } +func (r *responsesAPIRequest) GetStream() bool { return r.Stream } +func (r *responsesAPIRequest) SetModel(model string) { r.Model = model } +func (r *responsesAPIRequest) SetOutputTokens(outputTokens int) { r.MaxOutputTokens = &outputTokens } +func (r *responsesAPIRequest) GetOutputTokens() *int { return r.MaxOutputTokens } func (r *responsesAPIRequest) Validate() error { if r.Background { return llmerrors.NewProviderError(llmerrors.ErrUnsupported, "background responses not supported") @@ -112,7 +117,7 @@ func (r *responsesAPIRequest) UnmarshalJSON(data []byte) error { for key := range raw { switch strings.ToLower(key) { - case "model", "stream", "store", "background": + case "model", "stream", "store", "background", "max_output_tokens": delete(raw, key) default: } @@ -123,7 +128,7 @@ func (r *responsesAPIRequest) UnmarshalJSON(data []byte) error { } func (r responsesAPIRequest) MarshalJSON() ([]byte, error) { - final := make(map[string]json.RawMessage, len(r.raw)+2) // Current len + taken fields. + final := make(map[string]json.RawMessage, len(r.raw)+5) // Current len + taken fields. maps.Copy(final, r.raw) if err := marshalField(final, "model", r.Model); err != nil { return nil, trace.Wrap(err) @@ -139,6 +144,11 @@ func (r responsesAPIRequest) MarshalJSON() ([]byte, error) { if err := marshalField(final, "store", r.Store); err != nil { return nil, trace.Wrap(err) } + if r.MaxOutputTokens != nil { + if err := marshalField(final, "max_output_tokens", r.MaxOutputTokens); err != nil { + return nil, trace.Wrap(err) + } + } res, err := utils.FastMarshal(final) return res, trace.Wrap(err) } @@ -158,10 +168,11 @@ type chatCompletionsAPIRequestStreamOptions struct { // // https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create type chatCompletionsAPIRequest struct { - Model string `json:"model"` - Stream bool `json:"stream"` - StreamOptions chatCompletionsAPIRequestStreamOptions `json:"stream_options"` - Store bool `json:"store"` + Model string `json:"model"` + Stream bool `json:"stream"` + StreamOptions chatCompletionsAPIRequestStreamOptions `json:"stream_options"` + Store bool `json:"store"` + MaxCompletionTokens *int `json:"max_completion_tokens"` raw map[string]json.RawMessage `json:"-"` } @@ -176,6 +187,10 @@ func (r *chatCompletionsAPIRequest) EnableReportUsage() { func (r *chatCompletionsAPIRequest) GetModel() string { return r.Model } func (r *chatCompletionsAPIRequest) GetStream() bool { return r.Stream } func (r *chatCompletionsAPIRequest) SetModel(model string) { r.Model = model } +func (r *chatCompletionsAPIRequest) GetOutputTokens() *int { return r.MaxCompletionTokens } +func (r *chatCompletionsAPIRequest) SetOutputTokens(outputTokens int) { + r.MaxCompletionTokens = &outputTokens +} func (r *chatCompletionsAPIRequest) Validate() error { if r.Store { return llmerrors.NewProviderError(llmerrors.ErrUnsupported, "storing chat completions not supported") @@ -198,7 +213,7 @@ func (r *chatCompletionsAPIRequest) UnmarshalJSON(data []byte) error { for key := range raw { switch strings.ToLower(key) { - case "model", "stream", "stream_options", "store": + case "model", "stream", "stream_options", "store", "max_completion_tokens": delete(raw, key) default: } @@ -209,7 +224,7 @@ func (r *chatCompletionsAPIRequest) UnmarshalJSON(data []byte) error { } func (r chatCompletionsAPIRequest) MarshalJSON() ([]byte, error) { - final := make(map[string]json.RawMessage, len(r.raw)+4) // Current len + taken fields. + final := make(map[string]json.RawMessage, len(r.raw)+5) // Current len + taken fields. maps.Copy(final, r.raw) if err := marshalField(final, "model", r.Model); err != nil { return nil, trace.Wrap(err) @@ -220,6 +235,11 @@ func (r chatCompletionsAPIRequest) MarshalJSON() ([]byte, error) { if err := marshalField(final, "store", r.Store); err != nil { return nil, trace.Wrap(err) } + if r.MaxCompletionTokens != nil { + if err := marshalField(final, "max_completion_tokens", r.MaxCompletionTokens); err != nil { + return nil, trace.Wrap(err) + } + } // We must include `stream_options` only for streaming requests, as some // providers might reject it as bad request. if r.Stream { diff --git a/lib/srv/app/llm/openai/types_test.go b/lib/srv/app/llm/openai/types_test.go index 138ac5a098c6..b0e6548b5f28 100644 --- a/lib/srv/app/llm/openai/types_test.go +++ b/lib/srv/app/llm/openai/types_test.go @@ -238,6 +238,18 @@ func TestEncodeResponsesRequest(t *testing.T) { require.JSONEq(tt, `{"model": "", "input":"Hello","store":false,"background":false}`, resp) }, }, + "set max output tokens via SetOutputTokens": { + input: `{"model":"gpt-5","stream":false,"max_output_tokens":1024,"input":"Hello"}`, + modifyRequest: func(r *responsesAPIRequest) { + r.SetOutputTokens(100) + }, + expectError: require.NoError, + expectValue: func(tt require.TestingT, i1 any, i2 ...any) { + resp, ok := i1.(string) + require.True(tt, ok, "expect type to be %T but got %T", resp, i1) + require.JSONEq(tt, `{"model":"gpt-5","store":false,"max_output_tokens":100,"input":"Hello","background":false}`, resp) + }, + }, } { t.Run(name, func(t *testing.T) { var r responsesAPIRequest @@ -480,6 +492,18 @@ func TestEncodeChatCompletionsRequest(t *testing.T) { require.JSONEq(tt, `{"model": "", "stream": false, "messages":[{"role":"user","content":"Hello"}],"store":false}`, resp) }, }, + "set max output tokens via SetOutputTokens": { + input: `{"model":"gpt-5","stream":false,"max_completion_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}`, + modifyRequest: func(r *chatCompletionsAPIRequest) { + r.SetOutputTokens(100) + }, + expectError: require.NoError, + expectValue: func(tt require.TestingT, i1 any, i2 ...any) { + resp, ok := i1.(string) + require.True(tt, ok, "expect type to be %T but got %T", resp, i1) + require.JSONEq(tt, `{"model":"gpt-5","stream":false,"store":false,"max_completion_tokens":100,"messages":[{"role":"user","content":"Hello"}]}`, resp) + }, + }, } { t.Run(name, func(t *testing.T) { var r chatCompletionsAPIRequest diff --git a/lib/srv/app/llm/request/request.go b/lib/srv/app/llm/request/request.go index 8d3b184c6452..4f62a6dc4ad3 100644 --- a/lib/srv/app/llm/request/request.go +++ b/lib/srv/app/llm/request/request.go @@ -25,8 +25,13 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/types" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" ) +// ReserveFunc reserves tokens out of the application limits for the request +// being built. +type ReserveFunc func(context.Context, llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) + // Config is config used to create a new provide request. type Config struct { // Logger is the logger used to emit log entries. @@ -42,6 +47,8 @@ type Config struct { // SignBedrockRequest signs the AWS Bedrock request. // Required for the AWS Bedrock provider. SignBedrockRequest func(ctx context.Context, app types.Application, request *http.Request, requestBody []byte) error + // Reserve is the function used to reserve tokens for the request. + Reserve ReserveFunc } func (c *Config) CheckAndSetDefaults() error { @@ -60,6 +67,9 @@ func (c *Config) CheckAndSetDefaults() error { if c.GetAPIKeyFunc == nil { return trace.BadParameter("get api key function is required") } + if c.Reserve == nil { + return trace.BadParameter("reserve function is required") + } if c.App.GetLLM().Provider == types.LLMProviderAWSBedrock { if c.SignBedrockRequest == nil { return trace.BadParameter("sign aws bedrock request function is required for the bedrock provider") @@ -69,6 +79,19 @@ func (c *Config) CheckAndSetDefaults() error { return nil } +// Request is the provider request built out of the downstream request. It is +// partially filled when building it fails, so its fields must be checked before +// being used. +type Request struct { + // HTTPRequest is the request to be sent to the provider. + HTTPRequest *http.Request + // Info contains the request information. + Info RequestInfo + // SettleFunc settles the tokens reserved for the request. It is only + // present after the reservation is made. + SettleFunc llmlimiter.SettleFunc +} + // RequestInfo interface that contains the request information. type RequestInfo interface { // RequestedModel returns the requested model name. diff --git a/lib/srv/app/llm/testing/testing.go b/lib/srv/app/llm/testing/testing.go index b3a493abb288..e6973910d45f 100644 --- a/lib/srv/app/llm/testing/testing.go +++ b/lib/srv/app/llm/testing/testing.go @@ -17,14 +17,21 @@ package testing import ( + "cmp" + "context" + "crypto/sha256" + "encoding/hex" "io" "net/http" "strings" "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/httplib/sse" "github.com/gravitational/teleport/lib/itertools/stream" + llmlimiter "github.com/gravitational/teleport/lib/srv/app/llm/limiter" + llmrequest "github.com/gravitational/teleport/lib/srv/app/llm/request" ) // DiscardResponseWriter is a minimal [http.ResponseWriter] + [http.Flusher] @@ -81,3 +88,36 @@ func ReadSSEOneEvent(str string) (sse.Event, error) { } return events[0], nil } + +// SignAWSRequest mock that signs a request, adding the necessary headers to +// the request. +func SignAWSRequest(_ context.Context, _ types.Application, req *http.Request, reqBody []byte) error { + hash := sha256.New() + hash.Write(reqBody) + req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(hash.Sum(nil))) + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=test") + return nil +} + +// ReserveFunc implements [llmrequest.ReserveFunc]. +func ReserveFunc(info llmlimiter.ReserveInfo, err error) llmrequest.ReserveFunc { + return func(context.Context, llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) { + return info, llmlimiter.EmptySettleFunc, err + } +} + +// NoopReserveFunc implements [llmrequest.ReserveFunc] that does not affect +// the handler. It grants whatever is asked for, mirroring the noop limiter +// used when no quota is configured. +func NoopReserveFunc(_ context.Context, req llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) { + usage := cmp.Or(req.Usage, req.MaxUsage) + if usage == nil { + return llmlimiter.ReserveInfo{}, llmlimiter.EmptySettleFunc, trace.BadParameter("must provide Usage or MaxUsage") + } + return llmlimiter.ReserveInfo{OutputTokens: usage.OutputTokens}, llmlimiter.EmptySettleFunc, nil +} + +// NotApplicableReserveFunc returns NotApplicable for all limit requests. +func NotApplicableReserveFunc(context.Context, llmlimiter.ReserveRequest) (llmlimiter.ReserveInfo, llmlimiter.SettleFunc, error) { + return llmlimiter.ReserveInfo{NotApplicable: true}, llmlimiter.EmptySettleFunc, nil +} From 459d13c79ccc4437b6178ce4927cb2602b1344e7 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 29 Jul 2026 16:58:47 -0400 Subject: [PATCH 054/347] Fix Terraform scoped_token support for bot tokens. (#69163) Terraform was previously requitring the `spec.assigned_scope` field. This field is only required when creating an agent token. Bot tokens must have an empty `spec.assigned_scope` field. --- .../data-sources/scoped_token.mdx | 2 +- .../resources/scoped_token.mdx | 2 +- .../protoc-gen-terraform-scopedtoken.yaml | 1 - .../fixtures/scoped_token_bot_0_create.tf | 22 ++++++++++ .../terraform/testlib/scoped_token_test.go | 40 +++++++++++++++++++ .../scopes/joining/v1/token_terraform.go | 2 +- 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 integrations/terraform/testlib/fixtures/scoped_token_bot_0_create.tf diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx index 79b7b7d2c8bb..0cdcab9e9d2e 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx @@ -50,13 +50,13 @@ Optional: Required: -- `assigned_scope` (String) The scope to which this token is assigned. Must be equivalent or descendent to the scope of the token itself. - `join_method` (String) The joining method required in order to use this token. Note that not all join methods support joining with scoped tokens. - `roles` (List of String) The list of roles associated with the token. They will be converted to metadata in the SSH and X509 certificates issued to the user of the token. - `usage_mode` (String) The usage mode of the token. Can be "single_use" or "unlimited". Single use tokens can only be used to provision a single resource. Unlimited tokens can be be used to provision any number of resources until it expires. Optional: +- `assigned_scope` (String) The scope to which this token is assigned. Must be equivalent or descendent to the scope of the token itself. - `aws` (Attributes) The AWS-specific configuration used with the "ec2" and "iam" join methods. (see [below for nested schema](#nested-schema-for-specaws)) - `azure` (Attributes) The Azure-specific configuration used with the "azure" join method. (see [below for nested schema](#nested-schema-for-specazure)) - `azure_devops` (Attributes) The Azure Devops-specific configuration used with the "azure_devops" join method. (see [below for nested schema](#nested-schema-for-specazure_devops)) diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx index 3e0cf40e2c10..f12265d0e24d 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx @@ -94,13 +94,13 @@ Optional: Required: -- `assigned_scope` (String) The scope to which this token is assigned. Must be equivalent or descendent to the scope of the token itself. - `join_method` (String) The joining method required in order to use this token. Note that not all join methods support joining with scoped tokens. - `roles` (List of String) The list of roles associated with the token. They will be converted to metadata in the SSH and X509 certificates issued to the user of the token. - `usage_mode` (String) The usage mode of the token. Can be "single_use" or "unlimited". Single use tokens can only be used to provision a single resource. Unlimited tokens can be be used to provision any number of resources until it expires. Optional: +- `assigned_scope` (String) The scope to which this token is assigned. Must be equivalent or descendent to the scope of the token itself. - `aws` (Attributes) The AWS-specific configuration used with the "ec2" and "iam" join methods. (see [below for nested schema](#nested-schema-for-specaws)) - `azure` (Attributes) The Azure-specific configuration used with the "azure" join method. (see [below for nested schema](#nested-schema-for-specazure)) - `azure_devops` (Attributes) The Azure Devops-specific configuration used with the "azure_devops" join method. (see [below for nested schema](#nested-schema-for-specazure_devops)) diff --git a/integrations/terraform/protoc-gen-terraform-scopedtoken.yaml b/integrations/terraform/protoc-gen-terraform-scopedtoken.yaml index 58c2a6bce47b..e4e3c144686a 100644 --- a/integrations/terraform/protoc-gen-terraform-scopedtoken.yaml +++ b/integrations/terraform/protoc-gen-terraform-scopedtoken.yaml @@ -50,7 +50,6 @@ required_fields: - "ScopedToken.version" - "ScopedToken.scope" - "ScopedToken.spec" - - "ScopedToken.spec.assigned_scope" - "ScopedToken.spec.join_method" - "ScopedToken.spec.roles" - "ScopedToken.spec.usage_mode" diff --git a/integrations/terraform/testlib/fixtures/scoped_token_bot_0_create.tf b/integrations/terraform/testlib/fixtures/scoped_token_bot_0_create.tf new file mode 100644 index 000000000000..1836526dbc36 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/scoped_token_bot_0_create.tf @@ -0,0 +1,22 @@ +resource "teleport_scoped_token" "test" { + version = "v1" + metadata = { + name = "test-bot-scoped-token" + } + scope = "/staging/aa" + spec = { + bot = "/staging/aa::server-enroller" + join_method = "kubernetes" + kubernetes = { + allow = [ + { service_account_name = "pod-sa", service_account_namespace = "default" } + ] + oidc = { + issuer = "https://container.googleapis.com/v1/projects/my-project/locations/us-central1-a/clusters/my-cluster-name" + } + type = "oidc" + } + roles = ["Bot"] + usage_mode = "bot" + } +} \ No newline at end of file diff --git a/integrations/terraform/testlib/scoped_token_test.go b/integrations/terraform/testlib/scoped_token_test.go index a7d66ca3fce1..628e5e116889 100644 --- a/integrations/terraform/testlib/scoped_token_test.go +++ b/integrations/terraform/testlib/scoped_token_test.go @@ -84,6 +84,46 @@ func (s *TerraformSuiteOSS) TestScopedToken() { }) } +func (s *TerraformSuiteOSS) TestScopedTokenBot() { + t := s.T() + ctx := t.Context() + + checkDestroyed := func(state *terraform.State) error { + _, err := s.client.GetScopedToken(ctx, joiningv1.GetScopedTokenRequest_builder{ + Name: "test-bot-scoped-token", + Scope: "/staging/aa", + }.Build()) + if !trace.IsNotFound(err) { + return trace.Errorf("expected not found, actual: %v", err) + } + return nil + } + + name := "teleport_scoped_token.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: s.getFixture("scoped_token_bot_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", types.KindScopedToken), + resource.TestCheckResourceAttr(name, "scope", "/staging/aa"), + resource.TestCheckResourceAttr(name, "spec.join_method", "kubernetes"), + resource.TestCheckResourceAttr(name, "spec.roles.0", "Bot"), + resource.TestCheckResourceAttr(name, "spec.usage_mode", "bot"), + ), + }, + { + Config: s.getFixture("scoped_token_bot_0_create.tf"), + PlanOnly: true, + }, + }, + }) +} + func (s *TerraformSuiteOSS) TestImportScopedToken() { t := s.T() ctx := t.Context() diff --git a/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go b/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go index ef68dae7995a..b2c052119405 100644 --- a/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go +++ b/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go @@ -105,7 +105,7 @@ func GenSchemaScopedToken(ctx context.Context) (github_com_hashicorp_terraform_p Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ "assigned_scope": { Description: "The scope to which this token is assigned. Must be equivalent or descendent to the scope of the token itself.", - Required: true, + Optional: true, Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, "aws": { From 51f935d9204fa86c722447f173b5b813d74c16fa Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:32:10 -0400 Subject: [PATCH 055/347] tctl: output resource SQN instead of resource name (#69148) Updates tctl get/create/rm to consistently use the SQN instead of the resource name alone. --- tool/tctl/common/resources/accesslist.go | 5 ++--- tool/tctl/common/resources/kube_cluster.go | 2 +- tool/tctl/common/resources/scoped_role.go | 8 ++++---- tool/tctl/common/resources/scoped_role_assignment.go | 10 +++++----- tool/tctl/common/resources/scoped_token.go | 4 ++-- tool/tctl/common/resources/workload_identity.go | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tool/tctl/common/resources/accesslist.go b/tool/tctl/common/resources/accesslist.go index 04b303d9f68e..2875466c8f7e 100644 --- a/tool/tctl/common/resources/accesslist.go +++ b/tool/tctl/common/resources/accesslist.go @@ -142,16 +142,15 @@ func createAccessList(ctx context.Context, client *authclient.Client, raw servic exists := (err == nil) if exists && !opts.Force { - return trace.AlreadyExists("Access list %q already exists", accessList.GetName()) + return trace.AlreadyExists("Access list %q already exists", accesslists.ScopeQualifiedName(accessList).String()) } if _, err := client.AccessListClient().UpsertAccessList(ctx, accessList); err != nil { return trace.Wrap(err) } - fmt.Printf("Access list %q has been %s\n", accessList.GetName(), upsertVerb(exists, opts.Force)) + fmt.Printf("Access list %q has been %s\n", accesslists.ScopeQualifiedName(accessList).String(), upsertVerb(exists, opts.Force)) return nil - } // deleteAccessList implements `tctl rm accesslist /scope::my-list` command. diff --git a/tool/tctl/common/resources/kube_cluster.go b/tool/tctl/common/resources/kube_cluster.go index 61ae0de23b2c..04eb1eebd9dd 100644 --- a/tool/tctl/common/resources/kube_cluster.go +++ b/tool/tctl/common/resources/kube_cluster.go @@ -244,7 +244,7 @@ func createScopedKubeCluster(ctx context.Context, client *authclient.Client, raw if err := client.UpdateKubernetesCluster(ctx, cluster); err != nil { return trace.Wrap(err) } - fmt.Printf("Kubernetes cluster %q has been updated\n", cluster.GetName()) + fmt.Printf("Kubernetes cluster %q has been updated\n", sqn.String()) return nil } return trace.Wrap(err) diff --git a/tool/tctl/common/resources/scoped_role.go b/tool/tctl/common/resources/scoped_role.go index 906f3d4803f7..62960aed1c7c 100644 --- a/tool/tctl/common/resources/scoped_role.go +++ b/tool/tctl/common/resources/scoped_role.go @@ -91,7 +91,7 @@ func createScopedRole(ctx context.Context, client *authclient.Client, raw servic fmt.Printf( "%v %q has been upserted\n", scopedaccess.KindScopedRole, - rsp.GetRole().GetMetadata().GetName(), + scopes.QualifiedName{Name: rsp.GetRole().GetMetadata().GetName(), Scope: rsp.GetRole().GetScope()}.String(), ) return nil } @@ -105,7 +105,7 @@ func createScopedRole(ctx context.Context, client *authclient.Client, raw servic fmt.Printf( "%v %q has been created\n", scopedaccess.KindScopedRole, - r.GetMetadata().GetName(), + scopes.QualifiedName{Name: r.GetMetadata().GetName(), Scope: r.GetScope()}.String(), ) return nil @@ -126,7 +126,7 @@ func updateScopedRole(ctx context.Context, client *authclient.Client, raw servic fmt.Printf( "%v %q has been updated\n", scopedaccess.KindScopedRole, - r.GetMetadata().GetName(), + scopes.QualifiedName{Name: r.GetMetadata().GetName(), Scope: r.GetScope()}.String(), ) return nil @@ -172,7 +172,7 @@ func deleteScopedRole(ctx context.Context, client *authclient.Client, subKind st fmt.Printf( "%v %q has been deleted\n", scopedaccess.KindScopedRole, - sqn.Name, + sqn.String(), ) return nil } diff --git a/tool/tctl/common/resources/scoped_role_assignment.go b/tool/tctl/common/resources/scoped_role_assignment.go index 36502777d346..a67a27b326aa 100644 --- a/tool/tctl/common/resources/scoped_role_assignment.go +++ b/tool/tctl/common/resources/scoped_role_assignment.go @@ -106,7 +106,7 @@ func createScopedRoleAssignment(ctx context.Context, client *authclient.Client, fmt.Printf( "%v %q has been upserted\n", scopedaccess.KindScopedRoleAssignment, - rsp.GetAssignment().GetMetadata().GetName(), + scopes.QualifiedName{Name: rsp.GetAssignment().GetMetadata().GetName(), Scope: rsp.GetAssignment().GetScope()}.String(), ) return nil } @@ -142,7 +142,7 @@ func updateScopedRoleAssignment(ctx context.Context, client *authclient.Client, fmt.Printf( "%v %q has been updated\n", scopedaccess.KindScopedRoleAssignment, - r.GetMetadata().GetName(), + scopes.QualifiedName{Name: r.GetMetadata().GetName(), Scope: r.GetScope()}.String(), ) return nil @@ -182,10 +182,10 @@ func getScopedRoleAssignment(ctx context.Context, client *authclient.Client, sub func deleteScopedRoleAssignment(ctx context.Context, client *authclient.Client, subKind string, sqn scopes.QualifiedName) error { if subKind == "" { return trace.BadParameter( - "%s requires a sub-kind to delete a resource, try:\n tctl rm %s/%s %s::%s", + "%s requires a sub-kind to delete a resource, try:\n tctl rm %s/%s %s", scopedaccess.KindScopedRoleAssignment, scopedaccess.KindScopedRoleAssignment, scopedaccess.SubKindDynamic, - sqn.Scope, sqn.Name, + sqn.String(), ) } if subKind == scopedaccess.SubKindMaterialized { @@ -202,7 +202,7 @@ func deleteScopedRoleAssignment(ctx context.Context, client *authclient.Client, fmt.Printf( "%v %q has been deleted\n", scopedaccess.KindScopedRoleAssignment, - sqn.Name, + sqn.String(), ) return nil } diff --git a/tool/tctl/common/resources/scoped_token.go b/tool/tctl/common/resources/scoped_token.go index 24ec9cf804f2..503c169981f3 100644 --- a/tool/tctl/common/resources/scoped_token.go +++ b/tool/tctl/common/resources/scoped_token.go @@ -109,7 +109,7 @@ func updateScopedToken(ctx context.Context, client *authclient.Client, raw servi return trace.Wrap(err) } - fmt.Printf("%v %q has been updated\n", types.KindScopedToken, token.GetMetadata().GetName()) + fmt.Printf("%v %q has been updated\n", types.KindScopedToken, scopes.QualifiedName{Name: token.GetMetadata().GetName(), Scope: token.GetScope()}.String()) return nil } @@ -193,7 +193,7 @@ func deleteScopedToken(ctx context.Context, client *authclient.Client, subKind s fmt.Printf( "%v %q has been deleted\n", types.KindScopedToken, - sqn.Name, + sqn.String(), ) return nil } diff --git a/tool/tctl/common/resources/workload_identity.go b/tool/tctl/common/resources/workload_identity.go index 490ed27b4154..76c8d500e461 100644 --- a/tool/tctl/common/resources/workload_identity.go +++ b/tool/tctl/common/resources/workload_identity.go @@ -201,7 +201,7 @@ func createWorkloadIdentity( } } - fmt.Printf("Workload Identity %q has been created\n", in.GetMetadata().GetName()) + fmt.Printf("Workload Identity %q has been created\n", scopes.QualifiedName{Name: in.GetMetadata().GetName(), Scope: in.GetScope()}.String()) return nil } From 124869b1f4f0196793bce9b7555a44ef0869381a Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Wed, 29 Jul 2026 18:22:36 -0600 Subject: [PATCH 056/347] docs: Update scopes and request access pages (#69086) --- .../connect-your-client/teleport-clients/request-access.mdx | 4 ++-- docs/pages/zero-trust-access/rbac-get-started/scopes.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/connect-your-client/teleport-clients/request-access.mdx b/docs/pages/connect-your-client/teleport-clients/request-access.mdx index 22b2f74e75a1..07134095a76d 100644 --- a/docs/pages/connect-your-client/teleport-clients/request-access.mdx +++ b/docs/pages/connect-your-client/teleport-clients/request-access.mdx @@ -14,7 +14,7 @@ If you cannot access a Teleport role or resource with your current Teleport permissions, you can create an Access Request to obtain elevated permissions for a limited time. -This page describes the `tsh` commands that allow you create Teleport Access +This page describes the `tsh` commands that allow you to create Teleport Access Requests. @@ -196,7 +196,7 @@ For the `API_GROUP` and `RESOURCE_NAME` value, you can match ranges of characters by supplying a wildcard (`*`) or regular expression. Regular expressions must begin with `^` and end with `$`. -For example, to create a request to access all namespaces prefixed with `dev` match +For example, to create a request to access all namespaces prefixed with `dev` that match the regular expression `/^dev-[a-z0-9-]+$/`, run the following command: ```code diff --git a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx index 22233b75a420..a60b0bfc203c 100644 --- a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx +++ b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx @@ -248,7 +248,7 @@ $ tsh ssh ubuntu@example-node Only static Kubernetes clusters support joining with scopes. Dynamic cluster registration, including those discovered by the Teleport Discovery -Service, dot not yet support scopes. +Service, does not yet support scopes. Log the scoped admin into the desired scope: @@ -555,7 +555,7 @@ successfully added user charlie to access list /examples/basic::example-access-l Access Lists that grant scoped roles always result in a "materialized" scoped role assignment for each of their members and owners. -A "materialized" scoped role assignment is an one that is automatically created +A "materialized" scoped role assignment is one that is automatically created and kept up to date for each user that is granted a scoped role by an Access List. Each materialized scoped role assignment assigns all of the scoped roles granted by the Access List to the user. From 644de390b8d9b327611be1760a556b8ba80bee4f Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Thu, 30 Jul 2026 09:01:05 +0100 Subject: [PATCH 057/347] Add Azure AKS discovery config status reporting: proto (#69128) --- .../v1/discoveryconfig_protohybrid.pb.go | 102 ++++++++++++++---- .../v1/discoveryconfig_protoopaque.pb.go | 96 +++++++++++++---- .../discoveryconfig/v1/discoveryconfig.proto | 6 ++ .../teleport-resources/discovery-config.mdx | 4 + 4 files changed, 166 insertions(+), 42 deletions(-) diff --git a/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protohybrid.pb.go b/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protohybrid.pb.go index 66c6b024c127..54a19e8d224b 100644 --- a/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protohybrid.pb.go @@ -663,7 +663,9 @@ type DiscoverSummary struct { // AWSEKS contains the summary for the AWS EKS discovered clusters. AwsEks *ResourceSummary `protobuf:"bytes,3,opt,name=aws_eks,json=awsEks,proto3" json:"aws_eks,omitempty"` // The summary for the Azure VM discovered instances. - AzureVms *ResourceSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3" json:"azure_vms,omitempty"` + AzureVms *ResourceSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3" json:"azure_vms,omitempty"` + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourceSummary `protobuf:"bytes,5,opt,name=azure_aks,json=azureAks,proto3" json:"azure_aks,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -721,6 +723,13 @@ func (x *DiscoverSummary) GetAzureVms() *ResourceSummary { return nil } +func (x *DiscoverSummary) GetAzureAks() *ResourceSummary { + if x != nil { + return x.AzureAks + } + return nil +} + func (x *DiscoverSummary) SetAwsEc2(v *ResourceSummary) { x.AwsEc2 = v } @@ -737,6 +746,10 @@ func (x *DiscoverSummary) SetAzureVms(v *ResourceSummary) { x.AzureVms = v } +func (x *DiscoverSummary) SetAzureAks(v *ResourceSummary) { + x.AzureAks = v +} + func (x *DiscoverSummary) HasAwsEc2() bool { if x == nil { return false @@ -765,6 +778,13 @@ func (x *DiscoverSummary) HasAzureVms() bool { return x.AzureVms != nil } +func (x *DiscoverSummary) HasAzureAks() bool { + if x == nil { + return false + } + return x.AzureAks != nil +} + func (x *DiscoverSummary) ClearAwsEc2() { x.AwsEc2 = nil } @@ -781,6 +801,10 @@ func (x *DiscoverSummary) ClearAzureVms() { x.AzureVms = nil } +func (x *DiscoverSummary) ClearAzureAks() { + x.AzureAks = nil +} + type DiscoverSummary_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -792,6 +816,8 @@ type DiscoverSummary_builder struct { AwsEks *ResourceSummary // The summary for the Azure VM discovered instances. AzureVms *ResourceSummary + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourceSummary } func (b0 DiscoverSummary_builder) Build() *DiscoverSummary { @@ -802,6 +828,7 @@ func (b0 DiscoverSummary_builder) Build() *DiscoverSummary { x.AwsRds = b.AwsRds x.AwsEks = b.AwsEks x.AzureVms = b.AzureVms + x.AzureAks = b.AzureAks return m0 } @@ -915,7 +942,9 @@ type IntegrationDiscoveredSummary struct { // AWSEKS contains the summary for the AWS EKS discovered clusters. AwsEks *ResourcesDiscoveredSummary `protobuf:"bytes,3,opt,name=aws_eks,json=awsEks,proto3" json:"aws_eks,omitempty"` // The summary for the Azure VM discovered instances. - AzureVms *ResourcesDiscoveredSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3" json:"azure_vms,omitempty"` + AzureVms *ResourcesDiscoveredSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3" json:"azure_vms,omitempty"` + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourcesDiscoveredSummary `protobuf:"bytes,5,opt,name=azure_aks,json=azureAks,proto3" json:"azure_aks,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -973,6 +1002,13 @@ func (x *IntegrationDiscoveredSummary) GetAzureVms() *ResourcesDiscoveredSummary return nil } +func (x *IntegrationDiscoveredSummary) GetAzureAks() *ResourcesDiscoveredSummary { + if x != nil { + return x.AzureAks + } + return nil +} + func (x *IntegrationDiscoveredSummary) SetAwsEc2(v *ResourcesDiscoveredSummary) { x.AwsEc2 = v } @@ -989,6 +1025,10 @@ func (x *IntegrationDiscoveredSummary) SetAzureVms(v *ResourcesDiscoveredSummary x.AzureVms = v } +func (x *IntegrationDiscoveredSummary) SetAzureAks(v *ResourcesDiscoveredSummary) { + x.AzureAks = v +} + func (x *IntegrationDiscoveredSummary) HasAwsEc2() bool { if x == nil { return false @@ -1017,6 +1057,13 @@ func (x *IntegrationDiscoveredSummary) HasAzureVms() bool { return x.AzureVms != nil } +func (x *IntegrationDiscoveredSummary) HasAzureAks() bool { + if x == nil { + return false + } + return x.AzureAks != nil +} + func (x *IntegrationDiscoveredSummary) ClearAwsEc2() { x.AwsEc2 = nil } @@ -1033,6 +1080,10 @@ func (x *IntegrationDiscoveredSummary) ClearAzureVms() { x.AzureVms = nil } +func (x *IntegrationDiscoveredSummary) ClearAzureAks() { + x.AzureAks = nil +} + type IntegrationDiscoveredSummary_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -1044,6 +1095,8 @@ type IntegrationDiscoveredSummary_builder struct { AwsEks *ResourcesDiscoveredSummary // The summary for the Azure VM discovered instances. AzureVms *ResourcesDiscoveredSummary + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourcesDiscoveredSummary } func (b0 IntegrationDiscoveredSummary_builder) Build() *IntegrationDiscoveredSummary { @@ -1054,6 +1107,7 @@ func (b0 IntegrationDiscoveredSummary_builder) Build() *IntegrationDiscoveredSum x.AwsRds = b.AwsRds x.AwsEks = b.AwsEks x.AzureVms = b.AzureVms + x.AzureAks = b.AzureAks return m0 } @@ -1242,20 +1296,22 @@ const file_teleport_discoveryconfig_v1_discoveryconfig_proto_rawDesc = "" + "\x15integration_summaries\x18\x03 \x03(\v2L.teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntryR\x14integrationSummaries\x1au\n" + "\x19IntegrationSummariesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12B\n" + - "\x05value\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.DiscoverSummaryR\x05value:\x028\x01\"\xb1\x02\n" + + "\x05value\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.DiscoverSummaryR\x05value:\x028\x01\"\xfc\x02\n" + "\x0fDiscoverSummary\x12E\n" + "\aaws_ec2\x18\x01 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsEc2\x12E\n" + "\aaws_rds\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsRds\x12E\n" + "\aaws_eks\x18\x03 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsEks\x12I\n" + - "\tazure_vms\x18\x04 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureVms\"\xb9\x01\n" + + "\tazure_vms\x18\x04 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureVms\x12I\n" + + "\tazure_aks\x18\x05 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureAks\"\xb9\x01\n" + "\x0fResourceSummary\x12Q\n" + "\acurrent\x18\x01 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\acurrent\x12S\n" + - "\bprevious\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bprevious\"\xea\x02\n" + + "\bprevious\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bprevious\"\xc0\x03\n" + "\x1cIntegrationDiscoveredSummary\x12P\n" + "\aaws_ec2\x18\x01 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsEc2\x12P\n" + "\aaws_rds\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsRds\x12P\n" + "\aaws_eks\x18\x03 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsEks\x12T\n" + - "\tazure_vms\x18\x04 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureVms\"\xd8\x01\n" + + "\tazure_vms\x18\x04 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureVms\x12T\n" + + "\tazure_aks\x18\x05 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureAks\"\xd8\x01\n" + "\x1aResourcesDiscoveredSummary\x12\x14\n" + "\x05found\x18\x01 \x01(\x04R\x05found\x12\x1a\n" + "\benrolled\x18\x02 \x01(\x04R\benrolled\x12\x16\n" + @@ -1313,22 +1369,24 @@ var file_teleport_discoveryconfig_v1_discoveryconfig_proto_depIdxs = []int32{ 6, // 16: teleport.discoveryconfig.v1.DiscoverSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourceSummary 6, // 17: teleport.discoveryconfig.v1.DiscoverSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourceSummary 6, // 18: teleport.discoveryconfig.v1.DiscoverSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourceSummary - 8, // 19: teleport.discoveryconfig.v1.ResourceSummary.current:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 20: teleport.discoveryconfig.v1.ResourceSummary.previous:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 21: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_ec2:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 22: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 23: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 24: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 18, // 25: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_start:type_name -> google.protobuf.Timestamp - 18, // 26: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_end:type_name -> google.protobuf.Timestamp - 7, // 27: teleport.discoveryconfig.v1.DiscoveryConfigStatus.IntegrationDiscoveredResourcesEntry.value:type_name -> teleport.discoveryconfig.v1.IntegrationDiscoveredSummary - 4, // 28: teleport.discoveryconfig.v1.DiscoveryConfigStatus.ServerStatusEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoveryStatusServer - 5, // 29: teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoverSummary - 30, // [30:30] is the sub-list for method output_type - 30, // [30:30] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 30, // [30:30] is the sub-list for extension extendee - 0, // [0:30] is the sub-list for field type_name + 6, // 19: teleport.discoveryconfig.v1.DiscoverSummary.azure_aks:type_name -> teleport.discoveryconfig.v1.ResourceSummary + 8, // 20: teleport.discoveryconfig.v1.ResourceSummary.current:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 21: teleport.discoveryconfig.v1.ResourceSummary.previous:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 22: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_ec2:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 23: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 24: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 25: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 26: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_aks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 18, // 27: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_start:type_name -> google.protobuf.Timestamp + 18, // 28: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_end:type_name -> google.protobuf.Timestamp + 7, // 29: teleport.discoveryconfig.v1.DiscoveryConfigStatus.IntegrationDiscoveredResourcesEntry.value:type_name -> teleport.discoveryconfig.v1.IntegrationDiscoveredSummary + 4, // 30: teleport.discoveryconfig.v1.DiscoveryConfigStatus.ServerStatusEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoveryStatusServer + 5, // 31: teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoverSummary + 32, // [32:32] is the sub-list for method output_type + 32, // [32:32] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name } func init() { file_teleport_discoveryconfig_v1_discoveryconfig_proto_init() } diff --git a/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protoopaque.pb.go b/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protoopaque.pb.go index d027dbc6a579..2e88ce58e012 100644 --- a/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/discoveryconfig/v1/discoveryconfig_protoopaque.pb.go @@ -658,6 +658,7 @@ type DiscoverSummary struct { xxx_hidden_AwsRds *ResourceSummary `protobuf:"bytes,2,opt,name=aws_rds,json=awsRds,proto3"` xxx_hidden_AwsEks *ResourceSummary `protobuf:"bytes,3,opt,name=aws_eks,json=awsEks,proto3"` xxx_hidden_AzureVms *ResourceSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3"` + xxx_hidden_AzureAks *ResourceSummary `protobuf:"bytes,5,opt,name=azure_aks,json=azureAks,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -715,6 +716,13 @@ func (x *DiscoverSummary) GetAzureVms() *ResourceSummary { return nil } +func (x *DiscoverSummary) GetAzureAks() *ResourceSummary { + if x != nil { + return x.xxx_hidden_AzureAks + } + return nil +} + func (x *DiscoverSummary) SetAwsEc2(v *ResourceSummary) { x.xxx_hidden_AwsEc2 = v } @@ -731,6 +739,10 @@ func (x *DiscoverSummary) SetAzureVms(v *ResourceSummary) { x.xxx_hidden_AzureVms = v } +func (x *DiscoverSummary) SetAzureAks(v *ResourceSummary) { + x.xxx_hidden_AzureAks = v +} + func (x *DiscoverSummary) HasAwsEc2() bool { if x == nil { return false @@ -759,6 +771,13 @@ func (x *DiscoverSummary) HasAzureVms() bool { return x.xxx_hidden_AzureVms != nil } +func (x *DiscoverSummary) HasAzureAks() bool { + if x == nil { + return false + } + return x.xxx_hidden_AzureAks != nil +} + func (x *DiscoverSummary) ClearAwsEc2() { x.xxx_hidden_AwsEc2 = nil } @@ -775,6 +794,10 @@ func (x *DiscoverSummary) ClearAzureVms() { x.xxx_hidden_AzureVms = nil } +func (x *DiscoverSummary) ClearAzureAks() { + x.xxx_hidden_AzureAks = nil +} + type DiscoverSummary_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -786,6 +809,8 @@ type DiscoverSummary_builder struct { AwsEks *ResourceSummary // The summary for the Azure VM discovered instances. AzureVms *ResourceSummary + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourceSummary } func (b0 DiscoverSummary_builder) Build() *DiscoverSummary { @@ -796,6 +821,7 @@ func (b0 DiscoverSummary_builder) Build() *DiscoverSummary { x.xxx_hidden_AwsRds = b.AwsRds x.xxx_hidden_AwsEks = b.AwsEks x.xxx_hidden_AzureVms = b.AzureVms + x.xxx_hidden_AzureAks = b.AzureAks return m0 } @@ -903,6 +929,7 @@ type IntegrationDiscoveredSummary struct { xxx_hidden_AwsRds *ResourcesDiscoveredSummary `protobuf:"bytes,2,opt,name=aws_rds,json=awsRds,proto3"` xxx_hidden_AwsEks *ResourcesDiscoveredSummary `protobuf:"bytes,3,opt,name=aws_eks,json=awsEks,proto3"` xxx_hidden_AzureVms *ResourcesDiscoveredSummary `protobuf:"bytes,4,opt,name=azure_vms,json=azureVms,proto3"` + xxx_hidden_AzureAks *ResourcesDiscoveredSummary `protobuf:"bytes,5,opt,name=azure_aks,json=azureAks,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -960,6 +987,13 @@ func (x *IntegrationDiscoveredSummary) GetAzureVms() *ResourcesDiscoveredSummary return nil } +func (x *IntegrationDiscoveredSummary) GetAzureAks() *ResourcesDiscoveredSummary { + if x != nil { + return x.xxx_hidden_AzureAks + } + return nil +} + func (x *IntegrationDiscoveredSummary) SetAwsEc2(v *ResourcesDiscoveredSummary) { x.xxx_hidden_AwsEc2 = v } @@ -976,6 +1010,10 @@ func (x *IntegrationDiscoveredSummary) SetAzureVms(v *ResourcesDiscoveredSummary x.xxx_hidden_AzureVms = v } +func (x *IntegrationDiscoveredSummary) SetAzureAks(v *ResourcesDiscoveredSummary) { + x.xxx_hidden_AzureAks = v +} + func (x *IntegrationDiscoveredSummary) HasAwsEc2() bool { if x == nil { return false @@ -1004,6 +1042,13 @@ func (x *IntegrationDiscoveredSummary) HasAzureVms() bool { return x.xxx_hidden_AzureVms != nil } +func (x *IntegrationDiscoveredSummary) HasAzureAks() bool { + if x == nil { + return false + } + return x.xxx_hidden_AzureAks != nil +} + func (x *IntegrationDiscoveredSummary) ClearAwsEc2() { x.xxx_hidden_AwsEc2 = nil } @@ -1020,6 +1065,10 @@ func (x *IntegrationDiscoveredSummary) ClearAzureVms() { x.xxx_hidden_AzureVms = nil } +func (x *IntegrationDiscoveredSummary) ClearAzureAks() { + x.xxx_hidden_AzureAks = nil +} + type IntegrationDiscoveredSummary_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -1031,6 +1080,8 @@ type IntegrationDiscoveredSummary_builder struct { AwsEks *ResourcesDiscoveredSummary // The summary for the Azure VM discovered instances. AzureVms *ResourcesDiscoveredSummary + // The summary for the Azure AKS discovered clusters. + AzureAks *ResourcesDiscoveredSummary } func (b0 IntegrationDiscoveredSummary_builder) Build() *IntegrationDiscoveredSummary { @@ -1041,6 +1092,7 @@ func (b0 IntegrationDiscoveredSummary_builder) Build() *IntegrationDiscoveredSum x.xxx_hidden_AwsRds = b.AwsRds x.xxx_hidden_AwsEks = b.AwsEks x.xxx_hidden_AzureVms = b.AzureVms + x.xxx_hidden_AzureAks = b.AzureAks return m0 } @@ -1223,20 +1275,22 @@ const file_teleport_discoveryconfig_v1_discoveryconfig_proto_rawDesc = "" + "\x15integration_summaries\x18\x03 \x03(\v2L.teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntryR\x14integrationSummaries\x1au\n" + "\x19IntegrationSummariesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12B\n" + - "\x05value\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.DiscoverSummaryR\x05value:\x028\x01\"\xb1\x02\n" + + "\x05value\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.DiscoverSummaryR\x05value:\x028\x01\"\xfc\x02\n" + "\x0fDiscoverSummary\x12E\n" + "\aaws_ec2\x18\x01 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsEc2\x12E\n" + "\aaws_rds\x18\x02 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsRds\x12E\n" + "\aaws_eks\x18\x03 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\x06awsEks\x12I\n" + - "\tazure_vms\x18\x04 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureVms\"\xb9\x01\n" + + "\tazure_vms\x18\x04 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureVms\x12I\n" + + "\tazure_aks\x18\x05 \x01(\v2,.teleport.discoveryconfig.v1.ResourceSummaryR\bazureAks\"\xb9\x01\n" + "\x0fResourceSummary\x12Q\n" + "\acurrent\x18\x01 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\acurrent\x12S\n" + - "\bprevious\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bprevious\"\xea\x02\n" + + "\bprevious\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bprevious\"\xc0\x03\n" + "\x1cIntegrationDiscoveredSummary\x12P\n" + "\aaws_ec2\x18\x01 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsEc2\x12P\n" + "\aaws_rds\x18\x02 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsRds\x12P\n" + "\aaws_eks\x18\x03 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\x06awsEks\x12T\n" + - "\tazure_vms\x18\x04 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureVms\"\xd8\x01\n" + + "\tazure_vms\x18\x04 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureVms\x12T\n" + + "\tazure_aks\x18\x05 \x01(\v27.teleport.discoveryconfig.v1.ResourcesDiscoveredSummaryR\bazureAks\"\xd8\x01\n" + "\x1aResourcesDiscoveredSummary\x12\x14\n" + "\x05found\x18\x01 \x01(\x04R\x05found\x12\x1a\n" + "\benrolled\x18\x02 \x01(\x04R\benrolled\x12\x16\n" + @@ -1294,22 +1348,24 @@ var file_teleport_discoveryconfig_v1_discoveryconfig_proto_depIdxs = []int32{ 6, // 16: teleport.discoveryconfig.v1.DiscoverSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourceSummary 6, // 17: teleport.discoveryconfig.v1.DiscoverSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourceSummary 6, // 18: teleport.discoveryconfig.v1.DiscoverSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourceSummary - 8, // 19: teleport.discoveryconfig.v1.ResourceSummary.current:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 20: teleport.discoveryconfig.v1.ResourceSummary.previous:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 21: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_ec2:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 22: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 23: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 8, // 24: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary - 18, // 25: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_start:type_name -> google.protobuf.Timestamp - 18, // 26: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_end:type_name -> google.protobuf.Timestamp - 7, // 27: teleport.discoveryconfig.v1.DiscoveryConfigStatus.IntegrationDiscoveredResourcesEntry.value:type_name -> teleport.discoveryconfig.v1.IntegrationDiscoveredSummary - 4, // 28: teleport.discoveryconfig.v1.DiscoveryConfigStatus.ServerStatusEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoveryStatusServer - 5, // 29: teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoverSummary - 30, // [30:30] is the sub-list for method output_type - 30, // [30:30] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 30, // [30:30] is the sub-list for extension extendee - 0, // [0:30] is the sub-list for field type_name + 6, // 19: teleport.discoveryconfig.v1.DiscoverSummary.azure_aks:type_name -> teleport.discoveryconfig.v1.ResourceSummary + 8, // 20: teleport.discoveryconfig.v1.ResourceSummary.current:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 21: teleport.discoveryconfig.v1.ResourceSummary.previous:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 22: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_ec2:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 23: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_rds:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 24: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.aws_eks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 25: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_vms:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 8, // 26: teleport.discoveryconfig.v1.IntegrationDiscoveredSummary.azure_aks:type_name -> teleport.discoveryconfig.v1.ResourcesDiscoveredSummary + 18, // 27: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_start:type_name -> google.protobuf.Timestamp + 18, // 28: teleport.discoveryconfig.v1.ResourcesDiscoveredSummary.sync_end:type_name -> google.protobuf.Timestamp + 7, // 29: teleport.discoveryconfig.v1.DiscoveryConfigStatus.IntegrationDiscoveredResourcesEntry.value:type_name -> teleport.discoveryconfig.v1.IntegrationDiscoveredSummary + 4, // 30: teleport.discoveryconfig.v1.DiscoveryConfigStatus.ServerStatusEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoveryStatusServer + 5, // 31: teleport.discoveryconfig.v1.DiscoveryStatusServer.IntegrationSummariesEntry.value:type_name -> teleport.discoveryconfig.v1.DiscoverSummary + 32, // [32:32] is the sub-list for method output_type + 32, // [32:32] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name } func init() { file_teleport_discoveryconfig_v1_discoveryconfig_proto_init() } diff --git a/api/proto/teleport/discoveryconfig/v1/discoveryconfig.proto b/api/proto/teleport/discoveryconfig/v1/discoveryconfig.proto index 8f57acf73fab..d8a96ed6d7f3 100644 --- a/api/proto/teleport/discoveryconfig/v1/discoveryconfig.proto +++ b/api/proto/teleport/discoveryconfig/v1/discoveryconfig.proto @@ -115,6 +115,9 @@ message DiscoverSummary { // The summary for the Azure VM discovered instances. ResourceSummary azure_vms = 4; + + // The summary for the Azure AKS discovered clusters. + ResourceSummary azure_aks = 5; } // ResourceSummary represents the summary of the discovered resources. @@ -139,6 +142,9 @@ message IntegrationDiscoveredSummary { // The summary for the Azure VM discovered instances. ResourcesDiscoveredSummary azure_vms = 4; + + // The summary for the Azure AKS discovered clusters. + ResourcesDiscoveredSummary azure_aks = 5; } // ResourcesDiscoveredSummary represents the AWS resources that were discovered. diff --git a/docs/pages/reference/infrastructure-as-code/teleport-resources/discovery-config.mdx b/docs/pages/reference/infrastructure-as-code/teleport-resources/discovery-config.mdx index 00bd880e5307..1041952d69b4 100644 --- a/docs/pages/reference/infrastructure-as-code/teleport-resources/discovery-config.mdx +++ b/docs/pages/reference/infrastructure-as-code/teleport-resources/discovery-config.mdx @@ -523,6 +523,7 @@ aws_ec2: # [...] aws_rds: # [...] aws_eks: # [...] azure_vms: # [...] +azure_aks: # [...] ``` |Field Name|Description|Type| @@ -530,6 +531,7 @@ azure_vms: # [...] |aws_ec2|AWSEC2 contains the summary for the AWS EC2 discovered instances.|[Resource Summary](#resource-summary)| |aws_eks|AWSEKS contains the summary for the AWS EKS discovered clusters.|[Resource Summary](#resource-summary)| |aws_rds|AWSRDS contains the summary for the AWS RDS discovered databases.|[Resource Summary](#resource-summary)| +|azure_aks|The summary for the Azure AKS discovered clusters.|[Resource Summary](#resource-summary)| |azure_vms|The summary for the Azure VM discovered instances.|[Resource Summary](#resource-summary)| ## Discovery Status Server @@ -664,6 +666,7 @@ aws_ec2: # [...] aws_rds: # [...] aws_eks: # [...] azure_vms: # [...] +azure_aks: # [...] ``` |Field Name|Description|Type| @@ -671,6 +674,7 @@ azure_vms: # [...] |aws_ec2|AWSEC2 contains the summary for the AWS EC2 discovered instances.|[Resources Discovered Summary](#resources-discovered-summary)| |aws_eks|AWSEKS contains the summary for the AWS EKS discovered clusters.|[Resources Discovered Summary](#resources-discovered-summary)| |aws_rds|AWSRDS contains the summary for the AWS RDS discovered databases.|[Resources Discovered Summary](#resources-discovered-summary)| +|azure_aks|The summary for the Azure AKS discovered clusters.|[Resources Discovered Summary](#resources-discovered-summary)| |azure_vms|The summary for the Azure VM discovered instances.|[Resources Discovered Summary](#resources-discovered-summary)| ## Join Method From cf67e6cb9c83b292522ab4a913be491d2df47312 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 30 Jul 2026 09:38:24 +0100 Subject: [PATCH 058/347] Display Bot SQN in `tctl get scoped_role_assignment` (#69155) * Display bot SQN in `tctl get scoped_role_assignment` * Prefer "assignee" rather than "target" * less wide --- .../rbac-get-started/scopes.mdx | 8 +- .../resources/scoped_role_assignment.go | 18 ++++- .../resources/scoped_role_assignment_test.go | 80 +++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 tool/tctl/common/resources/scoped_role_assignment_test.go diff --git a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx index a60b0bfc203c..d81402c26864 100644 --- a/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx +++ b/docs/pages/zero-trust-access/rbac-get-started/scopes.mdx @@ -563,10 +563,10 @@ Confirm a scoped role assignment has been materialized for each member: ```code $ tctl get scoped_role_assignments --format text -SubKind ID User Assigns ------------- ----------------------------------------------------------- ------- -------------------------------------------------- -materialized /examples/basic::acl-4ezohtYllrk-gbWjn0hj_npCNwhujMurruDLRg charlie /examples/basic::example-access -> /examples/basic -materialized /examples/basic::acl-XMyMVGoNZopPQ9WxuZ5RH6GM5FPHVwLSTFUsBg bob /examples/basic::example-access -> /examples/basic +SubKind ID Assignee Assigns +------------ ----------------------------------------------------------- ------------- -------------------------------------------------- +materialized /examples/basic::acl-4ezohtYllrk-gbWjn0hj_npCNwhujMurruDLRg user: charlie /examples/basic::example-access -> /examples/basic +materialized /examples/basic::acl-XMyMVGoNZopPQ9WxuZ5RH6GM5FPHVwLSTFUsBg user: bob /examples/basic::example-access -> /examples/basic ``` Access Lists and members can also be managed via the Web UI under diff --git a/tool/tctl/common/resources/scoped_role_assignment.go b/tool/tctl/common/resources/scoped_role_assignment.go index a67a27b326aa..750f0526961c 100644 --- a/tool/tctl/common/resources/scoped_role_assignment.go +++ b/tool/tctl/common/resources/scoped_role_assignment.go @@ -55,7 +55,7 @@ func (c *ScopedRoleAssignmentCollection) Resources() []types.Resource { } func (c *ScopedRoleAssignmentCollection) WriteText(w io.Writer, verbose bool) error { - headers := []string{"SubKind", "ID", "User", "Assigns"} + headers := []string{"SubKind", "ID", "Assignee", "Assigns"} rows := make([][]string, len(c.roleAssignments)) for i, item := range c.roleAssignments { @@ -66,7 +66,7 @@ func (c *ScopedRoleAssignmentCollection) WriteText(w io.Writer, verbose bool) er rows[i] = []string{ item.GetSubKind(), scopes.QualifiedName{Scope: item.GetScope(), Name: item.GetMetadata().GetName()}.String(), - item.GetSpec().GetUser(), + scopedRoleAssignmentAssignee(item), strings.Join(assigns, ", "), } } @@ -77,6 +77,20 @@ func (c *ScopedRoleAssignmentCollection) WriteText(w io.Writer, verbose bool) er return trace.Wrap(err) } +// scopedRoleAssignmentAssignee describes the identity that an assignment applies to, +// kind-prefixed to disambiguate the two forms (e.g. "user: alice", "bot: /staging::mybot"). +// Assignments apply to either a user (by name) or a bot (by scope-qualified name), and the +// two are mutually exclusive. +func scopedRoleAssignmentAssignee(assignment *scopedaccessv1.ScopedRoleAssignment) string { + if bot := assignment.GetSpec().GetBot(); bot != "" { + return fmt.Sprintf("%s: %s", types.KindBot, bot) + } + if user := assignment.GetSpec().GetUser(); user != "" { + return fmt.Sprintf("%s: %s", types.KindUser, user) + } + return "" +} + func scopedRoleAssignmentScopedHandler() ScopedHandler { return ScopedHandler{ getHandler: getScopedRoleAssignment, diff --git a/tool/tctl/common/resources/scoped_role_assignment_test.go b/tool/tctl/common/resources/scoped_role_assignment_test.go new file mode 100644 index 000000000000..3cb365ce9e98 --- /dev/null +++ b/tool/tctl/common/resources/scoped_role_assignment_test.go @@ -0,0 +1,80 @@ +/* + * Teleport + * Copyright (C) 2026 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 resources + +import ( + "testing" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/asciitable" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" +) + +func TestScopedRoleAssignmentCollection_WriteText(t *testing.T) { + assignments := []*scopedaccessv1.ScopedRoleAssignment{ + scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, + Version: types.V1, + Metadata: headerv1.Metadata_builder{Name: "alice-assignment"}.Build(), + Scope: "/staging", + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + User: "alice", + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{ + Role: "/staging::role1", + Scope: "/staging/west", + }.Build(), + }, + }.Build(), + }.Build(), + scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, + Version: types.V1, + Metadata: headerv1.Metadata_builder{Name: "mybot-assignment"}.Build(), + Scope: "/staging", + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + Bot: "/staging/west::mybot", + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{ + Role: "/staging::role1", + Scope: "/staging/west", + }.Build(), + scopedaccessv1.Assignment_builder{ + Role: "/staging::role2", + Scope: "/staging/west", + }.Build(), + }, + }.Build(), + }.Build(), + } + + table := asciitable.MakeTable( + []string{"SubKind", "ID", "Assignee", "Assigns"}, + []string{"dynamic", "/staging::alice-assignment", "user: alice", "/staging::role1 -> /staging/west"}, + []string{"dynamic", "/staging::mybot-assignment", "bot: /staging/west::mybot", "/staging::role1 -> /staging/west, /staging::role2 -> /staging/west"}, + ) + + formatted := table.AsBuffer().String() + + collectionFormatTest(t, NewScopedRoleAssignmentCollection(assignments), formatted, formatted) +} From e5610a07206ef25370d8d021dba226a0802f4fad Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Thu, 30 Jul 2026 10:49:48 +0100 Subject: [PATCH 059/347] events: allow athena to query for activity from specific `beam_id` (#68576) This PR extends audit log search to be able to search events for a specific `beam_id`. Only athena backend will support the search because beams only runs on Teleport cloud - uses athena as backend. Signed-off-by: Tiago Silva --- api/types/events/metadata.go | 6 ++++++ lib/events/api.go | 5 +++++ lib/events/athena/querier.go | 13 +++++++++++-- lib/events/dynamoevents/dynamoevents.go | 6 ++++++ lib/events/filelog.go | 6 ++++++ lib/events/firestoreevents/firestoreevents.go | 6 ++++++ lib/events/pgevents/pgevents.go | 6 ++++++ 7 files changed, 46 insertions(+), 2 deletions(-) diff --git a/api/types/events/metadata.go b/api/types/events/metadata.go index 03ed89cab547..f57c09edc748 100644 --- a/api/types/events/metadata.go +++ b/api/types/events/metadata.go @@ -107,3 +107,9 @@ func (m *SessionMetadata) GetSessionID() string { func (m *UserMetadata) GetUser() string { return m.User } + +// GetBeamID returns the identifier of the Beam this action is associated with, +// if any. +func (m *UserMetadata) GetBeamID() string { + return m.BeamID +} diff --git a/lib/events/api.go b/lib/events/api.go index 2c77b67f9740..90cb89466618 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -1346,6 +1346,11 @@ type SearchEventsRequest struct { StartKey string // Search is an optional search query to filter events. Search string + // BeamID optionally restricts results to events attributed to the given + // beam (matched against the event's user metadata beam_id). This filter is + // only supported by the Athena audit backend; other backends return a + // trace.NotImplemented error when it is set. + BeamID string } type SearchSessionEventsRequest struct { diff --git a/lib/events/athena/querier.go b/lib/events/athena/querier.go index 252bd1e2318c..a9135a56f5d9 100644 --- a/lib/events/athena/querier.go +++ b/lib/events/athena/querier.go @@ -208,7 +208,7 @@ func (q *querier) SearchEvents(ctx context.Context, req events.SearchEventsReque limit: req.Limit, order: req.Order, startKey: startKeyset, - filter: searchEventsFilter{eventTypes: req.EventTypes, search: req.Search}, + filter: searchEventsFilter{eventTypes: req.EventTypes, search: req.Search, beamID: req.BeamID}, sessionID: "", }) return events, keyset, trace.Wrap(err) @@ -220,7 +220,7 @@ func (q *querier) SearchEvents(ctx context.Context, req events.SearchEventsReque limit: req.Limit, order: req.Order, startKey: startKeyset, - filter: searchEventsFilter{eventTypes: req.EventTypes, search: req.Search}, + filter: searchEventsFilter{eventTypes: req.EventTypes, search: req.Search, beamID: req.BeamID}, sessionID: "", }) return events, keyset, trace.Wrap(err) @@ -664,6 +664,9 @@ type searchEventsFilter struct { eventTypes []string search string condition utils.FieldsCondition + // beamID, when set, restricts results to events whose top-level + // event_data beam_id equals it (see prepareQuery). + beamID string } type queryBuilder struct { @@ -748,6 +751,12 @@ func prepareQuery(params searchParams) (query string, execParams []string, err e } } + if params.filter.beamID != "" { + // beam_id is not a dedicated column; it lives at the top level of the + // event_data JSON (UserMetadata is inlined into every event). + qb.Append(" AND json_extract_scalar(event_data, '$.beam_id') = ?", withTicks(params.filter.beamID)) + } + if params.order == types.EventOrderAscending { if params.startKeyset != nil { qb.Append(` AND (event_time, uid) > (?,?)`, diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index c1a21e4e798d..ab99dbfa2361 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -776,6 +776,9 @@ type legacyCheckpointKey struct { // // This function may never return more than 1 MiB of event data. func (l *Log) SearchEvents(ctx context.Context, req events.SearchEventsRequest) ([]apievents.AuditEvent, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the dynamodb audit backend does not support the beam ID filter") + } values, next, err := l.searchEventsWithFilter(ctx, req.From, req.To, apidefaults.Namespace, req.Limit, req.Order, req.StartKey, searchEventsFilter{eventTypes: req.EventTypes, search: req.Search}, "") if err != nil { return nil, "", trace.Wrap(err) @@ -788,6 +791,9 @@ func (l *Log) SearchEvents(ctx context.Context, req events.SearchEventsRequest) } func (l *Log) SearchUnstructuredEvents(ctx context.Context, req events.SearchEventsRequest) ([]*auditlogpb.EventUnstructured, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the dynamodb audit backend does not support the beam ID filter") + } values, next, err := l.searchEventsWithFilter(ctx, req.From, req.To, apidefaults.Namespace, req.Limit, req.Order, req.StartKey, searchEventsFilter{eventTypes: req.EventTypes, search: req.Search}, "") if err != nil { return nil, "", trace.Wrap(err) diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 5fcd1157b411..741a01dc82da 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -197,6 +197,9 @@ func (l *FileLog) trimSizeAndMarshal(event apievents.AuditEvent) ([]byte, error) // // This function may never return more than 1 MiB of event data. func (l *FileLog) SearchEvents(ctx context.Context, req SearchEventsRequest) ([]apievents.AuditEvent, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the file audit backend does not support the beam ID filter") + } l.logger.DebugContext(ctx, "SearchEvents", "from", req.From, "to", req.To, "event_type", req.EventTypes, "limit", req.Limit, "search", req.Search) values, next, err := l.searchEventsWithFilter(ctx, req.From, req.To, req.Limit, req.Order, req.StartKey, searchEventsFilter{ eventTypes: req.EventTypes, @@ -214,6 +217,9 @@ func (l *FileLog) SearchEvents(ctx context.Context, req SearchEventsRequest) ([] } func (l *FileLog) SearchUnstructuredEvents(ctx context.Context, req SearchEventsRequest) ([]*auditlogpb.EventUnstructured, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the file audit backend does not support the beam ID filter") + } l.logger.DebugContext(ctx, "SearchUnstructuredEvents", "from", req.From, "to", req.To, "event_type", req.EventTypes, "limit", req.Limit) values, next, err := l.searchEventsWithFilter(ctx, req.From, req.To, req.Limit, req.Order, req.StartKey, searchEventsFilter{eventTypes: req.EventTypes}) if err != nil { diff --git a/lib/events/firestoreevents/firestoreevents.go b/lib/events/firestoreevents/firestoreevents.go index 76589b173e12..cbaf7c41bf43 100644 --- a/lib/events/firestoreevents/firestoreevents.go +++ b/lib/events/firestoreevents/firestoreevents.go @@ -453,6 +453,9 @@ func (l *Log) tryInsertEvent(docID uuid.UUID, e event) (inserted, isDuplicate bo // // This function may never return more than 1 MiB of event data. func (l *Log) SearchEvents(ctx context.Context, req events.SearchEventsRequest) ([]apievents.AuditEvent, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the firestore audit backend does not support the beam ID filter") + } values, next, err := l.searchEventsWithFilter( ctx, searchEventsWithFilterParams{ @@ -771,6 +774,9 @@ func (l *Log) Close() error { } func (l *Log) SearchUnstructuredEvents(ctx context.Context, req events.SearchEventsRequest) ([]*auditlogpb.EventUnstructured, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the firestore audit backend does not support the beam ID filter") + } values, next, err := l.searchEventsWithFilter( ctx, searchEventsWithFilterParams{ diff --git a/lib/events/pgevents/pgevents.go b/lib/events/pgevents/pgevents.go index 9bb9ad890a79..ee6cbf9cf944 100644 --- a/lib/events/pgevents/pgevents.go +++ b/lib/events/pgevents/pgevents.go @@ -664,6 +664,9 @@ func (l *Log) searchEvents( // SearchEvents implements [events.AuditLogger]. func (l *Log) SearchEvents(ctx context.Context, req events.SearchEventsRequest) ([]apievents.AuditEvent, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the postgres audit backend does not support the beam ID filter") + } var emptyCond *utils.ToFieldsConditionConfig const emptySessionID = "" @@ -681,6 +684,9 @@ func (l *Log) SearchEvents(ctx context.Context, req events.SearchEventsRequest) // SearchUnstructuredEvents implements [events.AuditLogger]. func (l *Log) SearchUnstructuredEvents(ctx context.Context, req events.SearchEventsRequest) ([]*auditlogpb.EventUnstructured, string, error) { + if req.BeamID != "" { + return nil, "", trace.NotImplemented("the postgres audit backend does not support the beam ID filter") + } var emptyCond *utils.ToFieldsConditionConfig const emptySessionID = "" From 709c27dc3c5c3b78c5a170b48ebf8ccb78775fd9 Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Thu, 30 Jul 2026 11:48:22 +0100 Subject: [PATCH 060/347] [5] Add UploadHandler replay-object storage for beam traffic replay (#68676) * Add UploadHandler replay-object storage for beam traffic replay Introduce two methods on events.UploadHandler for the beam traffic replay artifact: - UploadReplayObject(ctx, sessionID, name, r) writes a named artifact object (the manifest, or a numbered "index.N"/"blob.N" part) for a beam. - StreamReplayObjectRange(ctx, sessionID, name, offset, length) returns a ranged reader over one, so the replay service can page the index and fetch individual payload/attachment byte ranges without downloading whole objects. Implement both on the S3, filesessions, and in-memory backends; GCS and Azure return trace.NotImplemented (beams are only supported in teleport cloud for now), and the external-audit-storage wrapper delegates to its inner handler. Object names are validated by ValidateReplayObjectName against a strict allowlist (manifest | index.N | blob.N) before being joined into a storage key/path. The name ultimately derives from a client-supplied PayloadRef sent over the wire, so this prevents a forged name with path-traversal segments (e.g. "../.summary.json") from escaping the beam's object namespace and reading arbitrary bucket/disk objects. Signed-off-by: Tiago Silva * address feedback --------- Signed-off-by: Tiago Silva --- lib/events/azsessions/azsessions.go | 12 ++ lib/events/eventstest/uploader.go | 49 +++++++ lib/events/eventstest/uploader_test.go | 54 ++++++++ lib/events/filesessions/fileuploader.go | 45 ++++++ lib/events/filesessions/fileuploader_test.go | 100 +++++++++++++ lib/events/gcssessions/gcshandler.go | 10 ++ lib/events/s3sessions/s3handler.go | 67 +++++++++ lib/events/s3sessions/s3handler_test.go | 131 ++++++++++++++++++ lib/events/uploader.go | 34 +++++ lib/events/uploader_test.go | 69 +++++++++ .../externalauditstorage/error_counter.go | 17 +++ 11 files changed, 588 insertions(+) create mode 100644 lib/events/eventstest/uploader_test.go create mode 100644 lib/events/uploader_test.go diff --git a/lib/events/azsessions/azsessions.go b/lib/events/azsessions/azsessions.go index fc7054cfe440..d184bec50277 100644 --- a/lib/events/azsessions/azsessions.go +++ b/lib/events/azsessions/azsessions.go @@ -336,6 +336,12 @@ func (h *Handler) UploadSummary(ctx context.Context, sessionID session.ID, reade return path, nil } +// UploadReplayObject implements [events.UploadHandler]. Beam replay is not +// supported by the Azure Blob Storage session backend. +func (h *Handler) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + return "", trace.NotImplemented("beam replay is not supported by the Azure Blob Storage session backend") +} + // UploadMetadata implements [events.UploadHandler] and uploads the session // metadata. func (h *Handler) UploadMetadata(ctx context.Context, sessionID session.ID, reader io.Reader) (string, error) { @@ -415,6 +421,12 @@ func (h *Handler) blobStream(ctx context.Context, blobClient *blockblob.Client) return resp.Body, nil } +// StreamReplayObjectRange implements [events.UploadHandler]. Beam replay is +// not supported by the Azure Blob Storage session backend. +func (h *Handler) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + return nil, trace.NotImplemented("beam replay is not supported by the Azure Blob Storage session backend") +} + // StreamSessionMetadata implements [events.UploadHandler] and downloads a session's metadata. func (h *Handler) StreamSessionMetadata(ctx context.Context, sessionID session.ID) (io.ReadCloser, error) { return h.blobRetrier(ctx, sessionID, h.metadataBlob(sessionID)) diff --git a/lib/events/eventstest/uploader.go b/lib/events/eventstest/uploader.go index 04afe622e5be..6848e47692dc 100644 --- a/lib/events/eventstest/uploader.go +++ b/lib/events/eventstest/uploader.go @@ -56,6 +56,7 @@ func NewMemoryUploader(cfg ...MemoryUploaderConfig) *MemoryUploader { pendingSummaries: make(map[session.ID][]byte), metadata: make(map[session.ID][]byte), thumbnails: make(map[session.ID][]byte), + replays: make(map[string][]byte), } if len(cfg) != 0 { up.cfg = cfg[0] @@ -74,6 +75,9 @@ type MemoryUploader struct { pendingSummaries map[session.ID][]byte metadata map[session.ID][]byte thumbnails map[session.ID][]byte + // replays holds named beam-replay artifact objects, keyed by + // "/". + replays map[string][]byte // Clock is an optional [clockwork.Clock] to determine the time to associate // with uploads and parts. @@ -120,6 +124,13 @@ func (m *MemoryUploader) Reset() { m.pendingSummaries = make(map[session.ID][]byte) m.metadata = make(map[session.ID][]byte) m.thumbnails = make(map[session.ID][]byte) + m.replays = make(map[string][]byte) +} + +// replayKey returns the map key used to store a named replay object for a +// given session. +func replayKey(sessionID session.ID, name string) string { + return string(sessionID) + "/" + name } // CreateUpload creates a multipart upload @@ -321,6 +332,19 @@ func (m *MemoryUploader) UploadSummary(ctx context.Context, sessionID session.ID return string(sessionID), nil } +// UploadReplayObject uploads a named beam-replay artifact object and returns +// URL with uploaded file in case of success. +func (m *MemoryUploader) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + data, err := io.ReadAll(reader) + if err != nil { + return "", trace.ConvertSystemError(err) + } + m.mtx.Lock() + defer m.mtx.Unlock() + m.replays[replayKey(sessionID, name)] = data + return string(sessionID), nil +} + // UploadMetadata uploads session metadata and returns URL with uploaded file in // case of success. func (m *MemoryUploader) UploadMetadata(ctx context.Context, sessionID session.ID, readCloser io.Reader) (string, error) { @@ -382,6 +406,31 @@ func (m *MemoryUploader) StreamSessionSummary(ctx context.Context, sessionID ses return io.NopCloser(bytes.NewReader(data)), nil } +// StreamReplayObjectRange streams a ranged read over a named beam-replay +// artifact object and returns a ReadCloser for the content. A length <= 0 +// reads to the end of the object. +func (m *MemoryUploader) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + m.mtx.RLock() + defer m.mtx.RUnlock() + + data, ok := m.replays[replayKey(sessionID, name)] + if !ok { + return nil, trace.NotFound("replay object %q for session %q is not found", name, sessionID) + } + + total := int64(len(data)) + if offset < 0 || offset > total { + return nil, trace.BadParameter("offset %d out of range for replay object %q of length %d", offset, name, total) + } + + end := total + if length > 0 && offset+length < total { + end = offset + length + } + + return io.NopCloser(bytes.NewReader(data[offset:end])), nil +} + // StreamSessionMetadata streams session metadata and returns a ReadCloser for the content. func (m *MemoryUploader) StreamSessionMetadata(ctx context.Context, sessionID session.ID) (io.ReadCloser, error) { m.mtx.RLock() diff --git a/lib/events/eventstest/uploader_test.go b/lib/events/eventstest/uploader_test.go new file mode 100644 index 000000000000..63eed11659bd --- /dev/null +++ b/lib/events/eventstest/uploader_test.go @@ -0,0 +1,54 @@ +/* + * 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 eventstest + +import ( + "bytes" + "context" + "io" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/lib/session" +) + +func TestReplayObjectRoundTripAndRange(t *testing.T) { + ctx := context.Background() + u := NewMemoryUploader() + sid := session.ID("beam-1") + _, err := u.UploadReplayObject(ctx, sid, "blob.0", bytes.NewReader([]byte("0123456789"))) + require.NoError(t, err) + + rc, err := u.StreamReplayObjectRange(ctx, sid, "blob.0", 0, 0) + require.NoError(t, err) + got, _ := io.ReadAll(rc) + rc.Close() + require.Equal(t, "0123456789", string(got)) + + rc, err = u.StreamReplayObjectRange(ctx, sid, "blob.0", 3, 4) + require.NoError(t, err) + got, _ = io.ReadAll(rc) + rc.Close() + require.Equal(t, "3456", string(got)) + + _, err = u.StreamReplayObjectRange(ctx, sid, "missing", 0, 0) + require.True(t, trace.IsNotFound(err)) +} diff --git a/lib/events/filesessions/fileuploader.go b/lib/events/filesessions/fileuploader.go index e37885e94f7f..017a871a1c56 100644 --- a/lib/events/filesessions/fileuploader.go +++ b/lib/events/filesessions/fileuploader.go @@ -142,6 +142,27 @@ func (l *Handler) StreamSessionSummary(ctx context.Context, sessionID session.ID return nil, trace.NotFound("summary for session %v not found", sessionID) } +// StreamReplayObjectRange reads a ranged portion of a named beam-replay +// artifact object from a local directory. A length <= 0 reads to the end of +// the object. Returns a "not found" error if the object does not exist. +func (l *Handler) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + if err := events.ValidateReplayObjectName(name); err != nil { + return nil, trace.Wrap(err) + } + f, err := os.Open(l.replayPath(sessionID, name)) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + if _, err := f.Seek(offset, io.SeekStart); err != nil { + _ = f.Close() + return nil, trace.ConvertSystemError(err) + } + if length <= 0 { + return f, nil + } + return &limitedReadCloser{Reader: io.LimitReader(f, length), Closer: f}, nil +} + // StreamSessionMetadata reads session metadata from a local directory. func (l *Handler) StreamSessionMetadata(ctx context.Context, sessionID session.ID) (io.ReadCloser, error) { return openFile(l.metadataPath(sessionID)) @@ -182,6 +203,16 @@ func (l *Handler) UploadSummary(ctx context.Context, sessionID session.ID, reade return name, nil } +// UploadReplayObject writes a named beam-replay artifact object to a local +// directory. This function can be called only once for a given sessionID and +// name; subsequent calls will return an error. +func (l *Handler) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + if err := events.ValidateReplayObjectName(name); err != nil { + return "", trace.Wrap(err) + } + return uploadFile(l.replayPath(sessionID, name), reader) +} + // UploadMetadata writes session metadata to a local directory. func (l *Handler) UploadMetadata(ctx context.Context, sessionID session.ID, reader io.Reader) (string, error) { return uploadFile(l.metadataPath(sessionID), reader) @@ -200,6 +231,14 @@ func openFile(path string) (io.ReadCloser, error) { return f, nil } +// limitedReadCloser wraps a Reader (typically an [io.LimitReader] over an +// open file) together with the underlying Closer, so that callers can close +// the underlying file once done reading a bounded range of it. +type limitedReadCloser struct { + io.Reader + io.Closer +} + type fileUploadConfig struct { overwrite bool } @@ -253,6 +292,12 @@ func (l *Handler) metadataPath(sessionID session.ID) string { return filepath.Join(l.Directory, string(sessionID)+metadataExt) } +// replayPath returns the path of a named beam-replay artifact object for a +// given session, e.g. ".replay.". +func (l *Handler) replayPath(sessionID session.ID, name string) string { + return filepath.Join(l.Directory, string(sessionID)+".replay."+name) +} + func (l *Handler) thumbnailPath(sessionID session.ID) string { return filepath.Join(l.Directory, string(sessionID)+thumbnailExt) } diff --git a/lib/events/filesessions/fileuploader_test.go b/lib/events/filesessions/fileuploader_test.go index 6941f6f39dbb..93e1c9f516b7 100644 --- a/lib/events/filesessions/fileuploader_test.go +++ b/lib/events/filesessions/fileuploader_test.go @@ -19,7 +19,9 @@ package filesessions import ( + "bytes" "context" + "io" "os" "sync/atomic" "testing" @@ -29,6 +31,7 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/test" + "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils/log/logtest" ) @@ -78,3 +81,100 @@ func TestStreams(t *testing.T) { test.DownloadNotFound(t, handler) }) } + +// TestReplayObjectRoundTripAndRange verifies that replay objects can be +// uploaded and read back in full or in byte ranges, and that reading a +// nonexistent replay object returns a "not found" error. +func TestReplayObjectRoundTripAndRange(t *testing.T) { + dir := t.TempDir() + + handler, err := NewHandler(Config{ + Directory: dir, + OpenFile: os.OpenFile, + }) + require.NoError(t, err) + defer handler.Close() + + ctx := context.Background() + sid := session.ID("beam-1") + + _, err = handler.UploadReplayObject(ctx, sid, "blob.0", bytes.NewReader([]byte("0123456789"))) + require.NoError(t, err) + + rc, err := handler.StreamReplayObjectRange(ctx, sid, "blob.0", 0, 0) + require.NoError(t, err) + got, err := io.ReadAll(rc) + require.NoError(t, err) + require.NoError(t, rc.Close()) + require.Equal(t, "0123456789", string(got)) + + rc, err = handler.StreamReplayObjectRange(ctx, sid, "blob.0", 3, 4) + require.NoError(t, err) + got, err = io.ReadAll(rc) + require.NoError(t, err) + require.NoError(t, rc.Close()) + require.Equal(t, "3456", string(got)) + + // "blob.99" is a well-formed replay object name (matches the naming + // scheme ValidateReplayObjectName enforces) that was simply never + // uploaded -- distinct from a malformed name, which + // TestReplayObjectNameRejectsPathTraversal covers below. + _, err = handler.StreamReplayObjectRange(ctx, sid, "blob.99", 0, 0) + require.True(t, trace.IsNotFound(err)) +} + +// TestReplayObjectNameRejectsPathTraversal asserts that UploadReplayObject +// and StreamReplayObjectRange reject any object name outside the +// well-known manifest/index/blob naming scheme used by ReplaySink -- +// crucially, names containing path traversal segments, which would +// otherwise resolve straight through filepath.Join(l.Directory, ...) in +// replayPath to read or write files outside the beam's own replay +// artifact. +func TestReplayObjectNameRejectsPathTraversal(t *testing.T) { + dir := t.TempDir() + + handler, err := NewHandler(Config{ + Directory: dir, + OpenFile: os.OpenFile, + }) + require.NoError(t, err) + defer handler.Close() + + ctx := context.Background() + sid := session.ID("beam-1") + + badNames := []string{ + "../x", + "blob.0/../manifest", + "/../../etc/passwd", + "..", + "blob.-1", + "blob", + "manifest.json", + "", + } + for _, name := range badNames { + t.Run(name, func(t *testing.T) { + _, err := handler.UploadReplayObject(ctx, sid, name, bytes.NewReader([]byte("x"))) + require.True(t, trace.IsBadParameter(err), "UploadReplayObject(%q): got %v", name, err) + + _, err = handler.StreamReplayObjectRange(ctx, sid, name, 0, 0) + require.True(t, trace.IsBadParameter(err), "StreamReplayObjectRange(%q): got %v", name, err) + }) + } + + goodNames := []string{"manifest", "index.0", "index.12", "blob.0", "blob.12", "commands.0", "commands.7"} + for _, name := range goodNames { + t.Run(name, func(t *testing.T) { + _, err := handler.UploadReplayObject(ctx, sid, name, bytes.NewReader([]byte("ok"))) + require.NoError(t, err) + + rc, err := handler.StreamReplayObjectRange(ctx, sid, name, 0, 0) + require.NoError(t, err) + got, err := io.ReadAll(rc) + require.NoError(t, err) + require.NoError(t, rc.Close()) + require.Equal(t, "ok", string(got)) + }) + } +} diff --git a/lib/events/gcssessions/gcshandler.go b/lib/events/gcssessions/gcshandler.go index b83253d8c65b..25f0f7c62c17 100644 --- a/lib/events/gcssessions/gcshandler.go +++ b/lib/events/gcssessions/gcshandler.go @@ -275,6 +275,11 @@ func (h *Handler) UploadSummary(ctx context.Context, sessionID session.ID, reade return path, nil } +// UploadReplayObject is not supported by the GCS session backend. +func (h *Handler) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + return "", trace.NotImplemented("beam replay is not supported by the GCS session backend") +} + // UploadMetadata reads the session metadata from a reader and uploads it to a GCS // bucket. If successful, it returns URL of the uploaded object. func (h *Handler) UploadMetadata(ctx context.Context, sessionID session.ID, reader io.Reader) (string, error) { @@ -369,6 +374,11 @@ func (h *Handler) gcsStream(ctx context.Context, objectPath string) (io.ReadClos return reader, nil } +// StreamReplayObjectRange is not supported by the GCS session backend. +func (h *Handler) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + return nil, trace.NotImplemented("beam replay is not supported by the GCS session backend") +} + // StreamSessionMetadata downloads a session's metadata from a GCS bucket and // returns a ReadCloser for the content. Returns trace.NotFound error if the // metadata is not found. diff --git a/lib/events/s3sessions/s3handler.go b/lib/events/s3sessions/s3handler.go index abdba8e5531e..26cbc42db854 100644 --- a/lib/events/s3sessions/s3handler.go +++ b/lib/events/s3sessions/s3handler.go @@ -344,6 +344,18 @@ func (h *Handler) UploadSummary(ctx context.Context, sessionID session.ID, reade return path, trace.Wrap(err) } +// UploadReplayObject reads the content of a named beam-replay artifact object +// from a reader and uploads it to an S3 bucket. If successful, it returns URL +// of the uploaded object. This function can be called only once for a given +// sessionID and name; subsequent calls will return an error. +func (h *Handler) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + if err := events.ValidateReplayObjectName(name); err != nil { + return "", trace.Wrap(err) + } + path, err := h.uploadFile(ctx, h.replayPath(sessionID, name), reader) + return path, trace.Wrap(err) +} + // UploadMetadata reads the content of a session's metadata from a reader and // uploads it to an S3 bucket. If successful, it returns URL of the uploaded // object. @@ -460,6 +472,54 @@ func (h *Handler) StreamSessionSummary(ctx context.Context, sessionID session.ID return h.downloadFileRetrier(ctx, h.summaryPath(sessionID), nil /* versionID */) } +// StreamReplayObjectRange downloads a ranged portion of a named beam-replay +// artifact object from an S3 bucket and returns a ReadCloser for the content. +// A length <= 0 reads to the end of the object. Returns trace.NotFound error +// if the object is not found. +func (h *Handler) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + if err := events.ValidateReplayObjectName(name); err != nil { + return nil, trace.Wrap(err) + } + path := h.replayPath(sessionID, name) + + // Check existence and fetch content-length upfront so that NotFound is + // returned before the caller attempts any reads. + headOutput, err := h.client.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: aws.String(h.Bucket), + Key: aws.String(path), + }) + if err != nil { + return nil, awsutils.ConvertS3Error(err) + } + + contentLength := aws.ToInt64(headOutput.ContentLength) + if offset < 0 || offset > contentLength { + return nil, trace.BadParameter("offset %d out of range for replay object of length %d", offset, contentLength) + } + + rangeLength := contentLength - offset + if length > 0 && length < rangeLength { + rangeLength = length + } + if rangeLength == 0 { + return io.NopCloser(bytes.NewReader(nil)), nil + } + + end := offset + rangeLength - 1 + return downloadretrier.New(ctx, rangeLength, func(ctx context.Context, retryOffset int64) (io.ReadCloser, error) { + rangeStr := fmt.Sprintf("bytes=%d-%d", offset+retryOffset, end) + output, err := h.client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(h.Bucket), + Key: aws.String(path), + Range: aws.String(rangeStr), + }) + if err != nil { + return nil, awsutils.ConvertS3Error(err) + } + return output.Body, nil + }), nil +} + // StreamSessionMetadata downloads a session's metadata from an S3 bucket and // returns a ReadCloser for the content. Returns trace.NotFound error if the // metadata is not found. @@ -609,6 +669,13 @@ func (h *Handler) summaryPath(sessionID session.ID) string { return strings.TrimPrefix(path.Join(h.Path, string(sessionID)+".summary.json"), "/") } +func (h *Handler) replayPath(sessionID session.ID, name string) string { + if h.Path == "" { + return string(sessionID) + ".replay." + name + } + return strings.TrimPrefix(path.Join(h.Path, string(sessionID)+".replay."+name), "/") +} + func (h *Handler) metadataPath(sessionID session.ID) string { if h.Path == "" { return string(sessionID) + ".metadata" diff --git a/lib/events/s3sessions/s3handler_test.go b/lib/events/s3sessions/s3handler_test.go index b7ade6817449..1e554f21f207 100644 --- a/lib/events/s3sessions/s3handler_test.go +++ b/lib/events/s3sessions/s3handler_test.go @@ -19,18 +19,26 @@ package s3sessions import ( + "bytes" "context" + "errors" "fmt" + "io" "net/url" "os" + "strings" "testing" + "testing/iotest" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/gravitational/trace" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/lib/events/test" + "github.com/gravitational/teleport/lib/session" ) // TestStreams tests various streaming upload scenarios @@ -117,6 +125,129 @@ func (m *mockS3Client) PutBucketEncryption(ctx context.Context, params *s3.PutBu args := m.Called(ctx, params, optFns) return args.Get(0).(*s3.PutBucketEncryptionOutput), args.Error(1) } + +// TestReplayObjectNameRejectsPathTraversal asserts that UploadReplayObject +// and StreamReplayObjectRange reject any object name outside the +// well-known manifest/index/blob naming scheme used by ReplaySink -- +// crucially, names containing path traversal segments, which would +// otherwise resolve straight through path.Join in replayPath to read or +// write objects outside the beam's own replay artifact. The check must +// happen before any S3 call, so a zero-value Handler (no client) is enough +// to exercise it. +func TestReplayObjectNameRejectsPathTraversal(t *testing.T) { + h := &Handler{} + ctx := context.Background() + sid := session.ID("beam-1") + + badNames := []string{ + "../x", + "blob.0/../manifest", + "/../../etc/passwd", + "..", + "blob.-1", + "blob", + "manifest.json", + "", + } + for _, name := range badNames { + t.Run(name, func(t *testing.T) { + _, err := h.UploadReplayObject(ctx, sid, name, strings.NewReader("x")) + require.True(t, trace.IsBadParameter(err), "UploadReplayObject(%q): got %v", name, err) + + _, err = h.StreamReplayObjectRange(ctx, sid, name, 0, 0) + require.True(t, trace.IsBadParameter(err), "StreamReplayObjectRange(%q): got %v", name, err) + }) + } +} + +func TestStreamReplayObjectRangeRetries(t *testing.T) { + data := []byte("0123456789") + + tests := []struct { + name string + length int64 + firstBody func() io.ReadCloser + want []byte + wantRanges []string + }{ + { + name: "truncated range", + length: 6, + firstBody: func() io.ReadCloser { + return io.NopCloser(bytes.NewReader(data[2:5])) + }, + want: data[2:8], + wantRanges: []string{"bytes=2-7", "bytes=5-7"}, + }, + { + name: "connection error", + length: 0, + firstBody: func() io.ReadCloser { + return io.NopCloser(io.MultiReader( + bytes.NewReader(data[2:5]), + iotest.ErrReader(errors.New("connection reset")), + )) + }, + want: data[2:], + wantRanges: []string{"bytes=2-9", "bytes=5-9"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &replayRangeS3Client{ + t: t, + data: data, + firstBody: tt.firstBody(), + } + h := &Handler{ + Config: Config{Bucket: "recordings"}, + client: client, + } + + rc, err := h.StreamReplayObjectRange(t.Context(), session.ID("beam-1"), "blob.0", 2, tt.length) + require.NoError(t, err) + defer rc.Close() + + got, err := io.ReadAll(rc) + require.NoError(t, err) + require.Equal(t, tt.want, got) + require.Equal(t, tt.wantRanges, client.ranges) + }) + } +} + +type replayRangeS3Client struct { + s3Client + t *testing.T + data []byte + firstBody io.ReadCloser + ranges []string +} + +func (c *replayRangeS3Client) HeadObject(context.Context, *s3.HeadObjectInput, ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { + return &s3.HeadObjectOutput{ContentLength: aws.Int64(int64(len(c.data)))}, nil +} + +func (c *replayRangeS3Client) GetObject(_ context.Context, input *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + rangeStr := aws.ToString(input.Range) + c.ranges = append(c.ranges, rangeStr) + if c.firstBody != nil { + body := c.firstBody + c.firstBody = nil + return &s3.GetObjectOutput{Body: body}, nil + } + + var start, end int64 + _, err := fmt.Sscanf(rangeStr, "bytes=%d-%d", &start, &end) + require.NoError(c.t, err) + require.GreaterOrEqual(c.t, start, int64(0)) + require.Less(c.t, end, int64(len(c.data))) + return &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader(c.data[start : end+1])), + }, nil +} + func TestEnsureBucket(t *testing.T) { mockClient := &mockS3Client{} var gotRegion s3types.BucketLocationConstraint diff --git a/lib/events/uploader.go b/lib/events/uploader.go index 1dd9064adafd..430f01af252a 100644 --- a/lib/events/uploader.go +++ b/lib/events/uploader.go @@ -21,11 +21,39 @@ package events import ( "context" "io" + "regexp" "time" + "github.com/gravitational/trace" + "github.com/gravitational/teleport/lib/session" ) +// replayObjectNameRE matches the only object names ReplaySink ever writes +// for a beam's replay artifact: the manifest, and the blob/index/command/ +// agent/activity/conversation pages numbered from 0 (see partName/ +// indexPageName/commandsPageName/agentsPageName/activitiesPageName/ +// conversationsPageName/manifestObjectName in e/lib/beams/v1/replay). +var replayObjectNameRE = regexp.MustCompile(`^(manifest|index\.[0-9]+|blob\.[0-9]+|commands\.[0-9]+|agents\.[0-9]+|activities\.[0-9]+|conversations\.[0-9]+)$`) + +// ValidateReplayObjectName returns a trace.BadParameter error unless name is +// one of the well-known beam-replay artifact object names (the manifest, or +// a numbered index/blob/command-index part). +// +// UploadHandler implementations must call this before using a caller- or +// client-influenced name to build a storage path/key: name ultimately +// derives from a PayloadRef.PartName that travels over the wire in a +// FetchPayloadRequest, so without this check a forged name containing path +// traversal segments (e.g. "../other-session.summary.json") could escape +// the beam's own object namespace once joined into a filepath.Join or +// path.Join call. +func ValidateReplayObjectName(name string) error { + if !replayObjectNameRE.MatchString(name) { + return trace.BadParameter("invalid replay object name %q", name) + } + return nil +} + // UploadHandler uploads and downloads session-related files. type UploadHandler interface { // Upload uploads a session recording and returns a URL with uploaded file in @@ -47,6 +75,12 @@ type UploadHandler interface { // StreamSessionSummary streams a session summary and returns a ReadCloser for // the content. Returns a "not found" error if there's no such summary. StreamSessionSummary(ctx context.Context, sessionID session.ID) (io.ReadCloser, error) + // UploadReplayObject writes a named beam-replay artifact object (e.g. + // "manifest", "index.0", "blob.0") for a beam and returns its URL. + UploadReplayObject(ctx context.Context, sessionID session.ID, name string, readCloser io.Reader) (string, error) + // StreamReplayObjectRange returns a ranged reader over a replay object. + // A length <= 0 reads to the end of the object. + StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) // UploadMetadata uploads session metadata and returns a URL with the uploaded // file in case of success. Session metadata is a file with a [recordingmetadatav1.SessionRecordingMetadata] // protobuf message containing info about the session (duration, events, etc), as well as diff --git a/lib/events/uploader_test.go b/lib/events/uploader_test.go new file mode 100644 index 000000000000..964329ba160d --- /dev/null +++ b/lib/events/uploader_test.go @@ -0,0 +1,69 @@ +/* + * Teleport + * Copyright (C) 2026 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 events + +import ( + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +// TestValidateReplayObjectName covers every object name the beam replay sink +// writes, and the traversal attempts the check exists to stop. +func TestValidateReplayObjectName(t *testing.T) { + valid := []string{ + "manifest", + "blob.0", + "blob.12", + "index.0", + "commands.3", + "agents.0", + "activities.0", + "conversations.7", + } + for _, name := range valid { + t.Run(name, func(t *testing.T) { + require.NoError(t, ValidateReplayObjectName(name)) + }) + } + + invalid := []string{ + "", + "manifest.json", + "manifests", + "blob", + "blob.", + "blob.x", + "blob.0.0", + "../other-session.summary.json", + "blob.0/../../etc/passwd", + "/manifest", + "blob.0\n", + "activities.0 ", + "summary", + } + for _, name := range invalid { + t.Run("rejects "+name, func(t *testing.T) { + err := ValidateReplayObjectName(name) + require.Error(t, err) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got %v", err) + }) + } +} diff --git a/lib/integrations/externalauditstorage/error_counter.go b/lib/integrations/externalauditstorage/error_counter.go index a9d941947503..3dbbd6334bf2 100644 --- a/lib/integrations/externalauditstorage/error_counter.go +++ b/lib/integrations/externalauditstorage/error_counter.go @@ -395,6 +395,13 @@ func (c *ErrorCountingSessionHandler) UploadSummary(ctx context.Context, session return res, err } +// UploadReplayObject calls [c.wrapped.UploadReplayObject] and counts the error or success. +func (c *ErrorCountingSessionHandler) UploadReplayObject(ctx context.Context, sessionID session.ID, name string, reader io.Reader) (string, error) { + res, err := c.wrapped.UploadReplayObject(ctx, sessionID, name, reader) + c.uploads.observe(err) + return res, err +} + // UploadMetadata calls [c.wrapped.UploadMetadata] and counts the error or success. func (c *ErrorCountingSessionHandler) UploadMetadata(ctx context.Context, sessionID session.ID, reader io.Reader) (string, error) { res, err := c.wrapped.UploadMetadata(ctx, sessionID, reader) @@ -429,6 +436,16 @@ func (c *ErrorCountingSessionHandler) StreamSessionSummary(ctx context.Context, return newErrorReportReader(rc, c.downloads), nil } +// StreamReplayObjectRange calls [c.wrapped.StreamReplayObjectRange] and counts the error or success. +func (c *ErrorCountingSessionHandler) StreamReplayObjectRange(ctx context.Context, sessionID session.ID, name string, offset, length int64) (io.ReadCloser, error) { + rc, err := c.wrapped.StreamReplayObjectRange(ctx, sessionID, name, offset, length) + if err != nil { + c.downloads.observe(err) + return nil, err + } + return newErrorReportReader(rc, c.downloads), nil +} + // StreamSessionMetadata calls [c.wrapped.StreamSessionMetadata] and counts the error or success. func (c *ErrorCountingSessionHandler) StreamSessionMetadata(ctx context.Context, sessionID session.ID) (io.ReadCloser, error) { rc, err := c.wrapped.StreamSessionMetadata(ctx, sessionID) From 6f1fca119d508cf2716d368a281ceb0d619a5dcf Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Thu, 30 Jul 2026 12:33:32 +0100 Subject: [PATCH 061/347] summarizer: add beam activity verdict fields and session kind (#68703) Extend the summarizer schema for beam activity summaries, which reuse the existing Summary/EnhancedSummary types. Add a BeamInstructionConsistency enum (consistent/partially_consistent/inconsistent) plus instruction_consistency and instruction_assessment fields on EnhancedSummary. This is the machine-readable verdict for whether a beam agent's actions followed the instructions it received from the LLM, modeled on AccessRequestConsistency. Add types.BeamSummarySessionKind ("beam"), the synthetic session kind use to select an InferencePolicy for beam activity summarization. --- .../v1/summarizer_protohybrid.pb.go | 256 +++++++++++----- .../v1/summarizer_protoopaque.pb.go | 286 ++++++++++++------ .../teleport/summarizer/v1/summarizer.proto | 16 + api/types/session_tracker.go | 4 + api/types/summarizer/summarizer.go | 1 + 5 files changed, 386 insertions(+), 177 deletions(-) diff --git a/api/gen/proto/go/teleport/summarizer/v1/summarizer_protohybrid.pb.go b/api/gen/proto/go/teleport/summarizer/v1/summarizer_protohybrid.pb.go index e83f07ca02d3..9c6d0ba46489 100644 --- a/api/gen/proto/go/teleport/summarizer/v1/summarizer_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/summarizer/v1/summarizer_protohybrid.pb.go @@ -501,6 +501,56 @@ func (x NeedsReviewReason) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } +// BeamInstructionConsistency indicates whether a beam agent's actions were +// consistent with the instructions it received from the LLM. Modeled on +// AccessRequestConsistency. +type BeamInstructionConsistency int32 + +const ( + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED BeamInstructionConsistency = 0 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT BeamInstructionConsistency = 1 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT BeamInstructionConsistency = 2 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT BeamInstructionConsistency = 3 +) + +// Enum value maps for BeamInstructionConsistency. +var ( + BeamInstructionConsistency_name = map[int32]string{ + 0: "BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED", + 1: "BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT", + 2: "BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT", + 3: "BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT", + } + BeamInstructionConsistency_value = map[string]int32{ + "BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED": 0, + "BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT": 1, + "BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT": 2, + "BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT": 3, + } +) + +func (x BeamInstructionConsistency) Enum() *BeamInstructionConsistency { + p := new(BeamInstructionConsistency) + *p = x + return p +} + +func (x BeamInstructionConsistency) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BeamInstructionConsistency) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_summarizer_v1_summarizer_proto_enumTypes[6].Descriptor() +} + +func (BeamInstructionConsistency) Type() protoreflect.EnumType { + return &file_teleport_summarizer_v1_summarizer_proto_enumTypes[6] +} + +func (x BeamInstructionConsistency) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + // InferenceModel resource specifies a session summarization inference model // configuration. It tells Teleport how to use a specific provider and model to // summarize sessions. @@ -3787,8 +3837,14 @@ type EnhancedSummary struct { // SessionEvents contains a list of significant events that occurred during the session, such as command executions or // desktop interactions. SessionEvents []*SessionEvent `protobuf:"bytes,16,rep,name=session_events,json=sessionEvents,proto3" json:"session_events,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // InstructionConsistency indicates whether a beam agent's actions followed the + // instructions it received from the LLM. Only set for beam activity summaries. + InstructionConsistency BeamInstructionConsistency `protobuf:"varint,17,opt,name=instruction_consistency,json=instructionConsistency,proto3,enum=teleport.summarizer.v1.BeamInstructionConsistency" json:"instruction_consistency,omitempty"` + // InstructionAssessment is a free-text explanation of how the agent's actions + // related to its LLM instructions. Empty for non-beam summaries. + InstructionAssessment string `protobuf:"bytes,18,opt,name=instruction_assessment,json=instructionAssessment,proto3" json:"instruction_assessment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *EnhancedSummary) Reset() { @@ -3930,6 +3986,20 @@ func (x *EnhancedSummary) GetSessionEvents() []*SessionEvent { return nil } +func (x *EnhancedSummary) GetInstructionConsistency() BeamInstructionConsistency { + if x != nil { + return x.InstructionConsistency + } + return BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED +} + +func (x *EnhancedSummary) GetInstructionAssessment() string { + if x != nil { + return x.InstructionAssessment + } + return "" +} + func (x *EnhancedSummary) SetShortDescription(v string) { x.ShortDescription = v } @@ -3996,6 +4066,14 @@ func (x *EnhancedSummary) SetSessionEvents(v []*SessionEvent) { x.SessionEvents = v } +func (x *EnhancedSummary) SetInstructionConsistency(v BeamInstructionConsistency) { + x.InstructionConsistency = v +} + +func (x *EnhancedSummary) SetInstructionAssessment(v string) { + x.InstructionAssessment = v +} + // Deprecated: Marked as deprecated in teleport/summarizer/v1/summarizer.proto. func (x *EnhancedSummary) HasNeedsFurtherReview() bool { if x == nil { @@ -4054,6 +4132,12 @@ type EnhancedSummary_builder struct { // SessionEvents contains a list of significant events that occurred during the session, such as command executions or // desktop interactions. SessionEvents []*SessionEvent + // InstructionConsistency indicates whether a beam agent's actions followed the + // instructions it received from the LLM. Only set for beam activity summaries. + InstructionConsistency BeamInstructionConsistency + // InstructionAssessment is a free-text explanation of how the agent's actions + // related to its LLM instructions. Empty for non-beam summaries. + InstructionAssessment string } func (b0 EnhancedSummary_builder) Build() *EnhancedSummary { @@ -4076,6 +4160,8 @@ func (b0 EnhancedSummary_builder) Build() *EnhancedSummary { x.AccessRequests = b.AccessRequests x.NeedsFurtherReviewReasons = b.NeedsFurtherReviewReasons x.SessionEvents = b.SessionEvents + x.InstructionConsistency = b.InstructionConsistency + x.InstructionAssessment = b.InstructionAssessment return m0 } @@ -4598,7 +4684,8 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "\bseverity\x18\x03 \x01(\x0e2!.teleport.summarizer.v1.RiskLevelR\bseverity\"L\n" + "\x0fRiskScoreReason\x12\x16\n" + "\x06reason\x18\x01 \x01(\tR\x06reason\x12!\n" + - "\fscore_impact\x18\x02 \x01(\x05R\vscoreImpact\"\xf1\b\n" + + "\fscore_impact\x18\x02 \x01(\x05R\vscoreImpact\"\x95\n" + + "\n" + "\x0fEnhancedSummary\x12+\n" + "\x11short_description\x18\x01 \x01(\tR\x10shortDescription\x121\n" + "\x14detailed_description\x18\x02 \x01(\tR\x13detailedDescription\x12@\n" + @@ -4619,7 +4706,9 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "\x19access_request_assessment\x18\r \x01(\tR\x17accessRequestAssessment\x12V\n" + "\x0faccess_requests\x18\x0e \x03(\v2-.teleport.summarizer.v1.AccessRequestSnapshotR\x0eaccessRequests\x12j\n" + "\x1cneeds_further_review_reasons\x18\x0f \x03(\x0e2).teleport.summarizer.v1.NeedsReviewReasonR\x19needsFurtherReviewReasons\x12K\n" + - "\x0esession_events\x18\x10 \x03(\v2$.teleport.summarizer.v1.SessionEventR\rsessionEventsB\x17\n" + + "\x0esession_events\x18\x10 \x03(\v2$.teleport.summarizer.v1.SessionEventR\rsessionEvents\x12k\n" + + "\x17instruction_consistency\x18\x11 \x01(\x0e22.teleport.summarizer.v1.BeamInstructionConsistencyR\x16instructionConsistency\x125\n" + + "\x16instruction_assessment\x18\x12 \x01(\tR\x15instructionAssessmentB\x17\n" + "\x15_needs_further_review\"\xd3\x01\n" + "\x0eRetrievalModel\x12\x12\n" + "\x04kind\x18\x01 \x01(\tR\x04kind\x12\x19\n" + @@ -4691,99 +4780,106 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "2NEEDS_REVIEW_REASON_FAILED_TO_FETCH_ACCESS_REQUEST\x10\x03\x128\n" + "4NEEDS_REVIEW_REASON_ACCESS_REQUEST_RESOURCE_MISMATCH\x10\x04\x121\n" + "-NEEDS_REVIEW_REASON_OUTPUT_NOT_FULLY_CAPTURED\x10\x05\x12*\n" + - "&NEEDS_REVIEW_REASON_CLASSIFIER_MATCHED\x10\x06BXZVgithub.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1;summarizerv1b\x06proto3" - -var file_teleport_summarizer_v1_summarizer_proto_enumTypes = make([]protoimpl.EnumInfo, 6) + "&NEEDS_REVIEW_REASON_CLASSIFIER_MATCHED\x10\x06*\xdd\x01\n" + + "\x1aBeamInstructionConsistency\x12,\n" + + "(BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED\x10\x00\x12+\n" + + "'BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT\x10\x01\x125\n" + + "1BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT\x10\x02\x12-\n" + + ")BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT\x10\x03BXZVgithub.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1;summarizerv1b\x06proto3" + +var file_teleport_summarizer_v1_summarizer_proto_enumTypes = make([]protoimpl.EnumInfo, 7) var file_teleport_summarizer_v1_summarizer_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_teleport_summarizer_v1_summarizer_proto_goTypes = []any{ - (ClassifierActionMode)(0), // 0: teleport.summarizer.v1.ClassifierActionMode - (SummaryState)(0), // 1: teleport.summarizer.v1.SummaryState - (CommandCategory)(0), // 2: teleport.summarizer.v1.CommandCategory - (RiskLevel)(0), // 3: teleport.summarizer.v1.RiskLevel - (ThreatCategory)(0), // 4: teleport.summarizer.v1.ThreatCategory - (NeedsReviewReason)(0), // 5: teleport.summarizer.v1.NeedsReviewReason - (*InferenceModel)(nil), // 6: teleport.summarizer.v1.InferenceModel - (*InferenceModelSpec)(nil), // 7: teleport.summarizer.v1.InferenceModelSpec - (*OpenAIProvider)(nil), // 8: teleport.summarizer.v1.OpenAIProvider - (*BedrockProvider)(nil), // 9: teleport.summarizer.v1.BedrockProvider - (*InferenceSecret)(nil), // 10: teleport.summarizer.v1.InferenceSecret - (*InferenceSecretSpec)(nil), // 11: teleport.summarizer.v1.InferenceSecretSpec - (*InferencePolicy)(nil), // 12: teleport.summarizer.v1.InferencePolicy - (*InferencePolicySpec)(nil), // 13: teleport.summarizer.v1.InferencePolicySpec - (*Classifier)(nil), // 14: teleport.summarizer.v1.Classifier - (*ClassifierSpec)(nil), // 15: teleport.summarizer.v1.ClassifierSpec - (*ClassifierActions)(nil), // 16: teleport.summarizer.v1.ClassifierActions - (*ClassifierMatch)(nil), // 17: teleport.summarizer.v1.ClassifierMatch - (*Summary)(nil), // 18: teleport.summarizer.v1.Summary - (*SessionEvent)(nil), // 19: teleport.summarizer.v1.SessionEvent - (*CommandEventDetails)(nil), // 20: teleport.summarizer.v1.CommandEventDetails - (*DesktopEventDetails)(nil), // 21: teleport.summarizer.v1.DesktopEventDetails - (*CommandAnalysis)(nil), // 22: teleport.summarizer.v1.CommandAnalysis - (*SecurityRecommendation)(nil), // 23: teleport.summarizer.v1.SecurityRecommendation - (*RiskScoreReason)(nil), // 24: teleport.summarizer.v1.RiskScoreReason - (*EnhancedSummary)(nil), // 25: teleport.summarizer.v1.EnhancedSummary - (*RetrievalModel)(nil), // 26: teleport.summarizer.v1.RetrievalModel - (*RetrievalModelSpec)(nil), // 27: teleport.summarizer.v1.RetrievalModelSpec - (*v1.Metadata)(nil), // 28: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 30: google.protobuf.Struct - (*durationpb.Duration)(nil), // 31: google.protobuf.Duration - (AccessRequestConsistency)(0), // 32: teleport.summarizer.v1.AccessRequestConsistency - (*AccessRequestSnapshot)(nil), // 33: teleport.summarizer.v1.AccessRequestSnapshot + (ClassifierActionMode)(0), // 0: teleport.summarizer.v1.ClassifierActionMode + (SummaryState)(0), // 1: teleport.summarizer.v1.SummaryState + (CommandCategory)(0), // 2: teleport.summarizer.v1.CommandCategory + (RiskLevel)(0), // 3: teleport.summarizer.v1.RiskLevel + (ThreatCategory)(0), // 4: teleport.summarizer.v1.ThreatCategory + (NeedsReviewReason)(0), // 5: teleport.summarizer.v1.NeedsReviewReason + (BeamInstructionConsistency)(0), // 6: teleport.summarizer.v1.BeamInstructionConsistency + (*InferenceModel)(nil), // 7: teleport.summarizer.v1.InferenceModel + (*InferenceModelSpec)(nil), // 8: teleport.summarizer.v1.InferenceModelSpec + (*OpenAIProvider)(nil), // 9: teleport.summarizer.v1.OpenAIProvider + (*BedrockProvider)(nil), // 10: teleport.summarizer.v1.BedrockProvider + (*InferenceSecret)(nil), // 11: teleport.summarizer.v1.InferenceSecret + (*InferenceSecretSpec)(nil), // 12: teleport.summarizer.v1.InferenceSecretSpec + (*InferencePolicy)(nil), // 13: teleport.summarizer.v1.InferencePolicy + (*InferencePolicySpec)(nil), // 14: teleport.summarizer.v1.InferencePolicySpec + (*Classifier)(nil), // 15: teleport.summarizer.v1.Classifier + (*ClassifierSpec)(nil), // 16: teleport.summarizer.v1.ClassifierSpec + (*ClassifierActions)(nil), // 17: teleport.summarizer.v1.ClassifierActions + (*ClassifierMatch)(nil), // 18: teleport.summarizer.v1.ClassifierMatch + (*Summary)(nil), // 19: teleport.summarizer.v1.Summary + (*SessionEvent)(nil), // 20: teleport.summarizer.v1.SessionEvent + (*CommandEventDetails)(nil), // 21: teleport.summarizer.v1.CommandEventDetails + (*DesktopEventDetails)(nil), // 22: teleport.summarizer.v1.DesktopEventDetails + (*CommandAnalysis)(nil), // 23: teleport.summarizer.v1.CommandAnalysis + (*SecurityRecommendation)(nil), // 24: teleport.summarizer.v1.SecurityRecommendation + (*RiskScoreReason)(nil), // 25: teleport.summarizer.v1.RiskScoreReason + (*EnhancedSummary)(nil), // 26: teleport.summarizer.v1.EnhancedSummary + (*RetrievalModel)(nil), // 27: teleport.summarizer.v1.RetrievalModel + (*RetrievalModelSpec)(nil), // 28: teleport.summarizer.v1.RetrievalModelSpec + (*v1.Metadata)(nil), // 29: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 31: google.protobuf.Struct + (*durationpb.Duration)(nil), // 32: google.protobuf.Duration + (AccessRequestConsistency)(0), // 33: teleport.summarizer.v1.AccessRequestConsistency + (*AccessRequestSnapshot)(nil), // 34: teleport.summarizer.v1.AccessRequestSnapshot } var file_teleport_summarizer_v1_summarizer_proto_depIdxs = []int32{ - 28, // 0: teleport.summarizer.v1.InferenceModel.metadata:type_name -> teleport.header.v1.Metadata - 7, // 1: teleport.summarizer.v1.InferenceModel.spec:type_name -> teleport.summarizer.v1.InferenceModelSpec - 8, // 2: teleport.summarizer.v1.InferenceModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider - 9, // 3: teleport.summarizer.v1.InferenceModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider - 28, // 4: teleport.summarizer.v1.InferenceSecret.metadata:type_name -> teleport.header.v1.Metadata - 11, // 5: teleport.summarizer.v1.InferenceSecret.spec:type_name -> teleport.summarizer.v1.InferenceSecretSpec - 28, // 6: teleport.summarizer.v1.InferencePolicy.metadata:type_name -> teleport.header.v1.Metadata - 13, // 7: teleport.summarizer.v1.InferencePolicy.spec:type_name -> teleport.summarizer.v1.InferencePolicySpec - 28, // 8: teleport.summarizer.v1.Classifier.metadata:type_name -> teleport.header.v1.Metadata - 15, // 9: teleport.summarizer.v1.Classifier.spec:type_name -> teleport.summarizer.v1.ClassifierSpec - 16, // 10: teleport.summarizer.v1.ClassifierSpec.actions:type_name -> teleport.summarizer.v1.ClassifierActions + 29, // 0: teleport.summarizer.v1.InferenceModel.metadata:type_name -> teleport.header.v1.Metadata + 8, // 1: teleport.summarizer.v1.InferenceModel.spec:type_name -> teleport.summarizer.v1.InferenceModelSpec + 9, // 2: teleport.summarizer.v1.InferenceModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider + 10, // 3: teleport.summarizer.v1.InferenceModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider + 29, // 4: teleport.summarizer.v1.InferenceSecret.metadata:type_name -> teleport.header.v1.Metadata + 12, // 5: teleport.summarizer.v1.InferenceSecret.spec:type_name -> teleport.summarizer.v1.InferenceSecretSpec + 29, // 6: teleport.summarizer.v1.InferencePolicy.metadata:type_name -> teleport.header.v1.Metadata + 14, // 7: teleport.summarizer.v1.InferencePolicy.spec:type_name -> teleport.summarizer.v1.InferencePolicySpec + 29, // 8: teleport.summarizer.v1.Classifier.metadata:type_name -> teleport.header.v1.Metadata + 16, // 9: teleport.summarizer.v1.Classifier.spec:type_name -> teleport.summarizer.v1.ClassifierSpec + 17, // 10: teleport.summarizer.v1.ClassifierSpec.actions:type_name -> teleport.summarizer.v1.ClassifierActions 0, // 11: teleport.summarizer.v1.ClassifierActions.emit_audit_event:type_name -> teleport.summarizer.v1.ClassifierActionMode 3, // 12: teleport.summarizer.v1.ClassifierActions.risk_level_floor:type_name -> teleport.summarizer.v1.RiskLevel 0, // 13: teleport.summarizer.v1.ClassifierActions.flag_for_review:type_name -> teleport.summarizer.v1.ClassifierActionMode 1, // 14: teleport.summarizer.v1.Summary.state:type_name -> teleport.summarizer.v1.SummaryState - 29, // 15: teleport.summarizer.v1.Summary.inference_started_at:type_name -> google.protobuf.Timestamp - 29, // 16: teleport.summarizer.v1.Summary.inference_finished_at:type_name -> google.protobuf.Timestamp - 30, // 17: teleport.summarizer.v1.Summary.session_end_event:type_name -> google.protobuf.Struct - 25, // 18: teleport.summarizer.v1.Summary.enhanced_summary:type_name -> teleport.summarizer.v1.EnhancedSummary - 17, // 19: teleport.summarizer.v1.Summary.classifier_matches:type_name -> teleport.summarizer.v1.ClassifierMatch - 31, // 20: teleport.summarizer.v1.Summary.processed_duration:type_name -> google.protobuf.Duration - 31, // 21: teleport.summarizer.v1.Summary.total_duration:type_name -> google.protobuf.Duration + 30, // 15: teleport.summarizer.v1.Summary.inference_started_at:type_name -> google.protobuf.Timestamp + 30, // 16: teleport.summarizer.v1.Summary.inference_finished_at:type_name -> google.protobuf.Timestamp + 31, // 17: teleport.summarizer.v1.Summary.session_end_event:type_name -> google.protobuf.Struct + 26, // 18: teleport.summarizer.v1.Summary.enhanced_summary:type_name -> teleport.summarizer.v1.EnhancedSummary + 18, // 19: teleport.summarizer.v1.Summary.classifier_matches:type_name -> teleport.summarizer.v1.ClassifierMatch + 32, // 20: teleport.summarizer.v1.Summary.processed_duration:type_name -> google.protobuf.Duration + 32, // 21: teleport.summarizer.v1.Summary.total_duration:type_name -> google.protobuf.Duration 2, // 22: teleport.summarizer.v1.SessionEvent.category:type_name -> teleport.summarizer.v1.CommandCategory 3, // 23: teleport.summarizer.v1.SessionEvent.risk_level:type_name -> teleport.summarizer.v1.RiskLevel 4, // 24: teleport.summarizer.v1.SessionEvent.threat_category:type_name -> teleport.summarizer.v1.ThreatCategory - 31, // 25: teleport.summarizer.v1.SessionEvent.start_offset:type_name -> google.protobuf.Duration - 31, // 26: teleport.summarizer.v1.SessionEvent.end_offset:type_name -> google.protobuf.Duration - 20, // 27: teleport.summarizer.v1.SessionEvent.command_event_details:type_name -> teleport.summarizer.v1.CommandEventDetails - 21, // 28: teleport.summarizer.v1.SessionEvent.desktop_event_details:type_name -> teleport.summarizer.v1.DesktopEventDetails + 32, // 25: teleport.summarizer.v1.SessionEvent.start_offset:type_name -> google.protobuf.Duration + 32, // 26: teleport.summarizer.v1.SessionEvent.end_offset:type_name -> google.protobuf.Duration + 21, // 27: teleport.summarizer.v1.SessionEvent.command_event_details:type_name -> teleport.summarizer.v1.CommandEventDetails + 22, // 28: teleport.summarizer.v1.SessionEvent.desktop_event_details:type_name -> teleport.summarizer.v1.DesktopEventDetails 2, // 29: teleport.summarizer.v1.CommandAnalysis.category:type_name -> teleport.summarizer.v1.CommandCategory 3, // 30: teleport.summarizer.v1.CommandAnalysis.risk_level:type_name -> teleport.summarizer.v1.RiskLevel 4, // 31: teleport.summarizer.v1.CommandAnalysis.threat_category:type_name -> teleport.summarizer.v1.ThreatCategory - 31, // 32: teleport.summarizer.v1.CommandAnalysis.start_offset:type_name -> google.protobuf.Duration - 31, // 33: teleport.summarizer.v1.CommandAnalysis.end_offset:type_name -> google.protobuf.Duration + 32, // 32: teleport.summarizer.v1.CommandAnalysis.start_offset:type_name -> google.protobuf.Duration + 32, // 33: teleport.summarizer.v1.CommandAnalysis.end_offset:type_name -> google.protobuf.Duration 3, // 34: teleport.summarizer.v1.SecurityRecommendation.severity:type_name -> teleport.summarizer.v1.RiskLevel 3, // 35: teleport.summarizer.v1.EnhancedSummary.risk_level:type_name -> teleport.summarizer.v1.RiskLevel - 22, // 36: teleport.summarizer.v1.EnhancedSummary.commands:type_name -> teleport.summarizer.v1.CommandAnalysis + 23, // 36: teleport.summarizer.v1.EnhancedSummary.commands:type_name -> teleport.summarizer.v1.CommandAnalysis 5, // 37: teleport.summarizer.v1.EnhancedSummary.needs_further_review:type_name -> teleport.summarizer.v1.NeedsReviewReason - 24, // 38: teleport.summarizer.v1.EnhancedSummary.risk_score_reasons:type_name -> teleport.summarizer.v1.RiskScoreReason - 32, // 39: teleport.summarizer.v1.EnhancedSummary.access_request_consistency:type_name -> teleport.summarizer.v1.AccessRequestConsistency - 33, // 40: teleport.summarizer.v1.EnhancedSummary.access_requests:type_name -> teleport.summarizer.v1.AccessRequestSnapshot + 25, // 38: teleport.summarizer.v1.EnhancedSummary.risk_score_reasons:type_name -> teleport.summarizer.v1.RiskScoreReason + 33, // 39: teleport.summarizer.v1.EnhancedSummary.access_request_consistency:type_name -> teleport.summarizer.v1.AccessRequestConsistency + 34, // 40: teleport.summarizer.v1.EnhancedSummary.access_requests:type_name -> teleport.summarizer.v1.AccessRequestSnapshot 5, // 41: teleport.summarizer.v1.EnhancedSummary.needs_further_review_reasons:type_name -> teleport.summarizer.v1.NeedsReviewReason - 19, // 42: teleport.summarizer.v1.EnhancedSummary.session_events:type_name -> teleport.summarizer.v1.SessionEvent - 28, // 43: teleport.summarizer.v1.RetrievalModel.metadata:type_name -> teleport.header.v1.Metadata - 27, // 44: teleport.summarizer.v1.RetrievalModel.spec:type_name -> teleport.summarizer.v1.RetrievalModelSpec - 8, // 45: teleport.summarizer.v1.RetrievalModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider - 9, // 46: teleport.summarizer.v1.RetrievalModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider - 47, // [47:47] is the sub-list for method output_type - 47, // [47:47] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 20, // 42: teleport.summarizer.v1.EnhancedSummary.session_events:type_name -> teleport.summarizer.v1.SessionEvent + 6, // 43: teleport.summarizer.v1.EnhancedSummary.instruction_consistency:type_name -> teleport.summarizer.v1.BeamInstructionConsistency + 29, // 44: teleport.summarizer.v1.RetrievalModel.metadata:type_name -> teleport.header.v1.Metadata + 28, // 45: teleport.summarizer.v1.RetrievalModel.spec:type_name -> teleport.summarizer.v1.RetrievalModelSpec + 9, // 46: teleport.summarizer.v1.RetrievalModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider + 10, // 47: teleport.summarizer.v1.RetrievalModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider + 48, // [48:48] is the sub-list for method output_type + 48, // [48:48] is the sub-list for method input_type + 48, // [48:48] is the sub-list for extension type_name + 48, // [48:48] is the sub-list for extension extendee + 0, // [0:48] is the sub-list for field type_name } func init() { file_teleport_summarizer_v1_summarizer_proto_init() } @@ -4810,7 +4906,7 @@ func file_teleport_summarizer_v1_summarizer_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_summarizer_v1_summarizer_proto_rawDesc), len(file_teleport_summarizer_v1_summarizer_proto_rawDesc)), - NumEnums: 6, + NumEnums: 7, NumMessages: 22, NumExtensions: 0, NumServices: 0, diff --git a/api/gen/proto/go/teleport/summarizer/v1/summarizer_protoopaque.pb.go b/api/gen/proto/go/teleport/summarizer/v1/summarizer_protoopaque.pb.go index 1cf5ed7264e3..5c159b75fe83 100644 --- a/api/gen/proto/go/teleport/summarizer/v1/summarizer_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/summarizer/v1/summarizer_protoopaque.pb.go @@ -501,6 +501,56 @@ func (x NeedsReviewReason) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } +// BeamInstructionConsistency indicates whether a beam agent's actions were +// consistent with the instructions it received from the LLM. Modeled on +// AccessRequestConsistency. +type BeamInstructionConsistency int32 + +const ( + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED BeamInstructionConsistency = 0 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT BeamInstructionConsistency = 1 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT BeamInstructionConsistency = 2 + BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT BeamInstructionConsistency = 3 +) + +// Enum value maps for BeamInstructionConsistency. +var ( + BeamInstructionConsistency_name = map[int32]string{ + 0: "BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED", + 1: "BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT", + 2: "BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT", + 3: "BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT", + } + BeamInstructionConsistency_value = map[string]int32{ + "BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED": 0, + "BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT": 1, + "BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT": 2, + "BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT": 3, + } +) + +func (x BeamInstructionConsistency) Enum() *BeamInstructionConsistency { + p := new(BeamInstructionConsistency) + *p = x + return p +} + +func (x BeamInstructionConsistency) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BeamInstructionConsistency) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_summarizer_v1_summarizer_proto_enumTypes[6].Descriptor() +} + +func (BeamInstructionConsistency) Type() protoreflect.EnumType { + return &file_teleport_summarizer_v1_summarizer_proto_enumTypes[6] +} + +func (x BeamInstructionConsistency) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + // InferenceModel resource specifies a session summarization inference model // configuration. It tells Teleport how to use a specific provider and model to // summarize sessions. @@ -3557,23 +3607,25 @@ func (b0 RiskScoreReason_builder) Build() *RiskScoreReason { // EnhancedSummary represents an enhanced summary of a session recording, // with structured data extracted from the session. type EnhancedSummary struct { - state protoimpl.MessageState `protogen:"opaque.v1"` - xxx_hidden_ShortDescription string `protobuf:"bytes,1,opt,name=short_description,json=shortDescription,proto3"` - xxx_hidden_DetailedDescription string `protobuf:"bytes,2,opt,name=detailed_description,json=detailedDescription,proto3"` - xxx_hidden_RiskLevel RiskLevel `protobuf:"varint,3,opt,name=risk_level,json=riskLevel,proto3,enum=teleport.summarizer.v1.RiskLevel"` - xxx_hidden_SuspiciousActivities []string `protobuf:"bytes,4,rep,name=suspicious_activities,json=suspiciousActivities,proto3"` - xxx_hidden_CompromiseIndicators bool `protobuf:"varint,5,opt,name=compromise_indicators,json=compromiseIndicators,proto3"` - xxx_hidden_NotableCommandIndexes []int32 `protobuf:"varint,6,rep,packed,name=notable_command_indexes,json=notableCommandIndexes,proto3"` - xxx_hidden_Commands *[]*CommandAnalysis `protobuf:"bytes,7,rep,name=commands,proto3"` - xxx_hidden_NeedsFurtherReview NeedsReviewReason `protobuf:"varint,8,opt,name=needs_further_review,json=needsFurtherReview,proto3,enum=teleport.summarizer.v1.NeedsReviewReason,oneof"` - xxx_hidden_RiskScore int32 `protobuf:"varint,9,opt,name=risk_score,json=riskScore,proto3"` - xxx_hidden_ShortTitle string `protobuf:"bytes,10,opt,name=short_title,json=shortTitle,proto3"` - xxx_hidden_RiskScoreReasons *[]*RiskScoreReason `protobuf:"bytes,11,rep,name=risk_score_reasons,json=riskScoreReasons,proto3"` - xxx_hidden_AccessRequestConsistency AccessRequestConsistency `protobuf:"varint,12,opt,name=access_request_consistency,json=accessRequestConsistency,proto3,enum=teleport.summarizer.v1.AccessRequestConsistency"` - xxx_hidden_AccessRequestAssessment string `protobuf:"bytes,13,opt,name=access_request_assessment,json=accessRequestAssessment,proto3"` - xxx_hidden_AccessRequests *[]*AccessRequestSnapshot `protobuf:"bytes,14,rep,name=access_requests,json=accessRequests,proto3"` - xxx_hidden_NeedsFurtherReviewReasons []NeedsReviewReason `protobuf:"varint,15,rep,packed,name=needs_further_review_reasons,json=needsFurtherReviewReasons,proto3,enum=teleport.summarizer.v1.NeedsReviewReason"` - xxx_hidden_SessionEvents *[]*SessionEvent `protobuf:"bytes,16,rep,name=session_events,json=sessionEvents,proto3"` + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_ShortDescription string `protobuf:"bytes,1,opt,name=short_description,json=shortDescription,proto3"` + xxx_hidden_DetailedDescription string `protobuf:"bytes,2,opt,name=detailed_description,json=detailedDescription,proto3"` + xxx_hidden_RiskLevel RiskLevel `protobuf:"varint,3,opt,name=risk_level,json=riskLevel,proto3,enum=teleport.summarizer.v1.RiskLevel"` + xxx_hidden_SuspiciousActivities []string `protobuf:"bytes,4,rep,name=suspicious_activities,json=suspiciousActivities,proto3"` + xxx_hidden_CompromiseIndicators bool `protobuf:"varint,5,opt,name=compromise_indicators,json=compromiseIndicators,proto3"` + xxx_hidden_NotableCommandIndexes []int32 `protobuf:"varint,6,rep,packed,name=notable_command_indexes,json=notableCommandIndexes,proto3"` + xxx_hidden_Commands *[]*CommandAnalysis `protobuf:"bytes,7,rep,name=commands,proto3"` + xxx_hidden_NeedsFurtherReview NeedsReviewReason `protobuf:"varint,8,opt,name=needs_further_review,json=needsFurtherReview,proto3,enum=teleport.summarizer.v1.NeedsReviewReason,oneof"` + xxx_hidden_RiskScore int32 `protobuf:"varint,9,opt,name=risk_score,json=riskScore,proto3"` + xxx_hidden_ShortTitle string `protobuf:"bytes,10,opt,name=short_title,json=shortTitle,proto3"` + xxx_hidden_RiskScoreReasons *[]*RiskScoreReason `protobuf:"bytes,11,rep,name=risk_score_reasons,json=riskScoreReasons,proto3"` + xxx_hidden_AccessRequestConsistency AccessRequestConsistency `protobuf:"varint,12,opt,name=access_request_consistency,json=accessRequestConsistency,proto3,enum=teleport.summarizer.v1.AccessRequestConsistency"` + xxx_hidden_AccessRequestAssessment string `protobuf:"bytes,13,opt,name=access_request_assessment,json=accessRequestAssessment,proto3"` + xxx_hidden_AccessRequests *[]*AccessRequestSnapshot `protobuf:"bytes,14,rep,name=access_requests,json=accessRequests,proto3"` + xxx_hidden_NeedsFurtherReviewReasons []NeedsReviewReason `protobuf:"varint,15,rep,packed,name=needs_further_review_reasons,json=needsFurtherReviewReasons,proto3,enum=teleport.summarizer.v1.NeedsReviewReason"` + xxx_hidden_SessionEvents *[]*SessionEvent `protobuf:"bytes,16,rep,name=session_events,json=sessionEvents,proto3"` + xxx_hidden_InstructionConsistency BeamInstructionConsistency `protobuf:"varint,17,opt,name=instruction_consistency,json=instructionConsistency,proto3,enum=teleport.summarizer.v1.BeamInstructionConsistency"` + xxx_hidden_InstructionAssessment string `protobuf:"bytes,18,opt,name=instruction_assessment,json=instructionAssessment,proto3"` XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_presence [1]uint32 unknownFields protoimpl.UnknownFields @@ -3729,6 +3781,20 @@ func (x *EnhancedSummary) GetSessionEvents() []*SessionEvent { return nil } +func (x *EnhancedSummary) GetInstructionConsistency() BeamInstructionConsistency { + if x != nil { + return x.xxx_hidden_InstructionConsistency + } + return BeamInstructionConsistency_BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED +} + +func (x *EnhancedSummary) GetInstructionAssessment() string { + if x != nil { + return x.xxx_hidden_InstructionAssessment + } + return "" +} + func (x *EnhancedSummary) SetShortDescription(v string) { x.xxx_hidden_ShortDescription = v } @@ -3761,7 +3827,7 @@ func (x *EnhancedSummary) SetCommands(v []*CommandAnalysis) { // Deprecated: Marked as deprecated in teleport/summarizer/v1/summarizer.proto. func (x *EnhancedSummary) SetNeedsFurtherReview(v NeedsReviewReason) { x.xxx_hidden_NeedsFurtherReview = v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 7, 16) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 7, 18) } func (x *EnhancedSummary) SetRiskScore(v int32) { @@ -3796,6 +3862,14 @@ func (x *EnhancedSummary) SetSessionEvents(v []*SessionEvent) { x.xxx_hidden_SessionEvents = &v } +func (x *EnhancedSummary) SetInstructionConsistency(v BeamInstructionConsistency) { + x.xxx_hidden_InstructionConsistency = v +} + +func (x *EnhancedSummary) SetInstructionAssessment(v string) { + x.xxx_hidden_InstructionAssessment = v +} + // Deprecated: Marked as deprecated in teleport/summarizer/v1/summarizer.proto. func (x *EnhancedSummary) HasNeedsFurtherReview() bool { if x == nil { @@ -3855,6 +3929,12 @@ type EnhancedSummary_builder struct { // SessionEvents contains a list of significant events that occurred during the session, such as command executions or // desktop interactions. SessionEvents []*SessionEvent + // InstructionConsistency indicates whether a beam agent's actions followed the + // instructions it received from the LLM. Only set for beam activity summaries. + InstructionConsistency BeamInstructionConsistency + // InstructionAssessment is a free-text explanation of how the agent's actions + // related to its LLM instructions. Empty for non-beam summaries. + InstructionAssessment string } func (b0 EnhancedSummary_builder) Build() *EnhancedSummary { @@ -3869,7 +3949,7 @@ func (b0 EnhancedSummary_builder) Build() *EnhancedSummary { x.xxx_hidden_NotableCommandIndexes = b.NotableCommandIndexes x.xxx_hidden_Commands = &b.Commands if b.NeedsFurtherReview != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 7, 16) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 7, 18) x.xxx_hidden_NeedsFurtherReview = *b.NeedsFurtherReview } x.xxx_hidden_RiskScore = b.RiskScore @@ -3880,6 +3960,8 @@ func (b0 EnhancedSummary_builder) Build() *EnhancedSummary { x.xxx_hidden_AccessRequests = &b.AccessRequests x.xxx_hidden_NeedsFurtherReviewReasons = b.NeedsFurtherReviewReasons x.xxx_hidden_SessionEvents = &b.SessionEvents + x.xxx_hidden_InstructionConsistency = b.InstructionConsistency + x.xxx_hidden_InstructionAssessment = b.InstructionAssessment return m0 } @@ -4381,7 +4463,8 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "\bseverity\x18\x03 \x01(\x0e2!.teleport.summarizer.v1.RiskLevelR\bseverity\"L\n" + "\x0fRiskScoreReason\x12\x16\n" + "\x06reason\x18\x01 \x01(\tR\x06reason\x12!\n" + - "\fscore_impact\x18\x02 \x01(\x05R\vscoreImpact\"\xf1\b\n" + + "\fscore_impact\x18\x02 \x01(\x05R\vscoreImpact\"\x95\n" + + "\n" + "\x0fEnhancedSummary\x12+\n" + "\x11short_description\x18\x01 \x01(\tR\x10shortDescription\x121\n" + "\x14detailed_description\x18\x02 \x01(\tR\x13detailedDescription\x12@\n" + @@ -4402,7 +4485,9 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "\x19access_request_assessment\x18\r \x01(\tR\x17accessRequestAssessment\x12V\n" + "\x0faccess_requests\x18\x0e \x03(\v2-.teleport.summarizer.v1.AccessRequestSnapshotR\x0eaccessRequests\x12j\n" + "\x1cneeds_further_review_reasons\x18\x0f \x03(\x0e2).teleport.summarizer.v1.NeedsReviewReasonR\x19needsFurtherReviewReasons\x12K\n" + - "\x0esession_events\x18\x10 \x03(\v2$.teleport.summarizer.v1.SessionEventR\rsessionEventsB\x17\n" + + "\x0esession_events\x18\x10 \x03(\v2$.teleport.summarizer.v1.SessionEventR\rsessionEvents\x12k\n" + + "\x17instruction_consistency\x18\x11 \x01(\x0e22.teleport.summarizer.v1.BeamInstructionConsistencyR\x16instructionConsistency\x125\n" + + "\x16instruction_assessment\x18\x12 \x01(\tR\x15instructionAssessmentB\x17\n" + "\x15_needs_further_review\"\xd3\x01\n" + "\x0eRetrievalModel\x12\x12\n" + "\x04kind\x18\x01 \x01(\tR\x04kind\x12\x19\n" + @@ -4474,99 +4559,106 @@ const file_teleport_summarizer_v1_summarizer_proto_rawDesc = "" + "2NEEDS_REVIEW_REASON_FAILED_TO_FETCH_ACCESS_REQUEST\x10\x03\x128\n" + "4NEEDS_REVIEW_REASON_ACCESS_REQUEST_RESOURCE_MISMATCH\x10\x04\x121\n" + "-NEEDS_REVIEW_REASON_OUTPUT_NOT_FULLY_CAPTURED\x10\x05\x12*\n" + - "&NEEDS_REVIEW_REASON_CLASSIFIER_MATCHED\x10\x06BXZVgithub.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1;summarizerv1b\x06proto3" - -var file_teleport_summarizer_v1_summarizer_proto_enumTypes = make([]protoimpl.EnumInfo, 6) + "&NEEDS_REVIEW_REASON_CLASSIFIER_MATCHED\x10\x06*\xdd\x01\n" + + "\x1aBeamInstructionConsistency\x12,\n" + + "(BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED\x10\x00\x12+\n" + + "'BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT\x10\x01\x125\n" + + "1BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT\x10\x02\x12-\n" + + ")BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT\x10\x03BXZVgithub.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1;summarizerv1b\x06proto3" + +var file_teleport_summarizer_v1_summarizer_proto_enumTypes = make([]protoimpl.EnumInfo, 7) var file_teleport_summarizer_v1_summarizer_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_teleport_summarizer_v1_summarizer_proto_goTypes = []any{ - (ClassifierActionMode)(0), // 0: teleport.summarizer.v1.ClassifierActionMode - (SummaryState)(0), // 1: teleport.summarizer.v1.SummaryState - (CommandCategory)(0), // 2: teleport.summarizer.v1.CommandCategory - (RiskLevel)(0), // 3: teleport.summarizer.v1.RiskLevel - (ThreatCategory)(0), // 4: teleport.summarizer.v1.ThreatCategory - (NeedsReviewReason)(0), // 5: teleport.summarizer.v1.NeedsReviewReason - (*InferenceModel)(nil), // 6: teleport.summarizer.v1.InferenceModel - (*InferenceModelSpec)(nil), // 7: teleport.summarizer.v1.InferenceModelSpec - (*OpenAIProvider)(nil), // 8: teleport.summarizer.v1.OpenAIProvider - (*BedrockProvider)(nil), // 9: teleport.summarizer.v1.BedrockProvider - (*InferenceSecret)(nil), // 10: teleport.summarizer.v1.InferenceSecret - (*InferenceSecretSpec)(nil), // 11: teleport.summarizer.v1.InferenceSecretSpec - (*InferencePolicy)(nil), // 12: teleport.summarizer.v1.InferencePolicy - (*InferencePolicySpec)(nil), // 13: teleport.summarizer.v1.InferencePolicySpec - (*Classifier)(nil), // 14: teleport.summarizer.v1.Classifier - (*ClassifierSpec)(nil), // 15: teleport.summarizer.v1.ClassifierSpec - (*ClassifierActions)(nil), // 16: teleport.summarizer.v1.ClassifierActions - (*ClassifierMatch)(nil), // 17: teleport.summarizer.v1.ClassifierMatch - (*Summary)(nil), // 18: teleport.summarizer.v1.Summary - (*SessionEvent)(nil), // 19: teleport.summarizer.v1.SessionEvent - (*CommandEventDetails)(nil), // 20: teleport.summarizer.v1.CommandEventDetails - (*DesktopEventDetails)(nil), // 21: teleport.summarizer.v1.DesktopEventDetails - (*CommandAnalysis)(nil), // 22: teleport.summarizer.v1.CommandAnalysis - (*SecurityRecommendation)(nil), // 23: teleport.summarizer.v1.SecurityRecommendation - (*RiskScoreReason)(nil), // 24: teleport.summarizer.v1.RiskScoreReason - (*EnhancedSummary)(nil), // 25: teleport.summarizer.v1.EnhancedSummary - (*RetrievalModel)(nil), // 26: teleport.summarizer.v1.RetrievalModel - (*RetrievalModelSpec)(nil), // 27: teleport.summarizer.v1.RetrievalModelSpec - (*v1.Metadata)(nil), // 28: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 30: google.protobuf.Struct - (*durationpb.Duration)(nil), // 31: google.protobuf.Duration - (AccessRequestConsistency)(0), // 32: teleport.summarizer.v1.AccessRequestConsistency - (*AccessRequestSnapshot)(nil), // 33: teleport.summarizer.v1.AccessRequestSnapshot + (ClassifierActionMode)(0), // 0: teleport.summarizer.v1.ClassifierActionMode + (SummaryState)(0), // 1: teleport.summarizer.v1.SummaryState + (CommandCategory)(0), // 2: teleport.summarizer.v1.CommandCategory + (RiskLevel)(0), // 3: teleport.summarizer.v1.RiskLevel + (ThreatCategory)(0), // 4: teleport.summarizer.v1.ThreatCategory + (NeedsReviewReason)(0), // 5: teleport.summarizer.v1.NeedsReviewReason + (BeamInstructionConsistency)(0), // 6: teleport.summarizer.v1.BeamInstructionConsistency + (*InferenceModel)(nil), // 7: teleport.summarizer.v1.InferenceModel + (*InferenceModelSpec)(nil), // 8: teleport.summarizer.v1.InferenceModelSpec + (*OpenAIProvider)(nil), // 9: teleport.summarizer.v1.OpenAIProvider + (*BedrockProvider)(nil), // 10: teleport.summarizer.v1.BedrockProvider + (*InferenceSecret)(nil), // 11: teleport.summarizer.v1.InferenceSecret + (*InferenceSecretSpec)(nil), // 12: teleport.summarizer.v1.InferenceSecretSpec + (*InferencePolicy)(nil), // 13: teleport.summarizer.v1.InferencePolicy + (*InferencePolicySpec)(nil), // 14: teleport.summarizer.v1.InferencePolicySpec + (*Classifier)(nil), // 15: teleport.summarizer.v1.Classifier + (*ClassifierSpec)(nil), // 16: teleport.summarizer.v1.ClassifierSpec + (*ClassifierActions)(nil), // 17: teleport.summarizer.v1.ClassifierActions + (*ClassifierMatch)(nil), // 18: teleport.summarizer.v1.ClassifierMatch + (*Summary)(nil), // 19: teleport.summarizer.v1.Summary + (*SessionEvent)(nil), // 20: teleport.summarizer.v1.SessionEvent + (*CommandEventDetails)(nil), // 21: teleport.summarizer.v1.CommandEventDetails + (*DesktopEventDetails)(nil), // 22: teleport.summarizer.v1.DesktopEventDetails + (*CommandAnalysis)(nil), // 23: teleport.summarizer.v1.CommandAnalysis + (*SecurityRecommendation)(nil), // 24: teleport.summarizer.v1.SecurityRecommendation + (*RiskScoreReason)(nil), // 25: teleport.summarizer.v1.RiskScoreReason + (*EnhancedSummary)(nil), // 26: teleport.summarizer.v1.EnhancedSummary + (*RetrievalModel)(nil), // 27: teleport.summarizer.v1.RetrievalModel + (*RetrievalModelSpec)(nil), // 28: teleport.summarizer.v1.RetrievalModelSpec + (*v1.Metadata)(nil), // 29: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 31: google.protobuf.Struct + (*durationpb.Duration)(nil), // 32: google.protobuf.Duration + (AccessRequestConsistency)(0), // 33: teleport.summarizer.v1.AccessRequestConsistency + (*AccessRequestSnapshot)(nil), // 34: teleport.summarizer.v1.AccessRequestSnapshot } var file_teleport_summarizer_v1_summarizer_proto_depIdxs = []int32{ - 28, // 0: teleport.summarizer.v1.InferenceModel.metadata:type_name -> teleport.header.v1.Metadata - 7, // 1: teleport.summarizer.v1.InferenceModel.spec:type_name -> teleport.summarizer.v1.InferenceModelSpec - 8, // 2: teleport.summarizer.v1.InferenceModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider - 9, // 3: teleport.summarizer.v1.InferenceModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider - 28, // 4: teleport.summarizer.v1.InferenceSecret.metadata:type_name -> teleport.header.v1.Metadata - 11, // 5: teleport.summarizer.v1.InferenceSecret.spec:type_name -> teleport.summarizer.v1.InferenceSecretSpec - 28, // 6: teleport.summarizer.v1.InferencePolicy.metadata:type_name -> teleport.header.v1.Metadata - 13, // 7: teleport.summarizer.v1.InferencePolicy.spec:type_name -> teleport.summarizer.v1.InferencePolicySpec - 28, // 8: teleport.summarizer.v1.Classifier.metadata:type_name -> teleport.header.v1.Metadata - 15, // 9: teleport.summarizer.v1.Classifier.spec:type_name -> teleport.summarizer.v1.ClassifierSpec - 16, // 10: teleport.summarizer.v1.ClassifierSpec.actions:type_name -> teleport.summarizer.v1.ClassifierActions + 29, // 0: teleport.summarizer.v1.InferenceModel.metadata:type_name -> teleport.header.v1.Metadata + 8, // 1: teleport.summarizer.v1.InferenceModel.spec:type_name -> teleport.summarizer.v1.InferenceModelSpec + 9, // 2: teleport.summarizer.v1.InferenceModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider + 10, // 3: teleport.summarizer.v1.InferenceModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider + 29, // 4: teleport.summarizer.v1.InferenceSecret.metadata:type_name -> teleport.header.v1.Metadata + 12, // 5: teleport.summarizer.v1.InferenceSecret.spec:type_name -> teleport.summarizer.v1.InferenceSecretSpec + 29, // 6: teleport.summarizer.v1.InferencePolicy.metadata:type_name -> teleport.header.v1.Metadata + 14, // 7: teleport.summarizer.v1.InferencePolicy.spec:type_name -> teleport.summarizer.v1.InferencePolicySpec + 29, // 8: teleport.summarizer.v1.Classifier.metadata:type_name -> teleport.header.v1.Metadata + 16, // 9: teleport.summarizer.v1.Classifier.spec:type_name -> teleport.summarizer.v1.ClassifierSpec + 17, // 10: teleport.summarizer.v1.ClassifierSpec.actions:type_name -> teleport.summarizer.v1.ClassifierActions 0, // 11: teleport.summarizer.v1.ClassifierActions.emit_audit_event:type_name -> teleport.summarizer.v1.ClassifierActionMode 3, // 12: teleport.summarizer.v1.ClassifierActions.risk_level_floor:type_name -> teleport.summarizer.v1.RiskLevel 0, // 13: teleport.summarizer.v1.ClassifierActions.flag_for_review:type_name -> teleport.summarizer.v1.ClassifierActionMode 1, // 14: teleport.summarizer.v1.Summary.state:type_name -> teleport.summarizer.v1.SummaryState - 29, // 15: teleport.summarizer.v1.Summary.inference_started_at:type_name -> google.protobuf.Timestamp - 29, // 16: teleport.summarizer.v1.Summary.inference_finished_at:type_name -> google.protobuf.Timestamp - 30, // 17: teleport.summarizer.v1.Summary.session_end_event:type_name -> google.protobuf.Struct - 25, // 18: teleport.summarizer.v1.Summary.enhanced_summary:type_name -> teleport.summarizer.v1.EnhancedSummary - 17, // 19: teleport.summarizer.v1.Summary.classifier_matches:type_name -> teleport.summarizer.v1.ClassifierMatch - 31, // 20: teleport.summarizer.v1.Summary.processed_duration:type_name -> google.protobuf.Duration - 31, // 21: teleport.summarizer.v1.Summary.total_duration:type_name -> google.protobuf.Duration + 30, // 15: teleport.summarizer.v1.Summary.inference_started_at:type_name -> google.protobuf.Timestamp + 30, // 16: teleport.summarizer.v1.Summary.inference_finished_at:type_name -> google.protobuf.Timestamp + 31, // 17: teleport.summarizer.v1.Summary.session_end_event:type_name -> google.protobuf.Struct + 26, // 18: teleport.summarizer.v1.Summary.enhanced_summary:type_name -> teleport.summarizer.v1.EnhancedSummary + 18, // 19: teleport.summarizer.v1.Summary.classifier_matches:type_name -> teleport.summarizer.v1.ClassifierMatch + 32, // 20: teleport.summarizer.v1.Summary.processed_duration:type_name -> google.protobuf.Duration + 32, // 21: teleport.summarizer.v1.Summary.total_duration:type_name -> google.protobuf.Duration 2, // 22: teleport.summarizer.v1.SessionEvent.category:type_name -> teleport.summarizer.v1.CommandCategory 3, // 23: teleport.summarizer.v1.SessionEvent.risk_level:type_name -> teleport.summarizer.v1.RiskLevel 4, // 24: teleport.summarizer.v1.SessionEvent.threat_category:type_name -> teleport.summarizer.v1.ThreatCategory - 31, // 25: teleport.summarizer.v1.SessionEvent.start_offset:type_name -> google.protobuf.Duration - 31, // 26: teleport.summarizer.v1.SessionEvent.end_offset:type_name -> google.protobuf.Duration - 20, // 27: teleport.summarizer.v1.SessionEvent.command_event_details:type_name -> teleport.summarizer.v1.CommandEventDetails - 21, // 28: teleport.summarizer.v1.SessionEvent.desktop_event_details:type_name -> teleport.summarizer.v1.DesktopEventDetails + 32, // 25: teleport.summarizer.v1.SessionEvent.start_offset:type_name -> google.protobuf.Duration + 32, // 26: teleport.summarizer.v1.SessionEvent.end_offset:type_name -> google.protobuf.Duration + 21, // 27: teleport.summarizer.v1.SessionEvent.command_event_details:type_name -> teleport.summarizer.v1.CommandEventDetails + 22, // 28: teleport.summarizer.v1.SessionEvent.desktop_event_details:type_name -> teleport.summarizer.v1.DesktopEventDetails 2, // 29: teleport.summarizer.v1.CommandAnalysis.category:type_name -> teleport.summarizer.v1.CommandCategory 3, // 30: teleport.summarizer.v1.CommandAnalysis.risk_level:type_name -> teleport.summarizer.v1.RiskLevel 4, // 31: teleport.summarizer.v1.CommandAnalysis.threat_category:type_name -> teleport.summarizer.v1.ThreatCategory - 31, // 32: teleport.summarizer.v1.CommandAnalysis.start_offset:type_name -> google.protobuf.Duration - 31, // 33: teleport.summarizer.v1.CommandAnalysis.end_offset:type_name -> google.protobuf.Duration + 32, // 32: teleport.summarizer.v1.CommandAnalysis.start_offset:type_name -> google.protobuf.Duration + 32, // 33: teleport.summarizer.v1.CommandAnalysis.end_offset:type_name -> google.protobuf.Duration 3, // 34: teleport.summarizer.v1.SecurityRecommendation.severity:type_name -> teleport.summarizer.v1.RiskLevel 3, // 35: teleport.summarizer.v1.EnhancedSummary.risk_level:type_name -> teleport.summarizer.v1.RiskLevel - 22, // 36: teleport.summarizer.v1.EnhancedSummary.commands:type_name -> teleport.summarizer.v1.CommandAnalysis + 23, // 36: teleport.summarizer.v1.EnhancedSummary.commands:type_name -> teleport.summarizer.v1.CommandAnalysis 5, // 37: teleport.summarizer.v1.EnhancedSummary.needs_further_review:type_name -> teleport.summarizer.v1.NeedsReviewReason - 24, // 38: teleport.summarizer.v1.EnhancedSummary.risk_score_reasons:type_name -> teleport.summarizer.v1.RiskScoreReason - 32, // 39: teleport.summarizer.v1.EnhancedSummary.access_request_consistency:type_name -> teleport.summarizer.v1.AccessRequestConsistency - 33, // 40: teleport.summarizer.v1.EnhancedSummary.access_requests:type_name -> teleport.summarizer.v1.AccessRequestSnapshot + 25, // 38: teleport.summarizer.v1.EnhancedSummary.risk_score_reasons:type_name -> teleport.summarizer.v1.RiskScoreReason + 33, // 39: teleport.summarizer.v1.EnhancedSummary.access_request_consistency:type_name -> teleport.summarizer.v1.AccessRequestConsistency + 34, // 40: teleport.summarizer.v1.EnhancedSummary.access_requests:type_name -> teleport.summarizer.v1.AccessRequestSnapshot 5, // 41: teleport.summarizer.v1.EnhancedSummary.needs_further_review_reasons:type_name -> teleport.summarizer.v1.NeedsReviewReason - 19, // 42: teleport.summarizer.v1.EnhancedSummary.session_events:type_name -> teleport.summarizer.v1.SessionEvent - 28, // 43: teleport.summarizer.v1.RetrievalModel.metadata:type_name -> teleport.header.v1.Metadata - 27, // 44: teleport.summarizer.v1.RetrievalModel.spec:type_name -> teleport.summarizer.v1.RetrievalModelSpec - 8, // 45: teleport.summarizer.v1.RetrievalModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider - 9, // 46: teleport.summarizer.v1.RetrievalModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider - 47, // [47:47] is the sub-list for method output_type - 47, // [47:47] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 20, // 42: teleport.summarizer.v1.EnhancedSummary.session_events:type_name -> teleport.summarizer.v1.SessionEvent + 6, // 43: teleport.summarizer.v1.EnhancedSummary.instruction_consistency:type_name -> teleport.summarizer.v1.BeamInstructionConsistency + 29, // 44: teleport.summarizer.v1.RetrievalModel.metadata:type_name -> teleport.header.v1.Metadata + 28, // 45: teleport.summarizer.v1.RetrievalModel.spec:type_name -> teleport.summarizer.v1.RetrievalModelSpec + 9, // 46: teleport.summarizer.v1.RetrievalModelSpec.openai:type_name -> teleport.summarizer.v1.OpenAIProvider + 10, // 47: teleport.summarizer.v1.RetrievalModelSpec.bedrock:type_name -> teleport.summarizer.v1.BedrockProvider + 48, // [48:48] is the sub-list for method output_type + 48, // [48:48] is the sub-list for method input_type + 48, // [48:48] is the sub-list for extension type_name + 48, // [48:48] is the sub-list for extension extendee + 0, // [0:48] is the sub-list for field type_name } func init() { file_teleport_summarizer_v1_summarizer_proto_init() } @@ -4593,7 +4685,7 @@ func file_teleport_summarizer_v1_summarizer_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_summarizer_v1_summarizer_proto_rawDesc), len(file_teleport_summarizer_v1_summarizer_proto_rawDesc)), - NumEnums: 6, + NumEnums: 7, NumMessages: 22, NumExtensions: 0, NumServices: 0, diff --git a/api/proto/teleport/summarizer/v1/summarizer.proto b/api/proto/teleport/summarizer/v1/summarizer.proto index db9d75535ba3..4bc97e8ff3b6 100644 --- a/api/proto/teleport/summarizer/v1/summarizer.proto +++ b/api/proto/teleport/summarizer/v1/summarizer.proto @@ -573,6 +573,16 @@ message RiskScoreReason { int32 score_impact = 2; } +// BeamInstructionConsistency indicates whether a beam agent's actions were +// consistent with the instructions it received from the LLM. Modeled on +// AccessRequestConsistency. +enum BeamInstructionConsistency { + BEAM_INSTRUCTION_CONSISTENCY_UNSPECIFIED = 0; + BEAM_INSTRUCTION_CONSISTENCY_CONSISTENT = 1; + BEAM_INSTRUCTION_CONSISTENCY_PARTIALLY_CONSISTENT = 2; + BEAM_INSTRUCTION_CONSISTENCY_INCONSISTENT = 3; +} + // EnhancedSummary represents an enhanced summary of a session recording, // with structured data extracted from the session. message EnhancedSummary { @@ -614,6 +624,12 @@ message EnhancedSummary { // SessionEvents contains a list of significant events that occurred during the session, such as command executions or // desktop interactions. repeated SessionEvent session_events = 16; + // InstructionConsistency indicates whether a beam agent's actions followed the + // instructions it received from the LLM. Only set for beam activity summaries. + BeamInstructionConsistency instruction_consistency = 17; + // InstructionAssessment is a free-text explanation of how the agent's actions + // related to its LLM instructions. Empty for non-beam summaries. + string instruction_assessment = 18; } // RetrievalModel resource specifies the model configuration used to diff --git a/api/types/session_tracker.go b/api/types/session_tracker.go index 9283e2e99e39..6b0fd461bc9d 100644 --- a/api/types/session_tracker.go +++ b/api/types/session_tracker.go @@ -42,6 +42,10 @@ const ( LinuxDesktopSessionKind SessionKind = "linuxdesktop" GitSessionKind SessionKind = "git" UnknownSessionKind SessionKind = "" + + // BeamSummarySessionKind is a synthetic session kind used to select an + // InferencePolicy for beam activity summarization. + BeamSummarySessionKind SessionKind = "beam" ) // SessionParticipantMode is the mode that determines what you can do when you join a session. diff --git a/api/types/summarizer/summarizer.go b/api/types/summarizer/summarizer.go index d7eaed1f9ad3..9a6f017634ec 100644 --- a/api/types/summarizer/summarizer.go +++ b/api/types/summarizer/summarizer.go @@ -215,6 +215,7 @@ func ValidateInferencePolicy(p *summarizerv1.InferencePolicy) error { string(types.KubernetesSessionKind), string(types.DatabaseSessionKind), string(types.WindowsDesktopSessionKind), + string(types.BeamSummarySessionKind), } for _, kind := range kinds { if !slices.Contains(supportedKinds, kind) { From 17232fd49d690157dc371ab42953fa0c8035ee06 Mon Sep 17 00:00:00 2001 From: Tener Date: Thu, 30 Jul 2026 13:53:07 +0200 Subject: [PATCH 062/347] Docs: add GCP database discovery guide (#68947) * add GCP database discovery guide * lint fix * address review feedback * address PR comments --- .../auto-discovery/auto-discovery.mdx | 5 + .../auto-discovery/databases/databases.mdx | 20 +- .../auto-discovery/databases/gcp.mdx | 283 ++++++++++++++++++ .../auto-discovery/reference/labels.mdx | 21 ++ .../enrollment/google-cloud/google-cloud.mdx | 3 + .../google-cloud/mysql-cloudsql.mdx | 2 +- .../google-cloud/postgres-cloudsql.mdx | 2 +- .../database-access/reference/labels.mdx | 5 +- .../cloudsql_enable_iam_auth.mdx | 2 +- .../database-service-troubleshooting.mdx | 9 +- .../includes/discovery/discovery-config.yaml | 1 + .../discovery-service-troubleshooting.mdx | 11 +- 12 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 docs/pages/enroll-resources/auto-discovery/databases/gcp.mdx diff --git a/docs/pages/enroll-resources/auto-discovery/auto-discovery.mdx b/docs/pages/enroll-resources/auto-discovery/auto-discovery.mdx index 9a8fed6a9326..61b4208f5302 100644 --- a/docs/pages/enroll-resources/auto-discovery/auto-discovery.mdx +++ b/docs/pages/enroll-resources/auto-discovery/auto-discovery.mdx @@ -42,6 +42,11 @@ tagLists={ href: "./databases/aws/", icon: "aws", }, + { + name: "Google Cloud", + href: "./databases/gcp/", + icon: "googleCloud", + }, { name: "Azure", href: "../database-access/enrollment/azure/", diff --git a/docs/pages/enroll-resources/auto-discovery/databases/databases.mdx b/docs/pages/enroll-resources/auto-discovery/databases/databases.mdx index c07c13fe797a..4ce817a3cfbb 100644 --- a/docs/pages/enroll-resources/auto-discovery/databases/databases.mdx +++ b/docs/pages/enroll-resources/auto-discovery/databases/databases.mdx @@ -19,6 +19,7 @@ Teleport Database Service. ## Supported clouds - [AWS](aws.mdx): Discovery for AWS databases. +- [GCP](gcp.mdx): Discovery for Google Cloud databases. - [Azure](../../database-access/enrollment/azure/azure.mdx): Discovery for Azure databases. {/* TODO(gavin): Add an Azure discovery guide and permission reference */} @@ -57,15 +58,16 @@ information such as: - *Endpoint Info*: Connection endpoint the database can be reached at -You can override a discovered database's name by using a special tag attached -to the database in your cloud provider: - - ***key***: `TeleportDatabaseName` - - ***value***: desired name - -The Discovery Service will check if the database metadata includes the tag and -use its value as the resource name in Teleport. -The name override will be used verbatim, i.e. additional metadata will not be -appended to it. +You can override a discovered database's name by using provider metadata +attached to the database: + +- **AWS and Azure key**: `TeleportDatabaseName`. +- **Google Cloud key**: `teleport-database-name`. +- **Value**: the desired name. + +The Discovery Service checks the database metadata for the provider-specific key +and uses a valid value as the resource name in Teleport without appending +additional metadata. The Teleport Discovery Service only needs access to APIs that it can poll for diff --git a/docs/pages/enroll-resources/auto-discovery/databases/gcp.mdx b/docs/pages/enroll-resources/auto-discovery/databases/gcp.mdx new file mode 100644 index 000000000000..74250324b010 --- /dev/null +++ b/docs/pages/enroll-resources/auto-discovery/databases/gcp.mdx @@ -0,0 +1,283 @@ +--- +title: GCP Database Discovery +sidebar_label: GCP +description: Configure Teleport database discovery for Google Cloud. +tags: + - how-to + - zero-trust + - infrastructure-identity + - google-cloud +page_type: how-to +--- + +After you complete this guide, Teleport will automatically discover and register +Cloud SQL for PostgreSQL and MySQL instances and proxy connections to them. +Configure Teleport RBAC and database privileges separately. + +For manual enrollment guides, including Cloud Spanner and AlloyDB, see +[Enroll Google Cloud Databases](../../database-access/enrollment/google-cloud/google-cloud.mdx). + +## How it works + +(!docs/pages/includes/discovery/step-description.mdx serviceName="Database" resourceDesc="database" resourceKind="db"!) + +## Prerequisites + +(!docs/pages/includes/edition-prereqs-tabs.mdx!) + +- A Google Cloud project with one or more Cloud SQL instances. +- Permission to create service accounts and grant IAM roles in that project. +- A host to run the Teleport Discovery Service. +- A host, such as a Compute Engine instance, to run the Teleport Database + Service. The host must be able to reach your Cloud SQL instances over their + public IP, private IP (PSA), or Private Service Connect endpoint; the + Discovery Service does not need connectivity to your instances. For the + requirements of each option, such as authorized networks for public IP, see + [Choose how to connect to Cloud SQL](https://cloud.google.com/sql/docs/postgres/connection-options) + in the Google Cloud documentation. +- Both hosts must be able to reach the Teleport Proxy and Google Cloud APIs over + HTTPS. +- A Google Cloud service account for the Discovery Service, + [granted](https://cloud.google.com/iam/docs/service-accounts-create) the + predefined + [Cloud SQL Viewer](https://cloud.google.com/iam/docs/roles-permissions/cloudsql#cloudsql.viewer) + role (`roles/cloudsql.viewer`). +- A Google Cloud service account for the Database Service, + [granted](https://cloud.google.com/iam/docs/service-accounts-create) the + predefined + [Cloud SQL Client](https://cloud.google.com/iam/docs/roles-permissions/cloudsql#cloudsql.client) + role (`roles/cloudsql.client`). +- Each host must be able to authenticate to Google Cloud as its service account + using + [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials). +- (!docs/pages/includes/tctl.mdx!) + +## Step 1/6. Install Teleport + +Install Teleport on the host or hosts that will run the Teleport Discovery +Service and Teleport Database Service. + +(!docs/pages/includes/install-linux.mdx!) + +## Step 2/6. Deploy the Discovery Service + +### Create a Teleport config file + +The following configuration uses a shared-secret join token. If you run the +Discovery Service on a Google Compute Engine VM, you can instead use the +[GCP join method](../../../installation/agents/gcp.mdx) to join without a +shared secret. + +Create a `teleport.yaml` config file on the host that will run the Discovery +Service: + +```yaml +version: v3 +teleport: + join_params: + token_name: "/tmp/token" + method: token + proxy_server: "" +auth_service: + enabled: false +proxy_service: + enabled: false +ssh_service: + enabled: false +discovery_service: + enabled: true + discovery_group: "" +``` + +This enables the `discovery_service` and joins it to your Teleport cluster. + +(!docs/pages/includes/discovery/discovery-group.mdx!) + +### Generate a join token + +(!docs/pages/includes/tctl-token.mdx serviceName="Discovery" tokenType="discovery" tokenFile="/tmp/token" !) + +### Start the Discovery Service + +(!docs/pages/includes/start-teleport.mdx service="the Discovery Service"!) + +## Step 3/6. Configure Cloud SQL discovery + +Create a file named `discovery-cloudsql.yaml` with the same `discovery_group` +you configured above: + +```yaml +kind: discovery_config +version: v1 +metadata: + name: discovery-cloudsql +spec: + discovery_group: "" + gcp: + - types: ["cloudsql"] + # The Google Cloud projects to search. Use ["*"] to search every project + # the Discovery Service service account can access. + project_ids: [""] + # The Google Cloud regions to search. Use ["*"] to search all regions. + # For Cloud SQL, locations must be regions (e.g. "us-central1"), not zones. + locations: [""] + # Optional selector matched against imported user labels and Cloud SQL + # metadata such as engine, region, and endpoint type. Defaults to a + # wildcard that matches every instance. + labels: + "*": "*" +``` + +Create the `discovery_config`: + +```code +$ tctl create -f discovery-cloudsql.yaml +``` + +Discovery runs every five minutes by default, so matching instances can take up +to five minutes to appear as Teleport `db` resources. The Discovery Service +skips instances that are unsupported or unavailable or that expose no usable +connection endpoint. + +## Step 4/6. List registered databases + + +At this stage, `tctl get db` lists discovered database resources. A database +appears in `tsh db ls` or `tctl db ls` only after a Database Service matches it. + + +Verify that the Discovery Service has created `db` resources for the instances +you expect: + +```code +$ tctl get db +``` + +To check for a specific database, run the following command, assigning + to its Teleport resource name: + +```code +$ tctl get db/ +``` + +If you do not see the databases you expect, refer to +[Discovery Service troubleshooting](#discovery-service-troubleshooting). + +
+Discovered database names + +Discovered databases are named `-cloudsql--`, for +example `my-postgres-cloudsql-us-central1-my-project`. Teleport drops any +suffix part the instance name already contains. + +Both the full name and the instance name work with `tsh` and `tctl` commands. +`tsh db ls` and `tctl db ls` show the instance name unless you pass `--verbose`; +if an instance name matches more than one database, use the full name. + +Applying the `teleport-database-name` user label to an instance overrides the +name: Teleport uses the value verbatim, with no suffix, and the instance name no +longer works as an alias for it. The +value must be a valid, unique Teleport database name; Teleport skips instances +with an invalid value. + +For the labels applied to discovered databases, see the +[Google Cloud database labels reference](../reference/labels.mdx#google-cloud). + +
+ +
+Endpoint selection + +When an instance has multiple endpoints, Teleport registers the first available +in this order: public, private (PSA), then PSC. To override this selection, set +the Cloud SQL instance's `teleport-database-endpoint-type` user label to +`public`, `private`, or `psc`. Teleport does not try another endpoint type when +an override is set. + +The Database Service host must be able to reach whichever endpoint Teleport +registers. If that is a public IP, the host's egress IP address must also be in +the instance's authorized networks. + +
+ +## Step 5/6. Deploy the Database Service + +### Create a Teleport config file + +The following configuration uses a shared-secret join token. If you run the +Database Service on a Google Compute Engine VM, you can instead use the +[GCP join method](../../../installation/agents/gcp.mdx) to join without a +shared secret. + +Create a `teleport.yaml` config file on the host that will run the Database +Service: + +```yaml +version: v3 +teleport: + join_params: + token_name: "/tmp/token" + method: token + proxy_server: "" +auth_service: + enabled: false +proxy_service: + enabled: false +ssh_service: + enabled: false +db_service: + enabled: true + resources: + - labels: + "teleport.dev/origin": "cloud" + "teleport.dev/cloud": "GCP" + "teleport.dev/discovery-type": "cloudsql" + "project-id": "" + "region": "" +``` + +The Discovery Service adds the label `teleport.dev/origin: cloud` to every +database it registers. In this example, `db_service.resources` uses that label +and the other selectors to match auto-discovered Cloud SQL databases in + and . For each matching `db` +resource, the Database Service creates a short-lived `db_server` resource that +announces to the Teleport cluster that it is ready to proxy connections for +that `db`. + +(!docs/pages/includes/discovery/db-label-warning.mdx!) + +### Generate a join token + +(!docs/pages/includes/tctl-token.mdx serviceName="Database" tokenType="db" tokenFile="/tmp/token" !) + +### Start the Database Service + +(!docs/pages/includes/start-teleport.mdx service="the Database Service"!) + +## Step 6/6. List database servers + +Confirm that the Database Service has registered servers for the discovered +databases: + +```code +$ tctl db ls teleport.dev/origin=cloud,teleport.dev/cloud=GCP,teleport.dev/discovery-type=cloudsql,project-id=,region= +``` + +If the expected databases are missing, refer to +[Database Service troubleshooting](#database-service-troubleshooting). + +## Troubleshooting + +(!docs/pages/includes/discovery/discovery-service-troubleshooting.mdx resourceKind="database" tctlResource="db" cloud="GCP"!) + +(!docs/pages/includes/discovery/database-service-troubleshooting.mdx!) + +## Next steps + +- Configure user access with the + [Google Cloud enrollment guides](../../database-access/enrollment/google-cloud/google-cloud.mdx). +- Refer to the + [Google Cloud database labels reference](../reference/labels.mdx#google-cloud) + for the full set of imported and generated labels. +- Learn about + [Dynamic Database Registration](../../database-access/guides/dynamic-registration.mdx). diff --git a/docs/pages/enroll-resources/auto-discovery/reference/labels.mdx b/docs/pages/enroll-resources/auto-discovery/reference/labels.mdx index bc55ca4f279e..8036ef5dc9fe 100644 --- a/docs/pages/enroll-resources/auto-discovery/reference/labels.mdx +++ b/docs/pages/enroll-resources/auto-discovery/reference/labels.mdx @@ -127,6 +127,27 @@ See the GCP VM auto-discovery [guide](../../../enroll-resources/auto-discovery/s | `teleport.dev/project-id` | GCP project ID the VM is running in | | `teleport.dev/zone` | GCP zone where the VM is running | +### Databases + +See the Google Cloud database auto-discovery +[guide](../../../enroll-resources/auto-discovery/databases/gcp.mdx). + +| Label | Description | +|-------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| `endpoint-type` | Endpoint Teleport routes to: one of `public`, `private` (PSA), or `psc`. | +| `engine-version` | Cloud SQL database version, e.g. `POSTGRES_16` or `MYSQL_8_4`. | +| `engine` | Database protocol: `postgres` or `mysql`. | +| `instance-type` | Instance role: `primary` or `read-replica`. | +| `project-id` | GCP project ID the instance is running in. | +| `region` | GCP region where the instance is running. | +| `status` | Instance state at discovery time, e.g. `RUNNABLE`. | +| `teleport.dev/cloud` | Always `GCP`. | +| `teleport.dev/discovery-type` | `cloudsql` for Cloud SQL databases. | +| `teleport.dev/origin` | Always `cloud`. | +| `teleport.internal/discovered-name` | Original instance name when Teleport adds a suffix; otherwise absent. | +| `teleport.internal/discovery-config-name` | Dynamic discovery configuration name. Absent for static Discovery Service matchers. | +| `teleport.internal/discovery-group-name` | Discovery group name; absent when unset. | + ### Kubernetes clusters See the GCP GKE auto-discovery [guide](../../../enroll-resources/auto-discovery/kubernetes/gke-discovery.mdx). diff --git a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/google-cloud.mdx b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/google-cloud.mdx index f5b2263f058b..34fd5393bdb6 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/google-cloud.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/google-cloud.mdx @@ -16,3 +16,6 @@ following guides for instructions on enrolling a specific database: - [MySQL on Google Cloud SQL](mysql-cloudsql.mdx) - [Cloud Spanner](spanner.mdx) - [AlloyDB](alloydb.mdx) + +To configure automatic database discovery in Google Cloud, see +[GCP Database Discovery](../../../auto-discovery/databases/gcp.mdx). diff --git a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/mysql-cloudsql.mdx b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/mysql-cloudsql.mdx index 565f934f2a39..43a1800caa11 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/mysql-cloudsql.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/mysql-cloudsql.mdx @@ -91,7 +91,7 @@ Teleport uses [IAM authentication](https://cloud.google.com/sql/docs/mysql/iam-authentication) with Cloud SQL MySQL instances. -(!docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx type="MySQL" !) +(!docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx type="MySQL" flag="cloudsql_iam_authentication" !) ### (Optional) SSL mode "require trusted client certificates" diff --git a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/postgres-cloudsql.mdx b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/postgres-cloudsql.mdx index 426c401e2325..2b812b1e88fd 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/google-cloud/postgres-cloudsql.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/google-cloud/postgres-cloudsql.mdx @@ -59,7 +59,7 @@ Teleport uses service accounts to connect to Cloud SQL databases. Teleport uses [IAM database authentication](https://cloud.google.com/sql/docs/postgres/authentication) with Cloud SQL PostgreSQL instances. -(!docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx type="PostgreSQL"!) +(!docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx type="PostgreSQL" flag="cloudsql.iam_authentication"!) ### Create a database user diff --git a/docs/pages/enroll-resources/database-access/reference/labels.mdx b/docs/pages/enroll-resources/database-access/reference/labels.mdx index 22c79ba1220e..ad4cae49cf3e 100644 --- a/docs/pages/enroll-resources/database-access/reference/labels.mdx +++ b/docs/pages/enroll-resources/database-access/reference/labels.mdx @@ -34,9 +34,10 @@ Amazon RDS instance. The following tags will override Teleport's default behavior if assigned to the original cloud resources: -| Tag name | Description | +| Tag or label name | Description | | - | - | -| `TeleportDatabaseName` | Overrides the name of the discovered database. | +| `TeleportDatabaseName` | (AWS and Azure) Overrides the name of the discovered database. | +| `teleport-database-name` | (Google Cloud) Overrides the name of the discovered database. | | `teleport.dev/database_name` | (AWS only, legacy) Overrides the name of the discovered database. `TeleportDatabaseName` is preferred. | | `teleport.dev/db-admin` | (AWS only) Specifies the name of the admin user for Automatic User Provisioning. | | `teleport.dev/db-admin-default-database` | (AWS only) Overrides the default database the admin user logs into for Automatic User Provisioning. | diff --git a/docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx b/docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx index 42a749808442..cb46f662b141 100644 --- a/docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx +++ b/docs/pages/includes/database-access/cloudsql_enable_iam_auth.mdx @@ -1,5 +1,5 @@ If you're [creating](https://console.cloud.google.com/sql/choose-instance-engine) -a new {{ type }} instance, make sure to add the `cloudsql.iam_authentication` +a new {{ type }} instance, make sure to add the `{{ flag }}` database flag under "Customize your instance / Flags" section: ![Enable IAM Authentication](../../../img/database-access/guides/cloudsql/iam-new-instance@2x.png) diff --git a/docs/pages/includes/discovery/database-service-troubleshooting.mdx b/docs/pages/includes/discovery/database-service-troubleshooting.mdx index bb2f003d6d2a..7353dc2a63c4 100644 --- a/docs/pages/includes/discovery/database-service-troubleshooting.mdx +++ b/docs/pages/includes/discovery/database-service-troubleshooting.mdx @@ -39,8 +39,8 @@ spec: This section assumes you have already provisioned a database user and configured -Teleport RBAC for that database user by following a specific guide in -[Enroll AWS Databases](../../enroll-resources/database-access/enrollment/aws/aws.mdx). +Teleport RBAC for that database user by following the appropriate +[database enrollment guide](../../enroll-resources/database-access/enrollment/enrollment.mdx). If there are connection errors when you try to connect to a database, then @@ -67,6 +67,5 @@ Refer to the [Database Service troubleshooting guide](../../enroll-resources/database-access/troubleshooting.mdx) for more general troubleshooting steps. -Additionally, a guide specific to the type of database in -[Enroll AWS Databases](../../enroll-resources/database-access/enrollment/aws/aws.mdx). -may have more specific troubleshooting information. +The enrollment guide for your database may provide more specific +troubleshooting information. diff --git a/docs/pages/includes/discovery/discovery-config.yaml b/docs/pages/includes/discovery/discovery-config.yaml index 5520c81fc1d7..4e7bace2d8a0 100644 --- a/docs/pages/includes/discovery/discovery-config.yaml +++ b/docs/pages/includes/discovery/discovery-config.yaml @@ -164,6 +164,7 @@ discovery_service: # GCP resource types. Valid options are: # 'gke' - discovers and registers GKE Kubernetes clusters. # 'gce' - discovers and registers GCP compute instances. + # 'cloudsql' - discovers and registers Cloud SQL databases. - types: ["gce"] # IDs of GCP projects to search for resources from. project_ids: ["project-id"] diff --git a/docs/pages/includes/discovery/discovery-service-troubleshooting.mdx b/docs/pages/includes/discovery/discovery-service-troubleshooting.mdx index 483c911243fc..42699f81ecb2 100644 --- a/docs/pages/includes/discovery/discovery-service-troubleshooting.mdx +++ b/docs/pages/includes/discovery/discovery-service-troubleshooting.mdx @@ -8,13 +8,12 @@ expected {{ resourceKind }}s have already been registered with your Teleport cluster. If some {{ resourceKind }}s do not appear in the list, check if the Discovery -Service selector labels match the missing {{ resourceKind }} tags or look into -the Discovery Service logs for permission errors. +Service selector matches the missing {{ resourceKind }}s' provider labels or +tags, or check the Discovery Service logs for permission errors. -Check that the Discovery Service is running with credentials for the correct -{{ cloud }} account. It can discover resources in another {{ cloud }} account, -but it must be configured to assume the correct principal in the other -{{ cloud }} account if that's the case. +Check that the Discovery Service is running with credentials for the intended +{{ cloud }} environment. To discover resources in another account or project, +grant its principal access to the target account or project. Check if there is more than one Discovery Service instance running: From fd7909c066a159fdbe019628b05027a73b3bc40a Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Thu, 30 Jul 2026 13:14:53 +0100 Subject: [PATCH 063/347] httprecorder: size recorded body chunks below the per-event recording cap (#68705) Session recordings trim any event whose serialized size exceeds the 64KiB MaxProtoMessageSizeBytes cap. httprecorder split HTTP body-chunk events at 1MB, on the assumption that the recording layer could be configured to accept them. --- lib/events/stream_test.go | 62 +++++++++++++++++++++++ lib/httplib/httprecorder/recorder.go | 20 ++++++-- lib/httplib/httprecorder/recorder_test.go | 60 ++++++++++++++++++++++ 3 files changed, 137 insertions(+), 5 deletions(-) diff --git a/lib/events/stream_test.go b/lib/events/stream_test.go index 0feb8e0f85ad..bb5d823e561f 100644 --- a/lib/events/stream_test.go +++ b/lib/events/stream_test.go @@ -214,6 +214,68 @@ func TestProtoStreamLargeEvent(t *testing.T) { require.NoError(t, stream.Complete(ctx)) } +// makeBodyChunkEvent builds an AppSessionHTTPResponseBodyChunk event carrying +// dataSize bytes of recognizable, non-repeating-block data so that a +// round-tripped event can be checked for byte-for-byte equality. +func makeBodyChunkEvent(dataSize int) *apievents.AppSessionHTTPResponseBodyChunk { + data := make([]byte, dataSize) + for i := range data { + data[i] = byte(i % 251) + } + return &apievents.AppSessionHTTPResponseBodyChunk{ + Metadata: apievents.Metadata{ + Type: events.AppSessionHTTPResponseBodyChunkEvent, + Code: events.AppSessionHTTPResponseBodyChunkCode, + Index: 0, + Time: time.Now().UTC(), + }, + SessionMetadata: apievents.SessionMetadata{ + SessionID: "1", + }, + RequestId: "req-1", + ChunkIndex: 0, + IsLast: true, + Data: data, + } +} + +// TestProtoStreamDefaultCapTrimsLargeEvent documents that a streamer trims +// large events down to the 64KB cap (constants.MaxProtoMessageSizeBytes). +func TestProtoStreamDefaultCapTrimsLargeEvent(t *testing.T) { + ctx := context.Background() + uploader := eventstest.NewMemoryUploader() + + streamer, err := events.NewProtoStreamer(events.ProtoStreamerConfig{ + Uploader: uploader, + }) + require.NoError(t, err) + + sid := session.ID("default-cap-session") + stream, err := streamer.CreateAuditStream(ctx, sid) + require.NoError(t, err) + + const dataSize = 200 * 1024 // 200KB, > default 64KB cap + event := makeBodyChunkEvent(dataSize) + + require.NoError(t, stream.RecordEvent(ctx, eventstest.PrepareEvent(event))) + require.NoError(t, stream.Complete(ctx)) + + rc, err := uploader.StreamSessionRecording(ctx, sid) + require.NoError(t, err) + defer rc.Close() + + reader := events.NewProtoReader(rc, nil) + defer reader.Close() + + got, err := reader.Read(ctx) + require.NoError(t, err) + + chunk, ok := got.(*apievents.AppSessionHTTPResponseBodyChunk) + require.True(t, ok, "expected *apievents.AppSessionHTTPResponseBodyChunk, got %T", got) + require.Less(t, len(chunk.Data), dataSize, "data should have been trimmed down from the original size") + require.LessOrEqual(t, chunk.Size(), constants.MaxProtoMessageSizeBytes, "trimmed event must fit within the default cap") +} + // TestReadCorruptedRecording tests that the streamer can successfully decode the kind of corrupted // recordings that some older bugged versions of teleport might end up producing when under heavy load/throttling. func TestReadCorruptedRecording(t *testing.T) { diff --git a/lib/httplib/httprecorder/recorder.go b/lib/httplib/httprecorder/recorder.go index 35f24274d8ba..916d0c25b8b6 100644 --- a/lib/httplib/httprecorder/recorder.go +++ b/lib/httplib/httprecorder/recorder.go @@ -32,17 +32,27 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/events" ) const ( // maxChunkSize is the largest body payload stored in one chunk event. - // These events are never sent to the audit log, so the limit is to allow - // consumers to receive the message via streaming without being impacted - // by the gRPC message size limit. The default gRPC limit is 4MB, so - // 1MB is a safe choice. - maxChunkSize = 1024 * 1024 // 1MB + // + // Body chunk events are part of the session recording, and every streamer + // on the path to final storage trims any single event whose serialized + // size exceeds constants.MaxProtoMessageSizeBytes (64KiB): the local + // async file, the async upload streamer (api/client.auditStreamer, which + // re-records each event read back from disk) and the sync SyncStreamer. + // A chunk larger than that cap would have its body data silently + // truncated before it reaches storage. + // + // So a chunk must fit within that cap, including the event's own metadata + // (type/code/cluster name, session and request IDs, indices). That + // metadata is only a few hundred bytes for these events (they carry no + // AppMetadata or headers), so 4KiB of headroom is comfortably enough. + maxChunkSize = constants.MaxProtoMessageSizeBytes - 4*1024 ) // Config contains the inputs New needs to record one HTTP exchange. diff --git a/lib/httplib/httprecorder/recorder_test.go b/lib/httplib/httprecorder/recorder_test.go index 1da2fa8b9104..42f497730a07 100644 --- a/lib/httplib/httprecorder/recorder_test.go +++ b/lib/httplib/httprecorder/recorder_test.go @@ -32,14 +32,74 @@ import ( "testing/synctest" "time" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/constants" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/eventstest" ) +// TestBodyChunkEventFitsRecordingCap asserts that a body chunk carrying a +// full maxChunkSize payload, plus generously oversized event metadata, +// serializes below the per-event session-recording cap +// (constants.MaxProtoMessageSizeBytes, 64KiB). +// +// Body chunk events are part of the session recording, and every streamer on +// the way to final storage -- the local file, the async upload streamer +// (api/client.auditStreamer) and the sync SyncStreamer -- trims any event +// larger than that cap. If a single chunk doesn't fit, its body data is +// silently truncated before it's stored. Keeping maxChunkSize under the cap +// (minus headroom for metadata) is what guarantees chunks survive intact. +func TestBodyChunkEventFitsRecordingCap(t *testing.T) { + for _, tc := range []struct { + name string + event apievents.AuditEvent + }{ + { + name: "response", + event: &apievents.AppSessionHTTPResponseBodyChunk{ + Metadata: apievents.Metadata{ + Type: events.AppSessionHTTPResponseBodyChunkEvent, + Code: events.AppSessionHTTPResponseBodyChunkCode, + ID: uuid.NewString(), + ClusterName: strings.Repeat("c", 255), // pathologically long + Index: 1 << 40, + }, + SessionMetadata: apievents.SessionMetadata{SessionID: uuid.NewString()}, + RequestId: uuid.NewString(), + ChunkIndex: 1 << 40, + IsLast: true, + Data: bytes.Repeat([]byte("x"), maxChunkSize), + }, + }, + { + name: "request", + event: &apievents.AppSessionHTTPRequestBodyChunk{ + Metadata: apievents.Metadata{ + Type: events.AppSessionHTTPRequestBodyChunkEvent, + Code: events.AppSessionHTTPRequestBodyChunkCode, + ID: uuid.NewString(), + ClusterName: strings.Repeat("c", 255), + Index: 1 << 40, + }, + SessionMetadata: apievents.SessionMetadata{SessionID: uuid.NewString()}, + RequestId: uuid.NewString(), + ChunkIndex: 1 << 40, + IsLast: true, + Data: bytes.Repeat([]byte("x"), maxChunkSize), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + require.LessOrEqual(t, tc.event.Size(), constants.MaxProtoMessageSizeBytes, + "a full-size body chunk must fit the per-event recording cap so it survives the audit stream without being trimmed") + }) + } +} + // newCaptureRecorder returns a capturing recorder with a no-op event preparer. func newCaptureRecorder() (*eventstest.MockRecorderEmitter, events.SessionPreparerRecorder) { c := &eventstest.MockRecorderEmitter{} From 7c9bd7f4e4b4a855599c4dc6b81a2819aa13a25f Mon Sep 17 00:00:00 2001 From: Luke Okraszewski Date: Thu, 30 Jul 2026 16:05:18 +0100 Subject: [PATCH 064/347] Bump e (#69184) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 2509af9b5ea5..00680295366d 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 2509af9b5ea5356d2f6172f9dfe5d49a98f633a3 +Subproject commit 00680295366d3acd802959ce432cb98449b4a199 From 13f1bbcd792d02f8b1d9de03a3b4a008f9c506f1 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 30 Jul 2026 11:40:05 -0400 Subject: [PATCH 065/347] Beams: Support automatically deploying Beams into the closest region to the user (#68890) * Create beams in the closest region to the user * revert e * cleanup * update cli docs * typo in label godoc * feedback --- api/client/webclient/webclient.go | 2 + .../teleport/beams/v1/beam_protohybrid.pb.go | 52 ++++++++++++++--- .../teleport/beams/v1/beam_protoopaque.pb.go | 56 +++++++++++++++---- .../beams/v1/beam_service_protohybrid.pb.go | 40 ++++++++++++- .../beams/v1/beam_service_protoopaque.pb.go | 37 +++++++++++- api/proto/teleport/beams/v1/beam.proto | 8 +++ .../teleport/beams/v1/beam_service.proto | 7 +++ api/types/constants.go | 4 ++ docs/pages/beams/beams.mdx | 8 +-- docs/pages/reference/cli/tsh.mdx | 1 + lib/web/proxy_settings.go | 1 + tool/tsh/common/beams.go | 18 ++++-- tool/tsh/common/beams_add.go | 48 ++++++++++++++-- tool/tsh/common/beams_ls.go | 3 +- tool/tsh/common/beams_ls_test.go | 6 +- tool/tsh/common/beams_publish_test.go | 1 + tool/tsh/common/beams_scp_test.go | 1 + tool/tsh/common/beams_unpublish_test.go | 2 + .../TestBeamsLSCommand/JSON_format.golden | 9 ++- .../TestBeamsLSCommand/YAML_format.golden | 3 + .../TestBeamsLSCommand/text_format.golden | 10 ++-- .../text_format_with_all.golden | 10 ++-- 22 files changed, 275 insertions(+), 52 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 71ee5f98f3ab..920cddb53f13 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -526,6 +526,8 @@ type ProxySettings struct { TLSRoutingEnabled bool `json:"tls_routing_enabled"` // ScopesEnabled determines if the scoped rbac feature set is enabled. ScopesEnabled bool `json:"scopes_enabled"` + // GroupID is the proxy group ID for the proxy that handled the request. + GroupID string `json:"group_id"` } // AutoUpdateSettings contains information about the auto update requirements. diff --git a/api/gen/proto/go/teleport/beams/v1/beam_protohybrid.pb.go b/api/gen/proto/go/teleport/beams/v1/beam_protohybrid.pb.go index 36a3e88eeada..2db78376c8bd 100644 --- a/api/gen/proto/go/teleport/beams/v1/beam_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/beams/v1/beam_protohybrid.pb.go @@ -378,9 +378,13 @@ type BeamSpec struct { // Note: we do not use the `metadata.expires` field for this, because Teleport // is responsible for garbage-collecting the compute resources, so we need the // actual record not to be deleted until we're ready. - Expires *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires,proto3" json:"expires,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Expires *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires,proto3" json:"expires,omitempty"` + // RequestedRegion is the Cloud region requested for Beam routing. + // This field may differ from the actual resolved region in BeamStatus.Region, + // in the case that the RequestedRegion was unable to fulfill the request. + RequestedRegion string `protobuf:"bytes,5,opt,name=requested_region,json=requestedRegion,proto3" json:"requested_region,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BeamSpec) Reset() { @@ -436,6 +440,13 @@ func (x *BeamSpec) GetExpires() *timestamppb.Timestamp { return nil } +func (x *BeamSpec) GetRequestedRegion() string { + if x != nil { + return x.RequestedRegion + } + return "" +} + func (x *BeamSpec) SetEgress(v EgressMode) { x.Egress = v } @@ -452,6 +463,10 @@ func (x *BeamSpec) SetExpires(v *timestamppb.Timestamp) { x.Expires = v } +func (x *BeamSpec) SetRequestedRegion(v string) { + x.RequestedRegion = v +} + func (x *BeamSpec) HasPublish() bool { if x == nil { return false @@ -490,6 +505,10 @@ type BeamSpec_builder struct { // is responsible for garbage-collecting the compute resources, so we need the // actual record not to be deleted until we're ready. Expires *timestamppb.Timestamp + // RequestedRegion is the Cloud region requested for Beam routing. + // This field may differ from the actual resolved region in BeamStatus.Region, + // in the case that the RequestedRegion was unable to fulfill the request. + RequestedRegion string } func (b0 BeamSpec_builder) Build() *BeamSpec { @@ -500,6 +519,7 @@ func (b0 BeamSpec_builder) Build() *BeamSpec { x.AllowedDomains = b.AllowedDomains x.Publish = b.Publish x.Expires = b.Expires + x.RequestedRegion = b.RequestedRegion return m0 } @@ -625,6 +645,8 @@ type BeamStatus struct { WorkloadIdentityName string `protobuf:"bytes,11,opt,name=workload_identity_name,json=workloadIdentityName,proto3" json:"workload_identity_name,omitempty"` // ComputeStatus represents the current state of the beam's compute resources. ComputeStatus ComputeStatus `protobuf:"varint,12,opt,name=compute_status,json=computeStatus,proto3,enum=teleport.beams.v1.ComputeStatus" json:"compute_status,omitempty"` + // Region is the Cloud region where the Beam was created. + Region string `protobuf:"bytes,13,opt,name=region,proto3" json:"region,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -738,6 +760,13 @@ func (x *BeamStatus) GetComputeStatus() ComputeStatus { return ComputeStatus_COMPUTE_STATUS_UNSPECIFIED } +func (x *BeamStatus) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + func (x *BeamStatus) SetUser(v string) { x.User = v } @@ -786,6 +815,10 @@ func (x *BeamStatus) SetComputeStatus(v ComputeStatus) { x.ComputeStatus = v } +func (x *BeamStatus) SetRegion(v string) { + x.Region = v +} + type BeamStatus_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -820,6 +853,8 @@ type BeamStatus_builder struct { WorkloadIdentityName string // ComputeStatus represents the current state of the beam's compute resources. ComputeStatus ComputeStatus + // Region is the Cloud region where the Beam was created. + Region string } func (b0 BeamStatus_builder) Build() *BeamStatus { @@ -838,6 +873,7 @@ func (b0 BeamStatus_builder) Build() *BeamStatus { x.DelegationSessionId = b.DelegationSessionId x.WorkloadIdentityName = b.WorkloadIdentityName x.ComputeStatus = b.ComputeStatus + x.Region = b.Region return m0 } @@ -852,15 +888,16 @@ const file_teleport_beams_v1_beam_proto_rawDesc = "" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12/\n" + "\x04spec\x18\x05 \x01(\v2\x1b.teleport.beams.v1.BeamSpecR\x04spec\x125\n" + - "\x06status\x18\x06 \x01(\v2\x1d.teleport.beams.v1.BeamStatusR\x06status\"\xda\x01\n" + + "\x06status\x18\x06 \x01(\v2\x1d.teleport.beams.v1.BeamStatusR\x06status\"\x85\x02\n" + "\bBeamSpec\x125\n" + "\x06egress\x18\x01 \x01(\x0e2\x1d.teleport.beams.v1.EgressModeR\x06egress\x12'\n" + "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\x128\n" + "\apublish\x18\x03 \x01(\v2\x1e.teleport.beams.v1.PublishSpecR\apublish\x124\n" + - "\aexpires\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\"Z\n" + + "\aexpires\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\x12)\n" + + "\x10requested_region\x18\x05 \x01(\tR\x0frequestedRegion\"Z\n" + "\vPublishSpec\x12\x12\n" + "\x04port\x18\x01 \x01(\rR\x04port\x127\n" + - "\bprotocol\x18\x02 \x01(\x0e2\x1b.teleport.beams.v1.ProtocolR\bprotocol\"\xc1\x03\n" + + "\bprotocol\x18\x02 \x01(\x0e2\x1b.teleport.beams.v1.ProtocolR\bprotocol\"\xd9\x03\n" + "\n" + "BeamStatus\x12\x12\n" + "\x04user\x18\x01 \x01(\tR\x04user\x12\x14\n" + @@ -876,7 +913,8 @@ const file_teleport_beams_v1_beam_proto_rawDesc = "" + "\x15delegation_session_id\x18\n" + " \x01(\tR\x13delegationSessionId\x124\n" + "\x16workload_identity_name\x18\v \x01(\tR\x14workloadIdentityName\x12G\n" + - "\x0ecompute_status\x18\f \x01(\x0e2 .teleport.beams.v1.ComputeStatusR\rcomputeStatus*c\n" + + "\x0ecompute_status\x18\f \x01(\x0e2 .teleport.beams.v1.ComputeStatusR\rcomputeStatus\x12\x16\n" + + "\x06region\x18\r \x01(\tR\x06region*c\n" + "\n" + "EgressMode\x12\x1b\n" + "\x17EGRESS_MODE_UNSPECIFIED\x10\x00\x12\x1a\n" + diff --git a/api/gen/proto/go/teleport/beams/v1/beam_protoopaque.pb.go b/api/gen/proto/go/teleport/beams/v1/beam_protoopaque.pb.go index 4d026426a639..06c69e9691cb 100644 --- a/api/gen/proto/go/teleport/beams/v1/beam_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/beams/v1/beam_protoopaque.pb.go @@ -358,13 +358,14 @@ func (b0 Beam_builder) Build() *Beam { // BeamSpec contains the configuration of the beam. type BeamSpec struct { - state protoimpl.MessageState `protogen:"opaque.v1"` - xxx_hidden_Egress EgressMode `protobuf:"varint,1,opt,name=egress,proto3,enum=teleport.beams.v1.EgressMode"` - xxx_hidden_AllowedDomains []string `protobuf:"bytes,2,rep,name=allowed_domains,json=allowedDomains,proto3"` - xxx_hidden_Publish *PublishSpec `protobuf:"bytes,3,opt,name=publish,proto3"` - xxx_hidden_Expires *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires,proto3"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Egress EgressMode `protobuf:"varint,1,opt,name=egress,proto3,enum=teleport.beams.v1.EgressMode"` + xxx_hidden_AllowedDomains []string `protobuf:"bytes,2,rep,name=allowed_domains,json=allowedDomains,proto3"` + xxx_hidden_Publish *PublishSpec `protobuf:"bytes,3,opt,name=publish,proto3"` + xxx_hidden_Expires *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires,proto3"` + xxx_hidden_RequestedRegion string `protobuf:"bytes,5,opt,name=requested_region,json=requestedRegion,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BeamSpec) Reset() { @@ -420,6 +421,13 @@ func (x *BeamSpec) GetExpires() *timestamppb.Timestamp { return nil } +func (x *BeamSpec) GetRequestedRegion() string { + if x != nil { + return x.xxx_hidden_RequestedRegion + } + return "" +} + func (x *BeamSpec) SetEgress(v EgressMode) { x.xxx_hidden_Egress = v } @@ -436,6 +444,10 @@ func (x *BeamSpec) SetExpires(v *timestamppb.Timestamp) { x.xxx_hidden_Expires = v } +func (x *BeamSpec) SetRequestedRegion(v string) { + x.xxx_hidden_RequestedRegion = v +} + func (x *BeamSpec) HasPublish() bool { if x == nil { return false @@ -474,6 +486,10 @@ type BeamSpec_builder struct { // is responsible for garbage-collecting the compute resources, so we need the // actual record not to be deleted until we're ready. Expires *timestamppb.Timestamp + // RequestedRegion is the Cloud region requested for Beam routing. + // This field may differ from the actual resolved region in BeamStatus.Region, + // in the case that the RequestedRegion was unable to fulfill the request. + RequestedRegion string } func (b0 BeamSpec_builder) Build() *BeamSpec { @@ -484,6 +500,7 @@ func (b0 BeamSpec_builder) Build() *BeamSpec { x.xxx_hidden_AllowedDomains = b.AllowedDomains x.xxx_hidden_Publish = b.Publish x.xxx_hidden_Expires = b.Expires + x.xxx_hidden_RequestedRegion = b.RequestedRegion return m0 } @@ -582,6 +599,7 @@ type BeamStatus struct { xxx_hidden_DelegationSessionId string `protobuf:"bytes,10,opt,name=delegation_session_id,json=delegationSessionId,proto3"` xxx_hidden_WorkloadIdentityName string `protobuf:"bytes,11,opt,name=workload_identity_name,json=workloadIdentityName,proto3"` xxx_hidden_ComputeStatus ComputeStatus `protobuf:"varint,12,opt,name=compute_status,json=computeStatus,proto3,enum=teleport.beams.v1.ComputeStatus"` + xxx_hidden_Region string `protobuf:"bytes,13,opt,name=region,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -695,6 +713,13 @@ func (x *BeamStatus) GetComputeStatus() ComputeStatus { return ComputeStatus_COMPUTE_STATUS_UNSPECIFIED } +func (x *BeamStatus) GetRegion() string { + if x != nil { + return x.xxx_hidden_Region + } + return "" +} + func (x *BeamStatus) SetUser(v string) { x.xxx_hidden_User = v } @@ -743,6 +768,10 @@ func (x *BeamStatus) SetComputeStatus(v ComputeStatus) { x.xxx_hidden_ComputeStatus = v } +func (x *BeamStatus) SetRegion(v string) { + x.xxx_hidden_Region = v +} + type BeamStatus_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -777,6 +806,8 @@ type BeamStatus_builder struct { WorkloadIdentityName string // ComputeStatus represents the current state of the beam's compute resources. ComputeStatus ComputeStatus + // Region is the Cloud region where the Beam was created. + Region string } func (b0 BeamStatus_builder) Build() *BeamStatus { @@ -795,6 +826,7 @@ func (b0 BeamStatus_builder) Build() *BeamStatus { x.xxx_hidden_DelegationSessionId = b.DelegationSessionId x.xxx_hidden_WorkloadIdentityName = b.WorkloadIdentityName x.xxx_hidden_ComputeStatus = b.ComputeStatus + x.xxx_hidden_Region = b.Region return m0 } @@ -809,15 +841,16 @@ const file_teleport_beams_v1_beam_proto_rawDesc = "" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12/\n" + "\x04spec\x18\x05 \x01(\v2\x1b.teleport.beams.v1.BeamSpecR\x04spec\x125\n" + - "\x06status\x18\x06 \x01(\v2\x1d.teleport.beams.v1.BeamStatusR\x06status\"\xda\x01\n" + + "\x06status\x18\x06 \x01(\v2\x1d.teleport.beams.v1.BeamStatusR\x06status\"\x85\x02\n" + "\bBeamSpec\x125\n" + "\x06egress\x18\x01 \x01(\x0e2\x1d.teleport.beams.v1.EgressModeR\x06egress\x12'\n" + "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\x128\n" + "\apublish\x18\x03 \x01(\v2\x1e.teleport.beams.v1.PublishSpecR\apublish\x124\n" + - "\aexpires\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\"Z\n" + + "\aexpires\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\x12)\n" + + "\x10requested_region\x18\x05 \x01(\tR\x0frequestedRegion\"Z\n" + "\vPublishSpec\x12\x12\n" + "\x04port\x18\x01 \x01(\rR\x04port\x127\n" + - "\bprotocol\x18\x02 \x01(\x0e2\x1b.teleport.beams.v1.ProtocolR\bprotocol\"\xc1\x03\n" + + "\bprotocol\x18\x02 \x01(\x0e2\x1b.teleport.beams.v1.ProtocolR\bprotocol\"\xd9\x03\n" + "\n" + "BeamStatus\x12\x12\n" + "\x04user\x18\x01 \x01(\tR\x04user\x12\x14\n" + @@ -833,7 +866,8 @@ const file_teleport_beams_v1_beam_proto_rawDesc = "" + "\x15delegation_session_id\x18\n" + " \x01(\tR\x13delegationSessionId\x124\n" + "\x16workload_identity_name\x18\v \x01(\tR\x14workloadIdentityName\x12G\n" + - "\x0ecompute_status\x18\f \x01(\x0e2 .teleport.beams.v1.ComputeStatusR\rcomputeStatus*c\n" + + "\x0ecompute_status\x18\f \x01(\x0e2 .teleport.beams.v1.ComputeStatusR\rcomputeStatus\x12\x16\n" + + "\x06region\x18\r \x01(\tR\x06region*c\n" + "\n" + "EgressMode\x12\x1b\n" + "\x17EGRESS_MODE_UNSPECIFIED\x10\x00\x12\x1a\n" + diff --git a/api/gen/proto/go/teleport/beams/v1/beam_service_protohybrid.pb.go b/api/gen/proto/go/teleport/beams/v1/beam_service_protohybrid.pb.go index c28d2aac012f..b415dd7c6f2b 100644 --- a/api/gen/proto/go/teleport/beams/v1/beam_service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/beams/v1/beam_service_protohybrid.pb.go @@ -147,6 +147,11 @@ type CreateBeamRequest struct { // An allow-list of FQDNs that can be dialed from inside the beam. Can only // be provided when EGRESS_MODE_RESTRICTED is set. AllowedDomains []string `protobuf:"bytes,2,rep,name=allowed_domains,json=allowedDomains,proto3" json:"allowed_domains,omitempty"` + // ProxyRegion is the closest proxy region, discovered from /webapi/find. + ProxyRegion string `protobuf:"bytes,3,opt,name=proxy_region,json=proxyRegion,proto3" json:"proxy_region,omitempty"` + // OverrideRegion is the user-requested Beam routing region. + // This field may override ProxyRegion. + OverrideRegion string `protobuf:"bytes,4,opt,name=override_region,json=overrideRegion,proto3" json:"override_region,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -190,6 +195,20 @@ func (x *CreateBeamRequest) GetAllowedDomains() []string { return nil } +func (x *CreateBeamRequest) GetProxyRegion() string { + if x != nil { + return x.ProxyRegion + } + return "" +} + +func (x *CreateBeamRequest) GetOverrideRegion() string { + if x != nil { + return x.OverrideRegion + } + return "" +} + func (x *CreateBeamRequest) SetEgress(v EgressMode) { x.Egress = v } @@ -198,6 +217,14 @@ func (x *CreateBeamRequest) SetAllowedDomains(v []string) { x.AllowedDomains = v } +func (x *CreateBeamRequest) SetProxyRegion(v string) { + x.ProxyRegion = v +} + +func (x *CreateBeamRequest) SetOverrideRegion(v string) { + x.OverrideRegion = v +} + type CreateBeamRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -206,6 +233,11 @@ type CreateBeamRequest_builder struct { // An allow-list of FQDNs that can be dialed from inside the beam. Can only // be provided when EGRESS_MODE_RESTRICTED is set. AllowedDomains []string + // ProxyRegion is the closest proxy region, discovered from /webapi/find. + ProxyRegion string + // OverrideRegion is the user-requested Beam routing region. + // This field may override ProxyRegion. + OverrideRegion string } func (b0 CreateBeamRequest_builder) Build() *CreateBeamRequest { @@ -214,6 +246,8 @@ func (b0 CreateBeamRequest_builder) Build() *CreateBeamRequest { _, _ = b, x x.Egress = b.Egress x.AllowedDomains = b.AllowedDomains + x.ProxyRegion = b.ProxyRegion + x.OverrideRegion = b.OverrideRegion return m0 } @@ -1028,10 +1062,12 @@ var File_teleport_beams_v1_beam_service_proto protoreflect.FileDescriptor const file_teleport_beams_v1_beam_service_proto_rawDesc = "" + "\n" + - "$teleport/beams/v1/beam_service.proto\x12\x11teleport.beams.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cteleport/beams/v1/beam.proto\"s\n" + + "$teleport/beams/v1/beam_service.proto\x12\x11teleport.beams.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cteleport/beams/v1/beam.proto\"\xbf\x01\n" + "\x11CreateBeamRequest\x125\n" + "\x06egress\x18\x01 \x01(\x0e2\x1d.teleport.beams.v1.EgressModeR\x06egress\x12'\n" + - "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\"A\n" + + "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\x12!\n" + + "\fproxy_region\x18\x03 \x01(\tR\vproxyRegion\x12'\n" + + "\x0foverride_region\x18\x04 \x01(\tR\x0eoverrideRegion\"A\n" + "\x12CreateBeamResponse\x12+\n" + "\x04beam\x18\x01 \x01(\v2\x17.teleport.beams.v1.BeamR\x04beam\"@\n" + "\x11UpdateBeamRequest\x12+\n" + diff --git a/api/gen/proto/go/teleport/beams/v1/beam_service_protoopaque.pb.go b/api/gen/proto/go/teleport/beams/v1/beam_service_protoopaque.pb.go index 30d9d5ec809d..f7e21d6c356c 100644 --- a/api/gen/proto/go/teleport/beams/v1/beam_service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/beams/v1/beam_service_protoopaque.pb.go @@ -144,6 +144,8 @@ type CreateBeamRequest struct { state protoimpl.MessageState `protogen:"opaque.v1"` xxx_hidden_Egress EgressMode `protobuf:"varint,1,opt,name=egress,proto3,enum=teleport.beams.v1.EgressMode"` xxx_hidden_AllowedDomains []string `protobuf:"bytes,2,rep,name=allowed_domains,json=allowedDomains,proto3"` + xxx_hidden_ProxyRegion string `protobuf:"bytes,3,opt,name=proxy_region,json=proxyRegion,proto3"` + xxx_hidden_OverrideRegion string `protobuf:"bytes,4,opt,name=override_region,json=overrideRegion,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -187,6 +189,20 @@ func (x *CreateBeamRequest) GetAllowedDomains() []string { return nil } +func (x *CreateBeamRequest) GetProxyRegion() string { + if x != nil { + return x.xxx_hidden_ProxyRegion + } + return "" +} + +func (x *CreateBeamRequest) GetOverrideRegion() string { + if x != nil { + return x.xxx_hidden_OverrideRegion + } + return "" +} + func (x *CreateBeamRequest) SetEgress(v EgressMode) { x.xxx_hidden_Egress = v } @@ -195,6 +211,14 @@ func (x *CreateBeamRequest) SetAllowedDomains(v []string) { x.xxx_hidden_AllowedDomains = v } +func (x *CreateBeamRequest) SetProxyRegion(v string) { + x.xxx_hidden_ProxyRegion = v +} + +func (x *CreateBeamRequest) SetOverrideRegion(v string) { + x.xxx_hidden_OverrideRegion = v +} + type CreateBeamRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -203,6 +227,11 @@ type CreateBeamRequest_builder struct { // An allow-list of FQDNs that can be dialed from inside the beam. Can only // be provided when EGRESS_MODE_RESTRICTED is set. AllowedDomains []string + // ProxyRegion is the closest proxy region, discovered from /webapi/find. + ProxyRegion string + // OverrideRegion is the user-requested Beam routing region. + // This field may override ProxyRegion. + OverrideRegion string } func (b0 CreateBeamRequest_builder) Build() *CreateBeamRequest { @@ -211,6 +240,8 @@ func (b0 CreateBeamRequest_builder) Build() *CreateBeamRequest { _, _ = b, x x.xxx_hidden_Egress = b.Egress x.xxx_hidden_AllowedDomains = b.AllowedDomains + x.xxx_hidden_ProxyRegion = b.ProxyRegion + x.xxx_hidden_OverrideRegion = b.OverrideRegion return m0 } @@ -997,10 +1028,12 @@ var File_teleport_beams_v1_beam_service_proto protoreflect.FileDescriptor const file_teleport_beams_v1_beam_service_proto_rawDesc = "" + "\n" + - "$teleport/beams/v1/beam_service.proto\x12\x11teleport.beams.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cteleport/beams/v1/beam.proto\"s\n" + + "$teleport/beams/v1/beam_service.proto\x12\x11teleport.beams.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cteleport/beams/v1/beam.proto\"\xbf\x01\n" + "\x11CreateBeamRequest\x125\n" + "\x06egress\x18\x01 \x01(\x0e2\x1d.teleport.beams.v1.EgressModeR\x06egress\x12'\n" + - "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\"A\n" + + "\x0fallowed_domains\x18\x02 \x03(\tR\x0eallowedDomains\x12!\n" + + "\fproxy_region\x18\x03 \x01(\tR\vproxyRegion\x12'\n" + + "\x0foverride_region\x18\x04 \x01(\tR\x0eoverrideRegion\"A\n" + "\x12CreateBeamResponse\x12+\n" + "\x04beam\x18\x01 \x01(\v2\x17.teleport.beams.v1.BeamR\x04beam\"@\n" + "\x11UpdateBeamRequest\x12+\n" + diff --git a/api/proto/teleport/beams/v1/beam.proto b/api/proto/teleport/beams/v1/beam.proto index ec100acd6faf..211b6ac0a5d8 100644 --- a/api/proto/teleport/beams/v1/beam.proto +++ b/api/proto/teleport/beams/v1/beam.proto @@ -61,6 +61,11 @@ message BeamSpec { // is responsible for garbage-collecting the compute resources, so we need the // actual record not to be deleted until we're ready. google.protobuf.Timestamp expires = 4; + + // RequestedRegion is the Cloud region requested for Beam routing. + // This field may differ from the actual resolved region in BeamStatus.Region, + // in the case that the RequestedRegion was unable to fulfill the request. + string requested_region = 5; } // EgressMode configures outgoing network connectivity from the beam. @@ -150,6 +155,9 @@ message BeamStatus { // ComputeStatus represents the current state of the beam's compute resources. ComputeStatus compute_status = 12; + + // Region is the Cloud region where the Beam was created. + string region = 13; } // ComputeStatus represents the current state of the beam's compute resources. diff --git a/api/proto/teleport/beams/v1/beam_service.proto b/api/proto/teleport/beams/v1/beam_service.proto index 97fc53a7906d..f46e7d248a49 100644 --- a/api/proto/teleport/beams/v1/beam_service.proto +++ b/api/proto/teleport/beams/v1/beam_service.proto @@ -59,6 +59,13 @@ message CreateBeamRequest { // An allow-list of FQDNs that can be dialed from inside the beam. Can only // be provided when EGRESS_MODE_RESTRICTED is set. repeated string allowed_domains = 2; + + // ProxyRegion is the closest proxy region, discovered from /webapi/find. + string proxy_region = 3; + + // OverrideRegion is the user-requested Beam routing region. + // This field may override ProxyRegion. + string override_region = 4; } // CreateBeamResponse contains the results of the CreateBeam RPC. diff --git a/api/types/constants.go b/api/types/constants.go index f2c38bc206dd..53f550f1f963 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -1410,6 +1410,10 @@ const ( // BeamAppTypeLabel is the label used to denote the type of app created for // Beams. Valid values: "ingress" and "llm". BeamAppTypeLabel = BeamsInternalLabelPrefix + "app-type" + + // BeamRegionLabel is the label used to track the resolved routing region + // for a Beam. + BeamRegionLabel = BeamsInternalLabelPrefix + "region" ) const ( diff --git a/docs/pages/beams/beams.mdx b/docs/pages/beams/beams.mdx index de18033a19d9..1889f60622b1 100644 --- a/docs/pages/beams/beams.mdx +++ b/docs/pages/beams/beams.mdx @@ -152,10 +152,10 @@ View your active beams: ```code $ tsh beams ls -Name URL Expiry -------------- ---------------------------------------- -------------------- -warm-orbit https://warm-orbit.example.teleport.sh/ 2027-02-05 22:04 UTC -nimble-signal - 2027-02-06 12:15 UTC +ID URL Region Expires +------------- ---------------------------------------- ----------- -------------------- +warm-orbit https://warm-orbit.example.teleport.sh/ us-east-1 2027-02-05 22:04 UTC +nimble-signal - us-west-2 2027-02-06 12:15 UTC ``` ### Running commands diff --git a/docs/pages/reference/cli/tsh.mdx b/docs/pages/reference/cli/tsh.mdx index 43e4dc6016a8..b497e5887d10 100644 --- a/docs/pages/reference/cli/tsh.mdx +++ b/docs/pages/reference/cli/tsh.mdx @@ -277,6 +277,7 @@ Flags: |---|---|---| |`-f`, `--format`|`text` (one of: `text`, `json`, `yaml`)|Format output (text, json, yaml).| |`--[no-]console`|`true`|Connect to the beam via SSH after creation.| +|`--region`|*no default*|Region where the beam should live. If not specified, the closest region is selected.| ## tsh beams exec diff --git a/lib/web/proxy_settings.go b/lib/web/proxy_settings.go index ebc8b63d8f6e..7b9faea83f4d 100644 --- a/lib/web/proxy_settings.go +++ b/lib/web/proxy_settings.go @@ -78,6 +78,7 @@ func (p *ProxySettings) buildProxySettings(proxyListenerMode types.ProxyListener DialTimeout: sshDialTimeout, }, ScopesEnabled: p.ServiceConfig.ScopesFeatures.Enabled, + GroupID: p.ServiceConfig.Proxy.ProxyGroupID, } p.setProxyPublicAddressesSettings(&proxySettings) diff --git a/tool/tsh/common/beams.go b/tool/tsh/common/beams.go index 1810f3ddecf1..79ddb2054d75 100644 --- a/tool/tsh/common/beams.go +++ b/tool/tsh/common/beams.go @@ -60,11 +60,13 @@ func newBeamsCommands(app *kingpin.Application) beamsCommands { func formatBeam(beam *beamsv1.Beam, proxyAddr string) formattedBeam { return formattedBeam{ - ID: beam.GetStatus().GetAlias(), - UUID: beam.GetMetadata().GetName(), - Owner: beam.GetStatus().GetUser(), - Expires: beam.GetSpec().GetExpires().AsTime(), - URL: beamPublishURL(beam, proxyAddr), + ID: beam.GetStatus().GetAlias(), + UUID: beam.GetMetadata().GetName(), + Owner: beam.GetStatus().GetUser(), + Expires: beam.GetSpec().GetExpires().AsTime(), + URL: beamPublishURL(beam, proxyAddr), + RequestedRegion: beam.GetSpec().GetRequestedRegion(), + Region: beam.GetStatus().GetRegion(), } } @@ -123,6 +125,12 @@ type formattedBeam struct { // this address can only be dialed via VNet, otherwise you'll need to start // a local proxy. URL string `json:"url,omitempty"` + + // RequestedRegion is the region the client requested the Beam to be created in. + RequestedRegion string `json:"requested_region,omitempty"` + + // Region is the region the Beam was provisioned in. + Region string `json:"region,omitempty"` } // getBeam reads a beam by UUID or human-friendly name depending on the format diff --git a/tool/tsh/common/beams_add.go b/tool/tsh/common/beams_add.go index 97364d4ec066..bedd2594a725 100644 --- a/tool/tsh/common/beams_add.go +++ b/tool/tsh/common/beams_add.go @@ -19,6 +19,7 @@ package common import ( + "context" "fmt" "io" "strings" @@ -28,6 +29,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client/webclient" beamsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/beams/v1" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/defaults" @@ -40,6 +42,7 @@ type beamsAddCommand struct { *kingpin.CmdClause console bool format string + region string isTerminalOverwrite func(io.Writer) bool } @@ -48,6 +51,7 @@ func newBeamsAddCommand(parent *kingpin.CmdClause) *beamsAddCommand { CmdClause: parent.Command("add", "Start a new beam, and optionally connect to it via SSH."), } cmd.Flag("console", "Connect to the beam via SSH after creation.").Default("true").BoolVar(&cmd.console) + cmd.Flag("region", "Region where the beam should live. If not specified, the closest region is selected.").StringVar(&cmd.region) cmd.Flag("format", defaults.FormatFlagDescription(defaults.DefaultFormats...)). Short('f'). Default(teleport.Text). @@ -63,6 +67,10 @@ func (c *beamsAddCommand) run(cf *CLIConf) error { if err != nil { return trace.Wrap(err) } + proxyRegion, err := c.discoverProxyRegion(ctx, tc) + if err != nil { + return trace.Wrap(err) + } var beam *beamsv1.Beam err = client.RetryWithRelogin(ctx, tc, func() error { @@ -88,7 +96,9 @@ func (c *beamsAddCommand) run(cf *CLIConf) error { rsp, err := rootClient. BeamServiceClient(). CreateBeam(ctx, beamsv1.CreateBeamRequest_builder{ - Egress: beamsv1.EgressMode_EGRESS_MODE_UNRESTRICTED, + Egress: beamsv1.EgressMode_EGRESS_MODE_UNRESTRICTED, + ProxyRegion: proxyRegion, + OverrideRegion: c.region, }.Build()) if err != nil { return trace.Wrap(err) @@ -111,11 +121,7 @@ func (c *beamsAddCommand) run(cf *CLIConf) error { case teleport.YAML: return trace.Wrap(common.PrintYAML(cf.Stdout(), formatBeam(beam, proxyAddr))) default: - if _, err := fmt.Fprintf( - cf.Stdout(), - "Beam %q created.\n", - beam.GetStatus().GetAlias(), - ); err != nil { + if err := c.printCreatedMessage(cf.Stdout(), beam, c.region); err != nil { return trace.Wrap(err) } @@ -131,6 +137,36 @@ func (c *beamsAddCommand) run(cf *CLIConf) error { return nil } +func (c *beamsAddCommand) discoverProxyRegion(ctx context.Context, tc *client.TeleportClient) (string, error) { + resp, err := webclient.Find(&webclient.Config{ + Context: ctx, + ProxyAddr: tc.WebProxyAddr, + Insecure: tc.InsecureSkipVerify, + ExtraHeaders: tc.ExtraProxyHeaders, + }) + if err != nil { + return "", trace.Wrap(err, "discovering proxy region") + } + return resp.Proxy.GroupID, nil +} + +func (c *beamsAddCommand) printCreatedMessage(w io.Writer, beam *beamsv1.Beam, overrideRegion string) error { + region := beam.GetStatus().GetRegion() + alias := beam.GetStatus().GetAlias() + + switch { + case overrideRegion != "" && region != "" && overrideRegion != region: + _, err := fmt.Fprintf(w, "Region %s not supported. Beam %q created in %s.\n", overrideRegion, alias, region) + return trace.Wrap(err) + case region != "": + _, err := fmt.Fprintf(w, "Beam %q created in %s.\n", alias, region) + return trace.Wrap(err) + default: + _, err := fmt.Fprintf(w, "Beam %q created.\n", alias) + return trace.Wrap(err) + } +} + func (c *beamsAddCommand) printReconnectMessage(w io.Writer, alias string) error { gray := lipgloss.NewStyle().Foreground(lipgloss.Color("8")) _, err := fmt.Fprintln(w, gray.Render(fmt.Sprintf("\nTo reconnect to this beam, run:\n tsh beams ssh %s", alias))) diff --git a/tool/tsh/common/beams_ls.go b/tool/tsh/common/beams_ls.go index a1d42d239a8e..9c25bc6c41a6 100644 --- a/tool/tsh/common/beams_ls.go +++ b/tool/tsh/common/beams_ls.go @@ -158,7 +158,7 @@ func (c *beamsLSCommand) print(cf *CLIConf, beams []*beamsv1.Beam, proxyAddr str } func (c *beamsLSCommand) printTable(w io.Writer, beams []formattedBeam) error { - headings := []string{"ID", "URL", "Expires"} + headings := []string{"ID", "URL", "Region", "Expires"} // We only show the owner column when the user passed the `--all` flag, // otherwise there's no point as we're just showing them their own beams. @@ -171,6 +171,7 @@ func (c *beamsLSCommand) printTable(w io.Writer, beams []formattedBeam) error { row := []string{ beam.ID, beam.URL, + beam.Region, c.humanizeTimeFn(beam.Expires), } if c.all { diff --git a/tool/tsh/common/beams_ls_test.go b/tool/tsh/common/beams_ls_test.go index a905b03ff01a..87d57a498f47 100644 --- a/tool/tsh/common/beams_ls_test.go +++ b/tool/tsh/common/beams_ls_test.go @@ -44,6 +44,7 @@ func TestBeamsLSCommand(t *testing.T) { "11111111-1111-1111-1111-111111111111", "alice", "alpha-app", + "us-east-1", time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC), beamsv1.PublishSpec_builder{Protocol: beamsv1.Protocol_PROTOCOL_HTTP, Port: 8080}.Build(), ), @@ -52,6 +53,7 @@ func TestBeamsLSCommand(t *testing.T) { "22222222-2222-2222-2222-222222222222", "alice", "", + "us-west-2", time.Date(2026, time.January, 2, 4, 5, 6, 0, time.UTC), nil, ), @@ -60,6 +62,7 @@ func TestBeamsLSCommand(t *testing.T) { "33333333-3333-3333-3333-333333333333", "bob", "charlie-app", + "eu-central-1", time.Date(2026, time.January, 2, 5, 6, 7, 0, time.UTC), beamsv1.PublishSpec_builder{Protocol: beamsv1.Protocol_PROTOCOL_TCP, Port: 5432}.Build(), ), @@ -136,7 +139,7 @@ func TestBeamsLSCommand(t *testing.T) { } } -func makeTestBeam(alias, name, owner, appName string, expires time.Time, publish *beamsv1.PublishSpec) *beamsv1.Beam { +func makeTestBeam(alias, name, owner, appName, region string, expires time.Time, publish *beamsv1.PublishSpec) *beamsv1.Beam { return beamsv1.Beam_builder{ Metadata: headerv1.Metadata_builder{ Name: name, @@ -149,6 +152,7 @@ func makeTestBeam(alias, name, owner, appName string, expires time.Time, publish Alias: alias, User: owner, AppName: appName, + Region: region, }.Build(), }.Build() } diff --git a/tool/tsh/common/beams_publish_test.go b/tool/tsh/common/beams_publish_test.go index 014a3590ef49..4d3f4576c033 100644 --- a/tool/tsh/common/beams_publish_test.go +++ b/tool/tsh/common/beams_publish_test.go @@ -97,6 +97,7 @@ func TestBeamsPublishCommand(t *testing.T) { tt.beamName, "alice", tt.appName, + "", tt.expires, nil, ) diff --git a/tool/tsh/common/beams_scp_test.go b/tool/tsh/common/beams_scp_test.go index 7206681a9177..7f1cd8865050 100644 --- a/tool/tsh/common/beams_scp_test.go +++ b/tool/tsh/common/beams_scp_test.go @@ -109,6 +109,7 @@ func TestBeamsSCPCommandRun(t *testing.T) { "11111111-1111-1111-1111-111111111111", "alice", name+"-app", + "", time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC), nil, ) diff --git a/tool/tsh/common/beams_unpublish_test.go b/tool/tsh/common/beams_unpublish_test.go index 3cf3e0f88318..12d9178c3607 100644 --- a/tool/tsh/common/beams_unpublish_test.go +++ b/tool/tsh/common/beams_unpublish_test.go @@ -46,6 +46,7 @@ func TestBeamsUnpublishCommand(t *testing.T) { "11111111-1111-1111-1111-111111111111", "alice", "alpha-app", + "", time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC), beamsv1.PublishSpec_builder{Protocol: beamsv1.Protocol_PROTOCOL_HTTP, Port: 8080}.Build(), ), @@ -59,6 +60,7 @@ func TestBeamsUnpublishCommand(t *testing.T) { "11111111-1111-1111-1111-111111111111", "alice", "alpha-app", + "", time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC), nil, ), diff --git a/tool/tsh/common/testdata/TestBeamsLSCommand/JSON_format.golden b/tool/tsh/common/testdata/TestBeamsLSCommand/JSON_format.golden index 79bcbd11cc9d..a913d17fec2e 100644 --- a/tool/tsh/common/testdata/TestBeamsLSCommand/JSON_format.golden +++ b/tool/tsh/common/testdata/TestBeamsLSCommand/JSON_format.golden @@ -4,19 +4,22 @@ "uuid": "11111111-1111-1111-1111-111111111111", "owner": "alice", "expires": "2026-01-02T03:04:05Z", - "url": "https://alpha-app.proxy.example.com" + "url": "https://alpha-app.proxy.example.com", + "region": "us-east-1" }, { "id": "bravo", "uuid": "22222222-2222-2222-2222-222222222222", "owner": "alice", - "expires": "2026-01-02T04:05:06Z" + "expires": "2026-01-02T04:05:06Z", + "region": "us-west-2" }, { "id": "charlie", "uuid": "33333333-3333-3333-3333-333333333333", "owner": "bob", "expires": "2026-01-02T05:06:07Z", - "url": "tcp://charlie-app.proxy.example.com:5432" + "url": "tcp://charlie-app.proxy.example.com:5432", + "region": "eu-central-1" } ] diff --git a/tool/tsh/common/testdata/TestBeamsLSCommand/YAML_format.golden b/tool/tsh/common/testdata/TestBeamsLSCommand/YAML_format.golden index 7b1ea87f04b1..7cf2fac32fe9 100644 --- a/tool/tsh/common/testdata/TestBeamsLSCommand/YAML_format.golden +++ b/tool/tsh/common/testdata/TestBeamsLSCommand/YAML_format.golden @@ -1,15 +1,18 @@ - expires: "2026-01-02T03:04:05Z" id: alpha owner: alice + region: us-east-1 url: https://alpha-app.proxy.example.com uuid: 11111111-1111-1111-1111-111111111111 - expires: "2026-01-02T04:05:06Z" id: bravo owner: alice + region: us-west-2 uuid: 22222222-2222-2222-2222-222222222222 - expires: "2026-01-02T05:06:07Z" id: charlie owner: bob + region: eu-central-1 url: tcp://charlie-app.proxy.example.com:5432 uuid: 33333333-3333-3333-3333-333333333333 diff --git a/tool/tsh/common/testdata/TestBeamsLSCommand/text_format.golden b/tool/tsh/common/testdata/TestBeamsLSCommand/text_format.golden index adcc7e4c14bf..64db8de04c6b 100644 --- a/tool/tsh/common/testdata/TestBeamsLSCommand/text_format.golden +++ b/tool/tsh/common/testdata/TestBeamsLSCommand/text_format.golden @@ -1,5 +1,5 @@ -ID URL Expires -------- ---------------------------------------- ---------------- -alpha https://alpha-app.proxy.example.com 3 hours from now -bravo 4 hours from now -charlie tcp://charlie-app.proxy.example.com:5432 5 hours from now +ID URL Region Expires +------- ---------------------------------------- ------------ ---------------- +alpha https://alpha-app.proxy.example.com us-east-1 3 hours from now +bravo us-west-2 4 hours from now +charlie tcp://charlie-app.proxy.example.com:5432 eu-central-1 5 hours from now diff --git a/tool/tsh/common/testdata/TestBeamsLSCommand/text_format_with_all.golden b/tool/tsh/common/testdata/TestBeamsLSCommand/text_format_with_all.golden index 4d57bcd2e8b9..56bdec2d97b5 100644 --- a/tool/tsh/common/testdata/TestBeamsLSCommand/text_format_with_all.golden +++ b/tool/tsh/common/testdata/TestBeamsLSCommand/text_format_with_all.golden @@ -1,5 +1,5 @@ -ID URL Expires Owner -------- ---------------------------------------- ---------------- ----- -alpha https://alpha-app.proxy.example.com 3 hours from now alice -bravo 4 hours from now alice -charlie tcp://charlie-app.proxy.example.com:5432 5 hours from now bob +ID URL Region Expires Owner +------- ---------------------------------------- ------------ ---------------- ----- +alpha https://alpha-app.proxy.example.com us-east-1 3 hours from now alice +bravo us-west-2 4 hours from now alice +charlie tcp://charlie-app.proxy.example.com:5432 eu-central-1 5 hours from now bob From a50f701f059594b7fb2434973c725fece91772f9 Mon Sep 17 00:00:00 2001 From: Travis LaDuke Date: Thu, 30 Jul 2026 10:52:47 -0700 Subject: [PATCH 066/347] Add beam publish and unpublish usage events (#68091) --- gen/proto/go/prehog/v1alpha/teleport.pb.go | 1590 +++++++++++--------- gen/proto/ts/prehog/v1alpha/teleport_pb.ts | 178 ++- lib/usagereporter/teleport/types.go | 29 + proto/prehog/v1alpha/teleport.proto | 27 + 4 files changed, 1106 insertions(+), 718 deletions(-) diff --git a/gen/proto/go/prehog/v1alpha/teleport.pb.go b/gen/proto/go/prehog/v1alpha/teleport.pb.go index 0d1def119871..5ee33021291d 100644 --- a/gen/proto/go/prehog/v1alpha/teleport.pb.go +++ b/gen/proto/go/prehog/v1alpha/teleport.pb.go @@ -1597,7 +1597,7 @@ func (x UIDiscoverDeployServiceEvent_DeployMethod) Number() protoreflect.EnumNum // Deprecated: Use UIDiscoverDeployServiceEvent_DeployMethod.Descriptor instead. func (UIDiscoverDeployServiceEvent_DeployMethod) EnumDescriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{35, 0} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{37, 0} } // DeployType describes the type of deployment. @@ -1653,7 +1653,7 @@ func (x UIDiscoverDeployServiceEvent_DeployType) Number() protoreflect.EnumNumbe // Deprecated: Use UIDiscoverDeployServiceEvent_DeployType.Descriptor instead. func (UIDiscoverDeployServiceEvent_DeployType) EnumDescriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{35, 1} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{37, 1} } // ConfigMethod describes how the discovery config is configured. @@ -1714,7 +1714,7 @@ func (x UIDiscoverCreateDiscoveryConfigEvent_ConfigMethod) Number() protoreflect // Deprecated: Use UIDiscoverCreateDiscoveryConfigEvent_ConfigMethod.Descriptor instead. func (UIDiscoverCreateDiscoveryConfigEvent_ConfigMethod) EnumDescriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{36, 0} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{38, 0} } // ProposedState specifies the proposed state of an access request review. @@ -1767,7 +1767,7 @@ func (x AccessRequestReviewEvent_ProposedState) Number() protoreflect.EnumNumber // Deprecated: Use AccessRequestReviewEvent_ProposedState.Descriptor instead. func (AccessRequestReviewEvent_ProposedState) EnumDescriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{113, 0} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{115, 0} } // a successful user login @@ -2896,6 +2896,117 @@ func (x *BeamsDestroyedEvent) GetRegion() string { return "" } +// BeamsPublishedEvent is emitted when a beam app is published. +// +// PostHog event: tp.beams.published +type BeamsPublishedEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // anonymized beam ID + // + // PostHog property: tp.beam.beam_id + BeamId string `protobuf:"bytes,1,opt,name=beam_id,json=beamId,proto3" json:"beam_id,omitempty"` + // the protocol of the published app (http or tcp) + // + // PostHog property: tp.beam.protocol + Protocol string `protobuf:"bytes,2,opt,name=protocol,proto3" json:"protocol,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BeamsPublishedEvent) Reset() { + *x = BeamsPublishedEvent{} + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BeamsPublishedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeamsPublishedEvent) ProtoMessage() {} + +func (x *BeamsPublishedEvent) ProtoReflect() protoreflect.Message { + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeamsPublishedEvent.ProtoReflect.Descriptor instead. +func (*BeamsPublishedEvent) Descriptor() ([]byte, []int) { + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{15} +} + +func (x *BeamsPublishedEvent) GetBeamId() string { + if x != nil { + return x.BeamId + } + return "" +} + +func (x *BeamsPublishedEvent) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +// BeamsUnpublishedEvent is emitted when a beam app is unpublished. +// +// PostHog event: tp.beams.unpublished +type BeamsUnpublishedEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // anonymized beam ID + // + // PostHog property: tp.beam.beam_id + BeamId string `protobuf:"bytes,1,opt,name=beam_id,json=beamId,proto3" json:"beam_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BeamsUnpublishedEvent) Reset() { + *x = BeamsUnpublishedEvent{} + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BeamsUnpublishedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeamsUnpublishedEvent) ProtoMessage() {} + +func (x *BeamsUnpublishedEvent) ProtoReflect() protoreflect.Message { + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeamsUnpublishedEvent.ProtoReflect.Descriptor instead. +func (*BeamsUnpublishedEvent) Descriptor() ([]byte, []int) { + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{16} +} + +func (x *BeamsUnpublishedEvent) GetBeamId() string { + if x != nil { + return x.BeamId + } + return "" +} + // the issuance of a user certificate from the user CA // // PostHog event: tp.certificate.issued @@ -2936,7 +3047,7 @@ type UserCertificateIssuedEvent struct { func (x *UserCertificateIssuedEvent) Reset() { *x = UserCertificateIssuedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[15] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2948,7 +3059,7 @@ func (x *UserCertificateIssuedEvent) String() string { func (*UserCertificateIssuedEvent) ProtoMessage() {} func (x *UserCertificateIssuedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[15] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2961,7 +3072,7 @@ func (x *UserCertificateIssuedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserCertificateIssuedEvent.ProtoReflect.Descriptor instead. func (*UserCertificateIssuedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{15} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{17} } func (x *UserCertificateIssuedEvent) GetUserName() string { @@ -3062,7 +3173,7 @@ type SPIFFESVIDIssuedEvent struct { func (x *SPIFFESVIDIssuedEvent) Reset() { *x = SPIFFESVIDIssuedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[16] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3074,7 +3185,7 @@ func (x *SPIFFESVIDIssuedEvent) String() string { func (*SPIFFESVIDIssuedEvent) ProtoMessage() {} func (x *SPIFFESVIDIssuedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[16] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3087,7 +3198,7 @@ func (x *SPIFFESVIDIssuedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SPIFFESVIDIssuedEvent.ProtoReflect.Descriptor instead. func (*SPIFFESVIDIssuedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{16} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{18} } func (x *SPIFFESVIDIssuedEvent) GetUserName() string { @@ -3153,7 +3264,7 @@ type UIBannerClickEvent struct { func (x *UIBannerClickEvent) Reset() { *x = UIBannerClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[17] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3165,7 +3276,7 @@ func (x *UIBannerClickEvent) String() string { func (*UIBannerClickEvent) ProtoMessage() {} func (x *UIBannerClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[17] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3178,7 +3289,7 @@ func (x *UIBannerClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIBannerClickEvent.ProtoReflect.Descriptor instead. func (*UIBannerClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{17} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{19} } func (x *UIBannerClickEvent) GetUserName() string { @@ -3207,7 +3318,7 @@ type UIOnboardCompleteGoToDashboardClickEvent struct { func (x *UIOnboardCompleteGoToDashboardClickEvent) Reset() { *x = UIOnboardCompleteGoToDashboardClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[18] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3219,7 +3330,7 @@ func (x *UIOnboardCompleteGoToDashboardClickEvent) String() string { func (*UIOnboardCompleteGoToDashboardClickEvent) ProtoMessage() {} func (x *UIOnboardCompleteGoToDashboardClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[18] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3232,7 +3343,7 @@ func (x *UIOnboardCompleteGoToDashboardClickEvent) ProtoReflect() protoreflect.M // Deprecated: Use UIOnboardCompleteGoToDashboardClickEvent.ProtoReflect.Descriptor instead. func (*UIOnboardCompleteGoToDashboardClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{18} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{20} } func (x *UIOnboardCompleteGoToDashboardClickEvent) GetUserName() string { @@ -3254,7 +3365,7 @@ type UIOnboardAddFirstResourceClickEvent struct { func (x *UIOnboardAddFirstResourceClickEvent) Reset() { *x = UIOnboardAddFirstResourceClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[19] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3266,7 +3377,7 @@ func (x *UIOnboardAddFirstResourceClickEvent) String() string { func (*UIOnboardAddFirstResourceClickEvent) ProtoMessage() {} func (x *UIOnboardAddFirstResourceClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[19] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3279,7 +3390,7 @@ func (x *UIOnboardAddFirstResourceClickEvent) ProtoReflect() protoreflect.Messag // Deprecated: Use UIOnboardAddFirstResourceClickEvent.ProtoReflect.Descriptor instead. func (*UIOnboardAddFirstResourceClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{19} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{21} } func (x *UIOnboardAddFirstResourceClickEvent) GetUserName() string { @@ -3301,7 +3412,7 @@ type UIOnboardAddFirstResourceLaterClickEvent struct { func (x *UIOnboardAddFirstResourceLaterClickEvent) Reset() { *x = UIOnboardAddFirstResourceLaterClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[20] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3313,7 +3424,7 @@ func (x *UIOnboardAddFirstResourceLaterClickEvent) String() string { func (*UIOnboardAddFirstResourceLaterClickEvent) ProtoMessage() {} func (x *UIOnboardAddFirstResourceLaterClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[20] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3326,7 +3437,7 @@ func (x *UIOnboardAddFirstResourceLaterClickEvent) ProtoReflect() protoreflect.M // Deprecated: Use UIOnboardAddFirstResourceLaterClickEvent.ProtoReflect.Descriptor instead. func (*UIOnboardAddFirstResourceLaterClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{20} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{22} } func (x *UIOnboardAddFirstResourceLaterClickEvent) GetUserName() string { @@ -3348,7 +3459,7 @@ type UIOnboardSetCredentialSubmitEvent struct { func (x *UIOnboardSetCredentialSubmitEvent) Reset() { *x = UIOnboardSetCredentialSubmitEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[21] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3360,7 +3471,7 @@ func (x *UIOnboardSetCredentialSubmitEvent) String() string { func (*UIOnboardSetCredentialSubmitEvent) ProtoMessage() {} func (x *UIOnboardSetCredentialSubmitEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[21] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3373,7 +3484,7 @@ func (x *UIOnboardSetCredentialSubmitEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIOnboardSetCredentialSubmitEvent.ProtoReflect.Descriptor instead. func (*UIOnboardSetCredentialSubmitEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{21} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{23} } func (x *UIOnboardSetCredentialSubmitEvent) GetUserName() string { @@ -3397,7 +3508,7 @@ type UIOnboardRegisterChallengeSubmitEvent struct { func (x *UIOnboardRegisterChallengeSubmitEvent) Reset() { *x = UIOnboardRegisterChallengeSubmitEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[22] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3409,7 +3520,7 @@ func (x *UIOnboardRegisterChallengeSubmitEvent) String() string { func (*UIOnboardRegisterChallengeSubmitEvent) ProtoMessage() {} func (x *UIOnboardRegisterChallengeSubmitEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[22] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3422,7 +3533,7 @@ func (x *UIOnboardRegisterChallengeSubmitEvent) ProtoReflect() protoreflect.Mess // Deprecated: Use UIOnboardRegisterChallengeSubmitEvent.ProtoReflect.Descriptor instead. func (*UIOnboardRegisterChallengeSubmitEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{22} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{24} } func (x *UIOnboardRegisterChallengeSubmitEvent) GetUserName() string { @@ -3458,7 +3569,7 @@ type UIOnboardQuestionnaireSubmitEvent struct { func (x *UIOnboardQuestionnaireSubmitEvent) Reset() { *x = UIOnboardQuestionnaireSubmitEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[23] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3470,7 +3581,7 @@ func (x *UIOnboardQuestionnaireSubmitEvent) String() string { func (*UIOnboardQuestionnaireSubmitEvent) ProtoMessage() {} func (x *UIOnboardQuestionnaireSubmitEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[23] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3483,7 +3594,7 @@ func (x *UIOnboardQuestionnaireSubmitEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIOnboardQuestionnaireSubmitEvent.ProtoReflect.Descriptor instead. func (*UIOnboardQuestionnaireSubmitEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{23} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{25} } func (x *UIOnboardQuestionnaireSubmitEvent) GetUserName() string { @@ -3505,7 +3616,7 @@ type UIRecoveryCodesContinueClickEvent struct { func (x *UIRecoveryCodesContinueClickEvent) Reset() { *x = UIRecoveryCodesContinueClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[24] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3517,7 +3628,7 @@ func (x *UIRecoveryCodesContinueClickEvent) String() string { func (*UIRecoveryCodesContinueClickEvent) ProtoMessage() {} func (x *UIRecoveryCodesContinueClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[24] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3530,7 +3641,7 @@ func (x *UIRecoveryCodesContinueClickEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIRecoveryCodesContinueClickEvent.ProtoReflect.Descriptor instead. func (*UIRecoveryCodesContinueClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{24} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{26} } func (x *UIRecoveryCodesContinueClickEvent) GetUserName() string { @@ -3552,7 +3663,7 @@ type UIRecoveryCodesCopyClickEvent struct { func (x *UIRecoveryCodesCopyClickEvent) Reset() { *x = UIRecoveryCodesCopyClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[25] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3564,7 +3675,7 @@ func (x *UIRecoveryCodesCopyClickEvent) String() string { func (*UIRecoveryCodesCopyClickEvent) ProtoMessage() {} func (x *UIRecoveryCodesCopyClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[25] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3577,7 +3688,7 @@ func (x *UIRecoveryCodesCopyClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIRecoveryCodesCopyClickEvent.ProtoReflect.Descriptor instead. func (*UIRecoveryCodesCopyClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{25} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{27} } func (x *UIRecoveryCodesCopyClickEvent) GetUserName() string { @@ -3599,7 +3710,7 @@ type UIRecoveryCodesPrintClickEvent struct { func (x *UIRecoveryCodesPrintClickEvent) Reset() { *x = UIRecoveryCodesPrintClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[26] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3611,7 +3722,7 @@ func (x *UIRecoveryCodesPrintClickEvent) String() string { func (*UIRecoveryCodesPrintClickEvent) ProtoMessage() {} func (x *UIRecoveryCodesPrintClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[26] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3624,7 +3735,7 @@ func (x *UIRecoveryCodesPrintClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIRecoveryCodesPrintClickEvent.ProtoReflect.Descriptor instead. func (*UIRecoveryCodesPrintClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{26} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{28} } func (x *UIRecoveryCodesPrintClickEvent) GetUserName() string { @@ -3650,7 +3761,7 @@ type DiscoverMetadata struct { func (x *DiscoverMetadata) Reset() { *x = DiscoverMetadata{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[27] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3662,7 +3773,7 @@ func (x *DiscoverMetadata) String() string { func (*DiscoverMetadata) ProtoMessage() {} func (x *DiscoverMetadata) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[27] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3675,7 +3786,7 @@ func (x *DiscoverMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverMetadata.ProtoReflect.Descriptor instead. func (*DiscoverMetadata) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{27} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{29} } func (x *DiscoverMetadata) GetId() string { @@ -3710,7 +3821,7 @@ type DiscoverResourceMetadata struct { func (x *DiscoverResourceMetadata) Reset() { *x = DiscoverResourceMetadata{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[28] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3722,7 +3833,7 @@ func (x *DiscoverResourceMetadata) String() string { func (*DiscoverResourceMetadata) ProtoMessage() {} func (x *DiscoverResourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[28] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3735,7 +3846,7 @@ func (x *DiscoverResourceMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverResourceMetadata.ProtoReflect.Descriptor instead. func (*DiscoverResourceMetadata) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{28} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{30} } func (x *DiscoverResourceMetadata) GetResource() DiscoverResource { @@ -3760,7 +3871,7 @@ type DiscoverStepStatus struct { func (x *DiscoverStepStatus) Reset() { *x = DiscoverStepStatus{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[29] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3772,7 +3883,7 @@ func (x *DiscoverStepStatus) String() string { func (*DiscoverStepStatus) ProtoMessage() {} func (x *DiscoverStepStatus) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[29] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3785,7 +3896,7 @@ func (x *DiscoverStepStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverStepStatus.ProtoReflect.Descriptor instead. func (*DiscoverStepStatus) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{29} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{31} } func (x *DiscoverStepStatus) GetStatus() DiscoverStatus { @@ -3813,7 +3924,7 @@ type UIDiscoverStartedEvent struct { func (x *UIDiscoverStartedEvent) Reset() { *x = UIDiscoverStartedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[30] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3825,7 +3936,7 @@ func (x *UIDiscoverStartedEvent) String() string { func (*UIDiscoverStartedEvent) ProtoMessage() {} func (x *UIDiscoverStartedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[30] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3838,7 +3949,7 @@ func (x *UIDiscoverStartedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverStartedEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverStartedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{30} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{32} } func (x *UIDiscoverStartedEvent) GetMetadata() *DiscoverMetadata { @@ -3868,7 +3979,7 @@ type UIDiscoverResourceSelectionEvent struct { func (x *UIDiscoverResourceSelectionEvent) Reset() { *x = UIDiscoverResourceSelectionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[31] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3880,7 +3991,7 @@ func (x *UIDiscoverResourceSelectionEvent) String() string { func (*UIDiscoverResourceSelectionEvent) ProtoMessage() {} func (x *UIDiscoverResourceSelectionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[31] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3893,7 +4004,7 @@ func (x *UIDiscoverResourceSelectionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverResourceSelectionEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverResourceSelectionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{31} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{33} } func (x *UIDiscoverResourceSelectionEvent) GetMetadata() *DiscoverMetadata { @@ -3931,7 +4042,7 @@ type UIDiscoverIntegrationAWSOIDCConnectEvent struct { func (x *UIDiscoverIntegrationAWSOIDCConnectEvent) Reset() { *x = UIDiscoverIntegrationAWSOIDCConnectEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[32] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3943,7 +4054,7 @@ func (x *UIDiscoverIntegrationAWSOIDCConnectEvent) String() string { func (*UIDiscoverIntegrationAWSOIDCConnectEvent) ProtoMessage() {} func (x *UIDiscoverIntegrationAWSOIDCConnectEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[32] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3956,7 +4067,7 @@ func (x *UIDiscoverIntegrationAWSOIDCConnectEvent) ProtoReflect() protoreflect.M // Deprecated: Use UIDiscoverIntegrationAWSOIDCConnectEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverIntegrationAWSOIDCConnectEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{32} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{34} } func (x *UIDiscoverIntegrationAWSOIDCConnectEvent) GetMetadata() *DiscoverMetadata { @@ -3994,7 +4105,7 @@ type UIDiscoverDatabaseRDSEnrollEvent struct { func (x *UIDiscoverDatabaseRDSEnrollEvent) Reset() { *x = UIDiscoverDatabaseRDSEnrollEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[33] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4006,7 +4117,7 @@ func (x *UIDiscoverDatabaseRDSEnrollEvent) String() string { func (*UIDiscoverDatabaseRDSEnrollEvent) ProtoMessage() {} func (x *UIDiscoverDatabaseRDSEnrollEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[33] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4019,7 +4130,7 @@ func (x *UIDiscoverDatabaseRDSEnrollEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverDatabaseRDSEnrollEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDatabaseRDSEnrollEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{33} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{35} } func (x *UIDiscoverDatabaseRDSEnrollEvent) GetMetadata() *DiscoverMetadata { @@ -4063,7 +4174,7 @@ type UIDiscoverKubeEKSEnrollEvent struct { func (x *UIDiscoverKubeEKSEnrollEvent) Reset() { *x = UIDiscoverKubeEKSEnrollEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[34] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4075,7 +4186,7 @@ func (x *UIDiscoverKubeEKSEnrollEvent) String() string { func (*UIDiscoverKubeEKSEnrollEvent) ProtoMessage() {} func (x *UIDiscoverKubeEKSEnrollEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[34] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4088,7 +4199,7 @@ func (x *UIDiscoverKubeEKSEnrollEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverKubeEKSEnrollEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverKubeEKSEnrollEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{34} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{36} } func (x *UIDiscoverKubeEKSEnrollEvent) GetMetadata() *DiscoverMetadata { @@ -4132,7 +4243,7 @@ type UIDiscoverDeployServiceEvent struct { func (x *UIDiscoverDeployServiceEvent) Reset() { *x = UIDiscoverDeployServiceEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[35] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4144,7 +4255,7 @@ func (x *UIDiscoverDeployServiceEvent) String() string { func (*UIDiscoverDeployServiceEvent) ProtoMessage() {} func (x *UIDiscoverDeployServiceEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[35] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4157,7 +4268,7 @@ func (x *UIDiscoverDeployServiceEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverDeployServiceEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDeployServiceEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{35} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{37} } func (x *UIDiscoverDeployServiceEvent) GetMetadata() *DiscoverMetadata { @@ -4208,7 +4319,7 @@ type UIDiscoverCreateDiscoveryConfigEvent struct { func (x *UIDiscoverCreateDiscoveryConfigEvent) Reset() { *x = UIDiscoverCreateDiscoveryConfigEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[36] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4220,7 +4331,7 @@ func (x *UIDiscoverCreateDiscoveryConfigEvent) String() string { func (*UIDiscoverCreateDiscoveryConfigEvent) ProtoMessage() {} func (x *UIDiscoverCreateDiscoveryConfigEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[36] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4233,7 +4344,7 @@ func (x *UIDiscoverCreateDiscoveryConfigEvent) ProtoReflect() protoreflect.Messa // Deprecated: Use UIDiscoverCreateDiscoveryConfigEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverCreateDiscoveryConfigEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{36} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{38} } func (x *UIDiscoverCreateDiscoveryConfigEvent) GetMetadata() *DiscoverMetadata { @@ -4276,7 +4387,7 @@ type UIDiscoverDatabaseRegisterEvent struct { func (x *UIDiscoverDatabaseRegisterEvent) Reset() { *x = UIDiscoverDatabaseRegisterEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[37] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4288,7 +4399,7 @@ func (x *UIDiscoverDatabaseRegisterEvent) String() string { func (*UIDiscoverDatabaseRegisterEvent) ProtoMessage() {} func (x *UIDiscoverDatabaseRegisterEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[37] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4301,7 +4412,7 @@ func (x *UIDiscoverDatabaseRegisterEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverDatabaseRegisterEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDatabaseRegisterEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{37} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{39} } func (x *UIDiscoverDatabaseRegisterEvent) GetMetadata() *DiscoverMetadata { @@ -4337,7 +4448,7 @@ type UIDiscoverDatabaseConfigureMTLSEvent struct { func (x *UIDiscoverDatabaseConfigureMTLSEvent) Reset() { *x = UIDiscoverDatabaseConfigureMTLSEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[38] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4349,7 +4460,7 @@ func (x *UIDiscoverDatabaseConfigureMTLSEvent) String() string { func (*UIDiscoverDatabaseConfigureMTLSEvent) ProtoMessage() {} func (x *UIDiscoverDatabaseConfigureMTLSEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[38] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4362,7 +4473,7 @@ func (x *UIDiscoverDatabaseConfigureMTLSEvent) ProtoReflect() protoreflect.Messa // Deprecated: Use UIDiscoverDatabaseConfigureMTLSEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDatabaseConfigureMTLSEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{38} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{40} } func (x *UIDiscoverDatabaseConfigureMTLSEvent) GetMetadata() *DiscoverMetadata { @@ -4398,7 +4509,7 @@ type UIDiscoverDesktopActiveDirectoryToolsInstallEvent struct { func (x *UIDiscoverDesktopActiveDirectoryToolsInstallEvent) Reset() { *x = UIDiscoverDesktopActiveDirectoryToolsInstallEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[39] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4410,7 +4521,7 @@ func (x *UIDiscoverDesktopActiveDirectoryToolsInstallEvent) String() string { func (*UIDiscoverDesktopActiveDirectoryToolsInstallEvent) ProtoMessage() {} func (x *UIDiscoverDesktopActiveDirectoryToolsInstallEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[39] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4423,7 +4534,7 @@ func (x *UIDiscoverDesktopActiveDirectoryToolsInstallEvent) ProtoReflect() proto // Deprecated: Use UIDiscoverDesktopActiveDirectoryToolsInstallEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDesktopActiveDirectoryToolsInstallEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{39} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{41} } func (x *UIDiscoverDesktopActiveDirectoryToolsInstallEvent) GetMetadata() *DiscoverMetadata { @@ -4459,7 +4570,7 @@ type UIDiscoverDesktopActiveDirectoryConfigureEvent struct { func (x *UIDiscoverDesktopActiveDirectoryConfigureEvent) Reset() { *x = UIDiscoverDesktopActiveDirectoryConfigureEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[40] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4471,7 +4582,7 @@ func (x *UIDiscoverDesktopActiveDirectoryConfigureEvent) String() string { func (*UIDiscoverDesktopActiveDirectoryConfigureEvent) ProtoMessage() {} func (x *UIDiscoverDesktopActiveDirectoryConfigureEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[40] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4484,7 +4595,7 @@ func (x *UIDiscoverDesktopActiveDirectoryConfigureEvent) ProtoReflect() protoref // Deprecated: Use UIDiscoverDesktopActiveDirectoryConfigureEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDesktopActiveDirectoryConfigureEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{40} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{42} } func (x *UIDiscoverDesktopActiveDirectoryConfigureEvent) GetMetadata() *DiscoverMetadata { @@ -4522,7 +4633,7 @@ type UIDiscoverAutoDiscoveredResourcesEvent struct { func (x *UIDiscoverAutoDiscoveredResourcesEvent) Reset() { *x = UIDiscoverAutoDiscoveredResourcesEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[41] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4534,7 +4645,7 @@ func (x *UIDiscoverAutoDiscoveredResourcesEvent) String() string { func (*UIDiscoverAutoDiscoveredResourcesEvent) ProtoMessage() {} func (x *UIDiscoverAutoDiscoveredResourcesEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[41] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4547,7 +4658,7 @@ func (x *UIDiscoverAutoDiscoveredResourcesEvent) ProtoReflect() protoreflect.Mes // Deprecated: Use UIDiscoverAutoDiscoveredResourcesEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverAutoDiscoveredResourcesEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{41} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{43} } func (x *UIDiscoverAutoDiscoveredResourcesEvent) GetMetadata() *DiscoverMetadata { @@ -4590,7 +4701,7 @@ type UIDiscoverEC2InstanceSelectionEvent struct { func (x *UIDiscoverEC2InstanceSelectionEvent) Reset() { *x = UIDiscoverEC2InstanceSelectionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[42] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4602,7 +4713,7 @@ func (x *UIDiscoverEC2InstanceSelectionEvent) String() string { func (*UIDiscoverEC2InstanceSelectionEvent) ProtoMessage() {} func (x *UIDiscoverEC2InstanceSelectionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[42] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4615,7 +4726,7 @@ func (x *UIDiscoverEC2InstanceSelectionEvent) ProtoReflect() protoreflect.Messag // Deprecated: Use UIDiscoverEC2InstanceSelectionEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverEC2InstanceSelectionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{42} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{44} } func (x *UIDiscoverEC2InstanceSelectionEvent) GetMetadata() *DiscoverMetadata { @@ -4651,7 +4762,7 @@ type UIDiscoverDeployEICEEvent struct { func (x *UIDiscoverDeployEICEEvent) Reset() { *x = UIDiscoverDeployEICEEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[43] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4663,7 +4774,7 @@ func (x *UIDiscoverDeployEICEEvent) String() string { func (*UIDiscoverDeployEICEEvent) ProtoMessage() {} func (x *UIDiscoverDeployEICEEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[43] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4676,7 +4787,7 @@ func (x *UIDiscoverDeployEICEEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverDeployEICEEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDeployEICEEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{43} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{45} } func (x *UIDiscoverDeployEICEEvent) GetMetadata() *DiscoverMetadata { @@ -4712,7 +4823,7 @@ type UIDiscoverCreateNodeEvent struct { func (x *UIDiscoverCreateNodeEvent) Reset() { *x = UIDiscoverCreateNodeEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[44] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4724,7 +4835,7 @@ func (x *UIDiscoverCreateNodeEvent) String() string { func (*UIDiscoverCreateNodeEvent) ProtoMessage() {} func (x *UIDiscoverCreateNodeEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[44] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4737,7 +4848,7 @@ func (x *UIDiscoverCreateNodeEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverCreateNodeEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverCreateNodeEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{44} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{46} } func (x *UIDiscoverCreateNodeEvent) GetMetadata() *DiscoverMetadata { @@ -4773,7 +4884,7 @@ type UIDiscoverCreateAppServerEvent struct { func (x *UIDiscoverCreateAppServerEvent) Reset() { *x = UIDiscoverCreateAppServerEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[45] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4785,7 +4896,7 @@ func (x *UIDiscoverCreateAppServerEvent) String() string { func (*UIDiscoverCreateAppServerEvent) ProtoMessage() {} func (x *UIDiscoverCreateAppServerEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[45] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4798,7 +4909,7 @@ func (x *UIDiscoverCreateAppServerEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverCreateAppServerEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverCreateAppServerEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{45} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{47} } func (x *UIDiscoverCreateAppServerEvent) GetMetadata() *DiscoverMetadata { @@ -4834,7 +4945,7 @@ type UIDiscoverDatabaseConfigureIAMPolicyEvent struct { func (x *UIDiscoverDatabaseConfigureIAMPolicyEvent) Reset() { *x = UIDiscoverDatabaseConfigureIAMPolicyEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[46] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4846,7 +4957,7 @@ func (x *UIDiscoverDatabaseConfigureIAMPolicyEvent) String() string { func (*UIDiscoverDatabaseConfigureIAMPolicyEvent) ProtoMessage() {} func (x *UIDiscoverDatabaseConfigureIAMPolicyEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[46] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4859,7 +4970,7 @@ func (x *UIDiscoverDatabaseConfigureIAMPolicyEvent) ProtoReflect() protoreflect. // Deprecated: Use UIDiscoverDatabaseConfigureIAMPolicyEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverDatabaseConfigureIAMPolicyEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{46} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{48} } func (x *UIDiscoverDatabaseConfigureIAMPolicyEvent) GetMetadata() *DiscoverMetadata { @@ -4895,7 +5006,7 @@ type UIDiscoverPrincipalsConfigureEvent struct { func (x *UIDiscoverPrincipalsConfigureEvent) Reset() { *x = UIDiscoverPrincipalsConfigureEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[47] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4907,7 +5018,7 @@ func (x *UIDiscoverPrincipalsConfigureEvent) String() string { func (*UIDiscoverPrincipalsConfigureEvent) ProtoMessage() {} func (x *UIDiscoverPrincipalsConfigureEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[47] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4920,7 +5031,7 @@ func (x *UIDiscoverPrincipalsConfigureEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIDiscoverPrincipalsConfigureEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverPrincipalsConfigureEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{47} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{49} } func (x *UIDiscoverPrincipalsConfigureEvent) GetMetadata() *DiscoverMetadata { @@ -4957,7 +5068,7 @@ type UIDiscoverTestConnectionEvent struct { func (x *UIDiscoverTestConnectionEvent) Reset() { *x = UIDiscoverTestConnectionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[48] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4969,7 +5080,7 @@ func (x *UIDiscoverTestConnectionEvent) String() string { func (*UIDiscoverTestConnectionEvent) ProtoMessage() {} func (x *UIDiscoverTestConnectionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[48] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4982,7 +5093,7 @@ func (x *UIDiscoverTestConnectionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverTestConnectionEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverTestConnectionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{48} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{50} } func (x *UIDiscoverTestConnectionEvent) GetMetadata() *DiscoverMetadata { @@ -5018,7 +5129,7 @@ type UIDiscoverCompletedEvent struct { func (x *UIDiscoverCompletedEvent) Reset() { *x = UIDiscoverCompletedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[49] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5030,7 +5141,7 @@ func (x *UIDiscoverCompletedEvent) String() string { func (*UIDiscoverCompletedEvent) ProtoMessage() {} func (x *UIDiscoverCompletedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[49] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5043,7 +5154,7 @@ func (x *UIDiscoverCompletedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIDiscoverCompletedEvent.ProtoReflect.Descriptor instead. func (*UIDiscoverCompletedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{49} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{51} } func (x *UIDiscoverCompletedEvent) GetMetadata() *DiscoverMetadata { @@ -5080,7 +5191,7 @@ type RoleCreateEvent struct { func (x *RoleCreateEvent) Reset() { *x = RoleCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[50] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5092,7 +5203,7 @@ func (x *RoleCreateEvent) String() string { func (*RoleCreateEvent) ProtoMessage() {} func (x *RoleCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[50] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5105,7 +5216,7 @@ func (x *RoleCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RoleCreateEvent.ProtoReflect.Descriptor instead. func (*RoleCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{50} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{52} } func (x *RoleCreateEvent) GetUserName() string { @@ -5145,7 +5256,7 @@ type BotCreateEvent struct { func (x *BotCreateEvent) Reset() { *x = BotCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[51] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5157,7 +5268,7 @@ func (x *BotCreateEvent) String() string { func (*BotCreateEvent) ProtoMessage() {} func (x *BotCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[51] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5170,7 +5281,7 @@ func (x *BotCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use BotCreateEvent.ProtoReflect.Descriptor instead. func (*BotCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{51} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{53} } func (x *BotCreateEvent) GetUserName() string { @@ -5236,7 +5347,7 @@ type BotJoinEvent struct { func (x *BotJoinEvent) Reset() { *x = BotJoinEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[52] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5248,7 +5359,7 @@ func (x *BotJoinEvent) String() string { func (*BotJoinEvent) ProtoMessage() {} func (x *BotJoinEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[52] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5261,7 +5372,7 @@ func (x *BotJoinEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use BotJoinEvent.ProtoReflect.Descriptor instead. func (*BotJoinEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{52} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{54} } func (x *BotJoinEvent) GetBotName() string { @@ -5310,7 +5421,7 @@ type UICreateNewRoleClickEvent struct { func (x *UICreateNewRoleClickEvent) Reset() { *x = UICreateNewRoleClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[53] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5322,7 +5433,7 @@ func (x *UICreateNewRoleClickEvent) String() string { func (*UICreateNewRoleClickEvent) ProtoMessage() {} func (x *UICreateNewRoleClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[53] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5335,7 +5446,7 @@ func (x *UICreateNewRoleClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UICreateNewRoleClickEvent.ProtoReflect.Descriptor instead. func (*UICreateNewRoleClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{53} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{55} } func (x *UICreateNewRoleClickEvent) GetUserName() string { @@ -5371,7 +5482,7 @@ type UICreateNewRoleSaveClickEvent struct { func (x *UICreateNewRoleSaveClickEvent) Reset() { *x = UICreateNewRoleSaveClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[54] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5383,7 +5494,7 @@ func (x *UICreateNewRoleSaveClickEvent) String() string { func (*UICreateNewRoleSaveClickEvent) ProtoMessage() {} func (x *UICreateNewRoleSaveClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[54] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5396,7 +5507,7 @@ func (x *UICreateNewRoleSaveClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UICreateNewRoleSaveClickEvent.ProtoReflect.Descriptor instead. func (*UICreateNewRoleSaveClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{54} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{56} } func (x *UICreateNewRoleSaveClickEvent) GetUserName() string { @@ -5445,7 +5556,7 @@ type UICreateNewRoleCancelClickEvent struct { func (x *UICreateNewRoleCancelClickEvent) Reset() { *x = UICreateNewRoleCancelClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[55] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5457,7 +5568,7 @@ func (x *UICreateNewRoleCancelClickEvent) String() string { func (*UICreateNewRoleCancelClickEvent) ProtoMessage() {} func (x *UICreateNewRoleCancelClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[55] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5470,7 +5581,7 @@ func (x *UICreateNewRoleCancelClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UICreateNewRoleCancelClickEvent.ProtoReflect.Descriptor instead. func (*UICreateNewRoleCancelClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{55} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{57} } func (x *UICreateNewRoleCancelClickEvent) GetUserName() string { @@ -5491,7 +5602,7 @@ type UICreateNewRoleViewDocumentationClickEvent struct { func (x *UICreateNewRoleViewDocumentationClickEvent) Reset() { *x = UICreateNewRoleViewDocumentationClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[56] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5503,7 +5614,7 @@ func (x *UICreateNewRoleViewDocumentationClickEvent) String() string { func (*UICreateNewRoleViewDocumentationClickEvent) ProtoMessage() {} func (x *UICreateNewRoleViewDocumentationClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[56] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5516,7 +5627,7 @@ func (x *UICreateNewRoleViewDocumentationClickEvent) ProtoReflect() protoreflect // Deprecated: Use UICreateNewRoleViewDocumentationClickEvent.ProtoReflect.Descriptor instead. func (*UICreateNewRoleViewDocumentationClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{56} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{58} } func (x *UICreateNewRoleViewDocumentationClickEvent) GetUserName() string { @@ -5538,7 +5649,7 @@ type UICallToActionClickEvent struct { func (x *UICallToActionClickEvent) Reset() { *x = UICallToActionClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[57] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5550,7 +5661,7 @@ func (x *UICallToActionClickEvent) String() string { func (*UICallToActionClickEvent) ProtoMessage() {} func (x *UICallToActionClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[57] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5563,7 +5674,7 @@ func (x *UICallToActionClickEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UICallToActionClickEvent.ProtoReflect.Descriptor instead. func (*UICallToActionClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{57} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{59} } func (x *UICallToActionClickEvent) GetUserName() string { @@ -5598,7 +5709,7 @@ type KubeRequestEvent struct { func (x *KubeRequestEvent) Reset() { *x = KubeRequestEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[58] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5610,7 +5721,7 @@ func (x *KubeRequestEvent) String() string { func (*KubeRequestEvent) ProtoMessage() {} func (x *KubeRequestEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[58] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5623,7 +5734,7 @@ func (x *KubeRequestEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use KubeRequestEvent.ProtoReflect.Descriptor instead. func (*KubeRequestEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{58} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{60} } func (x *KubeRequestEvent) GetUserName() string { @@ -5659,7 +5770,7 @@ type SFTPEvent struct { func (x *SFTPEvent) Reset() { *x = SFTPEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[59] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5671,7 +5782,7 @@ func (x *SFTPEvent) String() string { func (*SFTPEvent) ProtoMessage() {} func (x *SFTPEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[59] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5684,7 +5795,7 @@ func (x *SFTPEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SFTPEvent.ProtoReflect.Descriptor instead. func (*SFTPEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{59} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{61} } func (x *SFTPEvent) GetUserName() string { @@ -5730,7 +5841,7 @@ type AgentMetadataEvent struct { func (x *AgentMetadataEvent) Reset() { *x = AgentMetadataEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[60] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5742,7 +5853,7 @@ func (x *AgentMetadataEvent) String() string { func (*AgentMetadataEvent) ProtoMessage() {} func (x *AgentMetadataEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[60] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5755,7 +5866,7 @@ func (x *AgentMetadataEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentMetadataEvent.ProtoReflect.Descriptor instead. func (*AgentMetadataEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{60} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{62} } func (x *AgentMetadataEvent) GetVersion() string { @@ -5863,7 +5974,7 @@ type AssistCompletionEvent struct { func (x *AssistCompletionEvent) Reset() { *x = AssistCompletionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[61] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5875,7 +5986,7 @@ func (x *AssistCompletionEvent) String() string { func (*AssistCompletionEvent) ProtoMessage() {} func (x *AssistCompletionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[61] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5888,7 +5999,7 @@ func (x *AssistCompletionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AssistCompletionEvent.ProtoReflect.Descriptor instead. func (*AssistCompletionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{61} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{63} } func (x *AssistCompletionEvent) GetUserName() string { @@ -5948,7 +6059,7 @@ type AssistExecutionEvent struct { func (x *AssistExecutionEvent) Reset() { *x = AssistExecutionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[62] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5960,7 +6071,7 @@ func (x *AssistExecutionEvent) String() string { func (*AssistExecutionEvent) ProtoMessage() {} func (x *AssistExecutionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[62] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5973,7 +6084,7 @@ func (x *AssistExecutionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AssistExecutionEvent.ProtoReflect.Descriptor instead. func (*AssistExecutionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{62} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{64} } func (x *AssistExecutionEvent) GetUserName() string { @@ -6033,7 +6144,7 @@ type AssistNewConversationEvent struct { func (x *AssistNewConversationEvent) Reset() { *x = AssistNewConversationEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[63] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6045,7 +6156,7 @@ func (x *AssistNewConversationEvent) String() string { func (*AssistNewConversationEvent) ProtoMessage() {} func (x *AssistNewConversationEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[63] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6058,7 +6169,7 @@ func (x *AssistNewConversationEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AssistNewConversationEvent.ProtoReflect.Descriptor instead. func (*AssistNewConversationEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{63} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{65} } func (x *AssistNewConversationEvent) GetUserName() string { @@ -6095,7 +6206,7 @@ type AssistAccessRequestEvent struct { func (x *AssistAccessRequestEvent) Reset() { *x = AssistAccessRequestEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[64] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6107,7 +6218,7 @@ func (x *AssistAccessRequestEvent) String() string { func (*AssistAccessRequestEvent) ProtoMessage() {} func (x *AssistAccessRequestEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[64] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6120,7 +6231,7 @@ func (x *AssistAccessRequestEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AssistAccessRequestEvent.ProtoReflect.Descriptor instead. func (*AssistAccessRequestEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{64} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{66} } func (x *AssistAccessRequestEvent) GetUserName() string { @@ -6178,7 +6289,7 @@ type AssistActionEvent struct { func (x *AssistActionEvent) Reset() { *x = AssistActionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[65] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6190,7 +6301,7 @@ func (x *AssistActionEvent) String() string { func (*AssistActionEvent) ProtoMessage() {} func (x *AssistActionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[65] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6203,7 +6314,7 @@ func (x *AssistActionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AssistActionEvent.ProtoReflect.Descriptor instead. func (*AssistActionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{65} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{67} } func (x *AssistActionEvent) GetUserName() string { @@ -6256,7 +6367,7 @@ type AccessListMetadata struct { func (x *AccessListMetadata) Reset() { *x = AccessListMetadata{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[66] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6268,7 +6379,7 @@ func (x *AccessListMetadata) String() string { func (*AccessListMetadata) ProtoMessage() {} func (x *AccessListMetadata) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[66] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6281,7 +6392,7 @@ func (x *AccessListMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListMetadata.ProtoReflect.Descriptor instead. func (*AccessListMetadata) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{66} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{68} } func (x *AccessListMetadata) GetId() string { @@ -6317,7 +6428,7 @@ type AccessListCreateEvent struct { func (x *AccessListCreateEvent) Reset() { *x = AccessListCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[67] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6329,7 +6440,7 @@ func (x *AccessListCreateEvent) String() string { func (*AccessListCreateEvent) ProtoMessage() {} func (x *AccessListCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[67] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6342,7 +6453,7 @@ func (x *AccessListCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListCreateEvent.ProtoReflect.Descriptor instead. func (*AccessListCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{67} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{69} } func (x *AccessListCreateEvent) GetUserName() string { @@ -6371,7 +6482,7 @@ type AccessListUpdateEvent struct { func (x *AccessListUpdateEvent) Reset() { *x = AccessListUpdateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[68] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6383,7 +6494,7 @@ func (x *AccessListUpdateEvent) String() string { func (*AccessListUpdateEvent) ProtoMessage() {} func (x *AccessListUpdateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[68] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6396,7 +6507,7 @@ func (x *AccessListUpdateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListUpdateEvent.ProtoReflect.Descriptor instead. func (*AccessListUpdateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{68} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{70} } func (x *AccessListUpdateEvent) GetUserName() string { @@ -6425,7 +6536,7 @@ type AccessListDeleteEvent struct { func (x *AccessListDeleteEvent) Reset() { *x = AccessListDeleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[69] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6437,7 +6548,7 @@ func (x *AccessListDeleteEvent) String() string { func (*AccessListDeleteEvent) ProtoMessage() {} func (x *AccessListDeleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[69] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6450,7 +6561,7 @@ func (x *AccessListDeleteEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListDeleteEvent.ProtoReflect.Descriptor instead. func (*AccessListDeleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{69} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{71} } func (x *AccessListDeleteEvent) GetUserName() string { @@ -6481,7 +6592,7 @@ type AccessListMemberCreateEvent struct { func (x *AccessListMemberCreateEvent) Reset() { *x = AccessListMemberCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[70] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6493,7 +6604,7 @@ func (x *AccessListMemberCreateEvent) String() string { func (*AccessListMemberCreateEvent) ProtoMessage() {} func (x *AccessListMemberCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[70] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6506,7 +6617,7 @@ func (x *AccessListMemberCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListMemberCreateEvent.ProtoReflect.Descriptor instead. func (*AccessListMemberCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{70} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{72} } func (x *AccessListMemberCreateEvent) GetUserName() string { @@ -6544,7 +6655,7 @@ type AccessListMemberUpdateEvent struct { func (x *AccessListMemberUpdateEvent) Reset() { *x = AccessListMemberUpdateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[71] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6556,7 +6667,7 @@ func (x *AccessListMemberUpdateEvent) String() string { func (*AccessListMemberUpdateEvent) ProtoMessage() {} func (x *AccessListMemberUpdateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[71] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6569,7 +6680,7 @@ func (x *AccessListMemberUpdateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListMemberUpdateEvent.ProtoReflect.Descriptor instead. func (*AccessListMemberUpdateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{71} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{73} } func (x *AccessListMemberUpdateEvent) GetUserName() string { @@ -6607,7 +6718,7 @@ type AccessListMemberDeleteEvent struct { func (x *AccessListMemberDeleteEvent) Reset() { *x = AccessListMemberDeleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[72] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6619,7 +6730,7 @@ func (x *AccessListMemberDeleteEvent) String() string { func (*AccessListMemberDeleteEvent) ProtoMessage() {} func (x *AccessListMemberDeleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[72] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6632,7 +6743,7 @@ func (x *AccessListMemberDeleteEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListMemberDeleteEvent.ProtoReflect.Descriptor instead. func (*AccessListMemberDeleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{72} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{74} } func (x *AccessListMemberDeleteEvent) GetUserName() string { @@ -6676,7 +6787,7 @@ type AccessListGrantsToUserEvent struct { func (x *AccessListGrantsToUserEvent) Reset() { *x = AccessListGrantsToUserEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[73] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6688,7 +6799,7 @@ func (x *AccessListGrantsToUserEvent) String() string { func (*AccessListGrantsToUserEvent) ProtoMessage() {} func (x *AccessListGrantsToUserEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[73] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6701,7 +6812,7 @@ func (x *AccessListGrantsToUserEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListGrantsToUserEvent.ProtoReflect.Descriptor instead. func (*AccessListGrantsToUserEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{73} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{75} } func (x *AccessListGrantsToUserEvent) GetUserName() string { @@ -6761,7 +6872,7 @@ type AccessListReviewCreateEvent struct { func (x *AccessListReviewCreateEvent) Reset() { *x = AccessListReviewCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[74] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6773,7 +6884,7 @@ func (x *AccessListReviewCreateEvent) String() string { func (*AccessListReviewCreateEvent) ProtoMessage() {} func (x *AccessListReviewCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[74] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6786,7 +6897,7 @@ func (x *AccessListReviewCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListReviewCreateEvent.ProtoReflect.Descriptor instead. func (*AccessListReviewCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{74} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{76} } func (x *AccessListReviewCreateEvent) GetUserName() string { @@ -6852,7 +6963,7 @@ type AccessListReviewDeleteEvent struct { func (x *AccessListReviewDeleteEvent) Reset() { *x = AccessListReviewDeleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[75] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6864,7 +6975,7 @@ func (x *AccessListReviewDeleteEvent) String() string { func (*AccessListReviewDeleteEvent) ProtoMessage() {} func (x *AccessListReviewDeleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[75] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6877,7 +6988,7 @@ func (x *AccessListReviewDeleteEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListReviewDeleteEvent.ProtoReflect.Descriptor instead. func (*AccessListReviewDeleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{75} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{77} } func (x *AccessListReviewDeleteEvent) GetUserName() string { @@ -6915,7 +7026,7 @@ type AccessListReviewComplianceEvent struct { func (x *AccessListReviewComplianceEvent) Reset() { *x = AccessListReviewComplianceEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[76] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6927,7 +7038,7 @@ func (x *AccessListReviewComplianceEvent) String() string { func (*AccessListReviewComplianceEvent) ProtoMessage() {} func (x *AccessListReviewComplianceEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[76] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6940,7 +7051,7 @@ func (x *AccessListReviewComplianceEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListReviewComplianceEvent.ProtoReflect.Descriptor instead. func (*AccessListReviewComplianceEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{76} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{78} } func (x *AccessListReviewComplianceEvent) GetTotalAccessLists() int32 { @@ -6974,7 +7085,7 @@ type IntegrationEnrollMetadata struct { func (x *IntegrationEnrollMetadata) Reset() { *x = IntegrationEnrollMetadata{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[77] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6986,7 +7097,7 @@ func (x *IntegrationEnrollMetadata) String() string { func (*IntegrationEnrollMetadata) ProtoMessage() {} func (x *IntegrationEnrollMetadata) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[77] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6999,7 +7110,7 @@ func (x *IntegrationEnrollMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use IntegrationEnrollMetadata.ProtoReflect.Descriptor instead. func (*IntegrationEnrollMetadata) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{77} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{79} } func (x *IntegrationEnrollMetadata) GetId() string { @@ -7034,7 +7145,7 @@ type UIIntegrationEnrollStartEvent struct { func (x *UIIntegrationEnrollStartEvent) Reset() { *x = UIIntegrationEnrollStartEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[78] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7046,7 +7157,7 @@ func (x *UIIntegrationEnrollStartEvent) String() string { func (*UIIntegrationEnrollStartEvent) ProtoMessage() {} func (x *UIIntegrationEnrollStartEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[78] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7059,7 +7170,7 @@ func (x *UIIntegrationEnrollStartEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIIntegrationEnrollStartEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollStartEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{78} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{80} } func (x *UIIntegrationEnrollStartEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7080,7 +7191,7 @@ type UIIntegrationEnrollCompleteEvent struct { func (x *UIIntegrationEnrollCompleteEvent) Reset() { *x = UIIntegrationEnrollCompleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[79] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7092,7 +7203,7 @@ func (x *UIIntegrationEnrollCompleteEvent) String() string { func (*UIIntegrationEnrollCompleteEvent) ProtoMessage() {} func (x *UIIntegrationEnrollCompleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[79] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7105,7 +7216,7 @@ func (x *UIIntegrationEnrollCompleteEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIIntegrationEnrollCompleteEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollCompleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{79} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{81} } func (x *UIIntegrationEnrollCompleteEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7131,7 +7242,7 @@ type IntegrationEnrollStepStatus struct { func (x *IntegrationEnrollStepStatus) Reset() { *x = IntegrationEnrollStepStatus{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[80] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7143,7 +7254,7 @@ func (x *IntegrationEnrollStepStatus) String() string { func (*IntegrationEnrollStepStatus) ProtoMessage() {} func (x *IntegrationEnrollStepStatus) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[80] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7156,7 +7267,7 @@ func (x *IntegrationEnrollStepStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use IntegrationEnrollStepStatus.ProtoReflect.Descriptor instead. func (*IntegrationEnrollStepStatus) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{80} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{82} } func (x *IntegrationEnrollStepStatus) GetCode() IntegrationEnrollStatusCode { @@ -7189,7 +7300,7 @@ type UIIntegrationEnrollStepEvent struct { func (x *UIIntegrationEnrollStepEvent) Reset() { *x = UIIntegrationEnrollStepEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[81] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7201,7 +7312,7 @@ func (x *UIIntegrationEnrollStepEvent) String() string { func (*UIIntegrationEnrollStepEvent) ProtoMessage() {} func (x *UIIntegrationEnrollStepEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[81] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7214,7 +7325,7 @@ func (x *UIIntegrationEnrollStepEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIIntegrationEnrollStepEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollStepEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{81} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{83} } func (x *UIIntegrationEnrollStepEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7254,7 +7365,7 @@ type UIIntegrationEnrollSectionOpenEvent struct { func (x *UIIntegrationEnrollSectionOpenEvent) Reset() { *x = UIIntegrationEnrollSectionOpenEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[82] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7266,7 +7377,7 @@ func (x *UIIntegrationEnrollSectionOpenEvent) String() string { func (*UIIntegrationEnrollSectionOpenEvent) ProtoMessage() {} func (x *UIIntegrationEnrollSectionOpenEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[82] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7279,7 +7390,7 @@ func (x *UIIntegrationEnrollSectionOpenEvent) ProtoReflect() protoreflect.Messag // Deprecated: Use UIIntegrationEnrollSectionOpenEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollSectionOpenEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{82} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{84} } func (x *UIIntegrationEnrollSectionOpenEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7319,7 +7430,7 @@ type UIIntegrationEnrollFieldCompleteEvent struct { func (x *UIIntegrationEnrollFieldCompleteEvent) Reset() { *x = UIIntegrationEnrollFieldCompleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[83] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7331,7 +7442,7 @@ func (x *UIIntegrationEnrollFieldCompleteEvent) String() string { func (*UIIntegrationEnrollFieldCompleteEvent) ProtoMessage() {} func (x *UIIntegrationEnrollFieldCompleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[83] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7344,7 +7455,7 @@ func (x *UIIntegrationEnrollFieldCompleteEvent) ProtoReflect() protoreflect.Mess // Deprecated: Use UIIntegrationEnrollFieldCompleteEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollFieldCompleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{83} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{85} } func (x *UIIntegrationEnrollFieldCompleteEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7384,7 +7495,7 @@ type UIIntegrationEnrollCodeCopyEvent struct { func (x *UIIntegrationEnrollCodeCopyEvent) Reset() { *x = UIIntegrationEnrollCodeCopyEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[84] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7396,7 +7507,7 @@ func (x *UIIntegrationEnrollCodeCopyEvent) String() string { func (*UIIntegrationEnrollCodeCopyEvent) ProtoMessage() {} func (x *UIIntegrationEnrollCodeCopyEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[84] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7409,7 +7520,7 @@ func (x *UIIntegrationEnrollCodeCopyEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIIntegrationEnrollCodeCopyEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollCodeCopyEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{84} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{86} } func (x *UIIntegrationEnrollCodeCopyEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7449,7 +7560,7 @@ type UIIntegrationEnrollLinkClickEvent struct { func (x *UIIntegrationEnrollLinkClickEvent) Reset() { *x = UIIntegrationEnrollLinkClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[85] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7461,7 +7572,7 @@ func (x *UIIntegrationEnrollLinkClickEvent) String() string { func (*UIIntegrationEnrollLinkClickEvent) ProtoMessage() {} func (x *UIIntegrationEnrollLinkClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[85] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7474,7 +7585,7 @@ func (x *UIIntegrationEnrollLinkClickEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIIntegrationEnrollLinkClickEvent.ProtoReflect.Descriptor instead. func (*UIIntegrationEnrollLinkClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{85} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{87} } func (x *UIIntegrationEnrollLinkClickEvent) GetMetadata() *IntegrationEnrollMetadata { @@ -7511,7 +7622,7 @@ type EditorChangeEvent struct { func (x *EditorChangeEvent) Reset() { *x = EditorChangeEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[86] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7523,7 +7634,7 @@ func (x *EditorChangeEvent) String() string { func (*EditorChangeEvent) ProtoMessage() {} func (x *EditorChangeEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[86] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7536,7 +7647,7 @@ func (x *EditorChangeEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use EditorChangeEvent.ProtoReflect.Descriptor instead. func (*EditorChangeEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{86} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{88} } func (x *EditorChangeEvent) GetUserName() string { @@ -7574,7 +7685,7 @@ type DeviceAuthenticateEvent struct { func (x *DeviceAuthenticateEvent) Reset() { *x = DeviceAuthenticateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[87] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7586,7 +7697,7 @@ func (x *DeviceAuthenticateEvent) String() string { func (*DeviceAuthenticateEvent) ProtoMessage() {} func (x *DeviceAuthenticateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[87] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7599,7 +7710,7 @@ func (x *DeviceAuthenticateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceAuthenticateEvent.ProtoReflect.Descriptor instead. func (*DeviceAuthenticateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{87} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{89} } func (x *DeviceAuthenticateEvent) GetDeviceId() string { @@ -7650,7 +7761,7 @@ type DeviceEnrollEvent struct { func (x *DeviceEnrollEvent) Reset() { *x = DeviceEnrollEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[88] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7662,7 +7773,7 @@ func (x *DeviceEnrollEvent) String() string { func (*DeviceEnrollEvent) ProtoMessage() {} func (x *DeviceEnrollEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[88] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7675,7 +7786,7 @@ func (x *DeviceEnrollEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceEnrollEvent.ProtoReflect.Descriptor instead. func (*DeviceEnrollEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{88} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{90} } func (x *DeviceEnrollEvent) GetDeviceId() string { @@ -7731,7 +7842,7 @@ type FeatureRecommendationEvent struct { func (x *FeatureRecommendationEvent) Reset() { *x = FeatureRecommendationEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[89] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7743,7 +7854,7 @@ func (x *FeatureRecommendationEvent) String() string { func (*FeatureRecommendationEvent) ProtoMessage() {} func (x *FeatureRecommendationEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[89] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7756,7 +7867,7 @@ func (x *FeatureRecommendationEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use FeatureRecommendationEvent.ProtoReflect.Descriptor instead. func (*FeatureRecommendationEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{89} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{91} } func (x *FeatureRecommendationEvent) GetUserName() string { @@ -7794,7 +7905,7 @@ type LicenseLimitEvent struct { func (x *LicenseLimitEvent) Reset() { *x = LicenseLimitEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[90] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7806,7 +7917,7 @@ func (x *LicenseLimitEvent) String() string { func (*LicenseLimitEvent) ProtoMessage() {} func (x *LicenseLimitEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[90] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7819,7 +7930,7 @@ func (x *LicenseLimitEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LicenseLimitEvent.ProtoReflect.Descriptor instead. func (*LicenseLimitEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{90} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{92} } func (x *LicenseLimitEvent) GetLicenseLimit() LicenseLimit { @@ -7851,7 +7962,7 @@ type DesktopDirectoryShareEvent struct { func (x *DesktopDirectoryShareEvent) Reset() { *x = DesktopDirectoryShareEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[91] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7863,7 +7974,7 @@ func (x *DesktopDirectoryShareEvent) String() string { func (*DesktopDirectoryShareEvent) ProtoMessage() {} func (x *DesktopDirectoryShareEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[91] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7876,7 +7987,7 @@ func (x *DesktopDirectoryShareEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DesktopDirectoryShareEvent.ProtoReflect.Descriptor instead. func (*DesktopDirectoryShareEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{91} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{93} } func (x *DesktopDirectoryShareEvent) GetDesktop() string { @@ -7918,7 +8029,7 @@ type DesktopClipboardEvent struct { func (x *DesktopClipboardEvent) Reset() { *x = DesktopClipboardEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[92] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7930,7 +8041,7 @@ func (x *DesktopClipboardEvent) String() string { func (*DesktopClipboardEvent) ProtoMessage() {} func (x *DesktopClipboardEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[92] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7943,7 +8054,7 @@ func (x *DesktopClipboardEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DesktopClipboardEvent.ProtoReflect.Descriptor instead. func (*DesktopClipboardEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{92} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{94} } func (x *DesktopClipboardEvent) GetDesktop() string { @@ -7980,7 +8091,7 @@ type TAGExecuteQueryEvent struct { func (x *TAGExecuteQueryEvent) Reset() { *x = TAGExecuteQueryEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[93] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7992,7 +8103,7 @@ func (x *TAGExecuteQueryEvent) String() string { func (*TAGExecuteQueryEvent) ProtoMessage() {} func (x *TAGExecuteQueryEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[93] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8005,7 +8116,7 @@ func (x *TAGExecuteQueryEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use TAGExecuteQueryEvent.ProtoReflect.Descriptor instead. func (*TAGExecuteQueryEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{93} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{95} } func (x *TAGExecuteQueryEvent) GetUserName() string { @@ -8050,7 +8161,7 @@ type AccessGraphSecretsScanAuthorizedKeysEvent struct { func (x *AccessGraphSecretsScanAuthorizedKeysEvent) Reset() { *x = AccessGraphSecretsScanAuthorizedKeysEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[94] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8062,7 +8173,7 @@ func (x *AccessGraphSecretsScanAuthorizedKeysEvent) String() string { func (*AccessGraphSecretsScanAuthorizedKeysEvent) ProtoMessage() {} func (x *AccessGraphSecretsScanAuthorizedKeysEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[94] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8075,7 +8186,7 @@ func (x *AccessGraphSecretsScanAuthorizedKeysEvent) ProtoReflect() protoreflect. // Deprecated: Use AccessGraphSecretsScanAuthorizedKeysEvent.ProtoReflect.Descriptor instead. func (*AccessGraphSecretsScanAuthorizedKeysEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{94} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{96} } func (x *AccessGraphSecretsScanAuthorizedKeysEvent) GetHostId() string { @@ -8108,7 +8219,7 @@ type AccessGraphSecretsScanSSHPrivateKeysEvent struct { func (x *AccessGraphSecretsScanSSHPrivateKeysEvent) Reset() { *x = AccessGraphSecretsScanSSHPrivateKeysEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[95] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8120,7 +8231,7 @@ func (x *AccessGraphSecretsScanSSHPrivateKeysEvent) String() string { func (*AccessGraphSecretsScanSSHPrivateKeysEvent) ProtoMessage() {} func (x *AccessGraphSecretsScanSSHPrivateKeysEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[95] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8133,7 +8244,7 @@ func (x *AccessGraphSecretsScanSSHPrivateKeysEvent) ProtoReflect() protoreflect. // Deprecated: Use AccessGraphSecretsScanSSHPrivateKeysEvent.ProtoReflect.Descriptor instead. func (*AccessGraphSecretsScanSSHPrivateKeysEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{95} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{97} } func (x *AccessGraphSecretsScanSSHPrivateKeysEvent) GetDeviceId() string { @@ -8173,7 +8284,7 @@ type AccessGraphGitlabScanEvent struct { func (x *AccessGraphGitlabScanEvent) Reset() { *x = AccessGraphGitlabScanEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[96] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8185,7 +8296,7 @@ func (x *AccessGraphGitlabScanEvent) String() string { func (*AccessGraphGitlabScanEvent) ProtoMessage() {} func (x *AccessGraphGitlabScanEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[96] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8198,7 +8309,7 @@ func (x *AccessGraphGitlabScanEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessGraphGitlabScanEvent.ProtoReflect.Descriptor instead. func (*AccessGraphGitlabScanEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{96} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{98} } func (x *AccessGraphGitlabScanEvent) GetTotalProjects() uint64 { @@ -8254,7 +8365,7 @@ type AccessGraphAWSScanEvent struct { func (x *AccessGraphAWSScanEvent) Reset() { *x = AccessGraphAWSScanEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[97] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8266,7 +8377,7 @@ func (x *AccessGraphAWSScanEvent) String() string { func (*AccessGraphAWSScanEvent) ProtoMessage() {} func (x *AccessGraphAWSScanEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[97] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8279,7 +8390,7 @@ func (x *AccessGraphAWSScanEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessGraphAWSScanEvent.ProtoReflect.Descriptor instead. func (*AccessGraphAWSScanEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{97} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{99} } func (x *AccessGraphAWSScanEvent) GetTotalEc2Instances() uint64 { @@ -8373,7 +8484,7 @@ type AccessGraphAccessPathChangedEvent struct { func (x *AccessGraphAccessPathChangedEvent) Reset() { *x = AccessGraphAccessPathChangedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[98] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8385,7 +8496,7 @@ func (x *AccessGraphAccessPathChangedEvent) String() string { func (*AccessGraphAccessPathChangedEvent) ProtoMessage() {} func (x *AccessGraphAccessPathChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[98] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8398,7 +8509,7 @@ func (x *AccessGraphAccessPathChangedEvent) ProtoReflect() protoreflect.Message // Deprecated: Use AccessGraphAccessPathChangedEvent.ProtoReflect.Descriptor instead. func (*AccessGraphAccessPathChangedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{98} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{100} } func (x *AccessGraphAccessPathChangedEvent) GetAffectedResourceSource() string { @@ -8428,7 +8539,7 @@ type UIAccessGraphCrownJewelDiffViewEvent struct { func (x *UIAccessGraphCrownJewelDiffViewEvent) Reset() { *x = UIAccessGraphCrownJewelDiffViewEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[99] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8440,7 +8551,7 @@ func (x *UIAccessGraphCrownJewelDiffViewEvent) String() string { func (*UIAccessGraphCrownJewelDiffViewEvent) ProtoMessage() {} func (x *UIAccessGraphCrownJewelDiffViewEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[99] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8453,7 +8564,7 @@ func (x *UIAccessGraphCrownJewelDiffViewEvent) ProtoReflect() protoreflect.Messa // Deprecated: Use UIAccessGraphCrownJewelDiffViewEvent.ProtoReflect.Descriptor instead. func (*UIAccessGraphCrownJewelDiffViewEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{99} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{101} } func (x *UIAccessGraphCrownJewelDiffViewEvent) GetAffectedResourceSource() string { @@ -8487,7 +8598,7 @@ type UIPageViewEvent struct { func (x *UIPageViewEvent) Reset() { *x = UIPageViewEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[100] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8499,7 +8610,7 @@ func (x *UIPageViewEvent) String() string { func (*UIPageViewEvent) ProtoMessage() {} func (x *UIPageViewEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[100] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8512,7 +8623,7 @@ func (x *UIPageViewEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIPageViewEvent.ProtoReflect.Descriptor instead. func (*UIPageViewEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{100} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{102} } func (x *UIPageViewEvent) GetUserName() string { @@ -8561,7 +8672,7 @@ type UIUsageReportingAlertCtaClickEvent struct { func (x *UIUsageReportingAlertCtaClickEvent) Reset() { *x = UIUsageReportingAlertCtaClickEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[101] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8573,7 +8684,7 @@ func (x *UIUsageReportingAlertCtaClickEvent) String() string { func (*UIUsageReportingAlertCtaClickEvent) ProtoMessage() {} func (x *UIUsageReportingAlertCtaClickEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[101] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8586,7 +8697,7 @@ func (x *UIUsageReportingAlertCtaClickEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIUsageReportingAlertCtaClickEvent.ProtoReflect.Descriptor instead. func (*UIUsageReportingAlertCtaClickEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{101} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{103} } func (x *UIUsageReportingAlertCtaClickEvent) GetUserName() string { @@ -8627,7 +8738,7 @@ type AccessGraphCrownJewelCreateEvent struct { func (x *AccessGraphCrownJewelCreateEvent) Reset() { *x = AccessGraphCrownJewelCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[102] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8639,7 +8750,7 @@ func (x *AccessGraphCrownJewelCreateEvent) String() string { func (*AccessGraphCrownJewelCreateEvent) ProtoMessage() {} func (x *AccessGraphCrownJewelCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[102] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8652,7 +8763,7 @@ func (x *AccessGraphCrownJewelCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessGraphCrownJewelCreateEvent.ProtoReflect.Descriptor instead. func (*AccessGraphCrownJewelCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{102} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{104} } // ExternalAuditStorageAuthenticateEvent is emitted when the External Audit @@ -8667,7 +8778,7 @@ type ExternalAuditStorageAuthenticateEvent struct { func (x *ExternalAuditStorageAuthenticateEvent) Reset() { *x = ExternalAuditStorageAuthenticateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[103] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8679,7 +8790,7 @@ func (x *ExternalAuditStorageAuthenticateEvent) String() string { func (*ExternalAuditStorageAuthenticateEvent) ProtoMessage() {} func (x *ExternalAuditStorageAuthenticateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[103] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8692,7 +8803,7 @@ func (x *ExternalAuditStorageAuthenticateEvent) ProtoReflect() protoreflect.Mess // Deprecated: Use ExternalAuditStorageAuthenticateEvent.ProtoReflect.Descriptor instead. func (*ExternalAuditStorageAuthenticateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{103} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{105} } // SecurityReportGetResultEvent is emitted when the user requests a security report. @@ -8710,7 +8821,7 @@ type SecurityReportGetResultEvent struct { func (x *SecurityReportGetResultEvent) Reset() { *x = SecurityReportGetResultEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[104] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8722,7 +8833,7 @@ func (x *SecurityReportGetResultEvent) String() string { func (*SecurityReportGetResultEvent) ProtoMessage() {} func (x *SecurityReportGetResultEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[104] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8735,7 +8846,7 @@ func (x *SecurityReportGetResultEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SecurityReportGetResultEvent.ProtoReflect.Descriptor instead. func (*SecurityReportGetResultEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{104} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{106} } func (x *SecurityReportGetResultEvent) GetUserName() string { @@ -8774,7 +8885,7 @@ type AuditQueryRunEvent struct { func (x *AuditQueryRunEvent) Reset() { *x = AuditQueryRunEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[105] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8786,7 +8897,7 @@ func (x *AuditQueryRunEvent) String() string { func (*AuditQueryRunEvent) ProtoMessage() {} func (x *AuditQueryRunEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[105] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8799,7 +8910,7 @@ func (x *AuditQueryRunEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AuditQueryRunEvent.ProtoReflect.Descriptor instead. func (*AuditQueryRunEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{105} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{107} } func (x *AuditQueryRunEvent) GetUserName() string { @@ -8843,7 +8954,7 @@ type DiscoveryFetchEvent struct { func (x *DiscoveryFetchEvent) Reset() { *x = DiscoveryFetchEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[106] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8855,7 +8966,7 @@ func (x *DiscoveryFetchEvent) String() string { func (*DiscoveryFetchEvent) ProtoMessage() {} func (x *DiscoveryFetchEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[106] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8868,7 +8979,7 @@ func (x *DiscoveryFetchEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryFetchEvent.ProtoReflect.Descriptor instead. func (*DiscoveryFetchEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{106} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{108} } func (x *DiscoveryFetchEvent) GetCloudProvider() string { @@ -8909,7 +9020,7 @@ type OktaAccessListSyncEvent struct { func (x *OktaAccessListSyncEvent) Reset() { *x = OktaAccessListSyncEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[107] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8921,7 +9032,7 @@ func (x *OktaAccessListSyncEvent) String() string { func (*OktaAccessListSyncEvent) ProtoMessage() {} func (x *OktaAccessListSyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[107] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8934,7 +9045,7 @@ func (x *OktaAccessListSyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use OktaAccessListSyncEvent.ProtoReflect.Descriptor instead. func (*OktaAccessListSyncEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{107} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{109} } func (x *OktaAccessListSyncEvent) GetNumAppFilters() int32 { @@ -9001,7 +9112,7 @@ type DatabaseUserCreatedEvent struct { func (x *DatabaseUserCreatedEvent) Reset() { *x = DatabaseUserCreatedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[108] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9013,7 +9124,7 @@ func (x *DatabaseUserCreatedEvent) String() string { func (*DatabaseUserCreatedEvent) ProtoMessage() {} func (x *DatabaseUserCreatedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[108] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9026,7 +9137,7 @@ func (x *DatabaseUserCreatedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DatabaseUserCreatedEvent.ProtoReflect.Descriptor instead. func (*DatabaseUserCreatedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{108} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{110} } func (x *DatabaseUserCreatedEvent) GetDatabase() *SessionStartDatabaseMetadata { @@ -9068,7 +9179,7 @@ type DatabaseUserPermissionsUpdateEvent struct { func (x *DatabaseUserPermissionsUpdateEvent) Reset() { *x = DatabaseUserPermissionsUpdateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[109] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9080,7 +9191,7 @@ func (x *DatabaseUserPermissionsUpdateEvent) String() string { func (*DatabaseUserPermissionsUpdateEvent) ProtoMessage() {} func (x *DatabaseUserPermissionsUpdateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[109] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9093,7 +9204,7 @@ func (x *DatabaseUserPermissionsUpdateEvent) ProtoReflect() protoreflect.Message // Deprecated: Use DatabaseUserPermissionsUpdateEvent.ProtoReflect.Descriptor instead. func (*DatabaseUserPermissionsUpdateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{109} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{111} } func (x *DatabaseUserPermissionsUpdateEvent) GetDatabase() *SessionStartDatabaseMetadata { @@ -9152,7 +9263,7 @@ type SessionRecordingAccessEvent struct { func (x *SessionRecordingAccessEvent) Reset() { *x = SessionRecordingAccessEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[110] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9164,7 +9275,7 @@ func (x *SessionRecordingAccessEvent) String() string { func (*SessionRecordingAccessEvent) ProtoMessage() {} func (x *SessionRecordingAccessEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[110] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9177,7 +9288,7 @@ func (x *SessionRecordingAccessEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRecordingAccessEvent.ProtoReflect.Descriptor instead. func (*SessionRecordingAccessEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{110} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{112} } func (x *SessionRecordingAccessEvent) GetSessionType() string { @@ -9240,7 +9351,7 @@ type UserTaskStateEvent struct { func (x *UserTaskStateEvent) Reset() { *x = UserTaskStateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[111] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9252,7 +9363,7 @@ func (x *UserTaskStateEvent) String() string { func (*UserTaskStateEvent) ProtoMessage() {} func (x *UserTaskStateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[111] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9265,7 +9376,7 @@ func (x *UserTaskStateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserTaskStateEvent.ProtoReflect.Descriptor instead. func (*UserTaskStateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{111} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{113} } func (x *UserTaskStateEvent) GetTaskType() string { @@ -9320,7 +9431,7 @@ type AccessRequestCreateEvent struct { func (x *AccessRequestCreateEvent) Reset() { *x = AccessRequestCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[112] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9332,7 +9443,7 @@ func (x *AccessRequestCreateEvent) String() string { func (*AccessRequestCreateEvent) ProtoMessage() {} func (x *AccessRequestCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[112] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9345,7 +9456,7 @@ func (x *AccessRequestCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessRequestCreateEvent.ProtoReflect.Descriptor instead. func (*AccessRequestCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{112} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{114} } func (x *AccessRequestCreateEvent) GetUserName() string { @@ -9388,7 +9499,7 @@ type AccessRequestReviewEvent struct { func (x *AccessRequestReviewEvent) Reset() { *x = AccessRequestReviewEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[113] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9400,7 +9511,7 @@ func (x *AccessRequestReviewEvent) String() string { func (*AccessRequestReviewEvent) ProtoMessage() {} func (x *AccessRequestReviewEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[113] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9413,7 +9524,7 @@ func (x *AccessRequestReviewEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessRequestReviewEvent.ProtoReflect.Descriptor instead. func (*AccessRequestReviewEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{113} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{115} } func (x *AccessRequestReviewEvent) GetUserName() string { @@ -9474,7 +9585,7 @@ type SessionSummaryAccessEvent struct { func (x *SessionSummaryAccessEvent) Reset() { *x = SessionSummaryAccessEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[114] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9486,7 +9597,7 @@ func (x *SessionSummaryAccessEvent) String() string { func (*SessionSummaryAccessEvent) ProtoMessage() {} func (x *SessionSummaryAccessEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[114] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9499,7 +9610,7 @@ func (x *SessionSummaryAccessEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionSummaryAccessEvent.ProtoReflect.Descriptor instead. func (*SessionSummaryAccessEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{114} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{116} } func (x *SessionSummaryAccessEvent) GetSessionType() string { @@ -9578,7 +9689,7 @@ type SessionSummaryCreateEvent struct { func (x *SessionSummaryCreateEvent) Reset() { *x = SessionSummaryCreateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[115] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9590,7 +9701,7 @@ func (x *SessionSummaryCreateEvent) String() string { func (*SessionSummaryCreateEvent) ProtoMessage() {} func (x *SessionSummaryCreateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[115] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9603,7 +9714,7 @@ func (x *SessionSummaryCreateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionSummaryCreateEvent.ProtoReflect.Descriptor instead. func (*SessionSummaryCreateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{115} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{117} } func (x *SessionSummaryCreateEvent) GetSessionType() string { @@ -9690,7 +9801,7 @@ type SessionSummarySearchEvent struct { func (x *SessionSummarySearchEvent) Reset() { *x = SessionSummarySearchEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[116] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9702,7 +9813,7 @@ func (x *SessionSummarySearchEvent) String() string { func (*SessionSummarySearchEvent) ProtoMessage() {} func (x *SessionSummarySearchEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[116] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9715,7 +9826,7 @@ func (x *SessionSummarySearchEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionSummarySearchEvent.ProtoReflect.Descriptor instead. func (*SessionSummarySearchEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{116} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{118} } func (x *SessionSummarySearchEvent) GetUserName() string { @@ -9767,7 +9878,7 @@ type DiscoveryConfigEvent struct { func (x *DiscoveryConfigEvent) Reset() { *x = DiscoveryConfigEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[117] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9779,7 +9890,7 @@ func (x *DiscoveryConfigEvent) String() string { func (*DiscoveryConfigEvent) ProtoMessage() {} func (x *DiscoveryConfigEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[117] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9792,7 +9903,7 @@ func (x *DiscoveryConfigEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryConfigEvent.ProtoReflect.Descriptor instead. func (*DiscoveryConfigEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{117} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{119} } func (x *DiscoveryConfigEvent) GetAction() DiscoveryConfigAction { @@ -9847,7 +9958,7 @@ type IdentitySecurityGraphSizeEvent struct { func (x *IdentitySecurityGraphSizeEvent) Reset() { *x = IdentitySecurityGraphSizeEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[118] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9859,7 +9970,7 @@ func (x *IdentitySecurityGraphSizeEvent) String() string { func (*IdentitySecurityGraphSizeEvent) ProtoMessage() {} func (x *IdentitySecurityGraphSizeEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[118] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9872,7 +9983,7 @@ func (x *IdentitySecurityGraphSizeEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use IdentitySecurityGraphSizeEvent.ProtoReflect.Descriptor instead. func (*IdentitySecurityGraphSizeEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{118} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{120} } func (x *IdentitySecurityGraphSizeEvent) GetProvider() string { @@ -9911,7 +10022,7 @@ type IdentitySecurityAuditLogsIngestedEvent struct { func (x *IdentitySecurityAuditLogsIngestedEvent) Reset() { *x = IdentitySecurityAuditLogsIngestedEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[119] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9923,7 +10034,7 @@ func (x *IdentitySecurityAuditLogsIngestedEvent) String() string { func (*IdentitySecurityAuditLogsIngestedEvent) ProtoMessage() {} func (x *IdentitySecurityAuditLogsIngestedEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[119] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9936,7 +10047,7 @@ func (x *IdentitySecurityAuditLogsIngestedEvent) ProtoReflect() protoreflect.Mes // Deprecated: Use IdentitySecurityAuditLogsIngestedEvent.ProtoReflect.Descriptor instead. func (*IdentitySecurityAuditLogsIngestedEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{119} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{121} } func (x *IdentitySecurityAuditLogsIngestedEvent) GetProvider() string { @@ -9966,7 +10077,7 @@ type AccessListStepStatus struct { func (x *AccessListStepStatus) Reset() { *x = AccessListStepStatus{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[120] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9978,7 +10089,7 @@ func (x *AccessListStepStatus) String() string { func (*AccessListStepStatus) ProtoMessage() {} func (x *AccessListStepStatus) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[120] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9991,7 +10102,7 @@ func (x *AccessListStepStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListStepStatus.ProtoReflect.Descriptor instead. func (*AccessListStepStatus) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{120} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{122} } func (x *AccessListStepStatus) GetStatus() AccessListStatus { @@ -10020,7 +10131,7 @@ type UIAccessListDefineAccessEvent struct { func (x *UIAccessListDefineAccessEvent) Reset() { *x = UIAccessListDefineAccessEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[121] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10032,7 +10143,7 @@ func (x *UIAccessListDefineAccessEvent) String() string { func (*UIAccessListDefineAccessEvent) ProtoMessage() {} func (x *UIAccessListDefineAccessEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[121] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10045,7 +10156,7 @@ func (x *UIAccessListDefineAccessEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListDefineAccessEvent.ProtoReflect.Descriptor instead. func (*UIAccessListDefineAccessEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{121} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{123} } func (x *UIAccessListDefineAccessEvent) GetMetadata() *AccessListMetadata { @@ -10074,7 +10185,7 @@ type UIAccessListDefineIdentitiesEvent struct { func (x *UIAccessListDefineIdentitiesEvent) Reset() { *x = UIAccessListDefineIdentitiesEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[122] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10086,7 +10197,7 @@ func (x *UIAccessListDefineIdentitiesEvent) String() string { func (*UIAccessListDefineIdentitiesEvent) ProtoMessage() {} func (x *UIAccessListDefineIdentitiesEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[122] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10099,7 +10210,7 @@ func (x *UIAccessListDefineIdentitiesEvent) ProtoReflect() protoreflect.Message // Deprecated: Use UIAccessListDefineIdentitiesEvent.ProtoReflect.Descriptor instead. func (*UIAccessListDefineIdentitiesEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{122} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{124} } func (x *UIAccessListDefineIdentitiesEvent) GetMetadata() *AccessListMetadata { @@ -10128,7 +10239,7 @@ type UIAccessListDefineBasicInfoEvent struct { func (x *UIAccessListDefineBasicInfoEvent) Reset() { *x = UIAccessListDefineBasicInfoEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[123] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10140,7 +10251,7 @@ func (x *UIAccessListDefineBasicInfoEvent) String() string { func (*UIAccessListDefineBasicInfoEvent) ProtoMessage() {} func (x *UIAccessListDefineBasicInfoEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[123] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10153,7 +10264,7 @@ func (x *UIAccessListDefineBasicInfoEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListDefineBasicInfoEvent.ProtoReflect.Descriptor instead. func (*UIAccessListDefineBasicInfoEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{123} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{125} } func (x *UIAccessListDefineBasicInfoEvent) GetMetadata() *AccessListMetadata { @@ -10182,7 +10293,7 @@ type UIAccessListDefineMembersEvent struct { func (x *UIAccessListDefineMembersEvent) Reset() { *x = UIAccessListDefineMembersEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[124] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10194,7 +10305,7 @@ func (x *UIAccessListDefineMembersEvent) String() string { func (*UIAccessListDefineMembersEvent) ProtoMessage() {} func (x *UIAccessListDefineMembersEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[124] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10207,7 +10318,7 @@ func (x *UIAccessListDefineMembersEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListDefineMembersEvent.ProtoReflect.Descriptor instead. func (*UIAccessListDefineMembersEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{124} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{126} } func (x *UIAccessListDefineMembersEvent) GetMetadata() *AccessListMetadata { @@ -10236,7 +10347,7 @@ type UIAccessListDefineOwnersEvent struct { func (x *UIAccessListDefineOwnersEvent) Reset() { *x = UIAccessListDefineOwnersEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[125] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10248,7 +10359,7 @@ func (x *UIAccessListDefineOwnersEvent) String() string { func (*UIAccessListDefineOwnersEvent) ProtoMessage() {} func (x *UIAccessListDefineOwnersEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[125] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10261,7 +10372,7 @@ func (x *UIAccessListDefineOwnersEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListDefineOwnersEvent.ProtoReflect.Descriptor instead. func (*UIAccessListDefineOwnersEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{125} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{127} } func (x *UIAccessListDefineOwnersEvent) GetMetadata() *AccessListMetadata { @@ -10290,7 +10401,7 @@ type UIAccessListStartEvent struct { func (x *UIAccessListStartEvent) Reset() { *x = UIAccessListStartEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[126] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10302,7 +10413,7 @@ func (x *UIAccessListStartEvent) String() string { func (*UIAccessListStartEvent) ProtoMessage() {} func (x *UIAccessListStartEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[126] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10315,7 +10426,7 @@ func (x *UIAccessListStartEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListStartEvent.ProtoReflect.Descriptor instead. func (*UIAccessListStartEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{126} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{128} } func (x *UIAccessListStartEvent) GetMetadata() *AccessListMetadata { @@ -10344,7 +10455,7 @@ type UIAccessListCustomEvent struct { func (x *UIAccessListCustomEvent) Reset() { *x = UIAccessListCustomEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[127] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10356,7 +10467,7 @@ func (x *UIAccessListCustomEvent) String() string { func (*UIAccessListCustomEvent) ProtoMessage() {} func (x *UIAccessListCustomEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[127] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10369,7 +10480,7 @@ func (x *UIAccessListCustomEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListCustomEvent.ProtoReflect.Descriptor instead. func (*UIAccessListCustomEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{127} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{129} } func (x *UIAccessListCustomEvent) GetMetadata() *AccessListMetadata { @@ -10401,7 +10512,7 @@ type UIAccessListCompleteEvent struct { func (x *UIAccessListCompleteEvent) Reset() { *x = UIAccessListCompleteEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[128] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10413,7 +10524,7 @@ func (x *UIAccessListCompleteEvent) String() string { func (*UIAccessListCompleteEvent) ProtoMessage() {} func (x *UIAccessListCompleteEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[128] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10426,7 +10537,7 @@ func (x *UIAccessListCompleteEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListCompleteEvent.ProtoReflect.Descriptor instead. func (*UIAccessListCompleteEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{128} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{130} } func (x *UIAccessListCompleteEvent) GetMetadata() *AccessListMetadata { @@ -10462,7 +10573,7 @@ type UIAccessListIntegrateEvent struct { func (x *UIAccessListIntegrateEvent) Reset() { *x = UIAccessListIntegrateEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[129] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10474,7 +10585,7 @@ func (x *UIAccessListIntegrateEvent) String() string { func (*UIAccessListIntegrateEvent) ProtoMessage() {} func (x *UIAccessListIntegrateEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[129] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10487,7 +10598,7 @@ func (x *UIAccessListIntegrateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIAccessListIntegrateEvent.ProtoReflect.Descriptor instead. func (*UIAccessListIntegrateEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{129} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{131} } func (x *UIAccessListIntegrateEvent) GetMetadata() *AccessListMetadata { @@ -10534,7 +10645,7 @@ type UIInteractionEvent struct { func (x *UIInteractionEvent) Reset() { *x = UIInteractionEvent{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[130] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10546,7 +10657,7 @@ func (x *UIInteractionEvent) String() string { func (*UIInteractionEvent) ProtoMessage() {} func (x *UIInteractionEvent) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[130] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10559,7 +10670,7 @@ func (x *UIInteractionEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UIInteractionEvent.ProtoReflect.Descriptor instead. func (*UIInteractionEvent) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{130} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{132} } func (x *UIInteractionEvent) GetUserName() string { @@ -10728,6 +10839,8 @@ type SubmitEventRequest struct { // *SubmitEventRequest_UiInteraction // *SubmitEventRequest_BeamsCreated // *SubmitEventRequest_BeamsDestroyed + // *SubmitEventRequest_BeamsPublished + // *SubmitEventRequest_BeamsUnpublished Event isSubmitEventRequest_Event `protobuf_oneof:"event"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -10735,7 +10848,7 @@ type SubmitEventRequest struct { func (x *SubmitEventRequest) Reset() { *x = SubmitEventRequest{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[131] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10747,7 +10860,7 @@ func (x *SubmitEventRequest) String() string { func (*SubmitEventRequest) ProtoMessage() {} func (x *SubmitEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[131] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10760,7 +10873,7 @@ func (x *SubmitEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitEventRequest.ProtoReflect.Descriptor instead. func (*SubmitEventRequest) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{131} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{133} } func (x *SubmitEventRequest) GetClusterName() string { @@ -11853,6 +11966,24 @@ func (x *SubmitEventRequest) GetBeamsDestroyed() *BeamsDestroyedEvent { return nil } +func (x *SubmitEventRequest) GetBeamsPublished() *BeamsPublishedEvent { + if x != nil { + if x, ok := x.Event.(*SubmitEventRequest_BeamsPublished); ok { + return x.BeamsPublished + } + } + return nil +} + +func (x *SubmitEventRequest) GetBeamsUnpublished() *BeamsUnpublishedEvent { + if x != nil { + if x, ok := x.Event.(*SubmitEventRequest_BeamsUnpublished); ok { + return x.BeamsUnpublished + } + } + return nil +} + type isSubmitEventRequest_Event interface { isSubmitEventRequest_Event() } @@ -12334,6 +12465,14 @@ type SubmitEventRequest_BeamsDestroyed struct { BeamsDestroyed *BeamsDestroyedEvent `protobuf:"bytes,124,opt,name=beams_destroyed,json=beamsDestroyed,proto3,oneof"` } +type SubmitEventRequest_BeamsPublished struct { + BeamsPublished *BeamsPublishedEvent `protobuf:"bytes,125,opt,name=beams_published,json=beamsPublished,proto3,oneof"` +} + +type SubmitEventRequest_BeamsUnpublished struct { + BeamsUnpublished *BeamsUnpublishedEvent `protobuf:"bytes,126,opt,name=beams_unpublished,json=beamsUnpublished,proto3,oneof"` +} + func (*SubmitEventRequest_UserLogin) isSubmitEventRequest_Event() {} func (*SubmitEventRequest_SsoCreate) isSubmitEventRequest_Event() {} @@ -12572,6 +12711,10 @@ func (*SubmitEventRequest_BeamsCreated) isSubmitEventRequest_Event() {} func (*SubmitEventRequest_BeamsDestroyed) isSubmitEventRequest_Event() {} +func (*SubmitEventRequest_BeamsPublished) isSubmitEventRequest_Event() {} + +func (*SubmitEventRequest_BeamsUnpublished) isSubmitEventRequest_Event() {} + type SubmitEventResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -12580,7 +12723,7 @@ type SubmitEventResponse struct { func (x *SubmitEventResponse) Reset() { *x = SubmitEventResponse{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[132] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12592,7 +12735,7 @@ func (x *SubmitEventResponse) String() string { func (*SubmitEventResponse) ProtoMessage() {} func (x *SubmitEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[132] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12605,7 +12748,7 @@ func (x *SubmitEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitEventResponse.ProtoReflect.Descriptor instead. func (*SubmitEventResponse) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{132} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{134} } type SubmitEventsRequest struct { @@ -12618,7 +12761,7 @@ type SubmitEventsRequest struct { func (x *SubmitEventsRequest) Reset() { *x = SubmitEventsRequest{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[133] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12630,7 +12773,7 @@ func (x *SubmitEventsRequest) String() string { func (*SubmitEventsRequest) ProtoMessage() {} func (x *SubmitEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[133] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12643,7 +12786,7 @@ func (x *SubmitEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitEventsRequest.ProtoReflect.Descriptor instead. func (*SubmitEventsRequest) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{133} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{135} } func (x *SubmitEventsRequest) GetEvents() []*SubmitEventRequest { @@ -12661,7 +12804,7 @@ type SubmitEventsResponse struct { func (x *SubmitEventsResponse) Reset() { *x = SubmitEventsResponse{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[134] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12673,7 +12816,7 @@ func (x *SubmitEventsResponse) String() string { func (*SubmitEventsResponse) ProtoMessage() {} func (x *SubmitEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[134] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12686,7 +12829,7 @@ func (x *SubmitEventsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitEventsResponse.ProtoReflect.Descriptor instead. func (*SubmitEventsResponse) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{134} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{136} } type HelloTeleportRequest struct { @@ -12697,7 +12840,7 @@ type HelloTeleportRequest struct { func (x *HelloTeleportRequest) Reset() { *x = HelloTeleportRequest{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[135] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12709,7 +12852,7 @@ func (x *HelloTeleportRequest) String() string { func (*HelloTeleportRequest) ProtoMessage() {} func (x *HelloTeleportRequest) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[135] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12722,7 +12865,7 @@ func (x *HelloTeleportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HelloTeleportRequest.ProtoReflect.Descriptor instead. func (*HelloTeleportRequest) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{135} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{137} } type HelloTeleportResponse struct { @@ -12733,7 +12876,7 @@ type HelloTeleportResponse struct { func (x *HelloTeleportResponse) Reset() { *x = HelloTeleportResponse{} - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[136] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12745,7 +12888,7 @@ func (x *HelloTeleportResponse) String() string { func (*HelloTeleportResponse) ProtoMessage() {} func (x *HelloTeleportResponse) ProtoReflect() protoreflect.Message { - mi := &file_prehog_v1alpha_teleport_proto_msgTypes[136] + mi := &file_prehog_v1alpha_teleport_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12758,7 +12901,7 @@ func (x *HelloTeleportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HelloTeleportResponse.ProtoReflect.Descriptor instead. func (*HelloTeleportResponse) Descriptor() ([]byte, []int) { - return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{136} + return file_prehog_v1alpha_teleport_proto_rawDescGZIP(), []int{138} } var File_prehog_v1alpha_teleport_proto protoreflect.FileDescriptor @@ -12842,7 +12985,12 @@ const file_prehog_v1alpha_teleport_proto_rawDesc = "" + "\x13BeamsDestroyedEvent\x12\x17\n" + "\abeam_id\x18\x01 \x01(\tR\x06beamId\x129\n" + "\x06reason\x18\x02 \x01(\x0e2!.prehog.v1alpha.BeamDestroyReasonR\x06reason\x12\x16\n" + - "\x06region\x18\x03 \x01(\tR\x06region\"\xe7\x02\n" + + "\x06region\x18\x03 \x01(\tR\x06region\"J\n" + + "\x13BeamsPublishedEvent\x12\x17\n" + + "\abeam_id\x18\x01 \x01(\tR\x06beamId\x12\x1a\n" + + "\bprotocol\x18\x02 \x01(\tR\bprotocol\"0\n" + + "\x15BeamsUnpublishedEvent\x12\x17\n" + + "\abeam_id\x18\x01 \x01(\tR\x06beamId\"\xe7\x02\n" + "\x1aUserCertificateIssuedEvent\x12\x1b\n" + "\tuser_name\x18\x01 \x01(\tR\buserName\x12+\n" + "\x03ttl\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x12\x15\n" + @@ -13367,7 +13515,7 @@ const file_prehog_v1alpha_teleport_proto_rawDesc = "" + "\x06params\x18\x04 \x03(\v2..prehog.v1alpha.UIInteractionEvent.ParamsEntryR\x06params\x1a9\n" + "\vParamsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf7g\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9di\n" + "\x12SubmitEventRequest\x12!\n" + "\fcluster_name\x18\x01 \x01(\tR\vclusterName\x128\n" + "\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12)\n" + @@ -13495,7 +13643,9 @@ const file_prehog_v1alpha_teleport_proto_rawDesc = "" + "\x1csession_summary_search_event\x18y \x01(\v2).prehog.v1alpha.SessionSummarySearchEventH\x00R\x19sessionSummarySearchEvent\x12K\n" + "\x0eui_interaction\x18z \x01(\v2\".prehog.v1alpha.UIInteractionEventH\x00R\ruiInteraction\x12H\n" + "\rbeams_created\x18{ \x01(\v2!.prehog.v1alpha.BeamsCreatedEventH\x00R\fbeamsCreated\x12N\n" + - "\x0fbeams_destroyed\x18| \x01(\v2#.prehog.v1alpha.BeamsDestroyedEventH\x00R\x0ebeamsDestroyedB\a\n" + + "\x0fbeams_destroyed\x18| \x01(\v2#.prehog.v1alpha.BeamsDestroyedEventH\x00R\x0ebeamsDestroyed\x12N\n" + + "\x0fbeams_published\x18} \x01(\v2#.prehog.v1alpha.BeamsPublishedEventH\x00R\x0ebeamsPublished\x12T\n" + + "\x11beams_unpublished\x18~ \x01(\v2%.prehog.v1alpha.BeamsUnpublishedEventH\x00R\x10beamsUnpublishedB\a\n" + "\x05eventJ\x04\b\b\x10\tJ\x04\ba\x10bJ\x04\bb\x10cR\x1cui_onboard_get_started_clickR\x1baccess_request_create_eventR\x1baccess_request_review_event\"\x15\n" + "\x13SubmitEventResponse\"Q\n" + "\x13SubmitEventsRequest\x12:\n" + @@ -13734,7 +13884,7 @@ func file_prehog_v1alpha_teleport_proto_rawDescGZIP() []byte { } var file_prehog_v1alpha_teleport_proto_enumTypes = make([]protoimpl.EnumInfo, 25) -var file_prehog_v1alpha_teleport_proto_msgTypes = make([]protoimpl.MessageInfo, 138) +var file_prehog_v1alpha_teleport_proto_msgTypes = make([]protoimpl.MessageInfo, 140) var file_prehog_v1alpha_teleport_proto_goTypes = []any{ (UserOrigin)(0), // 0: prehog.v1alpha.UserOrigin (ResourceKind)(0), // 1: prehog.v1alpha.ResourceKind @@ -13776,131 +13926,133 @@ var file_prehog_v1alpha_teleport_proto_goTypes = []any{ (*SessionStartBeamMetadata)(nil), // 37: prehog.v1alpha.SessionStartBeamMetadata (*BeamsCreatedEvent)(nil), // 38: prehog.v1alpha.BeamsCreatedEvent (*BeamsDestroyedEvent)(nil), // 39: prehog.v1alpha.BeamsDestroyedEvent - (*UserCertificateIssuedEvent)(nil), // 40: prehog.v1alpha.UserCertificateIssuedEvent - (*SPIFFESVIDIssuedEvent)(nil), // 41: prehog.v1alpha.SPIFFESVIDIssuedEvent - (*UIBannerClickEvent)(nil), // 42: prehog.v1alpha.UIBannerClickEvent - (*UIOnboardCompleteGoToDashboardClickEvent)(nil), // 43: prehog.v1alpha.UIOnboardCompleteGoToDashboardClickEvent - (*UIOnboardAddFirstResourceClickEvent)(nil), // 44: prehog.v1alpha.UIOnboardAddFirstResourceClickEvent - (*UIOnboardAddFirstResourceLaterClickEvent)(nil), // 45: prehog.v1alpha.UIOnboardAddFirstResourceLaterClickEvent - (*UIOnboardSetCredentialSubmitEvent)(nil), // 46: prehog.v1alpha.UIOnboardSetCredentialSubmitEvent - (*UIOnboardRegisterChallengeSubmitEvent)(nil), // 47: prehog.v1alpha.UIOnboardRegisterChallengeSubmitEvent - (*UIOnboardQuestionnaireSubmitEvent)(nil), // 48: prehog.v1alpha.UIOnboardQuestionnaireSubmitEvent - (*UIRecoveryCodesContinueClickEvent)(nil), // 49: prehog.v1alpha.UIRecoveryCodesContinueClickEvent - (*UIRecoveryCodesCopyClickEvent)(nil), // 50: prehog.v1alpha.UIRecoveryCodesCopyClickEvent - (*UIRecoveryCodesPrintClickEvent)(nil), // 51: prehog.v1alpha.UIRecoveryCodesPrintClickEvent - (*DiscoverMetadata)(nil), // 52: prehog.v1alpha.DiscoverMetadata - (*DiscoverResourceMetadata)(nil), // 53: prehog.v1alpha.DiscoverResourceMetadata - (*DiscoverStepStatus)(nil), // 54: prehog.v1alpha.DiscoverStepStatus - (*UIDiscoverStartedEvent)(nil), // 55: prehog.v1alpha.UIDiscoverStartedEvent - (*UIDiscoverResourceSelectionEvent)(nil), // 56: prehog.v1alpha.UIDiscoverResourceSelectionEvent - (*UIDiscoverIntegrationAWSOIDCConnectEvent)(nil), // 57: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent - (*UIDiscoverDatabaseRDSEnrollEvent)(nil), // 58: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent - (*UIDiscoverKubeEKSEnrollEvent)(nil), // 59: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent - (*UIDiscoverDeployServiceEvent)(nil), // 60: prehog.v1alpha.UIDiscoverDeployServiceEvent - (*UIDiscoverCreateDiscoveryConfigEvent)(nil), // 61: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent - (*UIDiscoverDatabaseRegisterEvent)(nil), // 62: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent - (*UIDiscoverDatabaseConfigureMTLSEvent)(nil), // 63: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent - (*UIDiscoverDesktopActiveDirectoryToolsInstallEvent)(nil), // 64: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent - (*UIDiscoverDesktopActiveDirectoryConfigureEvent)(nil), // 65: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent - (*UIDiscoverAutoDiscoveredResourcesEvent)(nil), // 66: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent - (*UIDiscoverEC2InstanceSelectionEvent)(nil), // 67: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent - (*UIDiscoverDeployEICEEvent)(nil), // 68: prehog.v1alpha.UIDiscoverDeployEICEEvent - (*UIDiscoverCreateNodeEvent)(nil), // 69: prehog.v1alpha.UIDiscoverCreateNodeEvent - (*UIDiscoverCreateAppServerEvent)(nil), // 70: prehog.v1alpha.UIDiscoverCreateAppServerEvent - (*UIDiscoverDatabaseConfigureIAMPolicyEvent)(nil), // 71: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent - (*UIDiscoverPrincipalsConfigureEvent)(nil), // 72: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent - (*UIDiscoverTestConnectionEvent)(nil), // 73: prehog.v1alpha.UIDiscoverTestConnectionEvent - (*UIDiscoverCompletedEvent)(nil), // 74: prehog.v1alpha.UIDiscoverCompletedEvent - (*RoleCreateEvent)(nil), // 75: prehog.v1alpha.RoleCreateEvent - (*BotCreateEvent)(nil), // 76: prehog.v1alpha.BotCreateEvent - (*BotJoinEvent)(nil), // 77: prehog.v1alpha.BotJoinEvent - (*UICreateNewRoleClickEvent)(nil), // 78: prehog.v1alpha.UICreateNewRoleClickEvent - (*UICreateNewRoleSaveClickEvent)(nil), // 79: prehog.v1alpha.UICreateNewRoleSaveClickEvent - (*UICreateNewRoleCancelClickEvent)(nil), // 80: prehog.v1alpha.UICreateNewRoleCancelClickEvent - (*UICreateNewRoleViewDocumentationClickEvent)(nil), // 81: prehog.v1alpha.UICreateNewRoleViewDocumentationClickEvent - (*UICallToActionClickEvent)(nil), // 82: prehog.v1alpha.UICallToActionClickEvent - (*KubeRequestEvent)(nil), // 83: prehog.v1alpha.KubeRequestEvent - (*SFTPEvent)(nil), // 84: prehog.v1alpha.SFTPEvent - (*AgentMetadataEvent)(nil), // 85: prehog.v1alpha.AgentMetadataEvent - (*AssistCompletionEvent)(nil), // 86: prehog.v1alpha.AssistCompletionEvent - (*AssistExecutionEvent)(nil), // 87: prehog.v1alpha.AssistExecutionEvent - (*AssistNewConversationEvent)(nil), // 88: prehog.v1alpha.AssistNewConversationEvent - (*AssistAccessRequestEvent)(nil), // 89: prehog.v1alpha.AssistAccessRequestEvent - (*AssistActionEvent)(nil), // 90: prehog.v1alpha.AssistActionEvent - (*AccessListMetadata)(nil), // 91: prehog.v1alpha.AccessListMetadata - (*AccessListCreateEvent)(nil), // 92: prehog.v1alpha.AccessListCreateEvent - (*AccessListUpdateEvent)(nil), // 93: prehog.v1alpha.AccessListUpdateEvent - (*AccessListDeleteEvent)(nil), // 94: prehog.v1alpha.AccessListDeleteEvent - (*AccessListMemberCreateEvent)(nil), // 95: prehog.v1alpha.AccessListMemberCreateEvent - (*AccessListMemberUpdateEvent)(nil), // 96: prehog.v1alpha.AccessListMemberUpdateEvent - (*AccessListMemberDeleteEvent)(nil), // 97: prehog.v1alpha.AccessListMemberDeleteEvent - (*AccessListGrantsToUserEvent)(nil), // 98: prehog.v1alpha.AccessListGrantsToUserEvent - (*AccessListReviewCreateEvent)(nil), // 99: prehog.v1alpha.AccessListReviewCreateEvent - (*AccessListReviewDeleteEvent)(nil), // 100: prehog.v1alpha.AccessListReviewDeleteEvent - (*AccessListReviewComplianceEvent)(nil), // 101: prehog.v1alpha.AccessListReviewComplianceEvent - (*IntegrationEnrollMetadata)(nil), // 102: prehog.v1alpha.IntegrationEnrollMetadata - (*UIIntegrationEnrollStartEvent)(nil), // 103: prehog.v1alpha.UIIntegrationEnrollStartEvent - (*UIIntegrationEnrollCompleteEvent)(nil), // 104: prehog.v1alpha.UIIntegrationEnrollCompleteEvent - (*IntegrationEnrollStepStatus)(nil), // 105: prehog.v1alpha.IntegrationEnrollStepStatus - (*UIIntegrationEnrollStepEvent)(nil), // 106: prehog.v1alpha.UIIntegrationEnrollStepEvent - (*UIIntegrationEnrollSectionOpenEvent)(nil), // 107: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent - (*UIIntegrationEnrollFieldCompleteEvent)(nil), // 108: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent - (*UIIntegrationEnrollCodeCopyEvent)(nil), // 109: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent - (*UIIntegrationEnrollLinkClickEvent)(nil), // 110: prehog.v1alpha.UIIntegrationEnrollLinkClickEvent - (*EditorChangeEvent)(nil), // 111: prehog.v1alpha.EditorChangeEvent - (*DeviceAuthenticateEvent)(nil), // 112: prehog.v1alpha.DeviceAuthenticateEvent - (*DeviceEnrollEvent)(nil), // 113: prehog.v1alpha.DeviceEnrollEvent - (*FeatureRecommendationEvent)(nil), // 114: prehog.v1alpha.FeatureRecommendationEvent - (*LicenseLimitEvent)(nil), // 115: prehog.v1alpha.LicenseLimitEvent - (*DesktopDirectoryShareEvent)(nil), // 116: prehog.v1alpha.DesktopDirectoryShareEvent - (*DesktopClipboardEvent)(nil), // 117: prehog.v1alpha.DesktopClipboardEvent - (*TAGExecuteQueryEvent)(nil), // 118: prehog.v1alpha.TAGExecuteQueryEvent - (*AccessGraphSecretsScanAuthorizedKeysEvent)(nil), // 119: prehog.v1alpha.AccessGraphSecretsScanAuthorizedKeysEvent - (*AccessGraphSecretsScanSSHPrivateKeysEvent)(nil), // 120: prehog.v1alpha.AccessGraphSecretsScanSSHPrivateKeysEvent - (*AccessGraphGitlabScanEvent)(nil), // 121: prehog.v1alpha.AccessGraphGitlabScanEvent - (*AccessGraphAWSScanEvent)(nil), // 122: prehog.v1alpha.AccessGraphAWSScanEvent - (*AccessGraphAccessPathChangedEvent)(nil), // 123: prehog.v1alpha.AccessGraphAccessPathChangedEvent - (*UIAccessGraphCrownJewelDiffViewEvent)(nil), // 124: prehog.v1alpha.UIAccessGraphCrownJewelDiffViewEvent - (*UIPageViewEvent)(nil), // 125: prehog.v1alpha.UIPageViewEvent - (*UIUsageReportingAlertCtaClickEvent)(nil), // 126: prehog.v1alpha.UIUsageReportingAlertCtaClickEvent - (*AccessGraphCrownJewelCreateEvent)(nil), // 127: prehog.v1alpha.AccessGraphCrownJewelCreateEvent - (*ExternalAuditStorageAuthenticateEvent)(nil), // 128: prehog.v1alpha.ExternalAuditStorageAuthenticateEvent - (*SecurityReportGetResultEvent)(nil), // 129: prehog.v1alpha.SecurityReportGetResultEvent - (*AuditQueryRunEvent)(nil), // 130: prehog.v1alpha.AuditQueryRunEvent - (*DiscoveryFetchEvent)(nil), // 131: prehog.v1alpha.DiscoveryFetchEvent - (*OktaAccessListSyncEvent)(nil), // 132: prehog.v1alpha.OktaAccessListSyncEvent - (*DatabaseUserCreatedEvent)(nil), // 133: prehog.v1alpha.DatabaseUserCreatedEvent - (*DatabaseUserPermissionsUpdateEvent)(nil), // 134: prehog.v1alpha.DatabaseUserPermissionsUpdateEvent - (*SessionRecordingAccessEvent)(nil), // 135: prehog.v1alpha.SessionRecordingAccessEvent - (*UserTaskStateEvent)(nil), // 136: prehog.v1alpha.UserTaskStateEvent - (*AccessRequestCreateEvent)(nil), // 137: prehog.v1alpha.AccessRequestCreateEvent - (*AccessRequestReviewEvent)(nil), // 138: prehog.v1alpha.AccessRequestReviewEvent - (*SessionSummaryAccessEvent)(nil), // 139: prehog.v1alpha.SessionSummaryAccessEvent - (*SessionSummaryCreateEvent)(nil), // 140: prehog.v1alpha.SessionSummaryCreateEvent - (*SessionSummarySearchEvent)(nil), // 141: prehog.v1alpha.SessionSummarySearchEvent - (*DiscoveryConfigEvent)(nil), // 142: prehog.v1alpha.DiscoveryConfigEvent - (*IdentitySecurityGraphSizeEvent)(nil), // 143: prehog.v1alpha.IdentitySecurityGraphSizeEvent - (*IdentitySecurityAuditLogsIngestedEvent)(nil), // 144: prehog.v1alpha.IdentitySecurityAuditLogsIngestedEvent - (*AccessListStepStatus)(nil), // 145: prehog.v1alpha.AccessListStepStatus - (*UIAccessListDefineAccessEvent)(nil), // 146: prehog.v1alpha.UIAccessListDefineAccessEvent - (*UIAccessListDefineIdentitiesEvent)(nil), // 147: prehog.v1alpha.UIAccessListDefineIdentitiesEvent - (*UIAccessListDefineBasicInfoEvent)(nil), // 148: prehog.v1alpha.UIAccessListDefineBasicInfoEvent - (*UIAccessListDefineMembersEvent)(nil), // 149: prehog.v1alpha.UIAccessListDefineMembersEvent - (*UIAccessListDefineOwnersEvent)(nil), // 150: prehog.v1alpha.UIAccessListDefineOwnersEvent - (*UIAccessListStartEvent)(nil), // 151: prehog.v1alpha.UIAccessListStartEvent - (*UIAccessListCustomEvent)(nil), // 152: prehog.v1alpha.UIAccessListCustomEvent - (*UIAccessListCompleteEvent)(nil), // 153: prehog.v1alpha.UIAccessListCompleteEvent - (*UIAccessListIntegrateEvent)(nil), // 154: prehog.v1alpha.UIAccessListIntegrateEvent - (*UIInteractionEvent)(nil), // 155: prehog.v1alpha.UIInteractionEvent - (*SubmitEventRequest)(nil), // 156: prehog.v1alpha.SubmitEventRequest - (*SubmitEventResponse)(nil), // 157: prehog.v1alpha.SubmitEventResponse - (*SubmitEventsRequest)(nil), // 158: prehog.v1alpha.SubmitEventsRequest - (*SubmitEventsResponse)(nil), // 159: prehog.v1alpha.SubmitEventsResponse - (*HelloTeleportRequest)(nil), // 160: prehog.v1alpha.HelloTeleportRequest - (*HelloTeleportResponse)(nil), // 161: prehog.v1alpha.HelloTeleportResponse - nil, // 162: prehog.v1alpha.UIInteractionEvent.ParamsEntry - (*durationpb.Duration)(nil), // 163: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 164: google.protobuf.Timestamp + (*BeamsPublishedEvent)(nil), // 40: prehog.v1alpha.BeamsPublishedEvent + (*BeamsUnpublishedEvent)(nil), // 41: prehog.v1alpha.BeamsUnpublishedEvent + (*UserCertificateIssuedEvent)(nil), // 42: prehog.v1alpha.UserCertificateIssuedEvent + (*SPIFFESVIDIssuedEvent)(nil), // 43: prehog.v1alpha.SPIFFESVIDIssuedEvent + (*UIBannerClickEvent)(nil), // 44: prehog.v1alpha.UIBannerClickEvent + (*UIOnboardCompleteGoToDashboardClickEvent)(nil), // 45: prehog.v1alpha.UIOnboardCompleteGoToDashboardClickEvent + (*UIOnboardAddFirstResourceClickEvent)(nil), // 46: prehog.v1alpha.UIOnboardAddFirstResourceClickEvent + (*UIOnboardAddFirstResourceLaterClickEvent)(nil), // 47: prehog.v1alpha.UIOnboardAddFirstResourceLaterClickEvent + (*UIOnboardSetCredentialSubmitEvent)(nil), // 48: prehog.v1alpha.UIOnboardSetCredentialSubmitEvent + (*UIOnboardRegisterChallengeSubmitEvent)(nil), // 49: prehog.v1alpha.UIOnboardRegisterChallengeSubmitEvent + (*UIOnboardQuestionnaireSubmitEvent)(nil), // 50: prehog.v1alpha.UIOnboardQuestionnaireSubmitEvent + (*UIRecoveryCodesContinueClickEvent)(nil), // 51: prehog.v1alpha.UIRecoveryCodesContinueClickEvent + (*UIRecoveryCodesCopyClickEvent)(nil), // 52: prehog.v1alpha.UIRecoveryCodesCopyClickEvent + (*UIRecoveryCodesPrintClickEvent)(nil), // 53: prehog.v1alpha.UIRecoveryCodesPrintClickEvent + (*DiscoverMetadata)(nil), // 54: prehog.v1alpha.DiscoverMetadata + (*DiscoverResourceMetadata)(nil), // 55: prehog.v1alpha.DiscoverResourceMetadata + (*DiscoverStepStatus)(nil), // 56: prehog.v1alpha.DiscoverStepStatus + (*UIDiscoverStartedEvent)(nil), // 57: prehog.v1alpha.UIDiscoverStartedEvent + (*UIDiscoverResourceSelectionEvent)(nil), // 58: prehog.v1alpha.UIDiscoverResourceSelectionEvent + (*UIDiscoverIntegrationAWSOIDCConnectEvent)(nil), // 59: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent + (*UIDiscoverDatabaseRDSEnrollEvent)(nil), // 60: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent + (*UIDiscoverKubeEKSEnrollEvent)(nil), // 61: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent + (*UIDiscoverDeployServiceEvent)(nil), // 62: prehog.v1alpha.UIDiscoverDeployServiceEvent + (*UIDiscoverCreateDiscoveryConfigEvent)(nil), // 63: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent + (*UIDiscoverDatabaseRegisterEvent)(nil), // 64: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent + (*UIDiscoverDatabaseConfigureMTLSEvent)(nil), // 65: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent + (*UIDiscoverDesktopActiveDirectoryToolsInstallEvent)(nil), // 66: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent + (*UIDiscoverDesktopActiveDirectoryConfigureEvent)(nil), // 67: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent + (*UIDiscoverAutoDiscoveredResourcesEvent)(nil), // 68: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent + (*UIDiscoverEC2InstanceSelectionEvent)(nil), // 69: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent + (*UIDiscoverDeployEICEEvent)(nil), // 70: prehog.v1alpha.UIDiscoverDeployEICEEvent + (*UIDiscoverCreateNodeEvent)(nil), // 71: prehog.v1alpha.UIDiscoverCreateNodeEvent + (*UIDiscoverCreateAppServerEvent)(nil), // 72: prehog.v1alpha.UIDiscoverCreateAppServerEvent + (*UIDiscoverDatabaseConfigureIAMPolicyEvent)(nil), // 73: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent + (*UIDiscoverPrincipalsConfigureEvent)(nil), // 74: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent + (*UIDiscoverTestConnectionEvent)(nil), // 75: prehog.v1alpha.UIDiscoverTestConnectionEvent + (*UIDiscoverCompletedEvent)(nil), // 76: prehog.v1alpha.UIDiscoverCompletedEvent + (*RoleCreateEvent)(nil), // 77: prehog.v1alpha.RoleCreateEvent + (*BotCreateEvent)(nil), // 78: prehog.v1alpha.BotCreateEvent + (*BotJoinEvent)(nil), // 79: prehog.v1alpha.BotJoinEvent + (*UICreateNewRoleClickEvent)(nil), // 80: prehog.v1alpha.UICreateNewRoleClickEvent + (*UICreateNewRoleSaveClickEvent)(nil), // 81: prehog.v1alpha.UICreateNewRoleSaveClickEvent + (*UICreateNewRoleCancelClickEvent)(nil), // 82: prehog.v1alpha.UICreateNewRoleCancelClickEvent + (*UICreateNewRoleViewDocumentationClickEvent)(nil), // 83: prehog.v1alpha.UICreateNewRoleViewDocumentationClickEvent + (*UICallToActionClickEvent)(nil), // 84: prehog.v1alpha.UICallToActionClickEvent + (*KubeRequestEvent)(nil), // 85: prehog.v1alpha.KubeRequestEvent + (*SFTPEvent)(nil), // 86: prehog.v1alpha.SFTPEvent + (*AgentMetadataEvent)(nil), // 87: prehog.v1alpha.AgentMetadataEvent + (*AssistCompletionEvent)(nil), // 88: prehog.v1alpha.AssistCompletionEvent + (*AssistExecutionEvent)(nil), // 89: prehog.v1alpha.AssistExecutionEvent + (*AssistNewConversationEvent)(nil), // 90: prehog.v1alpha.AssistNewConversationEvent + (*AssistAccessRequestEvent)(nil), // 91: prehog.v1alpha.AssistAccessRequestEvent + (*AssistActionEvent)(nil), // 92: prehog.v1alpha.AssistActionEvent + (*AccessListMetadata)(nil), // 93: prehog.v1alpha.AccessListMetadata + (*AccessListCreateEvent)(nil), // 94: prehog.v1alpha.AccessListCreateEvent + (*AccessListUpdateEvent)(nil), // 95: prehog.v1alpha.AccessListUpdateEvent + (*AccessListDeleteEvent)(nil), // 96: prehog.v1alpha.AccessListDeleteEvent + (*AccessListMemberCreateEvent)(nil), // 97: prehog.v1alpha.AccessListMemberCreateEvent + (*AccessListMemberUpdateEvent)(nil), // 98: prehog.v1alpha.AccessListMemberUpdateEvent + (*AccessListMemberDeleteEvent)(nil), // 99: prehog.v1alpha.AccessListMemberDeleteEvent + (*AccessListGrantsToUserEvent)(nil), // 100: prehog.v1alpha.AccessListGrantsToUserEvent + (*AccessListReviewCreateEvent)(nil), // 101: prehog.v1alpha.AccessListReviewCreateEvent + (*AccessListReviewDeleteEvent)(nil), // 102: prehog.v1alpha.AccessListReviewDeleteEvent + (*AccessListReviewComplianceEvent)(nil), // 103: prehog.v1alpha.AccessListReviewComplianceEvent + (*IntegrationEnrollMetadata)(nil), // 104: prehog.v1alpha.IntegrationEnrollMetadata + (*UIIntegrationEnrollStartEvent)(nil), // 105: prehog.v1alpha.UIIntegrationEnrollStartEvent + (*UIIntegrationEnrollCompleteEvent)(nil), // 106: prehog.v1alpha.UIIntegrationEnrollCompleteEvent + (*IntegrationEnrollStepStatus)(nil), // 107: prehog.v1alpha.IntegrationEnrollStepStatus + (*UIIntegrationEnrollStepEvent)(nil), // 108: prehog.v1alpha.UIIntegrationEnrollStepEvent + (*UIIntegrationEnrollSectionOpenEvent)(nil), // 109: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent + (*UIIntegrationEnrollFieldCompleteEvent)(nil), // 110: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent + (*UIIntegrationEnrollCodeCopyEvent)(nil), // 111: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent + (*UIIntegrationEnrollLinkClickEvent)(nil), // 112: prehog.v1alpha.UIIntegrationEnrollLinkClickEvent + (*EditorChangeEvent)(nil), // 113: prehog.v1alpha.EditorChangeEvent + (*DeviceAuthenticateEvent)(nil), // 114: prehog.v1alpha.DeviceAuthenticateEvent + (*DeviceEnrollEvent)(nil), // 115: prehog.v1alpha.DeviceEnrollEvent + (*FeatureRecommendationEvent)(nil), // 116: prehog.v1alpha.FeatureRecommendationEvent + (*LicenseLimitEvent)(nil), // 117: prehog.v1alpha.LicenseLimitEvent + (*DesktopDirectoryShareEvent)(nil), // 118: prehog.v1alpha.DesktopDirectoryShareEvent + (*DesktopClipboardEvent)(nil), // 119: prehog.v1alpha.DesktopClipboardEvent + (*TAGExecuteQueryEvent)(nil), // 120: prehog.v1alpha.TAGExecuteQueryEvent + (*AccessGraphSecretsScanAuthorizedKeysEvent)(nil), // 121: prehog.v1alpha.AccessGraphSecretsScanAuthorizedKeysEvent + (*AccessGraphSecretsScanSSHPrivateKeysEvent)(nil), // 122: prehog.v1alpha.AccessGraphSecretsScanSSHPrivateKeysEvent + (*AccessGraphGitlabScanEvent)(nil), // 123: prehog.v1alpha.AccessGraphGitlabScanEvent + (*AccessGraphAWSScanEvent)(nil), // 124: prehog.v1alpha.AccessGraphAWSScanEvent + (*AccessGraphAccessPathChangedEvent)(nil), // 125: prehog.v1alpha.AccessGraphAccessPathChangedEvent + (*UIAccessGraphCrownJewelDiffViewEvent)(nil), // 126: prehog.v1alpha.UIAccessGraphCrownJewelDiffViewEvent + (*UIPageViewEvent)(nil), // 127: prehog.v1alpha.UIPageViewEvent + (*UIUsageReportingAlertCtaClickEvent)(nil), // 128: prehog.v1alpha.UIUsageReportingAlertCtaClickEvent + (*AccessGraphCrownJewelCreateEvent)(nil), // 129: prehog.v1alpha.AccessGraphCrownJewelCreateEvent + (*ExternalAuditStorageAuthenticateEvent)(nil), // 130: prehog.v1alpha.ExternalAuditStorageAuthenticateEvent + (*SecurityReportGetResultEvent)(nil), // 131: prehog.v1alpha.SecurityReportGetResultEvent + (*AuditQueryRunEvent)(nil), // 132: prehog.v1alpha.AuditQueryRunEvent + (*DiscoveryFetchEvent)(nil), // 133: prehog.v1alpha.DiscoveryFetchEvent + (*OktaAccessListSyncEvent)(nil), // 134: prehog.v1alpha.OktaAccessListSyncEvent + (*DatabaseUserCreatedEvent)(nil), // 135: prehog.v1alpha.DatabaseUserCreatedEvent + (*DatabaseUserPermissionsUpdateEvent)(nil), // 136: prehog.v1alpha.DatabaseUserPermissionsUpdateEvent + (*SessionRecordingAccessEvent)(nil), // 137: prehog.v1alpha.SessionRecordingAccessEvent + (*UserTaskStateEvent)(nil), // 138: prehog.v1alpha.UserTaskStateEvent + (*AccessRequestCreateEvent)(nil), // 139: prehog.v1alpha.AccessRequestCreateEvent + (*AccessRequestReviewEvent)(nil), // 140: prehog.v1alpha.AccessRequestReviewEvent + (*SessionSummaryAccessEvent)(nil), // 141: prehog.v1alpha.SessionSummaryAccessEvent + (*SessionSummaryCreateEvent)(nil), // 142: prehog.v1alpha.SessionSummaryCreateEvent + (*SessionSummarySearchEvent)(nil), // 143: prehog.v1alpha.SessionSummarySearchEvent + (*DiscoveryConfigEvent)(nil), // 144: prehog.v1alpha.DiscoveryConfigEvent + (*IdentitySecurityGraphSizeEvent)(nil), // 145: prehog.v1alpha.IdentitySecurityGraphSizeEvent + (*IdentitySecurityAuditLogsIngestedEvent)(nil), // 146: prehog.v1alpha.IdentitySecurityAuditLogsIngestedEvent + (*AccessListStepStatus)(nil), // 147: prehog.v1alpha.AccessListStepStatus + (*UIAccessListDefineAccessEvent)(nil), // 148: prehog.v1alpha.UIAccessListDefineAccessEvent + (*UIAccessListDefineIdentitiesEvent)(nil), // 149: prehog.v1alpha.UIAccessListDefineIdentitiesEvent + (*UIAccessListDefineBasicInfoEvent)(nil), // 150: prehog.v1alpha.UIAccessListDefineBasicInfoEvent + (*UIAccessListDefineMembersEvent)(nil), // 151: prehog.v1alpha.UIAccessListDefineMembersEvent + (*UIAccessListDefineOwnersEvent)(nil), // 152: prehog.v1alpha.UIAccessListDefineOwnersEvent + (*UIAccessListStartEvent)(nil), // 153: prehog.v1alpha.UIAccessListStartEvent + (*UIAccessListCustomEvent)(nil), // 154: prehog.v1alpha.UIAccessListCustomEvent + (*UIAccessListCompleteEvent)(nil), // 155: prehog.v1alpha.UIAccessListCompleteEvent + (*UIAccessListIntegrateEvent)(nil), // 156: prehog.v1alpha.UIAccessListIntegrateEvent + (*UIInteractionEvent)(nil), // 157: prehog.v1alpha.UIInteractionEvent + (*SubmitEventRequest)(nil), // 158: prehog.v1alpha.SubmitEventRequest + (*SubmitEventResponse)(nil), // 159: prehog.v1alpha.SubmitEventResponse + (*SubmitEventsRequest)(nil), // 160: prehog.v1alpha.SubmitEventsRequest + (*SubmitEventsResponse)(nil), // 161: prehog.v1alpha.SubmitEventsResponse + (*HelloTeleportRequest)(nil), // 162: prehog.v1alpha.HelloTeleportRequest + (*HelloTeleportResponse)(nil), // 163: prehog.v1alpha.HelloTeleportResponse + nil, // 164: prehog.v1alpha.UIInteractionEvent.ParamsEntry + (*durationpb.Duration)(nil), // 165: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 166: google.protobuf.Timestamp } var file_prehog_v1alpha_teleport_proto_depIdxs = []int32{ 0, // 0: prehog.v1alpha.UserLoginEvent.user_origin:type_name -> prehog.v1alpha.UserOrigin @@ -13914,101 +14066,101 @@ var file_prehog_v1alpha_teleport_proto_depIdxs = []int32{ 36, // 8: prehog.v1alpha.SessionStartEvent.mcp:type_name -> prehog.v1alpha.SessionStartMCPMetadata 37, // 9: prehog.v1alpha.SessionStartEvent.beam:type_name -> prehog.v1alpha.SessionStartBeamMetadata 3, // 10: prehog.v1alpha.BeamsDestroyedEvent.reason:type_name -> prehog.v1alpha.BeamDestroyReason - 163, // 11: prehog.v1alpha.UserCertificateIssuedEvent.ttl:type_name -> google.protobuf.Duration + 165, // 11: prehog.v1alpha.UserCertificateIssuedEvent.ttl:type_name -> google.protobuf.Duration 2, // 12: prehog.v1alpha.SPIFFESVIDIssuedEvent.user_kind:type_name -> prehog.v1alpha.UserKind 4, // 13: prehog.v1alpha.DiscoverResourceMetadata.resource:type_name -> prehog.v1alpha.DiscoverResource 5, // 14: prehog.v1alpha.DiscoverStepStatus.status:type_name -> prehog.v1alpha.DiscoverStatus - 52, // 15: prehog.v1alpha.UIDiscoverStartedEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 54, // 16: prehog.v1alpha.UIDiscoverStartedEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 17: prehog.v1alpha.UIDiscoverResourceSelectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 18: prehog.v1alpha.UIDiscoverResourceSelectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 19: prehog.v1alpha.UIDiscoverResourceSelectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 20: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 21: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 22: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 23: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 24: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 25: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 26: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 27: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 28: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 29: prehog.v1alpha.UIDiscoverDeployServiceEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 30: prehog.v1alpha.UIDiscoverDeployServiceEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 31: prehog.v1alpha.UIDiscoverDeployServiceEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 15: prehog.v1alpha.UIDiscoverStartedEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 56, // 16: prehog.v1alpha.UIDiscoverStartedEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 17: prehog.v1alpha.UIDiscoverResourceSelectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 18: prehog.v1alpha.UIDiscoverResourceSelectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 19: prehog.v1alpha.UIDiscoverResourceSelectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 20: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 21: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 22: prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 23: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 24: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 25: prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 26: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 27: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 28: prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 29: prehog.v1alpha.UIDiscoverDeployServiceEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 30: prehog.v1alpha.UIDiscoverDeployServiceEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 31: prehog.v1alpha.UIDiscoverDeployServiceEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus 21, // 32: prehog.v1alpha.UIDiscoverDeployServiceEvent.deploy_method:type_name -> prehog.v1alpha.UIDiscoverDeployServiceEvent.DeployMethod 22, // 33: prehog.v1alpha.UIDiscoverDeployServiceEvent.deploy_type:type_name -> prehog.v1alpha.UIDiscoverDeployServiceEvent.DeployType - 52, // 34: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 35: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 36: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 34: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 35: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 36: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus 23, // 37: prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.config_method:type_name -> prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent.ConfigMethod - 52, // 38: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 39: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 40: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 41: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 42: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 43: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 44: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 45: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 46: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 47: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 48: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 49: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 50: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 51: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 52: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 53: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 54: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 55: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 56: prehog.v1alpha.UIDiscoverDeployEICEEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 57: prehog.v1alpha.UIDiscoverDeployEICEEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 58: prehog.v1alpha.UIDiscoverDeployEICEEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 59: prehog.v1alpha.UIDiscoverCreateNodeEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 60: prehog.v1alpha.UIDiscoverCreateNodeEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 61: prehog.v1alpha.UIDiscoverCreateNodeEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 62: prehog.v1alpha.UIDiscoverCreateAppServerEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 63: prehog.v1alpha.UIDiscoverCreateAppServerEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 64: prehog.v1alpha.UIDiscoverCreateAppServerEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 65: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 66: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 67: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 68: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 69: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 70: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 71: prehog.v1alpha.UIDiscoverTestConnectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 72: prehog.v1alpha.UIDiscoverTestConnectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 73: prehog.v1alpha.UIDiscoverTestConnectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus - 52, // 74: prehog.v1alpha.UIDiscoverCompletedEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata - 53, // 75: prehog.v1alpha.UIDiscoverCompletedEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata - 54, // 76: prehog.v1alpha.UIDiscoverCompletedEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 38: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 39: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 40: prehog.v1alpha.UIDiscoverDatabaseRegisterEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 41: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 42: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 43: prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 44: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 45: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 46: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 47: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 48: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 49: prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 50: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 51: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 52: prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 53: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 54: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 55: prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 56: prehog.v1alpha.UIDiscoverDeployEICEEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 57: prehog.v1alpha.UIDiscoverDeployEICEEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 58: prehog.v1alpha.UIDiscoverDeployEICEEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 59: prehog.v1alpha.UIDiscoverCreateNodeEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 60: prehog.v1alpha.UIDiscoverCreateNodeEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 61: prehog.v1alpha.UIDiscoverCreateNodeEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 62: prehog.v1alpha.UIDiscoverCreateAppServerEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 63: prehog.v1alpha.UIDiscoverCreateAppServerEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 64: prehog.v1alpha.UIDiscoverCreateAppServerEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 65: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 66: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 67: prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 68: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 69: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 70: prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 71: prehog.v1alpha.UIDiscoverTestConnectionEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 72: prehog.v1alpha.UIDiscoverTestConnectionEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 73: prehog.v1alpha.UIDiscoverTestConnectionEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus + 54, // 74: prehog.v1alpha.UIDiscoverCompletedEvent.metadata:type_name -> prehog.v1alpha.DiscoverMetadata + 55, // 75: prehog.v1alpha.UIDiscoverCompletedEvent.resource:type_name -> prehog.v1alpha.DiscoverResourceMetadata + 56, // 76: prehog.v1alpha.UIDiscoverCompletedEvent.status:type_name -> prehog.v1alpha.DiscoverStepStatus 6, // 77: prehog.v1alpha.UICallToActionClickEvent.cta:type_name -> prehog.v1alpha.CTA 2, // 78: prehog.v1alpha.KubeRequestEvent.user_kind:type_name -> prehog.v1alpha.UserKind 2, // 79: prehog.v1alpha.SFTPEvent.user_kind:type_name -> prehog.v1alpha.UserKind 19, // 80: prehog.v1alpha.AccessListMetadata.preset:type_name -> prehog.v1alpha.AccessListPreset - 91, // 81: prehog.v1alpha.AccessListCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 82: prehog.v1alpha.AccessListUpdateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 83: prehog.v1alpha.AccessListDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 84: prehog.v1alpha.AccessListMemberCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 85: prehog.v1alpha.AccessListMemberUpdateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 86: prehog.v1alpha.AccessListMemberDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 87: prehog.v1alpha.AccessListReviewCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 91, // 88: prehog.v1alpha.AccessListReviewDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 81: prehog.v1alpha.AccessListCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 82: prehog.v1alpha.AccessListUpdateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 83: prehog.v1alpha.AccessListDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 84: prehog.v1alpha.AccessListMemberCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 85: prehog.v1alpha.AccessListMemberUpdateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 86: prehog.v1alpha.AccessListMemberDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 87: prehog.v1alpha.AccessListReviewCreateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 88: prehog.v1alpha.AccessListReviewDeleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata 7, // 89: prehog.v1alpha.IntegrationEnrollMetadata.kind:type_name -> prehog.v1alpha.IntegrationEnrollKind - 102, // 90: prehog.v1alpha.UIIntegrationEnrollStartEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata - 102, // 91: prehog.v1alpha.UIIntegrationEnrollCompleteEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 90: prehog.v1alpha.UIIntegrationEnrollStartEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 91: prehog.v1alpha.UIIntegrationEnrollCompleteEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 9, // 92: prehog.v1alpha.IntegrationEnrollStepStatus.code:type_name -> prehog.v1alpha.IntegrationEnrollStatusCode - 102, // 93: prehog.v1alpha.UIIntegrationEnrollStepEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 93: prehog.v1alpha.UIIntegrationEnrollStepEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 8, // 94: prehog.v1alpha.UIIntegrationEnrollStepEvent.step:type_name -> prehog.v1alpha.IntegrationEnrollStep - 105, // 95: prehog.v1alpha.UIIntegrationEnrollStepEvent.status:type_name -> prehog.v1alpha.IntegrationEnrollStepStatus - 102, // 96: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 107, // 95: prehog.v1alpha.UIIntegrationEnrollStepEvent.status:type_name -> prehog.v1alpha.IntegrationEnrollStepStatus + 104, // 96: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 8, // 97: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent.step:type_name -> prehog.v1alpha.IntegrationEnrollStep 10, // 98: prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent.section:type_name -> prehog.v1alpha.IntegrationEnrollSection - 102, // 99: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 99: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 8, // 100: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent.step:type_name -> prehog.v1alpha.IntegrationEnrollStep 11, // 101: prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent.field:type_name -> prehog.v1alpha.IntegrationEnrollField - 102, // 102: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 102: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 8, // 103: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent.step:type_name -> prehog.v1alpha.IntegrationEnrollStep 12, // 104: prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent.type:type_name -> prehog.v1alpha.IntegrationEnrollCodeType - 102, // 105: prehog.v1alpha.UIIntegrationEnrollLinkClickEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata + 104, // 105: prehog.v1alpha.UIIntegrationEnrollLinkClickEvent.metadata:type_name -> prehog.v1alpha.IntegrationEnrollMetadata 8, // 106: prehog.v1alpha.UIIntegrationEnrollLinkClickEvent.step:type_name -> prehog.v1alpha.IntegrationEnrollStep 13, // 107: prehog.v1alpha.EditorChangeEvent.status:type_name -> prehog.v1alpha.EditorChangeStatus 14, // 108: prehog.v1alpha.FeatureRecommendationEvent.feature:type_name -> prehog.v1alpha.Feature @@ -14021,156 +14173,158 @@ var file_prehog_v1alpha_teleport_proto_depIdxs = []int32{ 2, // 115: prehog.v1alpha.SessionSummarySearchEvent.user_kind:type_name -> prehog.v1alpha.UserKind 17, // 116: prehog.v1alpha.DiscoveryConfigEvent.action:type_name -> prehog.v1alpha.DiscoveryConfigAction 18, // 117: prehog.v1alpha.AccessListStepStatus.status:type_name -> prehog.v1alpha.AccessListStatus - 91, // 118: prehog.v1alpha.UIAccessListDefineAccessEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 119: prehog.v1alpha.UIAccessListDefineAccessEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 120: prehog.v1alpha.UIAccessListDefineIdentitiesEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 121: prehog.v1alpha.UIAccessListDefineIdentitiesEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 122: prehog.v1alpha.UIAccessListDefineBasicInfoEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 123: prehog.v1alpha.UIAccessListDefineBasicInfoEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 124: prehog.v1alpha.UIAccessListDefineMembersEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 125: prehog.v1alpha.UIAccessListDefineMembersEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 126: prehog.v1alpha.UIAccessListDefineOwnersEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 127: prehog.v1alpha.UIAccessListDefineOwnersEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 128: prehog.v1alpha.UIAccessListStartEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 129: prehog.v1alpha.UIAccessListStartEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 130: prehog.v1alpha.UIAccessListCustomEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 131: prehog.v1alpha.UIAccessListCustomEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 132: prehog.v1alpha.UIAccessListCompleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata - 145, // 133: prehog.v1alpha.UIAccessListCompleteEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus - 91, // 134: prehog.v1alpha.UIAccessListIntegrateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 93, // 118: prehog.v1alpha.UIAccessListDefineAccessEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 119: prehog.v1alpha.UIAccessListDefineAccessEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 120: prehog.v1alpha.UIAccessListDefineIdentitiesEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 121: prehog.v1alpha.UIAccessListDefineIdentitiesEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 122: prehog.v1alpha.UIAccessListDefineBasicInfoEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 123: prehog.v1alpha.UIAccessListDefineBasicInfoEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 124: prehog.v1alpha.UIAccessListDefineMembersEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 125: prehog.v1alpha.UIAccessListDefineMembersEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 126: prehog.v1alpha.UIAccessListDefineOwnersEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 127: prehog.v1alpha.UIAccessListDefineOwnersEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 128: prehog.v1alpha.UIAccessListStartEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 129: prehog.v1alpha.UIAccessListStartEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 130: prehog.v1alpha.UIAccessListCustomEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 131: prehog.v1alpha.UIAccessListCustomEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 132: prehog.v1alpha.UIAccessListCompleteEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata + 147, // 133: prehog.v1alpha.UIAccessListCompleteEvent.status:type_name -> prehog.v1alpha.AccessListStepStatus + 93, // 134: prehog.v1alpha.UIAccessListIntegrateEvent.metadata:type_name -> prehog.v1alpha.AccessListMetadata 20, // 135: prehog.v1alpha.UIAccessListIntegrateEvent.integrate:type_name -> prehog.v1alpha.AccessListIntegrate - 162, // 136: prehog.v1alpha.UIInteractionEvent.params:type_name -> prehog.v1alpha.UIInteractionEvent.ParamsEntry - 164, // 137: prehog.v1alpha.SubmitEventRequest.timestamp:type_name -> google.protobuf.Timestamp + 164, // 136: prehog.v1alpha.UIInteractionEvent.params:type_name -> prehog.v1alpha.UIInteractionEvent.ParamsEntry + 166, // 137: prehog.v1alpha.SubmitEventRequest.timestamp:type_name -> google.protobuf.Timestamp 25, // 138: prehog.v1alpha.SubmitEventRequest.user_login:type_name -> prehog.v1alpha.UserLoginEvent 27, // 139: prehog.v1alpha.SubmitEventRequest.sso_create:type_name -> prehog.v1alpha.SSOCreateEvent 28, // 140: prehog.v1alpha.SubmitEventRequest.resource_create:type_name -> prehog.v1alpha.ResourceCreateEvent 31, // 141: prehog.v1alpha.SubmitEventRequest.session_start:type_name -> prehog.v1alpha.SessionStartEvent - 42, // 142: prehog.v1alpha.SubmitEventRequest.ui_banner_click:type_name -> prehog.v1alpha.UIBannerClickEvent - 43, // 143: prehog.v1alpha.SubmitEventRequest.ui_onboard_complete_go_to_dashboard_click:type_name -> prehog.v1alpha.UIOnboardCompleteGoToDashboardClickEvent - 44, // 144: prehog.v1alpha.SubmitEventRequest.ui_onboard_add_first_resource_click:type_name -> prehog.v1alpha.UIOnboardAddFirstResourceClickEvent - 45, // 145: prehog.v1alpha.SubmitEventRequest.ui_onboard_add_first_resource_later_click:type_name -> prehog.v1alpha.UIOnboardAddFirstResourceLaterClickEvent - 46, // 146: prehog.v1alpha.SubmitEventRequest.ui_onboard_set_credential_submit:type_name -> prehog.v1alpha.UIOnboardSetCredentialSubmitEvent - 47, // 147: prehog.v1alpha.SubmitEventRequest.ui_onboard_register_challenge_submit:type_name -> prehog.v1alpha.UIOnboardRegisterChallengeSubmitEvent - 49, // 148: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_continue_click:type_name -> prehog.v1alpha.UIRecoveryCodesContinueClickEvent - 50, // 149: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_copy_click:type_name -> prehog.v1alpha.UIRecoveryCodesCopyClickEvent - 51, // 150: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_print_click:type_name -> prehog.v1alpha.UIRecoveryCodesPrintClickEvent - 55, // 151: prehog.v1alpha.SubmitEventRequest.ui_discover_started_event:type_name -> prehog.v1alpha.UIDiscoverStartedEvent - 56, // 152: prehog.v1alpha.SubmitEventRequest.ui_discover_resource_selection_event:type_name -> prehog.v1alpha.UIDiscoverResourceSelectionEvent - 40, // 153: prehog.v1alpha.SubmitEventRequest.user_certificate_issued_event:type_name -> prehog.v1alpha.UserCertificateIssuedEvent + 44, // 142: prehog.v1alpha.SubmitEventRequest.ui_banner_click:type_name -> prehog.v1alpha.UIBannerClickEvent + 45, // 143: prehog.v1alpha.SubmitEventRequest.ui_onboard_complete_go_to_dashboard_click:type_name -> prehog.v1alpha.UIOnboardCompleteGoToDashboardClickEvent + 46, // 144: prehog.v1alpha.SubmitEventRequest.ui_onboard_add_first_resource_click:type_name -> prehog.v1alpha.UIOnboardAddFirstResourceClickEvent + 47, // 145: prehog.v1alpha.SubmitEventRequest.ui_onboard_add_first_resource_later_click:type_name -> prehog.v1alpha.UIOnboardAddFirstResourceLaterClickEvent + 48, // 146: prehog.v1alpha.SubmitEventRequest.ui_onboard_set_credential_submit:type_name -> prehog.v1alpha.UIOnboardSetCredentialSubmitEvent + 49, // 147: prehog.v1alpha.SubmitEventRequest.ui_onboard_register_challenge_submit:type_name -> prehog.v1alpha.UIOnboardRegisterChallengeSubmitEvent + 51, // 148: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_continue_click:type_name -> prehog.v1alpha.UIRecoveryCodesContinueClickEvent + 52, // 149: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_copy_click:type_name -> prehog.v1alpha.UIRecoveryCodesCopyClickEvent + 53, // 150: prehog.v1alpha.SubmitEventRequest.ui_recovery_codes_print_click:type_name -> prehog.v1alpha.UIRecoveryCodesPrintClickEvent + 57, // 151: prehog.v1alpha.SubmitEventRequest.ui_discover_started_event:type_name -> prehog.v1alpha.UIDiscoverStartedEvent + 58, // 152: prehog.v1alpha.SubmitEventRequest.ui_discover_resource_selection_event:type_name -> prehog.v1alpha.UIDiscoverResourceSelectionEvent + 42, // 153: prehog.v1alpha.SubmitEventRequest.user_certificate_issued_event:type_name -> prehog.v1alpha.UserCertificateIssuedEvent 31, // 154: prehog.v1alpha.SubmitEventRequest.session_start_v2:type_name -> prehog.v1alpha.SessionStartEvent - 60, // 155: prehog.v1alpha.SubmitEventRequest.ui_discover_deploy_service_event:type_name -> prehog.v1alpha.UIDiscoverDeployServiceEvent - 62, // 156: prehog.v1alpha.SubmitEventRequest.ui_discover_database_register_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseRegisterEvent - 63, // 157: prehog.v1alpha.SubmitEventRequest.ui_discover_database_configure_mtls_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent - 64, // 158: prehog.v1alpha.SubmitEventRequest.ui_discover_desktop_active_directory_tools_install_event:type_name -> prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent - 65, // 159: prehog.v1alpha.SubmitEventRequest.ui_discover_desktop_active_directory_configure_event:type_name -> prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent - 66, // 160: prehog.v1alpha.SubmitEventRequest.ui_discover_auto_discovered_resources_event:type_name -> prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent - 71, // 161: prehog.v1alpha.SubmitEventRequest.ui_discover_database_configure_iam_policy_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent - 72, // 162: prehog.v1alpha.SubmitEventRequest.ui_discover_principals_configure_event:type_name -> prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent - 73, // 163: prehog.v1alpha.SubmitEventRequest.ui_discover_test_connection_event:type_name -> prehog.v1alpha.UIDiscoverTestConnectionEvent - 74, // 164: prehog.v1alpha.SubmitEventRequest.ui_discover_completed_event:type_name -> prehog.v1alpha.UIDiscoverCompletedEvent - 75, // 165: prehog.v1alpha.SubmitEventRequest.role_create:type_name -> prehog.v1alpha.RoleCreateEvent - 78, // 166: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_click:type_name -> prehog.v1alpha.UICreateNewRoleClickEvent - 79, // 167: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_save_click:type_name -> prehog.v1alpha.UICreateNewRoleSaveClickEvent - 80, // 168: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_cancel_click:type_name -> prehog.v1alpha.UICreateNewRoleCancelClickEvent - 81, // 169: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_view_documentation_click:type_name -> prehog.v1alpha.UICreateNewRoleViewDocumentationClickEvent - 83, // 170: prehog.v1alpha.SubmitEventRequest.kube_request:type_name -> prehog.v1alpha.KubeRequestEvent - 84, // 171: prehog.v1alpha.SubmitEventRequest.sftp:type_name -> prehog.v1alpha.SFTPEvent - 85, // 172: prehog.v1alpha.SubmitEventRequest.agent_metadata_event:type_name -> prehog.v1alpha.AgentMetadataEvent + 62, // 155: prehog.v1alpha.SubmitEventRequest.ui_discover_deploy_service_event:type_name -> prehog.v1alpha.UIDiscoverDeployServiceEvent + 64, // 156: prehog.v1alpha.SubmitEventRequest.ui_discover_database_register_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseRegisterEvent + 65, // 157: prehog.v1alpha.SubmitEventRequest.ui_discover_database_configure_mtls_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseConfigureMTLSEvent + 66, // 158: prehog.v1alpha.SubmitEventRequest.ui_discover_desktop_active_directory_tools_install_event:type_name -> prehog.v1alpha.UIDiscoverDesktopActiveDirectoryToolsInstallEvent + 67, // 159: prehog.v1alpha.SubmitEventRequest.ui_discover_desktop_active_directory_configure_event:type_name -> prehog.v1alpha.UIDiscoverDesktopActiveDirectoryConfigureEvent + 68, // 160: prehog.v1alpha.SubmitEventRequest.ui_discover_auto_discovered_resources_event:type_name -> prehog.v1alpha.UIDiscoverAutoDiscoveredResourcesEvent + 73, // 161: prehog.v1alpha.SubmitEventRequest.ui_discover_database_configure_iam_policy_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseConfigureIAMPolicyEvent + 74, // 162: prehog.v1alpha.SubmitEventRequest.ui_discover_principals_configure_event:type_name -> prehog.v1alpha.UIDiscoverPrincipalsConfigureEvent + 75, // 163: prehog.v1alpha.SubmitEventRequest.ui_discover_test_connection_event:type_name -> prehog.v1alpha.UIDiscoverTestConnectionEvent + 76, // 164: prehog.v1alpha.SubmitEventRequest.ui_discover_completed_event:type_name -> prehog.v1alpha.UIDiscoverCompletedEvent + 77, // 165: prehog.v1alpha.SubmitEventRequest.role_create:type_name -> prehog.v1alpha.RoleCreateEvent + 80, // 166: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_click:type_name -> prehog.v1alpha.UICreateNewRoleClickEvent + 81, // 167: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_save_click:type_name -> prehog.v1alpha.UICreateNewRoleSaveClickEvent + 82, // 168: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_cancel_click:type_name -> prehog.v1alpha.UICreateNewRoleCancelClickEvent + 83, // 169: prehog.v1alpha.SubmitEventRequest.ui_create_new_role_view_documentation_click:type_name -> prehog.v1alpha.UICreateNewRoleViewDocumentationClickEvent + 85, // 170: prehog.v1alpha.SubmitEventRequest.kube_request:type_name -> prehog.v1alpha.KubeRequestEvent + 86, // 171: prehog.v1alpha.SubmitEventRequest.sftp:type_name -> prehog.v1alpha.SFTPEvent + 87, // 172: prehog.v1alpha.SubmitEventRequest.agent_metadata_event:type_name -> prehog.v1alpha.AgentMetadataEvent 30, // 173: prehog.v1alpha.SubmitEventRequest.resource_heartbeat:type_name -> prehog.v1alpha.ResourceHeartbeatEvent - 57, // 174: prehog.v1alpha.SubmitEventRequest.ui_discover_integration_aws_oidc_connect_event:type_name -> prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent - 58, // 175: prehog.v1alpha.SubmitEventRequest.ui_discover_database_rds_enroll_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent - 82, // 176: prehog.v1alpha.SubmitEventRequest.ui_call_to_action_click_event:type_name -> prehog.v1alpha.UICallToActionClickEvent - 86, // 177: prehog.v1alpha.SubmitEventRequest.assist_completion:type_name -> prehog.v1alpha.AssistCompletionEvent - 103, // 178: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_start_event:type_name -> prehog.v1alpha.UIIntegrationEnrollStartEvent - 104, // 179: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_complete_event:type_name -> prehog.v1alpha.UIIntegrationEnrollCompleteEvent - 111, // 180: prehog.v1alpha.SubmitEventRequest.editor_change_event:type_name -> prehog.v1alpha.EditorChangeEvent - 76, // 181: prehog.v1alpha.SubmitEventRequest.bot_create:type_name -> prehog.v1alpha.BotCreateEvent - 48, // 182: prehog.v1alpha.SubmitEventRequest.ui_onboard_questionnaire_submit:type_name -> prehog.v1alpha.UIOnboardQuestionnaireSubmitEvent - 77, // 183: prehog.v1alpha.SubmitEventRequest.bot_join:type_name -> prehog.v1alpha.BotJoinEvent - 87, // 184: prehog.v1alpha.SubmitEventRequest.assist_execution:type_name -> prehog.v1alpha.AssistExecutionEvent - 88, // 185: prehog.v1alpha.SubmitEventRequest.assist_new_conversation:type_name -> prehog.v1alpha.AssistNewConversationEvent - 112, // 186: prehog.v1alpha.SubmitEventRequest.device_authenticate_event:type_name -> prehog.v1alpha.DeviceAuthenticateEvent - 114, // 187: prehog.v1alpha.SubmitEventRequest.feature_recommendation_event:type_name -> prehog.v1alpha.FeatureRecommendationEvent - 89, // 188: prehog.v1alpha.SubmitEventRequest.assist_access_request:type_name -> prehog.v1alpha.AssistAccessRequestEvent - 90, // 189: prehog.v1alpha.SubmitEventRequest.assist_action:type_name -> prehog.v1alpha.AssistActionEvent - 113, // 190: prehog.v1alpha.SubmitEventRequest.device_enroll_event:type_name -> prehog.v1alpha.DeviceEnrollEvent - 115, // 191: prehog.v1alpha.SubmitEventRequest.license_limit_event:type_name -> prehog.v1alpha.LicenseLimitEvent - 92, // 192: prehog.v1alpha.SubmitEventRequest.access_list_create:type_name -> prehog.v1alpha.AccessListCreateEvent - 93, // 193: prehog.v1alpha.SubmitEventRequest.access_list_update:type_name -> prehog.v1alpha.AccessListUpdateEvent - 94, // 194: prehog.v1alpha.SubmitEventRequest.access_list_delete:type_name -> prehog.v1alpha.AccessListDeleteEvent - 95, // 195: prehog.v1alpha.SubmitEventRequest.access_list_member_create:type_name -> prehog.v1alpha.AccessListMemberCreateEvent - 96, // 196: prehog.v1alpha.SubmitEventRequest.access_list_member_update:type_name -> prehog.v1alpha.AccessListMemberUpdateEvent - 97, // 197: prehog.v1alpha.SubmitEventRequest.access_list_member_delete:type_name -> prehog.v1alpha.AccessListMemberDeleteEvent - 98, // 198: prehog.v1alpha.SubmitEventRequest.access_list_grants_to_user:type_name -> prehog.v1alpha.AccessListGrantsToUserEvent - 67, // 199: prehog.v1alpha.SubmitEventRequest.ui_discover_ec2_instance_selection:type_name -> prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent - 68, // 200: prehog.v1alpha.SubmitEventRequest.ui_discover_deploy_eice:type_name -> prehog.v1alpha.UIDiscoverDeployEICEEvent - 69, // 201: prehog.v1alpha.SubmitEventRequest.ui_discover_create_node:type_name -> prehog.v1alpha.UIDiscoverCreateNodeEvent - 116, // 202: prehog.v1alpha.SubmitEventRequest.desktop_directory_share:type_name -> prehog.v1alpha.DesktopDirectoryShareEvent - 117, // 203: prehog.v1alpha.SubmitEventRequest.desktop_clipboard_transfer:type_name -> prehog.v1alpha.DesktopClipboardEvent - 118, // 204: prehog.v1alpha.SubmitEventRequest.tag_execute_query:type_name -> prehog.v1alpha.TAGExecuteQueryEvent - 128, // 205: prehog.v1alpha.SubmitEventRequest.external_audit_storage_authenticate:type_name -> prehog.v1alpha.ExternalAuditStorageAuthenticateEvent - 129, // 206: prehog.v1alpha.SubmitEventRequest.security_report_get_result:type_name -> prehog.v1alpha.SecurityReportGetResultEvent - 130, // 207: prehog.v1alpha.SubmitEventRequest.audit_query_run:type_name -> prehog.v1alpha.AuditQueryRunEvent - 131, // 208: prehog.v1alpha.SubmitEventRequest.discovery_fetch_event:type_name -> prehog.v1alpha.DiscoveryFetchEvent - 99, // 209: prehog.v1alpha.SubmitEventRequest.access_list_review_create:type_name -> prehog.v1alpha.AccessListReviewCreateEvent - 100, // 210: prehog.v1alpha.SubmitEventRequest.access_list_review_delete:type_name -> prehog.v1alpha.AccessListReviewDeleteEvent - 101, // 211: prehog.v1alpha.SubmitEventRequest.access_list_review_compliance:type_name -> prehog.v1alpha.AccessListReviewComplianceEvent + 59, // 174: prehog.v1alpha.SubmitEventRequest.ui_discover_integration_aws_oidc_connect_event:type_name -> prehog.v1alpha.UIDiscoverIntegrationAWSOIDCConnectEvent + 60, // 175: prehog.v1alpha.SubmitEventRequest.ui_discover_database_rds_enroll_event:type_name -> prehog.v1alpha.UIDiscoverDatabaseRDSEnrollEvent + 84, // 176: prehog.v1alpha.SubmitEventRequest.ui_call_to_action_click_event:type_name -> prehog.v1alpha.UICallToActionClickEvent + 88, // 177: prehog.v1alpha.SubmitEventRequest.assist_completion:type_name -> prehog.v1alpha.AssistCompletionEvent + 105, // 178: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_start_event:type_name -> prehog.v1alpha.UIIntegrationEnrollStartEvent + 106, // 179: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_complete_event:type_name -> prehog.v1alpha.UIIntegrationEnrollCompleteEvent + 113, // 180: prehog.v1alpha.SubmitEventRequest.editor_change_event:type_name -> prehog.v1alpha.EditorChangeEvent + 78, // 181: prehog.v1alpha.SubmitEventRequest.bot_create:type_name -> prehog.v1alpha.BotCreateEvent + 50, // 182: prehog.v1alpha.SubmitEventRequest.ui_onboard_questionnaire_submit:type_name -> prehog.v1alpha.UIOnboardQuestionnaireSubmitEvent + 79, // 183: prehog.v1alpha.SubmitEventRequest.bot_join:type_name -> prehog.v1alpha.BotJoinEvent + 89, // 184: prehog.v1alpha.SubmitEventRequest.assist_execution:type_name -> prehog.v1alpha.AssistExecutionEvent + 90, // 185: prehog.v1alpha.SubmitEventRequest.assist_new_conversation:type_name -> prehog.v1alpha.AssistNewConversationEvent + 114, // 186: prehog.v1alpha.SubmitEventRequest.device_authenticate_event:type_name -> prehog.v1alpha.DeviceAuthenticateEvent + 116, // 187: prehog.v1alpha.SubmitEventRequest.feature_recommendation_event:type_name -> prehog.v1alpha.FeatureRecommendationEvent + 91, // 188: prehog.v1alpha.SubmitEventRequest.assist_access_request:type_name -> prehog.v1alpha.AssistAccessRequestEvent + 92, // 189: prehog.v1alpha.SubmitEventRequest.assist_action:type_name -> prehog.v1alpha.AssistActionEvent + 115, // 190: prehog.v1alpha.SubmitEventRequest.device_enroll_event:type_name -> prehog.v1alpha.DeviceEnrollEvent + 117, // 191: prehog.v1alpha.SubmitEventRequest.license_limit_event:type_name -> prehog.v1alpha.LicenseLimitEvent + 94, // 192: prehog.v1alpha.SubmitEventRequest.access_list_create:type_name -> prehog.v1alpha.AccessListCreateEvent + 95, // 193: prehog.v1alpha.SubmitEventRequest.access_list_update:type_name -> prehog.v1alpha.AccessListUpdateEvent + 96, // 194: prehog.v1alpha.SubmitEventRequest.access_list_delete:type_name -> prehog.v1alpha.AccessListDeleteEvent + 97, // 195: prehog.v1alpha.SubmitEventRequest.access_list_member_create:type_name -> prehog.v1alpha.AccessListMemberCreateEvent + 98, // 196: prehog.v1alpha.SubmitEventRequest.access_list_member_update:type_name -> prehog.v1alpha.AccessListMemberUpdateEvent + 99, // 197: prehog.v1alpha.SubmitEventRequest.access_list_member_delete:type_name -> prehog.v1alpha.AccessListMemberDeleteEvent + 100, // 198: prehog.v1alpha.SubmitEventRequest.access_list_grants_to_user:type_name -> prehog.v1alpha.AccessListGrantsToUserEvent + 69, // 199: prehog.v1alpha.SubmitEventRequest.ui_discover_ec2_instance_selection:type_name -> prehog.v1alpha.UIDiscoverEC2InstanceSelectionEvent + 70, // 200: prehog.v1alpha.SubmitEventRequest.ui_discover_deploy_eice:type_name -> prehog.v1alpha.UIDiscoverDeployEICEEvent + 71, // 201: prehog.v1alpha.SubmitEventRequest.ui_discover_create_node:type_name -> prehog.v1alpha.UIDiscoverCreateNodeEvent + 118, // 202: prehog.v1alpha.SubmitEventRequest.desktop_directory_share:type_name -> prehog.v1alpha.DesktopDirectoryShareEvent + 119, // 203: prehog.v1alpha.SubmitEventRequest.desktop_clipboard_transfer:type_name -> prehog.v1alpha.DesktopClipboardEvent + 120, // 204: prehog.v1alpha.SubmitEventRequest.tag_execute_query:type_name -> prehog.v1alpha.TAGExecuteQueryEvent + 130, // 205: prehog.v1alpha.SubmitEventRequest.external_audit_storage_authenticate:type_name -> prehog.v1alpha.ExternalAuditStorageAuthenticateEvent + 131, // 206: prehog.v1alpha.SubmitEventRequest.security_report_get_result:type_name -> prehog.v1alpha.SecurityReportGetResultEvent + 132, // 207: prehog.v1alpha.SubmitEventRequest.audit_query_run:type_name -> prehog.v1alpha.AuditQueryRunEvent + 133, // 208: prehog.v1alpha.SubmitEventRequest.discovery_fetch_event:type_name -> prehog.v1alpha.DiscoveryFetchEvent + 101, // 209: prehog.v1alpha.SubmitEventRequest.access_list_review_create:type_name -> prehog.v1alpha.AccessListReviewCreateEvent + 102, // 210: prehog.v1alpha.SubmitEventRequest.access_list_review_delete:type_name -> prehog.v1alpha.AccessListReviewDeleteEvent + 103, // 211: prehog.v1alpha.SubmitEventRequest.access_list_review_compliance:type_name -> prehog.v1alpha.AccessListReviewComplianceEvent 26, // 212: prehog.v1alpha.SubmitEventRequest.mfa_authentication_event:type_name -> prehog.v1alpha.MFAAuthenticationEvent - 41, // 213: prehog.v1alpha.SubmitEventRequest.spiffe_svid_issued:type_name -> prehog.v1alpha.SPIFFESVIDIssuedEvent - 132, // 214: prehog.v1alpha.SubmitEventRequest.okta_access_list_sync:type_name -> prehog.v1alpha.OktaAccessListSyncEvent - 133, // 215: prehog.v1alpha.SubmitEventRequest.database_user_created:type_name -> prehog.v1alpha.DatabaseUserCreatedEvent - 134, // 216: prehog.v1alpha.SubmitEventRequest.database_user_permissions_updated:type_name -> prehog.v1alpha.DatabaseUserPermissionsUpdateEvent - 61, // 217: prehog.v1alpha.SubmitEventRequest.ui_discover_create_discovery_config:type_name -> prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent - 59, // 218: prehog.v1alpha.SubmitEventRequest.ui_discover_kube_eks_enroll_event:type_name -> prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent - 70, // 219: prehog.v1alpha.SubmitEventRequest.ui_discover_create_app_server_event:type_name -> prehog.v1alpha.UIDiscoverCreateAppServerEvent - 121, // 220: prehog.v1alpha.SubmitEventRequest.access_graph_gitlab_scan:type_name -> prehog.v1alpha.AccessGraphGitlabScanEvent - 119, // 221: prehog.v1alpha.SubmitEventRequest.access_graph_secrets_scan_authorized_keys:type_name -> prehog.v1alpha.AccessGraphSecretsScanAuthorizedKeysEvent - 120, // 222: prehog.v1alpha.SubmitEventRequest.access_graph_secrets_scan_ssh_private_keys:type_name -> prehog.v1alpha.AccessGraphSecretsScanSSHPrivateKeysEvent - 122, // 223: prehog.v1alpha.SubmitEventRequest.access_graph_aws_scan:type_name -> prehog.v1alpha.AccessGraphAWSScanEvent - 123, // 224: prehog.v1alpha.SubmitEventRequest.access_graph_access_path_changed:type_name -> prehog.v1alpha.AccessGraphAccessPathChangedEvent - 127, // 225: prehog.v1alpha.SubmitEventRequest.access_graph_crown_jewel_create:type_name -> prehog.v1alpha.AccessGraphCrownJewelCreateEvent - 124, // 226: prehog.v1alpha.SubmitEventRequest.ui_access_graph_crown_jewel_diff_view:type_name -> prehog.v1alpha.UIAccessGraphCrownJewelDiffViewEvent - 135, // 227: prehog.v1alpha.SubmitEventRequest.session_recording_access:type_name -> prehog.v1alpha.SessionRecordingAccessEvent - 136, // 228: prehog.v1alpha.SubmitEventRequest.user_task_state:type_name -> prehog.v1alpha.UserTaskStateEvent - 106, // 229: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_step_event:type_name -> prehog.v1alpha.UIIntegrationEnrollStepEvent - 137, // 230: prehog.v1alpha.SubmitEventRequest.access_request_create:type_name -> prehog.v1alpha.AccessRequestCreateEvent - 138, // 231: prehog.v1alpha.SubmitEventRequest.access_request_review:type_name -> prehog.v1alpha.AccessRequestReviewEvent - 107, // 232: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_section_open_event:type_name -> prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent - 108, // 233: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_field_complete_event:type_name -> prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent - 109, // 234: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_code_copy_event:type_name -> prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent - 110, // 235: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_link_click_event:type_name -> prehog.v1alpha.UIIntegrationEnrollLinkClickEvent - 140, // 236: prehog.v1alpha.SubmitEventRequest.session_summary_create_event:type_name -> prehog.v1alpha.SessionSummaryCreateEvent - 139, // 237: prehog.v1alpha.SubmitEventRequest.session_summary_access_event:type_name -> prehog.v1alpha.SessionSummaryAccessEvent - 142, // 238: prehog.v1alpha.SubmitEventRequest.discovery_config:type_name -> prehog.v1alpha.DiscoveryConfigEvent - 143, // 239: prehog.v1alpha.SubmitEventRequest.identity_security_graph_size_event:type_name -> prehog.v1alpha.IdentitySecurityGraphSizeEvent - 144, // 240: prehog.v1alpha.SubmitEventRequest.identity_security_audit_logs_ingested_event:type_name -> prehog.v1alpha.IdentitySecurityAuditLogsIngestedEvent - 146, // 241: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_access_event:type_name -> prehog.v1alpha.UIAccessListDefineAccessEvent - 147, // 242: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_identities_event:type_name -> prehog.v1alpha.UIAccessListDefineIdentitiesEvent - 148, // 243: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_basic_info_event:type_name -> prehog.v1alpha.UIAccessListDefineBasicInfoEvent - 149, // 244: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_members_event:type_name -> prehog.v1alpha.UIAccessListDefineMembersEvent - 150, // 245: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_owners_event:type_name -> prehog.v1alpha.UIAccessListDefineOwnersEvent - 151, // 246: prehog.v1alpha.SubmitEventRequest.ui_access_list_start_event:type_name -> prehog.v1alpha.UIAccessListStartEvent - 153, // 247: prehog.v1alpha.SubmitEventRequest.ui_access_list_complete_event:type_name -> prehog.v1alpha.UIAccessListCompleteEvent - 154, // 248: prehog.v1alpha.SubmitEventRequest.ui_access_list_integrate_event:type_name -> prehog.v1alpha.UIAccessListIntegrateEvent - 152, // 249: prehog.v1alpha.SubmitEventRequest.ui_access_list_custom_event:type_name -> prehog.v1alpha.UIAccessListCustomEvent - 125, // 250: prehog.v1alpha.SubmitEventRequest.ui_page_view:type_name -> prehog.v1alpha.UIPageViewEvent - 126, // 251: prehog.v1alpha.SubmitEventRequest.ui_usage_reporting_alert_cta_click:type_name -> prehog.v1alpha.UIUsageReportingAlertCtaClickEvent - 141, // 252: prehog.v1alpha.SubmitEventRequest.session_summary_search_event:type_name -> prehog.v1alpha.SessionSummarySearchEvent - 155, // 253: prehog.v1alpha.SubmitEventRequest.ui_interaction:type_name -> prehog.v1alpha.UIInteractionEvent + 43, // 213: prehog.v1alpha.SubmitEventRequest.spiffe_svid_issued:type_name -> prehog.v1alpha.SPIFFESVIDIssuedEvent + 134, // 214: prehog.v1alpha.SubmitEventRequest.okta_access_list_sync:type_name -> prehog.v1alpha.OktaAccessListSyncEvent + 135, // 215: prehog.v1alpha.SubmitEventRequest.database_user_created:type_name -> prehog.v1alpha.DatabaseUserCreatedEvent + 136, // 216: prehog.v1alpha.SubmitEventRequest.database_user_permissions_updated:type_name -> prehog.v1alpha.DatabaseUserPermissionsUpdateEvent + 63, // 217: prehog.v1alpha.SubmitEventRequest.ui_discover_create_discovery_config:type_name -> prehog.v1alpha.UIDiscoverCreateDiscoveryConfigEvent + 61, // 218: prehog.v1alpha.SubmitEventRequest.ui_discover_kube_eks_enroll_event:type_name -> prehog.v1alpha.UIDiscoverKubeEKSEnrollEvent + 72, // 219: prehog.v1alpha.SubmitEventRequest.ui_discover_create_app_server_event:type_name -> prehog.v1alpha.UIDiscoverCreateAppServerEvent + 123, // 220: prehog.v1alpha.SubmitEventRequest.access_graph_gitlab_scan:type_name -> prehog.v1alpha.AccessGraphGitlabScanEvent + 121, // 221: prehog.v1alpha.SubmitEventRequest.access_graph_secrets_scan_authorized_keys:type_name -> prehog.v1alpha.AccessGraphSecretsScanAuthorizedKeysEvent + 122, // 222: prehog.v1alpha.SubmitEventRequest.access_graph_secrets_scan_ssh_private_keys:type_name -> prehog.v1alpha.AccessGraphSecretsScanSSHPrivateKeysEvent + 124, // 223: prehog.v1alpha.SubmitEventRequest.access_graph_aws_scan:type_name -> prehog.v1alpha.AccessGraphAWSScanEvent + 125, // 224: prehog.v1alpha.SubmitEventRequest.access_graph_access_path_changed:type_name -> prehog.v1alpha.AccessGraphAccessPathChangedEvent + 129, // 225: prehog.v1alpha.SubmitEventRequest.access_graph_crown_jewel_create:type_name -> prehog.v1alpha.AccessGraphCrownJewelCreateEvent + 126, // 226: prehog.v1alpha.SubmitEventRequest.ui_access_graph_crown_jewel_diff_view:type_name -> prehog.v1alpha.UIAccessGraphCrownJewelDiffViewEvent + 137, // 227: prehog.v1alpha.SubmitEventRequest.session_recording_access:type_name -> prehog.v1alpha.SessionRecordingAccessEvent + 138, // 228: prehog.v1alpha.SubmitEventRequest.user_task_state:type_name -> prehog.v1alpha.UserTaskStateEvent + 108, // 229: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_step_event:type_name -> prehog.v1alpha.UIIntegrationEnrollStepEvent + 139, // 230: prehog.v1alpha.SubmitEventRequest.access_request_create:type_name -> prehog.v1alpha.AccessRequestCreateEvent + 140, // 231: prehog.v1alpha.SubmitEventRequest.access_request_review:type_name -> prehog.v1alpha.AccessRequestReviewEvent + 109, // 232: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_section_open_event:type_name -> prehog.v1alpha.UIIntegrationEnrollSectionOpenEvent + 110, // 233: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_field_complete_event:type_name -> prehog.v1alpha.UIIntegrationEnrollFieldCompleteEvent + 111, // 234: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_code_copy_event:type_name -> prehog.v1alpha.UIIntegrationEnrollCodeCopyEvent + 112, // 235: prehog.v1alpha.SubmitEventRequest.ui_integration_enroll_link_click_event:type_name -> prehog.v1alpha.UIIntegrationEnrollLinkClickEvent + 142, // 236: prehog.v1alpha.SubmitEventRequest.session_summary_create_event:type_name -> prehog.v1alpha.SessionSummaryCreateEvent + 141, // 237: prehog.v1alpha.SubmitEventRequest.session_summary_access_event:type_name -> prehog.v1alpha.SessionSummaryAccessEvent + 144, // 238: prehog.v1alpha.SubmitEventRequest.discovery_config:type_name -> prehog.v1alpha.DiscoveryConfigEvent + 145, // 239: prehog.v1alpha.SubmitEventRequest.identity_security_graph_size_event:type_name -> prehog.v1alpha.IdentitySecurityGraphSizeEvent + 146, // 240: prehog.v1alpha.SubmitEventRequest.identity_security_audit_logs_ingested_event:type_name -> prehog.v1alpha.IdentitySecurityAuditLogsIngestedEvent + 148, // 241: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_access_event:type_name -> prehog.v1alpha.UIAccessListDefineAccessEvent + 149, // 242: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_identities_event:type_name -> prehog.v1alpha.UIAccessListDefineIdentitiesEvent + 150, // 243: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_basic_info_event:type_name -> prehog.v1alpha.UIAccessListDefineBasicInfoEvent + 151, // 244: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_members_event:type_name -> prehog.v1alpha.UIAccessListDefineMembersEvent + 152, // 245: prehog.v1alpha.SubmitEventRequest.ui_access_list_define_owners_event:type_name -> prehog.v1alpha.UIAccessListDefineOwnersEvent + 153, // 246: prehog.v1alpha.SubmitEventRequest.ui_access_list_start_event:type_name -> prehog.v1alpha.UIAccessListStartEvent + 155, // 247: prehog.v1alpha.SubmitEventRequest.ui_access_list_complete_event:type_name -> prehog.v1alpha.UIAccessListCompleteEvent + 156, // 248: prehog.v1alpha.SubmitEventRequest.ui_access_list_integrate_event:type_name -> prehog.v1alpha.UIAccessListIntegrateEvent + 154, // 249: prehog.v1alpha.SubmitEventRequest.ui_access_list_custom_event:type_name -> prehog.v1alpha.UIAccessListCustomEvent + 127, // 250: prehog.v1alpha.SubmitEventRequest.ui_page_view:type_name -> prehog.v1alpha.UIPageViewEvent + 128, // 251: prehog.v1alpha.SubmitEventRequest.ui_usage_reporting_alert_cta_click:type_name -> prehog.v1alpha.UIUsageReportingAlertCtaClickEvent + 143, // 252: prehog.v1alpha.SubmitEventRequest.session_summary_search_event:type_name -> prehog.v1alpha.SessionSummarySearchEvent + 157, // 253: prehog.v1alpha.SubmitEventRequest.ui_interaction:type_name -> prehog.v1alpha.UIInteractionEvent 38, // 254: prehog.v1alpha.SubmitEventRequest.beams_created:type_name -> prehog.v1alpha.BeamsCreatedEvent 39, // 255: prehog.v1alpha.SubmitEventRequest.beams_destroyed:type_name -> prehog.v1alpha.BeamsDestroyedEvent - 156, // 256: prehog.v1alpha.SubmitEventsRequest.events:type_name -> prehog.v1alpha.SubmitEventRequest - 156, // 257: prehog.v1alpha.TeleportReportingService.SubmitEvent:input_type -> prehog.v1alpha.SubmitEventRequest - 158, // 258: prehog.v1alpha.TeleportReportingService.SubmitEvents:input_type -> prehog.v1alpha.SubmitEventsRequest - 160, // 259: prehog.v1alpha.TeleportReportingService.HelloTeleport:input_type -> prehog.v1alpha.HelloTeleportRequest - 157, // 260: prehog.v1alpha.TeleportReportingService.SubmitEvent:output_type -> prehog.v1alpha.SubmitEventResponse - 159, // 261: prehog.v1alpha.TeleportReportingService.SubmitEvents:output_type -> prehog.v1alpha.SubmitEventsResponse - 161, // 262: prehog.v1alpha.TeleportReportingService.HelloTeleport:output_type -> prehog.v1alpha.HelloTeleportResponse - 260, // [260:263] is the sub-list for method output_type - 257, // [257:260] is the sub-list for method input_type - 257, // [257:257] is the sub-list for extension type_name - 257, // [257:257] is the sub-list for extension extendee - 0, // [0:257] is the sub-list for field type_name + 40, // 256: prehog.v1alpha.SubmitEventRequest.beams_published:type_name -> prehog.v1alpha.BeamsPublishedEvent + 41, // 257: prehog.v1alpha.SubmitEventRequest.beams_unpublished:type_name -> prehog.v1alpha.BeamsUnpublishedEvent + 158, // 258: prehog.v1alpha.SubmitEventsRequest.events:type_name -> prehog.v1alpha.SubmitEventRequest + 158, // 259: prehog.v1alpha.TeleportReportingService.SubmitEvent:input_type -> prehog.v1alpha.SubmitEventRequest + 160, // 260: prehog.v1alpha.TeleportReportingService.SubmitEvents:input_type -> prehog.v1alpha.SubmitEventsRequest + 162, // 261: prehog.v1alpha.TeleportReportingService.HelloTeleport:input_type -> prehog.v1alpha.HelloTeleportRequest + 159, // 262: prehog.v1alpha.TeleportReportingService.SubmitEvent:output_type -> prehog.v1alpha.SubmitEventResponse + 161, // 263: prehog.v1alpha.TeleportReportingService.SubmitEvents:output_type -> prehog.v1alpha.SubmitEventsResponse + 163, // 264: prehog.v1alpha.TeleportReportingService.HelloTeleport:output_type -> prehog.v1alpha.HelloTeleportResponse + 262, // [262:265] is the sub-list for method output_type + 259, // [259:262] is the sub-list for method input_type + 259, // [259:259] is the sub-list for extension type_name + 259, // [259:259] is the sub-list for extension extendee + 0, // [0:259] is the sub-list for field type_name } func init() { file_prehog_v1alpha_teleport_proto_init() } @@ -14178,7 +14332,7 @@ func file_prehog_v1alpha_teleport_proto_init() { if File_prehog_v1alpha_teleport_proto != nil { return } - file_prehog_v1alpha_teleport_proto_msgTypes[131].OneofWrappers = []any{ + file_prehog_v1alpha_teleport_proto_msgTypes[133].OneofWrappers = []any{ (*SubmitEventRequest_UserLogin)(nil), (*SubmitEventRequest_SsoCreate)(nil), (*SubmitEventRequest_ResourceCreate)(nil), @@ -14297,6 +14451,8 @@ func file_prehog_v1alpha_teleport_proto_init() { (*SubmitEventRequest_UiInteraction)(nil), (*SubmitEventRequest_BeamsCreated)(nil), (*SubmitEventRequest_BeamsDestroyed)(nil), + (*SubmitEventRequest_BeamsPublished)(nil), + (*SubmitEventRequest_BeamsUnpublished)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -14304,7 +14460,7 @@ func file_prehog_v1alpha_teleport_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_prehog_v1alpha_teleport_proto_rawDesc), len(file_prehog_v1alpha_teleport_proto_rawDesc)), NumEnums: 25, - NumMessages: 138, + NumMessages: 140, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/ts/prehog/v1alpha/teleport_pb.ts b/gen/proto/ts/prehog/v1alpha/teleport_pb.ts index fb83f0d484cb..8d384ac275a9 100644 --- a/gen/proto/ts/prehog/v1alpha/teleport_pb.ts +++ b/gen/proto/ts/prehog/v1alpha/teleport_pb.ts @@ -546,6 +546,48 @@ export interface BeamsDestroyedEvent { */ region: string; } +/** + * BeamsPublishedEvent is emitted when a beam app is published. + * + * PostHog event: tp.beams.published + * + * @generated from protobuf message prehog.v1alpha.BeamsPublishedEvent + */ +export interface BeamsPublishedEvent { + /** + * anonymized beam ID + * + * PostHog property: tp.beam.beam_id + * + * @generated from protobuf field: string beam_id = 1; + */ + beamId: string; + /** + * the protocol of the published app (http or tcp) + * + * PostHog property: tp.beam.protocol + * + * @generated from protobuf field: string protocol = 2; + */ + protocol: string; +} +/** + * BeamsUnpublishedEvent is emitted when a beam app is unpublished. + * + * PostHog event: tp.beams.unpublished + * + * @generated from protobuf message prehog.v1alpha.BeamsUnpublishedEvent + */ +export interface BeamsUnpublishedEvent { + /** + * anonymized beam ID + * + * PostHog property: tp.beam.beam_id + * + * @generated from protobuf field: string beam_id = 1; + */ + beamId: string; +} /** * the issuance of a user certificate from the user CA * @@ -4443,6 +4485,18 @@ export interface SubmitEventRequest { * @generated from protobuf field: prehog.v1alpha.BeamsDestroyedEvent beams_destroyed = 124; */ beamsDestroyed: BeamsDestroyedEvent; + } | { + oneofKind: "beamsPublished"; + /** + * @generated from protobuf field: prehog.v1alpha.BeamsPublishedEvent beams_published = 125; + */ + beamsPublished: BeamsPublishedEvent; + } | { + oneofKind: "beamsUnpublished"; + /** + * @generated from protobuf field: prehog.v1alpha.BeamsUnpublishedEvent beams_unpublished = 126; + */ + beamsUnpublished: BeamsUnpublishedEvent; } | { oneofKind: undefined; }; @@ -6513,6 +6567,108 @@ class BeamsDestroyedEvent$Type extends MessageType { */ export const BeamsDestroyedEvent = new BeamsDestroyedEvent$Type(); // @generated message type with reflection information, may provide speed optimized methods +class BeamsPublishedEvent$Type extends MessageType { + constructor() { + super("prehog.v1alpha.BeamsPublishedEvent", [ + { no: 1, name: "beam_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "protocol", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): BeamsPublishedEvent { + const message = globalThis.Object.create((this.messagePrototype!)); + message.beamId = ""; + message.protocol = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: BeamsPublishedEvent): BeamsPublishedEvent { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string beam_id */ 1: + message.beamId = reader.string(); + break; + case /* string protocol */ 2: + message.protocol = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: BeamsPublishedEvent, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string beam_id = 1; */ + if (message.beamId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.beamId); + /* string protocol = 2; */ + if (message.protocol !== "") + writer.tag(2, WireType.LengthDelimited).string(message.protocol); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message prehog.v1alpha.BeamsPublishedEvent + */ +export const BeamsPublishedEvent = new BeamsPublishedEvent$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class BeamsUnpublishedEvent$Type extends MessageType { + constructor() { + super("prehog.v1alpha.BeamsUnpublishedEvent", [ + { no: 1, name: "beam_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): BeamsUnpublishedEvent { + const message = globalThis.Object.create((this.messagePrototype!)); + message.beamId = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: BeamsUnpublishedEvent): BeamsUnpublishedEvent { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string beam_id */ 1: + message.beamId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: BeamsUnpublishedEvent, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string beam_id = 1; */ + if (message.beamId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.beamId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message prehog.v1alpha.BeamsUnpublishedEvent + */ +export const BeamsUnpublishedEvent = new BeamsUnpublishedEvent$Type(); +// @generated message type with reflection information, may provide speed optimized methods class UserCertificateIssuedEvent$Type extends MessageType { constructor() { super("prehog.v1alpha.UserCertificateIssuedEvent", [ @@ -13925,7 +14081,9 @@ class SubmitEventRequest$Type extends MessageType { { no: 121, name: "session_summary_search_event", kind: "message", oneof: "event", T: () => SessionSummarySearchEvent }, { no: 122, name: "ui_interaction", kind: "message", oneof: "event", T: () => UIInteractionEvent }, { no: 123, name: "beams_created", kind: "message", oneof: "event", T: () => BeamsCreatedEvent }, - { no: 124, name: "beams_destroyed", kind: "message", oneof: "event", T: () => BeamsDestroyedEvent } + { no: 124, name: "beams_destroyed", kind: "message", oneof: "event", T: () => BeamsDestroyedEvent }, + { no: 125, name: "beams_published", kind: "message", oneof: "event", T: () => BeamsPublishedEvent }, + { no: 126, name: "beams_unpublished", kind: "message", oneof: "event", T: () => BeamsUnpublishedEvent } ]); } create(value?: PartialMessage): SubmitEventRequest { @@ -14659,6 +14817,18 @@ class SubmitEventRequest$Type extends MessageType { beamsDestroyed: BeamsDestroyedEvent.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).beamsDestroyed) }; break; + case /* prehog.v1alpha.BeamsPublishedEvent beams_published */ 125: + message.event = { + oneofKind: "beamsPublished", + beamsPublished: BeamsPublishedEvent.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).beamsPublished) + }; + break; + case /* prehog.v1alpha.BeamsUnpublishedEvent beams_unpublished */ 126: + message.event = { + oneofKind: "beamsUnpublished", + beamsUnpublished: BeamsUnpublishedEvent.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).beamsUnpublished) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -15034,6 +15204,12 @@ class SubmitEventRequest$Type extends MessageType { /* prehog.v1alpha.BeamsDestroyedEvent beams_destroyed = 124; */ if (message.event.oneofKind === "beamsDestroyed") BeamsDestroyedEvent.internalBinaryWrite(message.event.beamsDestroyed, writer.tag(124, WireType.LengthDelimited).fork(), options).join(); + /* prehog.v1alpha.BeamsPublishedEvent beams_published = 125; */ + if (message.event.oneofKind === "beamsPublished") + BeamsPublishedEvent.internalBinaryWrite(message.event.beamsPublished, writer.tag(125, WireType.LengthDelimited).fork(), options).join(); + /* prehog.v1alpha.BeamsUnpublishedEvent beams_unpublished = 126; */ + if (message.event.oneofKind === "beamsUnpublished") + BeamsUnpublishedEvent.internalBinaryWrite(message.event.beamsUnpublished, writer.tag(126, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/lib/usagereporter/teleport/types.go b/lib/usagereporter/teleport/types.go index d103fbf6e4ab..f2abd1bfa6b1 100644 --- a/lib/usagereporter/teleport/types.go +++ b/lib/usagereporter/teleport/types.go @@ -2366,6 +2366,35 @@ func (e *BeamsCreatedEvent) Anonymize(a utils.Anonymizer) *prehogv1a.SubmitEvent } } +// BeamsPublishedEvent is emitted when a beam app is published. +type BeamsPublishedEvent prehogv1a.BeamsPublishedEvent + +// Anonymize anonymizes the event. +func (e *BeamsPublishedEvent) Anonymize(a utils.Anonymizer) *prehogv1a.SubmitEventRequest { + return &prehogv1a.SubmitEventRequest{ + Event: &prehogv1a.SubmitEventRequest_BeamsPublished{ + BeamsPublished: &prehogv1a.BeamsPublishedEvent{ + BeamId: a.AnonymizeString(e.BeamId), + Protocol: e.Protocol, + }, + }, + } +} + +// BeamsUnpublishedEvent is emitted when a beam app is unpublished. +type BeamsUnpublishedEvent prehogv1a.BeamsUnpublishedEvent + +// Anonymize anonymizes the event. +func (e *BeamsUnpublishedEvent) Anonymize(a utils.Anonymizer) *prehogv1a.SubmitEventRequest { + return &prehogv1a.SubmitEventRequest{ + Event: &prehogv1a.SubmitEventRequest_BeamsUnpublished{ + BeamsUnpublished: &prehogv1a.BeamsUnpublishedEvent{ + BeamId: a.AnonymizeString(e.BeamId), + }, + }, + } +} + // BeamsDestroyedEvent is emitted when a beam VM is destroyed. type BeamsDestroyedEvent prehogv1a.BeamsDestroyedEvent diff --git a/proto/prehog/v1alpha/teleport.proto b/proto/prehog/v1alpha/teleport.proto index 11cb43bfb489..1130f4510ef2 100644 --- a/proto/prehog/v1alpha/teleport.proto +++ b/proto/prehog/v1alpha/teleport.proto @@ -394,6 +394,31 @@ message BeamsDestroyedEvent { string region = 3; } +// BeamsPublishedEvent is emitted when a beam app is published. +// +// PostHog event: tp.beams.published +message BeamsPublishedEvent { + // anonymized beam ID + // + // PostHog property: tp.beam.beam_id + string beam_id = 1; + + // the protocol of the published app (http or tcp) + // + // PostHog property: tp.beam.protocol + string protocol = 2; +} + +// BeamsUnpublishedEvent is emitted when a beam app is unpublished. +// +// PostHog event: tp.beams.unpublished +message BeamsUnpublishedEvent { + // anonymized beam ID + // + // PostHog property: tp.beam.beam_id + string beam_id = 1; +} + // the issuance of a user certificate from the user CA // // PostHog event: tp.certificate.issued @@ -2362,6 +2387,8 @@ message SubmitEventRequest { BeamsCreatedEvent beams_created = 123; BeamsDestroyedEvent beams_destroyed = 124; + BeamsPublishedEvent beams_published = 125; + BeamsUnpublishedEvent beams_unpublished = 126; } reserved 8; // UIOnboardGetStartedClickEvent From a277d16b2f3e6be5c7c6f45fd7ee978be518fb26 Mon Sep 17 00:00:00 2001 From: Bartosz Leper Date: Thu, 30 Jul 2026 20:12:43 +0200 Subject: [PATCH 067/347] Backend support for scoped login on web (#68884) * Backend support for scoped login on web * Review * Add support for MFA turned off * Compute device trust mode for scoped sessions * Review --- api/client/webclient/webconfig.go | 6 +- lib/auth/auth_test.go | 165 ++++++++++- lib/auth/auth_with_roles.go | 26 ++ lib/auth/auth_with_roles_test.go | 157 +++++++++++ lib/auth/export_test.go | 2 +- lib/auth/grpcserver.go | 6 +- lib/auth/sessions.go | 59 ++-- .../userpreferencesv1/service.go | 14 +- .../userpreferencesv1/service_test.go | 9 +- lib/client/weblogin.go | 2 + lib/web/apiserver.go | 104 +++++-- lib/web/apiserver_test.go | 258 ++++++++++++------ lib/web/login_helper_test.go | 2 + lib/web/sessions.go | 77 +++++- lib/web/ui/usercontext.go | 3 + 15 files changed, 734 insertions(+), 156 deletions(-) diff --git a/api/client/webclient/webconfig.go b/api/client/webclient/webconfig.go index bdcc7a224d96..99017c557467 100644 --- a/api/client/webclient/webconfig.go +++ b/api/client/webclient/webconfig.go @@ -27,19 +27,19 @@ const ( WebConfigAuthProviderOIDCType = "oidc" // WebConfigAuthProviderOIDCURL is OIDC webapi endpoint. // redirect_url MUST be the last query param, see the comment in parseSSORequestParams for an explanation. - WebConfigAuthProviderOIDCURL = "/v1/webapi/oidc/login/web?connector_id=:providerName&login_hint=:loginHint?&redirect_url=:redirect" + WebConfigAuthProviderOIDCURL = "/v1/webapi/oidc/login/web?connector_id=:providerName&login_hint=:loginHint?&scope=:scope?&redirect_url=:redirect" // WebConfigAuthProviderSAMLType is SAML provider type WebConfigAuthProviderSAMLType = "saml" // WebConfigAuthProviderSAMLURL is SAML webapi endpoint. // redirect_url MUST be the last query param, see the comment in parseSSORequestParams for an explanation. - WebConfigAuthProviderSAMLURL = "/v1/webapi/saml/sso?connector_id=:providerName&login_hint=:loginHint?&redirect_url=:redirect" + WebConfigAuthProviderSAMLURL = "/v1/webapi/saml/sso?connector_id=:providerName&login_hint=:loginHint?&scope=:scope?&redirect_url=:redirect" // WebConfigAuthProviderGitHubType is GitHub provider type WebConfigAuthProviderGitHubType = "github" // WebConfigAuthProviderGitHubURL is GitHub webapi endpoint // redirect_url MUST be the last query param, see the comment in parseSSORequestParams for an explanation. - WebConfigAuthProviderGitHubURL = "/v1/webapi/github/login/web?connector_id=:providerName&redirect_url=:redirect" + WebConfigAuthProviderGitHubURL = "/v1/webapi/github/login/web?connector_id=:providerName&scope=:scope?&redirect_url=:redirect" ) // WebConfig is web application configuration served by the backend to be used in frontend apps. diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index dddc2e20753b..729a7467520c 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -3633,13 +3633,24 @@ func TestGenerateKubernetesUserCert(t *testing.T) { func TestNewWebSession(t *testing.T) { t.Parallel() ctx := t.Context() - p := newAuthSuite(t) + + p, err := newTestPack(ctx, testPackOptions{ + DataDir: t.TempDir(), + ScopesFeatures: scopes.Features{Enabled: true}, + }) + require.NoError(t, err) + + t.Cleanup(func() { + if p.bk != nil { + p.bk.Close() + } + }) // Set a web idle timeout. duration := time.Duration(5) * time.Minute cfg := types.DefaultClusterNetworkingConfig() cfg.SetWebIdleTimeout(duration) - _, err := p.a.UpsertClusterNetworkingConfig(ctx, cfg) + _, err = p.a.UpsertClusterNetworkingConfig(ctx, cfg) require.NoError(t, err) // Create a user. @@ -3670,6 +3681,7 @@ func TestNewWebSession(t *testing.T) { require.NotEmpty(t, ws.GetTLSCert()) t.Run("expands user metadata name in logins", func(t *testing.T) { + ctx := t.Context() user, _, err := authtest.CreateUserAndRole(p.a, "templated-web-user", nil, nil, authtest.WithRoleMutator(func(role types.Role) { role.SetLogins(types.Allow, []string{"{{user.metadata.name}}"}) })) @@ -3683,13 +3695,158 @@ func TestNewWebSession(t *testing.T) { SessionTTL: apidefaults.CertDuration, } - _, checker, err := p.a.NewWebSession(ctx, req, nil /* opts */) + _, checkerCtx, err := p.a.NewWebSession(ctx, req, nil /* opts */) require.NoError(t, err) - logins, err := checker.CheckLoginDuration(0) + logins, err := checkerCtx.CertParams().GetSSHLoginsForTTL(ctx, 0) require.NoError(t, err) require.Contains(t, logins, user.GetName()) }) + + t.Run("scoped session", func(t *testing.T) { + ctx := t.Context() + scopedService := local.NewScopedAccessService(p.bk) + _, err := scopedService.CreateScopedRole(ctx, scopedaccessv1pb.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1pb.ScopedRole_builder{ + Kind: "scoped_role", + Metadata: headerv1.Metadata_builder{ + Name: "web-session", + }.Build(), + Scope: "/test", + Spec: scopedaccessv1pb.ScopedRoleSpec_builder{ + AssignableScopes: []string{"/test"}, + }.Build(), + Version: types.V1, + }.Build(), + }.Build()) + require.NoError(t, err) + + _, err = scopedService.CreateScopedRoleAssignment(ctx, scopedaccessv1pb.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1pb.ScopedRoleAssignment_builder{ + Kind: "scoped_role_assignment", + Metadata: headerv1.Metadata_builder{ + Name: uuid.NewString(), + }.Build(), + Scope: "/test", + SubKind: "dynamic", + Spec: scopedaccessv1pb.ScopedRoleAssignmentSpec_builder{ + User: user.GetName(), + Assignments: []*scopedaccessv1pb.Assignment{ + scopedaccessv1pb.Assignment_builder{ + Role: "/test::web-session", + Scope: "/test", + }.Build(), + }, + }.Build(), + Version: types.V1, + }.Build(), + }.Build()) + require.NoError(t, err) + + // Wait for scoped access cache to see the assignment. + pin := scopesv1pb.Pin_builder{Kind: scopesv1pb.PinKind_PIN_KIND_USER, Scope: "/test"}.Build() + require.EventuallyWithT(t, func(ct *assert.CollectT) { + err := p.a.ScopedAccessCache.PopulatePinnedAssignmentsForUser(ctx, user.GetName(), pin) + assert.NoError(ct, err) + assert.NotNil(ct, pin.GetAssignmentTree()) + }, 15*time.Second, 100*time.Millisecond) + + req := auth.NewWebSessionRequest{ + User: user.GetName(), + Scope: "/test", + } + + ws, _, err := p.a.NewWebSession(ctx, req, nil /* opts */) + require.NoError(t, err) + assert.Equal(t, user.GetName(), ws.GetUser()) + + _, identity := parseX509PEMAndIdentity(t, ws.GetTLSCert()) + sp := identity.ScopePin + require.NotNil(t, sp) + assert.Equal(t, "/test", sp.GetScope()) + assert.Equal(t, scopesv1pb.PinKind_PIN_KIND_USER, sp.GetKind()) + }) +} + +func TestNewWebSessionScopedTrustedDeviceRequirement(t *testing.T) { + t.Parallel() + ctx := t.Context() + + p, err := newTestPack(ctx, testPackOptions{ + DataDir: t.TempDir(), + Modules: modulestest.EnterpriseModules(), + ScopesFeatures: scopes.Features{Enabled: true}, + }) + require.NoError(t, err) + + authPref, err := p.a.GetAuthPreference(ctx) + require.NoError(t, err) + authPref.SetDeviceTrust(&types.DeviceTrust{ + Mode: constants.DeviceTrustModeRequired, + }) + _, err = p.a.UpsertAuthPreference(ctx, authPref) + require.NoError(t, err) + + _, _, err = authtest.CreateUserAndRole(p.a, "test-user", []string{}, nil) + require.NoError(t, err) + + scopedService := p.a.ScopedAccess() + _, err = scopedService.CreateScopedRole(ctx, scopedaccessv1pb.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1pb.ScopedRole_builder{ + Kind: "scoped_role", + Metadata: headerv1.Metadata_builder{ + Name: "some-scoped-role", + }.Build(), + Scope: "/foo", + Spec: scopedaccessv1pb.ScopedRoleSpec_builder{ + AssignableScopes: []string{"/foo/bar"}, + }.Build(), + Version: types.V1, + }.Build(), + }.Build()) + require.NoError(t, err) + + _, err = scopedService.CreateScopedRoleAssignment(ctx, scopedaccessv1pb.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1pb.ScopedRoleAssignment_builder{ + Kind: "scoped_role_assignment", + Metadata: headerv1.Metadata_builder{ + Name: uuid.NewString(), + }.Build(), + Scope: "/foo", + SubKind: "dynamic", + Spec: scopedaccessv1pb.ScopedRoleAssignmentSpec_builder{ + User: "test-user", + Assignments: []*scopedaccessv1pb.Assignment{ + scopedaccessv1pb.Assignment_builder{ + Role: "/foo::some-scoped-role", + Scope: "/foo/bar", + }.Build(), + }, + }.Build(), + Version: types.V1, + }.Build(), + }.Build()) + require.NoError(t, err) + + // Wait for scoped access cache to see the assignment. + pin := scopesv1pb.Pin_builder{Kind: scopesv1pb.PinKind_PIN_KIND_USER, Scope: "/foo/bar"}.Build() + require.EventuallyWithT(t, func(ct *assert.CollectT) { + err := p.a.ScopedAccessCache.PopulatePinnedAssignmentsForUser(ctx, "test-user", pin) + assert.NoError(ct, err) + assert.NotNil(ct, pin.GetAssignmentTree()) + }, 15*time.Second, 100*time.Millisecond) + + ws, _, err := p.a.NewWebSession(ctx, auth.NewWebSessionRequest{ + User: "test-user", + Scope: "/foo/bar", + }, nil /* opts */) + require.NoError(t, err) + + assert.Equal(t, types.TrustedDeviceRequirement_TRUSTED_DEVICE_REQUIREMENT_REQUIRED, ws.GetTrustedDeviceRequirement()) + + // Make sure this has actually created a scoped session. + _, identity := parseX509PEMAndIdentity(t, ws.GetTLSCert()) + assert.NotNil(t, identity.ScopePin) } func TestDeleteMFADeviceSync(t *testing.T) { diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index f7464b529c8f..df394d3430b3 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3591,6 +3591,32 @@ func (a *ServerWithRoles) SubmitAccessReview(ctx context.Context, submission typ return a.authServer.submitAccessReview(ctx, submission, &identity) } +func (a *ScopedServerWithRoles) GetAccessCapabilities(ctx context.Context, req types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) { + if unscoped, ok := a.UnscopedServerWithRoles(); ok { + return unscoped.GetAccessCapabilities(ctx, req) + } + + if a.scopedContext.User == nil { + return nil, trace.AccessDenied( + "access capabilities can only be retrieved by scoped identities if they are users", + ) + } + + // default to checking the capabilities of the caller + if req.User == "" { + req.User = a.scopedContext.User.GetName() + } + + // Scoped identities can only check their own capabilities + if err := a.scopedCurrentUserAction(req.User); err != nil { + return nil, trace.Wrap(err) + } + + // TODO(bl-nero): Implement once it's supported by the backend. Currently, + // there's no concept of scoped access requests. + return &types.AccessCapabilities{}, nil +} + func (a *ServerWithRoles) GetAccessCapabilities(ctx context.Context, req types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) { // default to checking the capabilities of the caller if req.User == "" { diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index d044e7674848..7956a73dff30 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -14847,3 +14847,160 @@ func TestRoleWindowsDesktopLeastPrivilege(t *testing.T) { require.True(t, trace.IsAccessDenied(err), "expected access denied, got: %v", err) }) } + +func TestGetAccessCapabilitiesRBAC(t *testing.T) { + t.Parallel() + + ctx := t.Context() + srv, err := authtest.NewAuthServer(authtest.AuthServerConfig{Dir: t.TempDir()}) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, srv.Close()) }) + + targetUser, err := types.NewUser("target") + require.NoError(t, err) + _, err = srv.AuthServer.CreateUser(ctx, targetUser) + require.NoError(t, err) + + tests := []struct { + name string + allowRules []types.Rule + callerUser string + queriedUser string + wantAccessDenied bool + }{ + { + name: "querying for self doesn't require permissions is allowed", + callerUser: "caller-self-default", + queriedUser: "", + }, + { + name: "querying for self doesn't require permissions is allowed (explicit user name)", + callerUser: "caller-self-explicit", + queriedUser: "caller-self-explicit", + }, + { + name: "querying for other user without permissions is denied", + callerUser: "caller-other-denied", + queriedUser: "target", + wantAccessDenied: true, + }, + { + name: "querying for other user with permissions is allowed", + allowRules: []types.Rule{ + types.NewRule(types.KindUser, []string{types.VerbRead}), + types.NewRule(types.KindRole, []string{types.VerbList, types.VerbRead}), + }, + callerUser: "caller-other-allowed", + queriedUser: "target", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := t.Context() + callerRole, err := types.NewRole(tt.callerUser+"-role", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: tt.allowRules, + }, + }) + require.NoError(t, err) + callerUser, err := authtest.CreateUser(ctx, srv.AuthServer, tt.callerUser, callerRole) + require.NoError(t, err) + + callerIdentity := authz.LocalUser{ + Username: tt.callerUser, + Identity: tlsca.Identity{ + Username: tt.callerUser, + Groups: callerUser.GetRoles(), + }, + } + authCtx, err := authz.ContextForLocalUser( + ctx, callerIdentity, srv.AuthServer.Services, srv.ClusterName, true, /* disableDeviceAuthz */ + ) + require.NoError(t, err) + authCtx.AdminActionAuthState = authz.AdminActionAuthMFAVerified + + server := auth.NewServerWithRoles(srv.AuthServer, srv.AuditLog, *authCtx) + _, err = server.GetAccessCapabilities(authz.ContextWithUser(ctx, callerIdentity), types.AccessCapabilitiesRequest{ + User: tt.queriedUser, + }) + if tt.wantAccessDenied { + require.True(t, trace.IsAccessDenied(err), "expected access denied, got %T: %v", err, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGetAccessCapabilitiesScopedRBAC(t *testing.T) { + t.Parallel() + + ctx := t.Context() + srv, err := authtest.NewAuthServer(authtest.AuthServerConfig{Dir: t.TempDir()}) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, srv.Close()) }) + + targetUser, err := types.NewUser("target") + require.NoError(t, err) + _, err = srv.AuthServer.CreateUser(ctx, targetUser) + require.NoError(t, err) + + tests := []struct { + name string + callerUser string + queriedUser string + wantAccessDenied bool + }{ + { + name: "querying for self is allowed", + callerUser: "scoped-caller-self", + queriedUser: "", + }, + { + name: "querying for other user is denied", + callerUser: "scoped-caller-other", + queriedUser: "target", + wantAccessDenied: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + callerUser, err := types.NewUser(tt.callerUser) + require.NoError(t, err) + callerUser, err = srv.AuthServer.CreateUser(t.Context(), callerUser) + require.NoError(t, err) + + callerIdentity := authz.LocalUser{ + Username: tt.callerUser, + Identity: tlsca.Identity{ + Username: tt.callerUser, + ScopePin: scopesv1.Pin_builder{ + Kind: scopesv1.PinKind_PIN_KIND_USER, + Scope: "/test", + }.Build(), + }, + } + + server := auth.NewScopedServerWithRoles(srv.AuthServer, srv.AuditLog, &authz.ScopedContext{ + User: callerUser, + Identity: callerIdentity, + }) + t.Cleanup(func() { server.Close() }) + + _, err = server.GetAccessCapabilities(authz.ContextWithUser(t.Context(), callerIdentity), types.AccessCapabilitiesRequest{ + User: tt.queriedUser, + }) + if tt.wantAccessDenied { + require.True(t, trace.IsAccessDenied(err), "expected access denied, got %T: %v", err, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/lib/auth/export_test.go b/lib/auth/export_test.go index dad143530192..ed3009bf0056 100644 --- a/lib/auth/export_test.go +++ b/lib/auth/export_test.go @@ -166,7 +166,7 @@ func (a *Server) NewWebSession( ctx context.Context, req NewWebSessionRequest, opts *newWebSessionOpts, -) (types.WebSession, services.AccessChecker, error) { +) (types.WebSession, *services.ScopedAccessCheckerContext, error) { return a.newWebSession(ctx, req, opts) } diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 1b739cd72831..ebff09ae7d2c 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -1293,11 +1293,11 @@ func (g *GRPCServer) GetAccessRequestAllowedPromotions(ctx context.Context, requ } func (g *GRPCServer) GetAccessCapabilities(ctx context.Context, req *types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) { - auth, err := g.authenticate(ctx) + auth, err := g.scopedAuthenticate(ctx) if err != nil { return nil, trace.Wrap(err) } - caps, err := auth.ServerWithRoles.GetAccessCapabilities(ctx, *req) + caps, err := auth.ScopedServerWithRoles.GetAccessCapabilities(ctx, *req) if err != nil { return nil, trace.Wrap(err) } @@ -6505,7 +6505,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { // Initialize and register the user preferences service. userPreferencesSrv, err := userpreferencesv1.NewService(&userpreferencesv1.ServiceConfig{ Backend: cfg.AuthServer.Services, - Authorizer: cfg.Authorizer, + Authorizer: cfg.ScopedAuthorizer, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index ccf769940c61..99d695f06dc1 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -55,11 +55,6 @@ import ( type NewWebSessionRequest = sessionreq.NewWebSessionRequest func (a *Server) CreateWebSessionFromReq(ctx context.Context, req NewWebSessionRequest) (types.WebSession, error) { - if req.Scope != "" { - // TODO(fspmarshall/scopes): add scoping support for web sessions - return nil, trace.BadParameter("web sessions cannot be pinned to a scope") - } - session, _, err := a.newWebSession(ctx, req, nil /* opts */) if err != nil { return nil, trace.Wrap(err) @@ -153,7 +148,16 @@ func (a *Server) newWebSession( ctx context.Context, req NewWebSessionRequest, opts *newWebSessionOpts, -) (types.WebSession, services.AccessChecker, error) { +) (types.WebSession, *services.ScopedAccessCheckerContext, error) { + if req.Scope != "" { + if req.DelegationSessionID != "" { + return nil, nil, trace.BadParameter("access delegation is only supported for unscoped sessions") + } + if err := a.scopesFeatures.AssertEnabled(); err != nil { + return nil, nil, trace.Wrap(err) + } + } + userState, err := a.GetUserOrLoginState(ctx, req.User) if err != nil { return nil, nil, trace.Wrap(err) @@ -168,15 +172,25 @@ func (a *Server) newWebSession( return nil, nil, trace.Wrap(err) } - checker, err := services.NewAccessChecker(&services.AccessInfo{ - Username: userState.GetName(), - Roles: req.Roles, - Traits: req.Traits, - AllowedResourceAccessIDs: req.RequestedResourceAccessIDs, - DelegationSessionID: req.DelegationSessionID, - }, clusterName.GetClusterName(), a) - if err != nil { - return nil, nil, trace.Wrap(err) + var checkerCtx *services.ScopedAccessCheckerContext + var unscopedChecker services.AccessChecker + if req.Scope != "" { + checkerCtx, err = a.AccessCheckerForScope(ctx, req.Scope, userState, req.RequestedResourceAccessIDs) + if err != nil { + return nil, nil, trace.Wrap(err) + } + } else { + unscopedChecker, err = services.NewAccessChecker(&services.AccessInfo{ + Username: userState.GetName(), + Roles: req.Roles, + Traits: req.Traits, + AllowedResourceAccessIDs: req.RequestedResourceAccessIDs, + DelegationSessionID: req.DelegationSessionID, + }, clusterName.GetClusterName(), a) + if err != nil { + return nil, nil, trace.Wrap(err) + } + checkerCtx = services.NewScopedAccessCheckerContextFromUnscoped(unscopedChecker) } idleTimeout, err := a.getWebIdleTimeout(ctx) @@ -207,7 +221,7 @@ func (a *Server) newWebSession( sessionTTL := req.SessionTTL if sessionTTL == 0 { - sessionTTL = checker.AdjustSessionTTL(apidefaults.CertDuration) + sessionTTL = checkerCtx.CertParams().AdjustSessionTTL(apidefaults.CertDuration) } if req.AttestWebSession { @@ -241,7 +255,7 @@ func (a *Server) newWebSession( TTL: sessionTTL, SSHPublicKey: sshAuthorizedKey, TLSPublicKey: tlsPublicKeyPEM, - CheckerContext: services.NewScopedAccessCheckerContextFromUnscoped(checker), // TODO(fspmarshall/scopes): add scoping support to newWebSession. + CheckerContext: checkerCtx, Traits: req.Traits, ActiveRequests: req.AccessRequests, } @@ -322,7 +336,14 @@ func (a *Server) newWebSession( } if tdr, err := a.calculateTrustedDeviceMode(ctx, func() ([]types.Role, error) { - return checker.Roles(), nil + if unscopedChecker != nil { + return unscopedChecker.Roles(), nil + } + // For scoped sessions, no traditional roles apply, so let's only compute + // the trusted device mode from global cluster settings. + // TODO(bl-nero): Update this once there's actual support for device trust + // in scoped sessions. + return nil, nil }); err != nil { a.logger.WarnContext(ctx, "Failed to calculate trusted device mode for session", "error", err) } else { @@ -336,7 +357,7 @@ func (a *Server) newWebSession( } } - return sess, checker, nil + return sess, checkerCtx, nil } func (a *Server) getWebIdleTimeout(ctx context.Context) (time.Duration, error) { diff --git a/lib/auth/userpreferences/userpreferencesv1/service.go b/lib/auth/userpreferences/userpreferencesv1/service.go index 90e9d7e0298b..70540c40ba6e 100644 --- a/lib/auth/userpreferences/userpreferencesv1/service.go +++ b/lib/auth/userpreferences/userpreferencesv1/service.go @@ -32,7 +32,7 @@ import ( // ServiceConfig holds configuration options for the user preferences service. type ServiceConfig struct { Backend services.UserPreferences - Authorizer authz.Authorizer + Authorizer authz.ScopedAuthorizer } // Service implements the teleport.userpreferences.v1.UserPreferencesService RPC service. @@ -40,7 +40,7 @@ type Service struct { userpreferences.UnimplementedUserPreferencesServiceServer backend services.UserPreferences - authorizer authz.Authorizer + authorizer authz.ScopedAuthorizer } // NewService returns a new user preferences gRPC service. @@ -60,11 +60,14 @@ func NewService(cfg *ServiceConfig) (*Service, error) { // GetUserPreferences returns the user preferences for a given user. func (a *Service) GetUserPreferences(ctx context.Context, _ *userpreferences.GetUserPreferencesRequest) (*userpreferences.GetUserPreferencesResponse, error) { - authCtx, err := a.authorizer.Authorize(ctx) + authCtx, err := a.authorizer.AuthorizeScoped(ctx) if err != nil { return nil, trace.Wrap(err) } + if authCtx.User == nil { + return nil, trace.AccessDenied("user preferences are only available to users, not scoped agents") + } username := authCtx.User.GetName() prefs, err := a.backend.GetUserPreferences(ctx, username) @@ -79,11 +82,14 @@ func (a *Service) GetUserPreferences(ctx context.Context, _ *userpreferences.Get // UpsertUserPreferences creates or updates user preferences for a given username. func (a *Service) UpsertUserPreferences(ctx context.Context, req *userpreferences.UpsertUserPreferencesRequest) (*emptypb.Empty, error) { - authCtx, err := a.authorizer.Authorize(ctx) + authCtx, err := a.authorizer.AuthorizeScoped(ctx) if err != nil { return nil, trace.Wrap(err) } + if authCtx.User == nil { + return nil, trace.AccessDenied("user preferences are only available to users, not scoped agents") + } username := authCtx.User.GetName() return &emptypb.Empty{}, trace.Wrap(a.backend.UpsertUserPreferences(ctx, username, req.GetPreferences())) diff --git a/lib/auth/userpreferences/userpreferencesv1/service_test.go b/lib/auth/userpreferences/userpreferencesv1/service_test.go index 028c308b4c98..387655cb0843 100644 --- a/lib/auth/userpreferences/userpreferencesv1/service_test.go +++ b/lib/auth/userpreferences/userpreferencesv1/service_test.go @@ -193,10 +193,11 @@ func initSvc(t *testing.T) (map[string]context.Context, *Service) { }) require.NoError(t, err) - authorizer, err := authz.NewAuthorizer(authz.AuthorizerOpts{ - ClusterName: "test-cluster", - AccessPoint: accessPoint, - LockWatcher: lockWatcher, + authorizer, err := authz.NewScopedAuthorizer(authz.AuthorizerOpts{ + ClusterName: "test-cluster", + AccessPoint: accessPoint, + LockWatcher: lockWatcher, + ScopedRoleReader: local.NewScopedAccessService(backend), }) require.NoError(t, err) diff --git a/lib/client/weblogin.go b/lib/client/weblogin.go index c576b67ce6a7..df903dd8843c 100644 --- a/lib/client/weblogin.go +++ b/lib/client/weblogin.go @@ -229,6 +229,8 @@ type AuthenticateWebUserRequest struct { User string `json:"user"` // WebauthnAssertionResponse is a signed WebAuthn credential assertion. WebauthnAssertionResponse *wantypes.CredentialAssertionResponse `json:"webauthnAssertionResponse,omitempty"` + // Scope is a scope for with the user is authenticated. Empty means unscoped. + Scope string `json:"scope,omitempty"` } type HeadlessRequest struct { diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 8815b6e16235..826c94fd8a0c 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -613,6 +613,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { sessionCache, err := newSessionCache(h.cfg.Context, sessionCacheOptions{ proxyClient: cfg.ProxyClient, accessPoint: cfg.AccessPoint, + scopedRoleReader: cfg.AccessPoint.ScopedRoleReader(), servers: []utils.NetAddr{cfg.AuthServers}, cipherSuites: cfg.CipherSuites, clock: h.clock, @@ -855,7 +856,7 @@ func (h *Handler) authenticateWebSession(w http.ResponseWriter, r *http.Request) if err != nil { return webSession{}, trace.Wrap(err) } - resp, err := newSessionResponse(ctx) + resp, err := newSessionResponse(r.Context(), ctx) if err != nil { return webSession{}, trace.Wrap(err) } @@ -1537,11 +1538,34 @@ func (h *Handler) getUserContext(w http.ResponseWriter, r *http.Request, p httpr }) userContext.AvailableScopes = assignedScopes + + userContext.Scope, err = scopeFromSessionContext(c) + if err != nil { + return nil, trace.Wrap(err) + } } return userContext, nil } +func scopeFromSessionContext(c *SessionContext) (string, error) { + id, err := c.GetIdentity() + if err != nil { + return "", trace.Wrap(err) + } + + if id == nil { + return "", nil + } + + pin := id.ScopePin + if pin == nil { + return "", nil + } + + return pin.GetScope(), nil +} + // PublicProxyAddr returns the publicly advertised proxy address func (h *Handler) PublicProxyAddr() string { return h.cfg.PublicProxyAddr @@ -2485,7 +2509,6 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr logger.ErrorContext(r.Context(), "Failed to parse request remote address", "error", err) return sso.LoginFailedRedirectURL } - response, err := h.cfg.ProxyClient.CreateGithubAuthRequest(r.Context(), types.GithubAuthRequest{ CSRFToken: req.CSRFToken, ConnectorID: req.ConnectorID, @@ -2493,6 +2516,7 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr ClientRedirectURL: req.ClientRedirectURL, ClientLoginIP: remoteAddr, ClientUserAgent: r.UserAgent(), + Scope: req.Scope, }) if err != nil { logger.ErrorContext(r.Context(), "Error creating auth request", "error", err) @@ -2818,6 +2842,9 @@ type CreateSessionReq struct { Pass string `json:"pass"` // SecondFactorToken is the OTP. SecondFactorToken string `json:"second_factor_token"` + // Scope is the scope for which this session is created. Empty means + // unscoped. + Scope string `json:"scope"` } // String returns text description of this response @@ -2853,14 +2880,26 @@ type CreateSessionResponse struct { TrustedDeviceRequirement int32 `json:"trustedDeviceRequirement,omitempty"` } -func newSessionResponse(sctx *SessionContext) (*CreateSessionResponse, error) { +func newSessionResponse(ctx context.Context, sctx *SessionContext) (*CreateSessionResponse, error) { accessChecker, err := sctx.GetUserAccessChecker() if err != nil { return nil, trace.Wrap(err) } - _, err = accessChecker.CheckLoginDuration(0) - if err != nil { - return nil, trace.Wrap(err) + + if accessChecker.AccessInfo().ScopePin == nil { + _, err = accessChecker.CheckLoginDuration(0) + if err != nil { + return nil, trace.Wrap(err) + } + } else { + checkerContext, err := sctx.GetUserScopedAccessCheckerContext(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + _, err = checkerContext.CertParams().GetSSHLoginsForTTL(ctx, 0) + if err != nil { + return nil, trace.Wrap(err) + } } token, err := sctx.getToken() @@ -2912,11 +2951,11 @@ func (h *Handler) createWebSession(w http.ResponseWriter, r *http.Request, p htt var webSession types.WebSession switch { case !cap.IsSecondFactorEnforced(): - webSession, err = h.auth.AuthWithoutOTP(r.Context(), req.User, req.Pass, clientMeta) + webSession, err = h.auth.AuthWithoutOTP(r.Context(), req.User, req.Pass, req.Scope, clientMeta) case req.SecondFactorToken == "" && !cap.IsSecondFactorEnforced(): - webSession, err = h.auth.AuthWithoutOTP(r.Context(), req.User, req.Pass, clientMeta) + webSession, err = h.auth.AuthWithoutOTP(r.Context(), req.User, req.Pass, req.Scope, clientMeta) case cap.IsSecondFactorTOTPAllowed(): - webSession, err = h.auth.AuthWithOTP(r.Context(), req.User, req.Pass, req.SecondFactorToken, clientMeta) + webSession, err = h.auth.AuthWithOTP(r.Context(), req.User, req.Pass, req.SecondFactorToken, req.Scope, clientMeta) default: return nil, trace.AccessDenied("direct login with password+otp not supported by this cluster") } @@ -2941,7 +2980,7 @@ func (h *Handler) createWebSession(w http.ResponseWriter, r *http.Request, p htt return nil, trace.AccessDenied("need auth") } - res, err := newSessionResponse(ctx) + res, err := newSessionResponse(r.Context(), ctx) return res, trace.Wrap(err) } @@ -3049,6 +3088,9 @@ type renewSessionRequest struct { // - ReloadUser (opt): similar to default but updates user related data (e.g login traits) by retrieving it from the backend // - default (none set): create new session with currently assigned roles func (h *Handler) renewWebSession(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (any, error) { + // TODO(bl-nero): Fix session renewal for scoped sessions. This endpoint + // doesn't work for scoped sessions, but currently, the web UI doesn't even + // call it in such scenario. req := renewSessionRequest{} if err := httplib.ReadResourceJSON(r, &req); err != nil { return nil, trace.Wrap(err) @@ -3070,7 +3112,7 @@ func (h *Handler) renewWebSession(w http.ResponseWriter, r *http.Request, params return nil, trace.Wrap(err) } - res, err := newSessionResponse(newContext) + res, err := newSessionResponse(r.Context(), newContext) return res, trace.Wrap(err) } @@ -3148,7 +3190,7 @@ func (h *Handler) changeUserAuthentication(w http.ResponseWriter, r *http.Reques } // Checks for at least one valid login. - if _, err := newSessionResponse(ctx); err != nil { + if _, err := newSessionResponse(r.Context(), ctx); err != nil { return nil, trace.Wrap(err) } @@ -3369,7 +3411,7 @@ func (h *Handler) mfaLoginFinishSession(w http.ResponseWriter, r *http.Request, return nil, trace.AccessDenied("need auth") } - return newSessionResponse(ctx) + return newSessionResponse(r.Context(), ctx) } // getClusters returns a list of cluster and its data. @@ -3456,7 +3498,7 @@ func (h *Handler) getSiteNamespaces(w http.ResponseWriter, r *http.Request, _ ht }, nil } -func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesRequest, error) { +func makeUnifiedResourceRequest(r *http.Request, scopePin *scopesv1.Pin) (*proto.ListUnifiedResourcesRequest, error) { values := r.URL.Query() limit, err := QueryLimitAsInt32(values, "limit", defaults.MaxIterationLimit) @@ -3480,16 +3522,23 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq } // set default kinds to be requested if none exist in the request + scopedIdentity := scopePin.GetScope() != "" if len(kinds) == 0 { - kinds = []string{ - types.KindApp, - types.KindDatabase, - types.KindNode, - types.KindWindowsDesktop, - types.KindLinuxDesktop, - types.KindKubernetesCluster, - types.KindSAMLIdPServiceProvider, - types.KindGitServer, + if scopedIdentity { + // TODO(bl-nero): Support more resource kinds when they are supported for + // scoped sessions. + kinds = []string{types.KindNode} + } else { + kinds = []string{ + types.KindApp, + types.KindDatabase, + types.KindNode, + types.KindWindowsDesktop, + types.KindLinuxDesktop, + types.KindKubernetesCluster, + types.KindSAMLIdPServiceProvider, + types.KindGitServer, + } } } @@ -3506,7 +3555,7 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq PredicateExpression: values.Get("query"), SearchKeywords: client.ParseSearchKeywords(values.Get("search"), ' '), UseSearchAsRoles: useSearchAsRoles, - IncludeLogins: true, + IncludeLogins: !scopedIdentity, IncludeRequestable: includeRequestable, }, nil } @@ -3553,7 +3602,7 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt return nil, trace.Wrap(err) } - req, err := makeUnifiedResourceRequest(request) + req, err := makeUnifiedResourceRequest(request, identity.ScopePin) if err != nil { return nil, trace.Wrap(err) } @@ -5794,6 +5843,8 @@ type SSORequestParams struct { CSRFToken string // LoginHint is the user's identifier (email) for identifier-first login. LoginHint string + // Scope is the scope that this session will be pinned to. + Scope string } // ParseSSORequestParams extracts the SSO request parameters from an http.Request, @@ -5835,11 +5886,14 @@ func ParseSSORequestParams(r *http.Request) (*SSORequestParams, error) { return nil, trace.Wrap(err) } + scope := query.Get("scope") + return &SSORequestParams{ ClientRedirectURL: clientRedirectURL, ConnectorID: connectorID, CSRFToken: csrfToken, LoginHint: loginHint, + Scope: scope, }, nil } diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 38de4272d760..5ce640689817 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -145,7 +145,7 @@ import ( "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/scopes" - "github.com/gravitational/teleport/lib/scopes/access" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" scopedapp "github.com/gravitational/teleport/lib/scopes/app" "github.com/gravitational/teleport/lib/secret" "github.com/gravitational/teleport/lib/service/servicecfg" @@ -324,6 +324,9 @@ type webSuiteConfig struct { // middleware adds optional middleware to the test handler. middleware func(next http.Handler) http.Handler + + // scopesFeatures controls scoped access feature flags. + scopesFeatures scopes.Features } func newWebSuiteWithConfig(t *testing.T, cfg webSuiteConfig) *WebSuite { @@ -365,6 +368,7 @@ func newWebSuiteWithConfig(t *testing.T, cfg webSuiteConfig) *WebSuite { ClusterNetworkingConfig: networkingConfig, AuthPreferenceSpec: cfg.authPreferenceSpec, Modules: cfg.modules, + ScopesFeatures: cfg.scopesFeatures, }, } @@ -675,6 +679,7 @@ func newWebSuiteWithConfig(t *testing.T, cfg webSuiteConfig) *WebSuite { ProxySSHAddr: "127.0.0.1", AccessPoint: s.server.Auth(), }, + ScopesFeatures: cfg.scopesFeatures, SessionControl: SessionControllerFunc(func(ctx context.Context, sctx *SessionContext, login, localAddr, remoteAddr string) (context.Context, error) { controller := srv.WebSessionController(proxySessionController) ctx, err := controller(ctx, sctx, login, localAddr, remoteAddr) @@ -3226,10 +3231,14 @@ func TestLogin_PrivateKeyEnabledError(t *testing.T) { func TestLogin(t *testing.T) { t.Parallel() - s := newWebSuite(t) + ctx := t.Context() + + s := newWebSuiteWithConfig(t, webSuiteConfig{ + scopesFeatures: scopes.Features{Enabled: true}, + }) ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{ Type: constants.Local, - SecondFactor: constants.SecondFactorOff, + SecondFactor: constants.SecondFactorOTP, }) require.NoError(t, err) _, err = s.server.Auth().UpsertAuthPreference(s.ctx, ap) @@ -3239,63 +3248,130 @@ func TestLogin(t *testing.T) { const user = "user1" const pass = "password1234" s.createUser(t, user, "root", pass, "") + otpSecret := newOTPSharedSecret() + dev, err := services.NewTOTPDevice("otp-device", otpSecret, s.clock.Now()) + require.NoError(t, err) + err = s.server.Auth().UpsertMFADevice(ctx, user, dev) + require.NoError(t, err) + + _, err = s.server.Auth().ScopedAccess().CreateScopedRole( + ctx, scopedaccessv1.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1.ScopedRole_builder{ + Kind: scopedaccess.KindScopedRole, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "prod-role", + }.Build(), + Scope: "/prod", + Spec: scopedaccessv1.ScopedRoleSpec_builder{ + AssignableScopes: []string{"/prod/east"}, + }.Build(), + }.Build(), + }.Build(), + ) + require.NoError(t, err) - clt := s.client(t) - ctx := context.Background() + _, err = s.server.Auth().ScopedAccess().CreateScopedRoleAssignment( + ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "prod-role-assignment", + }.Build(), + Scope: "/prod", + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + User: "user1", + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{ + Role: "/prod::prod-role", + Scope: "/prod/east", + }.Build(), + }, + }.Build(), + }.Build(), + }.Build(), + ) + require.NoError(t, err) - const ua = "test-ua" - sessionResp, httpResp := loginWebOTP(t, ctx, loginWebOTPParams{ - webClient: clt, - user: user, - password: pass, - userAgent: ua, - }) + clt := s.client(t) - events, _, err := s.server.AuthServer.AuditLog.SearchEvents(ctx, events.SearchEventsRequest{ - From: s.clock.Now().Add(-time.Hour), - To: s.clock.Now().Add(time.Hour), - EventTypes: []string{events.UserLoginEvent}, - Limit: 1, - Order: types.EventOrderDescending, - }) - require.NoError(t, err) - event := events[0].(*apievents.UserLogin) - require.True(t, event.Success) - require.Equal(t, ua, event.UserAgent) - require.True(t, strings.HasPrefix(event.RemoteAddr, "127.0.0.1:")) + cases := []struct { + name string + scope string + }{ + {name: "unscoped", scope: ""}, + {name: "scoped", scope: "/prod/east"}, + } - cookies := httpResp.Cookies() - require.Len(t, cookies, 1) - require.NotEmpty(t, sessionResp.SessionExpires) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := t.Context() + const ua = "test-ua" + s.clock.Advance(time.Minute) // Prevent reusing old OTP + sessionResp, httpResp := loginWebOTP(t, ctx, loginWebOTPParams{ + webClient: clt, + clock: s.clock, + user: user, + password: pass, + otpSecret: otpSecret, + scope: tc.scope, + userAgent: ua, + }) - // now make sure we are logged in by calling authenticated method - // we need to supply both session cookie and bearer token for - // request to succeed - jar, err := cookiejar.New(nil) - require.NoError(t, err) + events, _, err := s.server.AuthServer.AuditLog.SearchEvents(ctx, events.SearchEventsRequest{ + From: s.clock.Now().Add(-time.Hour), + To: s.clock.Now().Add(time.Hour), + EventTypes: []string{events.UserLoginEvent}, + Limit: 1, + Order: types.EventOrderDescending, + }) + require.NoError(t, err) + event := events[0].(*apievents.UserLogin) + require.True(t, event.Success) + require.Equal(t, ua, event.UserAgent) + require.True(t, strings.HasPrefix(event.RemoteAddr, "127.0.0.1:")) + + cookies := httpResp.Cookies() + require.Len(t, cookies, 1) + require.NotEmpty(t, sessionResp.SessionExpires) + + // now make sure we are logged in by calling authenticated method + // we need to supply both session cookie and bearer token for + // request to succeed + jar, err := cookiejar.New(nil) + require.NoError(t, err) - clt = s.client(t, roundtrip.BearerAuth(sessionResp.Token), roundtrip.CookieJar(jar)) - jar.SetCookies(s.url(), cookies) + clt = s.client(t, roundtrip.BearerAuth(sessionResp.Token), roundtrip.CookieJar(jar)) + jar.SetCookies(s.url(), cookies) - re, err := clt.Get(s.ctx, clt.Endpoint("webapi", "sites"), url.Values{}) - require.NoError(t, err) + re, err := clt.Get( + s.ctx, + clt.Endpoint("webapi", "sites", s.server.ClusterName(), "context"), + url.Values{}, + ) + require.NoError(t, err) - var clusters []webui.Cluster - require.NoError(t, json.Unmarshal(re.Bytes(), &clusters)) + var userContext webui.UserContext + require.NoError(t, json.Unmarshal(re.Bytes(), &userContext)) + assert.Equal(t, tc.scope, userContext.Scope) - // in absence of session cookie or bearer auth the same request fill fail + // in absence of session cookie or bearer auth the same request fill fail - // no session cookie: - clt = s.client(t, roundtrip.BearerAuth(sessionResp.Token)) - _, err = clt.Get(s.ctx, clt.Endpoint("webapi", "sites"), url.Values{}) - require.Error(t, err) - require.True(t, trace.IsAccessDenied(err)) + // no session cookie: + clt = s.client(t, roundtrip.BearerAuth(sessionResp.Token)) + _, err = clt.Get(s.ctx, clt.Endpoint("webapi", "sites"), url.Values{}) + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) - // no bearer token: - clt = s.client(t, roundtrip.CookieJar(jar)) - _, err = clt.Get(s.ctx, clt.Endpoint("webapi", "sites"), url.Values{}) - require.Error(t, err) - require.True(t, trace.IsAccessDenied(err)) + // no bearer token: + clt = s.client(t, roundtrip.CookieJar(jar)) + _, err = clt.Get(s.ctx, clt.Endpoint("webapi", "sites"), url.Values{}) + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }) + } } // TestEmptyMotD ensures that responses returned by both /webapi/ping and @@ -9662,21 +9738,17 @@ type testProxy struct { webURL url.URL } -// authPack returns new authenticated package consisting of created valid -// user, otp token, created web session and authenticated client. -func (r *testProxy) authPack(t *testing.T, teleportUser string, roles []types.Role) *authPack { +// authPackWithLoginParams returns new authenticated package consisting of +// created valid user, otp token, created web session and authenticated client +// with given login params. +func (r *testProxy) authPackWithLoginParams( + t *testing.T, teleportUser string, loginParams loginWebOTPParams, roles []types.Role, +) *authPack { ctx := context.Background() - const ( - pass = "abcdef123456" - rawSecret = "def456" - ) - u, err := user.Current() require.NoError(t, err) loginUser := u.Username - otpSecret := newOTPSharedSecret() - ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{ Type: constants.Local, SecondFactor: constants.SecondFactorOTP, @@ -9686,15 +9758,11 @@ func (r *testProxy) authPack(t *testing.T, teleportUser string, roles []types.Ro _, err = r.auth.Auth().UpsertAuthPreference(ctx, ap) require.NoError(t, err) - r.createUser(context.Background(), t, teleportUser, loginUser, pass, otpSecret, roles) + r.createUser( + context.Background(), t, teleportUser, loginUser, loginParams.password, loginParams.otpSecret, roles, + ) - sessionResp, httpResp := loginWebOTP(t, ctx, loginWebOTPParams{ - webClient: r.newClient(t), - clock: r.clock, - user: teleportUser, - password: pass, - otpSecret: otpSecret, - }) + sessionResp, httpResp := loginWebOTP(t, ctx, loginParams) jar, err := cookiejar.New(nil) require.NoError(t, err) @@ -9703,19 +9771,48 @@ func (r *testProxy) authPack(t *testing.T, teleportUser string, roles []types.Ro jar.SetCookies(&r.webURL, httpResp.Cookies()) return &authPack{ - otpSecret: otpSecret, + otpSecret: loginParams.otpSecret, user: teleportUser, login: loginUser, session: sessionResp, clt: clt, cookies: httpResp.Cookies(), - password: pass, + password: loginParams.password, device: &authtest.Device{ - TOTPSecret: otpSecret, + TOTPSecret: loginParams.otpSecret, }, } } +func (r *testProxy) authPack(t *testing.T, teleportUser string, roles []types.Role) *authPack { + return r.authPackWithLoginParams( + t, teleportUser, + loginWebOTPParams{ + webClient: r.newClient(t), + clock: r.clock, + user: teleportUser, + password: "abcdef123456", + otpSecret: newOTPSharedSecret(), + }, + roles, + ) +} + +func (r *testProxy) scopedAuthPack(t *testing.T, teleportUser string, scope string, roles []types.Role) *authPack { + return r.authPackWithLoginParams( + t, teleportUser, + loginWebOTPParams{ + webClient: r.newClient(t), + clock: r.clock, + user: teleportUser, + password: "abcdef123456", + otpSecret: newOTPSharedSecret(), + scope: scope, + }, + roles, + ) +} + func (r *testProxy) authPackFromPack(t *testing.T, pack *authPack) *authPack { jar, err := cookiejar.New(nil) require.NoError(t, err) @@ -9931,7 +10028,7 @@ func TestUserContextWithAccessRequest(t *testing.T) { require.Equal(t, accessRequestID, userContext.ConsumedAccessRequestID) } -func TestUerContextWithScopesNoAssignments(t *testing.T) { +func TestUserContextWithScopesNoAssignments(t *testing.T) { t.Parallel() env := newWebPack(t, 1, withScopesFeatures(scopes.Features{Enabled: true})) proxy := env.proxies[0] @@ -9954,7 +10051,7 @@ func TestUerContextWithScopesNoAssignments(t *testing.T) { require.Empty(t, userContext.AvailableScopes) } -func TestUerContextWithScopes(t *testing.T) { +func TestUserContextWithScopes(t *testing.T) { t.Parallel() env := newWebPack(t, 1, withScopesFeatures(scopes.Features{Enabled: true})) proxy := env.proxies[0] @@ -9963,7 +10060,7 @@ func TestUerContextWithScopes(t *testing.T) { // Create scoped roles. _, err := env.server.Auth().ScopedAccess().CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{ Role: scopedaccessv1.ScopedRole_builder{ - Kind: access.KindScopedRole, + Kind: scopedaccess.KindScopedRole, Version: types.V1, Metadata: headerv1.Metadata_builder{ Name: "role-a", @@ -9977,7 +10074,7 @@ func TestUerContextWithScopes(t *testing.T) { require.NoError(t, err) _, err = env.server.Auth().ScopedAccess().CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{ Role: scopedaccessv1.ScopedRole_builder{ - Kind: access.KindScopedRole, + Kind: scopedaccess.KindScopedRole, Version: types.V1, Metadata: headerv1.Metadata_builder{ Name: "role-b", @@ -9994,8 +10091,8 @@ func TestUerContextWithScopes(t *testing.T) { username := "dave" assignment1, err := env.server.Auth().ScopedAccess().CreateScopedRoleAssignment(ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ - Kind: access.KindScopedRoleAssignment, - SubKind: access.SubKindDynamic, + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, Version: types.V1, Metadata: headerv1.Metadata_builder{ Name: "assignment-1", @@ -10021,8 +10118,8 @@ func TestUerContextWithScopes(t *testing.T) { require.NoError(t, err) assignment2, err := env.server.Auth().ScopedAccess().CreateScopedRoleAssignment(ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ - Kind: access.KindScopedRoleAssignment, - SubKind: access.SubKindDynamic, + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, Version: types.V1, Metadata: headerv1.Metadata_builder{ Name: "assignment-2", @@ -10048,7 +10145,7 @@ func TestUerContextWithScopes(t *testing.T) { waitForSRACache(t, env.server.TLS, assignment1, assignment2) // Create and authenticate the test user. - pack := proxy.authPack(t, username, []types.Role{}) + pack := proxy.scopedAuthPack(t, username, "/test/a2", []types.Role{}) // Make a request to fetch the userContext. endpoint := pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "context") @@ -10061,7 +10158,8 @@ func TestUerContextWithScopes(t *testing.T) { require.NoError(t, err) // Verify that the userContext returned contains the assigned scopes. - require.Equal(t, []string{"/test/a1", "/test/a2", "/test/b1"}, userContext.AvailableScopes) + assert.Equal(t, []string{"/test/a1", "/test/a2", "/test/b1"}, userContext.AvailableScopes) + assert.Equal(t, "/test/a2", userContext.Scope) } func waitForSRACache(t *testing.T, srv *authtest.TLSServer, resps ...*scopedaccessv1.CreateScopedRoleAssignmentResponse) { diff --git a/lib/web/login_helper_test.go b/lib/web/login_helper_test.go index 2829a2d1400d..52d2e2a9babf 100644 --- a/lib/web/login_helper_test.go +++ b/lib/web/login_helper_test.go @@ -54,6 +54,7 @@ type loginWebOTPParams struct { // If empty then no OTP is sent in the request. otpSecret string + scope string // Optional. userAgent string // Optional. overrideContentType string // Optional. } @@ -110,6 +111,7 @@ func rawLoginWebOTP(ctx context.Context, params loginWebOTPParams) (resp *Draine User: params.user, Pass: params.password, SecondFactorToken: code, + Scope: params.scope, }) if err != nil { return nil, nil, trace.Wrap(err, "request marshal") diff --git a/lib/web/sessions.go b/lib/web/sessions.go index 7772aeae415b..5321d91ff054 100644 --- a/lib/web/sessions.go +++ b/lib/web/sessions.go @@ -108,6 +108,14 @@ type SessionContextConfig struct { // "GetNodes". UnsafeCachedAuthClient authclient.ReadProxyAccessPoint + // UnsafeScoopedRoleReader is a scoped role reader. It's authenticated with + // the identity of the node, hence it's "unsafe". + // + // Only use it where the identity of the caller will not affect the result of + // the RPC. For example, never call it to get a list of roles and return it + // to the user, as it may return more than the user is allowed to see. + UnsafeScopedRoleReader services.ScopedRoleReader + Parent *sessionCache // Resources is a persistent resource store this context is bound to. // The store maintains a list of resources between session renewals @@ -140,6 +148,10 @@ func (c *SessionContextConfig) CheckAndSetDefaults() error { return trace.BadParameter("Session required") } + if c.UnsafeCachedAuthClient == nil { + return trace.BadParameter("Scoped role reader required") + } + if c.Log == nil { c.Log = slog.With( "user", c.User, @@ -518,6 +530,37 @@ func (c *SessionContext) GetUserAccessChecker() (services.AccessChecker, error) return accessChecker, trace.Wrap(err) } +func (c *SessionContext) GetUserScopedAccessCheckerContext(ctx context.Context) (*services.ScopedAccessCheckerContext, error) { + cert, err := c.GetSSHCertificate() + if err != nil { + return nil, trace.Wrap(err) + } + + ident, err := sshca.DecodeIdentity(cert) + if err != nil { + return nil, trace.Wrap(err) + } + + accessInfo := services.AccessInfoFromLocalSSHIdentity(ident) + + if accessInfo.ScopePin == nil { + checker, err := c.GetUserAccessChecker() + if err != nil { + return nil, trace.Wrap(err) + } + + return services.NewScopedAccessCheckerContextFromUnscoped(checker), nil + } + + checkerCtx, err := services.NewScopedAccessCheckerContext( + ctx, accessInfo, c.cfg.RootClusterName, c.cfg.UnsafeScopedRoleReader, + ) + if err != nil { + return nil, trace.Wrap(err) + } + return checkerCtx, err +} + // GetProxyListenerMode returns cluster proxy listener mode form cluster networking config. func (c *SessionContext) GetProxyListenerMode(ctx context.Context) (types.ProxyListenerMode, error) { resp, err := c.cfg.UnsafeCachedAuthClient.GetClusterNetworkingConfig(ctx) @@ -627,11 +670,12 @@ func (c *SessionContext) expired(ctx context.Context) bool { const cachedSessionLingeringThreshold = 2 * time.Minute type sessionCacheOptions struct { - proxyClient authclient.ClientI - accessPoint authclient.ReadProxyAccessPoint - servers []utils.NetAddr - cipherSuites []uint16 - clock clockwork.Clock + proxyClient authclient.ClientI + scopedRoleReader services.ScopedRoleReader + accessPoint authclient.ReadProxyAccessPoint + servers []utils.NetAddr + cipherSuites []uint16 + clock clockwork.Clock // sessionLingeringThreshold specifies the time the session will linger // in the cache before getting purged after it has expired sessionLingeringThreshold time.Duration @@ -667,6 +711,7 @@ func newSessionCache(ctx context.Context, config sessionCacheOptions) (*sessionC cache := &sessionCache{ clusterName: clusterName.GetClusterName(), proxyClient: config.proxyClient, + scopedRoleReader: config.scopedRoleReader, accessPoint: config.accessPoint, sessions: make(map[string]*SessionContext), resources: make(map[string]*sessionResources), @@ -701,13 +746,14 @@ func newSessionCache(ctx context.Context, config sessionCacheOptions) (*sessionC // sessionCache handles web session authentication, // and holds in-memory contexts associated with each session type sessionCache struct { - log *slog.Logger - proxyClient authclient.ClientI - authServers []utils.NetAddr - accessPoint authclient.ReadProxyAccessPoint - closer *utils.CloseBroadcaster - clusterName string - clock clockwork.Clock + log *slog.Logger + proxyClient authclient.ClientI + authServers []utils.NetAddr + accessPoint authclient.ReadProxyAccessPoint + scopedRoleReader services.ScopedRoleReader + closer *utils.CloseBroadcaster + clusterName string + clock clockwork.Clock // sessionLingeringThreshold specifies the time the session will linger // in the cache before getting purged after it has expired sessionLingeringThreshold time.Duration @@ -937,6 +983,7 @@ func (s *sessionCache) releaseResourcesIfNoDeviceExtensions(ctx context.Context, func (s *sessionCache) AuthWithOTP( ctx context.Context, user, pass, otpToken string, + scope string, clientMeta *authclient.ForwardedClientMetadata, ) (types.WebSession, error) { return s.proxyClient.AuthenticateWebUser(ctx, authclient.AuthenticateUserRequest{ @@ -947,13 +994,14 @@ func (s *sessionCache) AuthWithOTP( Token: otpToken, }, ClientMetadata: clientMeta, + Scope: scope, }) } // AuthWithoutOTP authenticates the specified user with the given password. // Returns a new web session if successful. func (s *sessionCache) AuthWithoutOTP( - ctx context.Context, user, pass string, clientMeta *authclient.ForwardedClientMetadata, + ctx context.Context, user, pass string, scope string, clientMeta *authclient.ForwardedClientMetadata, ) (types.WebSession, error) { return s.proxyClient.AuthenticateWebUser(ctx, authclient.AuthenticateUserRequest{ Username: user, @@ -961,6 +1009,7 @@ func (s *sessionCache) AuthWithoutOTP( Password: []byte(pass), }, ClientMetadata: clientMeta, + Scope: scope, }) } @@ -970,6 +1019,7 @@ func (s *sessionCache) AuthenticateWebUser( authReq := authclient.AuthenticateUserRequest{ Username: req.User, ClientMetadata: clientMeta, + Scope: req.Scope, } if req.WebauthnAssertionResponse != nil { authReq.Webauthn = req.WebauthnAssertionResponse @@ -1227,6 +1277,7 @@ func (s *sessionCache) newSessionContextFromSession(ctx context.Context, session User: session.GetUser(), RootClient: userClient, UnsafeCachedAuthClient: s.accessPoint, + UnsafeScopedRoleReader: s.scopedRoleReader, Parent: s, Resources: s.upsertSessionContext(session.GetUser()), Session: session, diff --git a/lib/web/ui/usercontext.go b/lib/web/ui/usercontext.go index 7158891f59be..2bb169e79ec8 100644 --- a/lib/web/ui/usercontext.go +++ b/lib/web/ui/usercontext.go @@ -79,6 +79,9 @@ type UserContext struct { // AvailableScopes is a list of scopes available to the user through their // scoped role assignments. AvailableScopes []string `json:"availableScopes"` + // Scope contains the scope pinned to the current session. An empty string + // indicates an unscoped session. + Scope string `json:"scope"` } func getAccessStrategy(roleset services.RoleSet) accessStrategy { From 3d1739c543a458be0c824472fb12778a13704ddc Mon Sep 17 00:00:00 2001 From: Bartosz Leper Date: Thu, 30 Jul 2026 21:24:10 +0200 Subject: [PATCH 068/347] Frontend for logging in to a scope (#68885) * Backend support for scoped login on web * Frontend for logging in to a scope * Review * Review Also: disable the cluster dropdown for scoped sessions, as it's not yet supported. * Add support for MFA turned off * Review * Review * Compute device trust mode for scoped sessions * Review --- .../UnifiedResources/UnifiedResources.tsx | 8 +- .../teleport/src/Login/Login.story.tsx | 2 + .../teleport/src/Login/Login.test.tsx | 186 ++++++++++-------- web/packages/teleport/src/Login/Login.tsx | 2 + .../teleport/src/Login/useLogin.test.tsx | 18 +- web/packages/teleport/src/Login/useLogin.ts | 49 ++--- web/packages/teleport/src/Main/Main.tsx | 22 ++- .../teleport/src/Scopes/LoginScopePicker.tsx | 35 +++- web/packages/teleport/src/TopBar/TopBar.tsx | 32 ++- .../src/UnifiedResources/UnifiedResources.tsx | 16 +- .../components/FormLogin/FormLogin.story.tsx | 3 + .../components/FormLogin/FormLogin.test.tsx | 1 + .../src/components/FormLogin/FormLogin.tsx | 9 + web/packages/teleport/src/config.test.ts | 28 ++- web/packages/teleport/src/config.ts | 30 ++- web/packages/teleport/src/features.tsx | 6 + .../teleport/src/services/auth/auth.test.ts | 8 +- .../teleport/src/services/auth/auth.ts | 6 +- .../src/services/history/history.test.ts | 16 +- .../teleport/src/services/history/history.ts | 47 +++-- .../teleport/src/services/history/index.ts | 4 +- .../services/storageService/storageService.ts | 14 ++ .../src/services/storageService/types.ts | 1 + .../src/services/user/makeUserContext.ts | 2 + .../teleport/src/services/user/types.ts | 2 + .../teleport/src/services/user/user.test.ts | 1 + .../src/services/websession/websession.ts | 50 +++-- .../teleport/src/stores/storeUserContext.ts | 4 + web/packages/teleport/src/types.ts | 5 + .../ui/DocumentCluster/UnifiedResources.tsx | 3 +- 30 files changed, 415 insertions(+), 195 deletions(-) diff --git a/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx b/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx index 57cc922f65a9..1b3aedad161e 100644 --- a/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx +++ b/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx @@ -63,6 +63,7 @@ import { ListView } from './ListView/ListView'; import { ResourceTab } from './ResourceTab'; import { getResourceId } from './shared/StatusInfo'; import { mapResourceToViewItem } from './shared/viewItemsFactory'; +import './unifiedStyles.css'; import { IncludedResourceMode, PinningSupport, @@ -74,7 +75,6 @@ import { VisibleFilterPanelFields, VisibleResourceItemFields, } from './types'; -import './unifiedStyles.css'; // get 48 resources to start const INITIAL_FETCH_SIZE = 48; @@ -911,8 +911,12 @@ const ListFooter = styled.div` */ export function getResourceAvailabilityFilter( availableResourceMode: AvailableResourceMode, - canRequestAllResources: boolean + canRequestAllResources: boolean, + scopedSession: boolean = false ): ResourceAvailabilityFilter { + if (scopedSession) { + return { mode: 'accessible', canRequestAll: false }; + } switch (availableResourceMode) { case AvailableResourceMode.NONE: if (!canRequestAllResources) { diff --git a/web/packages/teleport/src/Login/Login.story.tsx b/web/packages/teleport/src/Login/Login.story.tsx index 71cca97ba4b6..6fe607c4bf6e 100644 --- a/web/packages/teleport/src/Login/Login.story.tsx +++ b/web/packages/teleport/src/Login/Login.story.tsx @@ -50,6 +50,7 @@ export const Otp = () => ; export const Webauthn = () => ; export const Optional = () => ; export const On = () => ; +export const Scoped = () => ; export const CommunityAcknowledgement = () => { cfg.edition = 'community'; return ; @@ -108,4 +109,5 @@ const sample: State = { acknowledgeMotd: () => null, licenseAcknowledged: true, setLicenseAcknowledged: () => {}, + scope: '', }; diff --git a/web/packages/teleport/src/Login/Login.test.tsx b/web/packages/teleport/src/Login/Login.test.tsx index 80ef5aa68852..3c1c6a7f6816 100644 --- a/web/packages/teleport/src/Login/Login.test.tsx +++ b/web/packages/teleport/src/Login/Login.test.tsx @@ -17,6 +17,7 @@ */ import { userEvent, UserEvent } from '@testing-library/user-event'; +import { MemoryRouter } from 'react-router'; import selectEvent from 'react-select-event'; import { fireEvent, render, screen, waitFor } from 'design/utils/testing'; @@ -39,100 +40,127 @@ beforeEach(() => { user = userEvent.setup(); }); +function LoginTest({ initialURL }: { initialURL?: string }) { + const initialEntries = initialURL ? [initialURL] : undefined; + return ( + + + + ); +} + test('basic rendering', () => { - render(); + render(); // test rendering of logo and title expect(screen.getByRole('img')).toBeInTheDocument(); expect(screen.getByText(/sign in to teleport/i)).toBeInTheDocument(); }); -test('login with redirect', async () => { - jest.spyOn(auth, 'login').mockResolvedValue({}); +describe.each([ + { + name: 'unscoped', + url: '/web/login', + scope: '', + ssoURL: + 'http://localhost/v1/webapi/github/login/web?connector_id=github&redirect_url=http%3A%2F%2Flocalhost%2Fweb', + }, + { + name: 'scoped', + url: '/web/login?scope=%2Fdev', + scope: '/dev', + ssoURL: + 'http://localhost/v1/webapi/github/login/web?connector_id=github&scope=%2Fdev&redirect_url=http%3A%2F%2Flocalhost%2Fweb', + }, +])('$name', ({ url, scope, ssoURL }) => { + test('login with redirect', async () => { + jest.spyOn(auth, 'login').mockResolvedValue({}); + + render(); + + // fill form + const user = userEvent.setup(); + await user.type(screen.getByPlaceholderText(/username/i), 'username'); + await user.type(screen.getByPlaceholderText(/password/i), '123'); + + // test login and redirect + await user.click(screen.getByText('Sign In')); + await waitFor(() => { + expect(auth.login).toHaveBeenCalledWith('username', '123', '', scope); + }); + expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); + }); - render(); + test('login with MFA, changing method to OTP', async () => { + jest.spyOn(cfg, 'getAuth2faType').mockImplementation(() => 'optional'); + jest.spyOn(auth, 'login').mockResolvedValue({}); - // fill form - const username = screen.getByPlaceholderText(/username/i); - const password = screen.getByPlaceholderText(/password/i); - fireEvent.change(username, { target: { value: 'username' } }); - fireEvent.change(password, { target: { value: '123' } }); + render(); - // test login and redirect - fireEvent.click(screen.getByText('Sign In')); - await waitFor(() => { - expect(auth.login).toHaveBeenCalledWith('username', '123', ''); - }); - expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); -}); + // fill form + const username = screen.getByLabelText(/username/i); + const password = screen.getByLabelText(/password/i); + const mfaType = screen.getByLabelText(/multi-factor type/i); + await user.type(username, 'username'); + await user.type(password, '123'); -test('login with MFA, changing method to OTP', async () => { - jest.spyOn(cfg, 'getAuth2faType').mockImplementation(() => 'optional'); - jest.spyOn(auth, 'login').mockResolvedValue({}); - - render(); - - // fill form - const username = screen.getByLabelText(/username/i); - const password = screen.getByLabelText(/password/i); - const mfaType = screen.getByLabelText(/multi-factor type/i); - await user.type(username, 'username'); - await user.type(password, '123'); - - expect( - screen.queryByLabelText(/authenticator code/i) - ).not.toBeInTheDocument(); - await selectEvent.select(mfaType, 'Authenticator App'); - const authCode = screen.getByLabelText(/authenticator code/i); - await user.type(authCode, '987654'); - - // test login and redirect - fireEvent.click(screen.getByText('Sign In')); - await waitFor(() => { - expect(auth.login).toHaveBeenCalledWith('username', '123', '987654'); + expect( + screen.queryByLabelText(/authenticator code/i) + ).not.toBeInTheDocument(); + await selectEvent.select(mfaType, 'Authenticator App'); + const authCode = screen.getByLabelText(/authenticator code/i); + await user.type(authCode, '987654'); + + // test login and redirect + fireEvent.click(screen.getByText('Sign In')); + await waitFor(() => { + expect(auth.login).toHaveBeenCalledWith( + 'username', + '123', + '987654', + scope + ); + }); + expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); }); - expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); -}); -test('login with SSO', () => { - jest.spyOn(cfg, 'getAuth2faType').mockImplementation(() => 'otp'); - jest.spyOn(cfg, 'getPrimaryAuthType').mockImplementation(() => 'sso'); - jest.spyOn(cfg, 'getAuthProviders').mockImplementation(() => [ - { - displayName: 'With GitHub', - type: 'github', - name: 'github', - url: '/github/login/web?connector_id=:providerName&redirect_url=:redirect?', - }, - ]); - - render(); - - // test login pathways - fireEvent.click(screen.getByText('With GitHub')); - expect(history.push).toHaveBeenCalledWith( - 'http://localhost/github/login/web?connector_id=github&redirect_url=http%3A%2F%2Flocalhost%2Fweb', - true - ); -}); + test('login with SSO', () => { + jest.spyOn(cfg, 'getAuth2faType').mockImplementation(() => 'otp'); + jest.spyOn(cfg, 'getPrimaryAuthType').mockImplementation(() => 'sso'); + jest.spyOn(cfg, 'getAuthProviders').mockImplementation(() => [ + { + displayName: 'With GitHub', + type: 'github', + name: 'github', + url: '/v1/webapi/github/login/web?connector_id=:providerName&scope=:scope?&redirect_url=:redirect', + }, + ]); + + render(); + + // test login pathways + fireEvent.click(screen.getByText('With GitHub')); + expect(history.push).toHaveBeenCalledWith(ssoURL, true); + }); -test('passwordless login', async () => { - jest.spyOn(cfg, 'getPrimaryAuthType').mockReturnValue('passwordless'); - jest.spyOn(auth, 'loginWithWebauthn').mockResolvedValue({}); + test('passwordless login', async () => { + jest.spyOn(cfg, 'getPrimaryAuthType').mockReturnValue('passwordless'); + jest.spyOn(auth, 'loginWithWebauthn').mockResolvedValue({}); - render(); + render(); - await user.click( - screen.getByRole('button', { name: 'Sign in with a Passkey' }) - ); - expect(auth.loginWithWebauthn).toHaveBeenCalledWith(undefined); // No credentials - expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); + await user.click( + screen.getByRole('button', { name: 'Sign in with a Passkey' }) + ); + expect(auth.loginWithWebauthn).toHaveBeenCalledWith(undefined, scope); // No credentials + expect(history.push).toHaveBeenCalledWith('http://localhost/web', true); + }); }); describe('test MOTD', () => { test('show motd only if motd is set', async () => { // default login form - const { unmount } = render(); + const { unmount } = render(); expect(screen.getByPlaceholderText(/username/i)).toBeInTheDocument(); expect( screen.queryByText('Welcome to cluster, your activity will be recorded.') @@ -146,7 +174,7 @@ describe('test MOTD', () => { () => 'Welcome to cluster, your activity will be recorded.' ); - render(); + render(); expect( screen.getByText('Welcome to cluster, your activity will be recorded.') @@ -160,7 +188,7 @@ describe('test MOTD', () => { .mockImplementation( () => 'Welcome to cluster, your activity will be recorded.' ); - render(); + render(); expect( screen.getByText('Welcome to cluster, your activity will be recorded.') ).toBeInTheDocument(); @@ -181,7 +209,7 @@ describe('test MOTD', () => { 'https://teleport.example.com/web/headless/5c5c1f73-ac5c-52ee-bc9e-0353094dcb4a' ); - render(); + render(); expect( screen.queryByText('Welcome to cluster, your activity will be recorded.') @@ -191,7 +219,7 @@ describe('test MOTD', () => { test('access changed message renders when the URL param is set', () => { jest.spyOn(history, 'hasAccessChangedParam').mockImplementation(() => true); - render(); + render(); expect(screen.getByText(/sign in to teleport/i)).toBeInTheDocument(); expect(screen.getByText(/Your access has changed/i)).toBeInTheDocument(); @@ -205,7 +233,7 @@ test('redirect to root if session is valid and path is not "/enterprise/saml-idp .mockReturnValue( 'http://localhost/web/login?redirect_url=http://localhost/web/cluster/localhost/resources' ); - render(); + render(); expect(history.replace).toHaveBeenCalledWith('/web'); }); @@ -216,6 +244,6 @@ test('redirect if session is valid and path matches "/enterprise/saml-idp/sso"', jest .spyOn(history, 'getRedirectParam') .mockReturnValue(samlIdPPath.toString()); - render(); + render(); expect(history.push).toHaveBeenCalledWith(samlIdPPath.toString(), true); }); diff --git a/web/packages/teleport/src/Login/Login.tsx b/web/packages/teleport/src/Login/Login.tsx index 293835cc9b90..5a96ab840dcd 100644 --- a/web/packages/teleport/src/Login/Login.tsx +++ b/web/packages/teleport/src/Login/Login.tsx @@ -52,6 +52,7 @@ export function LoginComponent({ motd, showMotd, acknowledgeMotd, + scope, }: State) { // while we are checking if a session is valid, we don't return anything // to prevent flickering. The check only happens for a frame or two so @@ -89,6 +90,7 @@ export function LoginComponent({ clearAttempt={clearAttempt} isPasswordlessEnabled={isPasswordlessEnabled} primaryAuthType={primaryAuthType} + scope={scope} /> )} diff --git a/web/packages/teleport/src/Login/useLogin.test.tsx b/web/packages/teleport/src/Login/useLogin.test.tsx index 6d525874002a..785786bb0293 100644 --- a/web/packages/teleport/src/Login/useLogin.test.tsx +++ b/web/packages/teleport/src/Login/useLogin.test.tsx @@ -17,6 +17,8 @@ */ import { renderHook } from '@testing-library/react'; +import { PropsWithChildren } from 'react'; +import { MemoryRouter } from 'react-router'; import cfg from 'teleport/config'; import history from 'teleport/services/history'; @@ -45,15 +47,19 @@ afterEach(() => { jest.resetAllMocks(); }); +function Wrapper({ children }: PropsWithChildren) { + return {children}; +} + it('redirect to root on path not matching "/enterprise/saml-idp/sso"', () => { jest.spyOn(history, 'getRedirectParam').mockReturnValue('http://localhost'); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); expect(history.replace).toHaveBeenCalledWith('/web'); jest .spyOn(history, 'getRedirectParam') .mockReturnValue('http://localhost/web/cluster/name/resources'); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); expect(history.replace).toHaveBeenCalledWith('/web'); }); @@ -63,7 +69,7 @@ it('redirect to SAML SSO path on matching "/enterprise/saml-idp/sso"', () => { jest .spyOn(history, 'getRedirectParam') .mockReturnValue(samlIdpPath.toString()); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); expect(history.push).toHaveBeenCalledWith(samlIdpPath.toString(), true); }); @@ -72,7 +78,7 @@ it('non-base domain redirects with base domain for a matching "/enterprise/saml- jest .spyOn(history, 'getRedirectParam') .mockReturnValue(samlIdpPath.toString()); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); const expectedPath = new URL('http://localhost' + cfg.routes.samlIdpSso); expect(history.push).toHaveBeenCalledWith(expectedPath.toString(), true); }); @@ -82,7 +88,7 @@ it('base domain with different path is redirected to root', async () => { jest .spyOn(history, 'getRedirectParam') .mockReturnValue(nonSamlIdpPath.toString()); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); expect(history.replace).toHaveBeenCalledWith('/web'); }); @@ -94,7 +100,7 @@ it('invalid session does nothing', async () => { .spyOn(history, 'getRedirectParam') .mockReturnValue(samlIdpPathWithDifferentBase.toString()); jest.spyOn(session, 'isValid').mockImplementation(() => false); - renderHook(() => useLogin()); + renderHook(() => useLogin(), { wrapper: Wrapper }); expect(history.replace).not.toHaveBeenCalled(); expect(history.push).not.toHaveBeenCalled(); }); diff --git a/web/packages/teleport/src/Login/useLogin.ts b/web/packages/teleport/src/Login/useLogin.ts index 6b39989f3c81..c9f9df488c04 100644 --- a/web/packages/teleport/src/Login/useLogin.ts +++ b/web/packages/teleport/src/Login/useLogin.ts @@ -17,7 +17,7 @@ */ import { useEffect, useState } from 'react'; -import { matchPath } from 'react-router'; +import { matchPath, useLocation } from 'react-router'; import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; import { useAttempt } from 'shared/hooks'; @@ -55,6 +55,11 @@ export default function useLogin() { setShowMotd(false); } + const location = useLocation(); + const scope: string | null = new URLSearchParams(location.search).get( + 'scope' + ); + // onSuccess can receive a device webtoken. If so, it will // enable a prompt to allow users to authorize the current function onSuccess({ @@ -64,6 +69,7 @@ export default function useLogin() { // deviceWebToken will only exist on a login response // from enterprise but just in case there is a version mismatch // between the webclient and proxy + storageService.setScopeSelected(scope != null); if (trustedDeviceRequirement === TrustedDeviceRequirement.REQUIRED) { session.setDeviceTrustRequired(); } @@ -76,7 +82,7 @@ export default function useLogin() { useEffect(() => { if (session.isValid()) { try { - const redirectUrlWithBase = new URL(getEntryRoute()); + const redirectUrlWithBase = new URL(history.getEntryRoute()); const matched = matchPath( cfg.routes.samlIdpSso, redirectUrlWithBase.pathname @@ -101,7 +107,7 @@ export default function useLogin() { attemptActions.start(); storageService.clearLoginTime(); auth - .login(email, password, token) + .login(email, password, token, scope || '') .then(onSuccess) .catch(err => { attemptActions.error(err); @@ -112,7 +118,7 @@ export default function useLogin() { attemptActions.start(); storageService.clearLoginTime(); auth - .loginWithWebauthn(creds) + .loginWithWebauthn(creds, scope || '') .then(onSuccess) .catch(err => { attemptActions.error(err); @@ -122,13 +128,15 @@ export default function useLogin() { function onLoginWithSso(provider: AuthProvider, loginHint?: string) { attemptActions.start(); storageService.clearLoginTime(); - const appStartRoute = getEntryRoute(); - const ssoUri = cfg.getSsoUrl( - provider.url, - provider.name, - appStartRoute, - loginHint - ); + const appStartRoute = history.getEntryRoute(); + const ssoUri = cfg.getSsoUrl({ + providerUrl: provider.url, + providerName: provider.name, + redirect: appStartRoute, + loginHint, + scope: scope || '', + }); + storageService.setScopeSelected(scope !== null); history.push(ssoUri, true); } @@ -156,6 +164,7 @@ export default function useLogin() { motd, showMotd, acknowledgeMotd, + scope, }; } @@ -180,27 +189,11 @@ function authorizeWithDeviceTrust(token: DeviceWebToken) { } function loginSuccess() { - const redirect = getEntryRoute(); + const redirect = history.getEntryRoute(); const withPageRefresh = true; history.push(redirect, withPageRefresh); } -/** - * getEntryRoute returns a base ensured redirect URL value that is safe - * for redirect. - * @returns base ensured URL string. - */ -function getEntryRoute() { - let entryUrl = history.getRedirectParam(); - if (entryUrl) { - entryUrl = history.ensureKnownRoute(entryUrl); - } else { - entryUrl = cfg.routes.root; - } - - return history.ensureBaseUrl(entryUrl); -} - export type State = ReturnType & { isRecoveryEnabled?: boolean; onRecover?: (isRecoverPassword: boolean) => void; diff --git a/web/packages/teleport/src/Main/Main.tsx b/web/packages/teleport/src/Main/Main.tsx index f24f5d9fe1ee..6fd43d4ccf72 100644 --- a/web/packages/teleport/src/Main/Main.tsx +++ b/web/packages/teleport/src/Main/Main.tsx @@ -38,6 +38,7 @@ import { import { marginTransitionCss } from 'shared/components/SlidingSidePanel/InfoGuide/const'; import { ToastNotifications } from 'shared/components/ToastNotification'; import useAttempt from 'shared/hooks/useAttemptNext'; +import { useStore } from 'shared/libs/stores'; import { BannerList } from 'teleport/components/BannerList'; import type { BannerType } from 'teleport/components/BannerList/BannerList'; @@ -72,6 +73,7 @@ export interface MainProps { export function Main(props: MainProps) { const ctx = useTeleport(); + const storeUser = useStore(ctx.storeUser); const location = useLocation(); const { attempt, setAttempt, run } = useAttempt('processing'); @@ -89,9 +91,15 @@ export function Main(props: MainProps) { const featureFlags = ctx.getFeatureFlags(); + const scope = storeUser?.getScope(); const features = useMemo( - () => props.features.filter(feature => feature.hasAccess(featureFlags)), - [featureFlags, props.features] + () => + props.features.filter( + feature => + feature.hasAccess(featureFlags) && + supportsCurrentScope(feature, scope) + ), + [featureFlags, props.features, scope] ); const { alerts, dismissAlert } = useAlerts(props.initialAlerts); @@ -130,7 +138,8 @@ export function Main(props: MainProps) { if ( cfg.scopesEnabled && availableScopes.length > 0 && - !isScopePickerRoute + !isScopePickerRoute && + !storageService.getScopeSelected() ) { return ; } @@ -258,6 +267,13 @@ function renderRoutes( return routes; } +function supportsCurrentScope( + feature: TeleportFeature, + scope: string +): boolean { + return !scope || feature.supportsScopes; +} + function FeatureRoutes({ lockedFeatures }: { lockedFeatures: LockedFeatures }) { const features = useFeatures(); const routes = renderRoutes(features, lockedFeatures); diff --git a/web/packages/teleport/src/Scopes/LoginScopePicker.tsx b/web/packages/teleport/src/Scopes/LoginScopePicker.tsx index cd03eacb76ea..8a729c5bfe40 100644 --- a/web/packages/teleport/src/Scopes/LoginScopePicker.tsx +++ b/web/packages/teleport/src/Scopes/LoginScopePicker.tsx @@ -26,6 +26,9 @@ import * as Icon from 'design/Icon'; import { H1, H2 } from 'design/Text'; import { useStore } from 'shared/libs/stores'; +import history from 'teleport/services/history/history'; +import { storageService } from 'teleport/services/storageService'; +import session from 'teleport/services/websession/websession'; import useTeleport from 'teleport/useTeleport'; /** @@ -39,6 +42,17 @@ export function LoginScopePicker() { const ctx = useTeleport(); const storeUser = useStore(ctx.storeUser); + function signInWithScope(scope: string) { + session.switchScope(scope); + } + + function signInWithoutScope() { + storageService.setScopeSelected(true); + const redirect = history.getEntryRoute(); + const withPageRefresh = true; + history.push(redirect, withPageRefresh); + } + return ( @@ -47,9 +61,28 @@ export function LoginScopePicker() { Choose a scope for your session: +
  • + signInWithoutScope()} + > + + + Teleport Home + + + Sign in + + +
  • {storeUser.getAvailableScopes().map(scope => (
  • - + signInWithScope(scope)} + > {scope} diff --git a/web/packages/teleport/src/TopBar/TopBar.tsx b/web/packages/teleport/src/TopBar/TopBar.tsx index 7154f0ea3a6b..852d96e1bf69 100644 --- a/web/packages/teleport/src/TopBar/TopBar.tsx +++ b/web/packages/teleport/src/TopBar/TopBar.tsx @@ -20,8 +20,10 @@ import React, { type JSX } from 'react'; import { Link, matchPath, useLocation } from 'react-router'; import styled, { css, useTheme } from 'styled-components'; -import { Box, breakpointsPx, Flex, Image, TopNav } from 'design'; +import { Box, breakpointsPx, Flex, Image, Text, TopNav } from 'design'; +import * as Icon from 'design/Icon'; import { HoverTooltip } from 'design/Tooltip'; +import { useStore } from 'shared/libs/stores'; import { logoSrc } from 'teleport/components/LogoHero/LogoHero'; import { UserMenuNav } from 'teleport/components/UserMenuNav'; @@ -31,6 +33,7 @@ import { useFeatures } from 'teleport/FeaturesContext'; import { useLayout } from 'teleport/Main/LayoutContext'; import { zIndexMap } from 'teleport/Navigation/zIndexMap'; import { Notifications } from 'teleport/Notifications'; +import useTeleport from 'teleport/useTeleport'; export function TopBar({ CustomLogo, @@ -42,6 +45,9 @@ export function TopBar({ const location = useLocation(); const features = useFeatures(); const { currentWidth } = useLayout(); + const ctx = useTeleport(); + const storeUser = useStore(ctx.storeUser); + const scope = storeUser.getScope(); // find active feature const feature = features.find( @@ -60,10 +66,24 @@ export function TopBar({ return ( - + + + {scope && !feature?.logoOnlyTopbar && ( + + + + {scope} + + + )} + {!feature?.logoOnlyTopbar && ( - + { + // TODO(bl-nero): enable notifications once they're supported by + // scopes. + !scope && + } )} @@ -135,12 +155,6 @@ const commonLogoWrapperStyles = css` align-items: center; height: 100%; margin-right: 0px; - @media screen and (min-width: ${p => p.theme.breakpoints.medium}) { - margin-right: 76px; - } - @media screen and (min-width: ${p => p.theme.breakpoints.large}) { - margin-right: 67px; - } `; const BoxLogoWrapper = styled(Box)` diff --git a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx index 7c57414d2c50..62108bb47563 100644 --- a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx +++ b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx @@ -45,6 +45,7 @@ import { getResourceId, openStatusInfoPanel, } from 'shared/components/UnifiedResources/shared/StatusInfo'; +import { useStore } from 'shared/libs/stores'; import { useTeleport } from 'teleport'; import AgentButtonAdd from 'teleport/components/AgentButtonAdd'; @@ -175,6 +176,7 @@ export function ClusterResources({ }) => ReactNode; }) { const teleCtx = useTeleport(); + const storeUser = useStore(teleCtx.storeUser); const flags = teleCtx.getFeatureFlags(); useNoMinWidth(); @@ -334,11 +336,15 @@ export function ClusterResources({ availableKinds={getAvailableKindsWithAccess(flags)} pinning={pinning} ClusterDropdown={ - + // TODO(bl-nero): Enable cluster dropdown for scoped sessions once + // it's supported on the backend. + storeUser.getScope() ? undefined : ( + + ) } NoResources={ ( /> ); +export const Scoped = () => ; + export const ServerError = () => { const attempt = { ...props.attempt, diff --git a/web/packages/teleport/src/components/FormLogin/FormLogin.test.tsx b/web/packages/teleport/src/components/FormLogin/FormLogin.test.tsx index 5b50d3c7818f..5468e1b99920 100644 --- a/web/packages/teleport/src/components/FormLogin/FormLogin.test.tsx +++ b/web/packages/teleport/src/components/FormLogin/FormLogin.test.tsx @@ -249,4 +249,5 @@ const props: Props = { onLoginWithWebauthn: null, isPasswordlessEnabled: false, primaryAuthType: 'local', + scope: '', }; diff --git a/web/packages/teleport/src/components/FormLogin/FormLogin.tsx b/web/packages/teleport/src/components/FormLogin/FormLogin.tsx index 8a5f3be22ffb..65cf6e5d8b5e 100644 --- a/web/packages/teleport/src/components/FormLogin/FormLogin.tsx +++ b/web/packages/teleport/src/components/FormLogin/FormLogin.tsx @@ -31,6 +31,7 @@ import { Text, } from 'design'; import * as Alerts from 'design/Alert'; +import * as Icon from 'design/Icon'; import { StepComponentProps, StepSlider } from 'design/StepSlider'; import { P } from 'design/Text/Text'; import FieldInput from 'shared/components/FieldInput'; @@ -68,6 +69,7 @@ export default function LoginForm(props: Props) { primaryAuthType, title = 'Sign in to Teleport', ssoTitle = 'Sign in to Teleport with SSO', + scope, } = props; const [showIdentifierFirstLogin, setShowIdentifierFirstLogin] = useState( @@ -115,6 +117,12 @@ export default function LoginForm(props: Props) { {title} + {scope && ( + + + {scope} + + )} {errorMessage && {errorMessage}} {showAccessChangedMessage && ( @@ -511,6 +519,7 @@ export type Props = { onLogin(username: string, password: string, token: string): void; autoFocus?: boolean; setShowIdentifierFirstLogin?: (value: boolean) => void; + scope: string; }; type AttemptState = ReturnType[0]; diff --git a/web/packages/teleport/src/config.test.ts b/web/packages/teleport/src/config.test.ts index 64932349a25a..8ab3b84ff6ed 100644 --- a/web/packages/teleport/src/config.test.ts +++ b/web/packages/teleport/src/config.test.ts @@ -82,24 +82,34 @@ test('getIntegrationsEnroll without extra params', () => { test('getSsoUrl', () => { const providerUrl = - '/v1/webapi/oidc/login/web?connector_id=:providerName&login_hint=:loginHint?&redirect_url=:redirect'; + '/v1/webapi/oidc/login/web?connector_id=:providerName&login_hint=:loginHint?&redirect_url=:redirect&scope=:scope?'; expect( - cfg.getSsoUrl(providerUrl, 'keycloak', 'example.com', undefined) + cfg.getSsoUrl({ + providerUrl, + providerName: 'keycloak', + redirect: 'example.com', + }) ).toEqual( 'http://localhost/v1/webapi/oidc/login/web?connector_id=keycloak&redirect_url=example.com' ); expect( - cfg.getSsoUrl(providerUrl, 'keycloak', 'example.com', 'user@example.com') + cfg.getSsoUrl({ + providerUrl, + providerName: 'keycloak', + redirect: 'example.com', + loginHint: 'user@example.com', + scope: '/foo/bar', + }) ).toEqual( - 'http://localhost/v1/webapi/oidc/login/web?connector_id=keycloak&login_hint=user%40example.com&redirect_url=example.com' + 'http://localhost/v1/webapi/oidc/login/web?connector_id=keycloak&login_hint=user%40example.com&redirect_url=example.com&scope=%2Ffoo%2Fbar' ); expect( - cfg.getSsoUrl( + cfg.getSsoUrl({ providerUrl, - 'keycloak', - 'example.com?a=b&c=d', - 'user@example.com' - ) + providerName: 'keycloak', + redirect: 'example.com?a=b&c=d', + loginHint: 'user@example.com', + }) ).toEqual( 'http://localhost/v1/webapi/oidc/login/web?connector_id=keycloak&login_hint=user%40example.com&redirect_url=example.com%3Fa%3Db%26c%3Dd' ); diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 4a6b992dc34d..9b3f56e4a55b 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -830,18 +830,38 @@ const cfg = { return searchString ? `${path}?${searchString}` : path; }, - getSsoUrl(providerUrl, providerName, redirect, loginHint) { + getSsoUrl({ + providerUrl, + providerName, + redirect, + loginHint, + scope, + }: { + providerUrl: string; + providerName: string; + redirect: string; + loginHint?: string; + scope?: string; + }) { loginHint = loginHint === '' ? undefined : loginHint; + scope = scope === '' ? undefined : scope; let basePath = cfg.baseUrl + - generateFullPath(providerUrl, { redirect, providerName, loginHint }); + generateFullPath(providerUrl, { + redirect, + providerName, + loginHint, + scope, + }); + const url = new URL(basePath); if (!loginHint) { - const url = new URL(basePath); url.searchParams.delete('login_hint', ''); - basePath = url.toString(); } - return basePath; + if (!scope) { + url.searchParams.delete('scope', ''); + } + return url.toString(); }, getAuditRoute(clusterId: string) { diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index a95a6e7e88cc..afd4e8f06128 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -141,6 +141,7 @@ export class FeatureUnifiedResources implements TeleportFeature { sideNavCategory = SideNavigationCategory.Resources; // TODO(rudream): Remove this once shortcuts to pinned/nodes/apps/dbs/desktops/kubes are implemented. standalone = true; + supportsScopes = true; route = { title: 'Resources', @@ -918,6 +919,8 @@ export class FeatureAccount implements TeleportFeature { 'change password', ], }; + + supportsScopes = true; } export class FeatureHelpAndSupport implements TeleportFeature { @@ -948,6 +951,8 @@ export class FeatureHelpAndSupport implements TeleportFeature { 'version', ], }; + + supportsScopes = true; } export class FeatureScopes implements TeleportFeature { @@ -959,6 +964,7 @@ export class FeatureScopes implements TeleportFeature { }; hideNavigation = true; + supportsScopes = true; hasAccess(): boolean { return cfg.scopesEnabled; diff --git a/web/packages/teleport/src/services/auth/auth.test.ts b/web/packages/teleport/src/services/auth/auth.test.ts index 6cf70fc4f2e1..53d21d4efeab 100644 --- a/web/packages/teleport/src/services/auth/auth.test.ts +++ b/web/packages/teleport/src/services/auth/auth.test.ts @@ -32,7 +32,7 @@ describe('services/auth', () => { test('login()', async () => { jest.spyOn(api, 'postWithOptions').mockResolvedValue({}); - await auth.login(email, password, ''); + await auth.login(email, password, '' /*otp*/, '' /*scope*/); expect(api.postWithOptions).toHaveBeenCalledWith( cfg.api.webSessionPath, expect.objectContaining({ @@ -40,20 +40,22 @@ describe('services/auth', () => { user: email, pass: password, second_factor_token: '', + scope: '', }, }) ); }); - test('login() OTP', async () => { + test('login() OTP and scope', async () => { jest.spyOn(api, 'postWithOptions').mockResolvedValue({}); const data = { user: email, pass: password, second_factor_token: 'xxx', + scope: '/prod/europe', }; - await auth.login(email, password, 'xxx'); + await auth.login(email, password, 'xxx', '/prod/europe'); expect(api.postWithOptions).toHaveBeenCalledWith( cfg.api.webSessionPath, expect.objectContaining({ data }) diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index db48493e6a7c..57285b1bcb67 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -116,11 +116,12 @@ const auth = { .then(parseMfaChallengeJson); }, - login(userId: string, password: string, otpCode: string) { + login(userId: string, password: string, otpCode: string, scope: string) { const data = { user: userId, pass: password, second_factor_token: otpCode, + scope, }; return api.postWithOptions(cfg.api.webSessionPath, { @@ -132,7 +133,7 @@ const auth = { }); }, - loginWithWebauthn(creds?: UserCredentials) { + loginWithWebauthn(creds?: UserCredentials, scope: string = '') { return auth .checkWebauthnSupport() .then(() => auth.mfaLoginBegin(creds)) @@ -145,6 +146,7 @@ const auth = { const request = { user: creds?.username, webauthnAssertionResponse: makeWebauthnAssertionResponse(res), + scope, }; return api.postWithOptions(cfg.api.mfaLoginFinish, { diff --git a/web/packages/teleport/src/services/history/history.test.ts b/web/packages/teleport/src/services/history/history.test.ts index 7976026545c7..df314cfe551e 100644 --- a/web/packages/teleport/src/services/history/history.test.ts +++ b/web/packages/teleport/src/services/history/history.test.ts @@ -133,7 +133,7 @@ describe('services/history', () => { history.goToLogin({ rememberLocation: true }); const expected = - '/web/login?redirect_uri=http://localhost/current-location'; + '/web/login?redirect_uri=http%3A%2F%2Flocalhost%2Fcurrent-location'; expect(history._pageRefresh).toHaveBeenCalledWith(expected); }); @@ -144,7 +144,7 @@ describe('services/history', () => { location.pathname = '/bogus-location'; history.goToLogin({ rememberLocation: true }); - const expected = '/web/login?redirect_uri=http://localhost/web'; + const expected = '/web/login?redirect_uri=http%3A%2F%2Flocalhost%2Fweb'; expect(history._pageRefresh).toHaveBeenCalledWith(expected); }); @@ -155,7 +155,7 @@ describe('services/history', () => { location.pathname = '/current-location'; history.goToLogin({ withAccessChangedMessage: true }); - const expected = '/web/login?access_changed'; + const expected = '/web/login?access_changed='; expect(history._pageRefresh).toHaveBeenCalledWith(expected); }); @@ -170,7 +170,7 @@ describe('services/history', () => { }); const expected = - '/web/login?access_changed&redirect_uri=http://localhost/current-location'; + '/web/login?access_changed=&redirect_uri=http%3A%2F%2Flocalhost%2Fcurrent-location'; expect(history._pageRefresh).toHaveBeenCalledWith(expected); }); @@ -197,7 +197,7 @@ describe('services/history', () => { }); const expected = - '/web/login?access_changed&redirect_uri=http://localhost/current-location%3Ftest%3Dvalue'; + '/web/login?access_changed=&redirect_uri=http%3A%2F%2Flocalhost%2Fcurrent-location%3Ftest%3Dvalue'; expect(history._pageRefresh).toHaveBeenCalledWith(expected); }); @@ -215,7 +215,7 @@ describe('services/history', () => { }); const expected = - '/web/login?access_changed&redirect_uri=http://localhost/current-location%3Ftest%3Dvalue'; + '/web/login?access_changed=&redirect_uri=http%3A%2F%2Flocalhost%2Fcurrent-location%3Ftest%3Dvalue'; expect(history._pageRefresh).toHaveBeenCalledWith(expected); window.history.replaceState({}, '', '/'); @@ -229,7 +229,7 @@ describe('services/history', () => { .mockReturnValue(['/web/login', '/current-location']); location.pathname = '/current-location'; expect(history.getScopePickerUrl()).toBe( - '/web/scope_picker?redirect_uri=http://localhost/current-location' + '/web/scope_picker?redirect_uri=http%3A%2F%2Flocalhost%2Fcurrent-location' ); }); }); @@ -240,7 +240,7 @@ describe('services/history', () => { .mockReturnValue(['/web/login', '/another-location']); location.pathname = '/bogus-location'; expect(history.getScopePickerUrl()).toBe( - '/web/scope_picker?redirect_uri=http://localhost/web' + '/web/scope_picker?redirect_uri=http%3A%2F%2Flocalhost%2Fweb' ); }); }); diff --git a/web/packages/teleport/src/services/history/history.ts b/web/packages/teleport/src/services/history/history.ts index f9048ccee53a..7d419e1c268b 100644 --- a/web/packages/teleport/src/services/history/history.ts +++ b/web/packages/teleport/src/services/history/history.ts @@ -27,6 +27,12 @@ interface NavigationFunctions { let _nav: NavigationFunctions | null = null; +export type LoginOptions = { + rememberLocation?: boolean; + withAccessChangedMessage?: boolean; + scope?: string; +}; + const history = { /** * Initialize with navigation functions from React Router. @@ -61,29 +67,33 @@ const history = { goToLogin({ rememberLocation = false, withAccessChangedMessage = false, - } = {}) { - const params: string[] = []; + scope = '', + }: LoginOptions = {}) { + const params = new URLSearchParams(); // withAccessChangedMessage determines whether the login page the user is redirected to should include a notice that // they were logged out due to their roles having changed. if (withAccessChangedMessage) { - params.push('access_changed'); + params.set('access_changed', ''); } if (rememberLocation) { const { search, pathname } = this.getLocation(); const knownRoute = this.ensureKnownRoute(pathname); const knownRedirect = this.ensureBaseUrl(knownRoute); - const query = search ? encodeURIComponent(search) : ''; - params.push(`redirect_uri=${knownRedirect}${query}`); + params.set('redirect_uri', knownRedirect + search); + } + + if (scope) { + params.set('scope', scope); } - const queryString = params.join('&'); + const queryString = params.toString(); const url = queryString ? `${cfg.routes.login}?${queryString}` : cfg.routes.login; - this._pageRefresh(url); + this._pageRefresh(url.toString()); }, /** @@ -91,15 +101,14 @@ const history = { * the scope has been picked. */ getScopePickerUrl() { - const params: string[] = []; + const params = new URLSearchParams(); const { search, pathname } = this.getLocation(); const knownRoute = this.ensureKnownRoute(pathname); const knownRedirect = this.ensureBaseUrl(knownRoute); - const query = search ? encodeURIComponent(search) : ''; - params.push(`redirect_uri=${knownRedirect}${query}`); + params.set(`redirect_uri`, knownRedirect + search); - const queryString = params.join('&'); + const queryString = params.toString(); const url = queryString ? `${cfg.routes.scopePicker}?${queryString}` : cfg.routes.scopePicker; @@ -174,6 +183,22 @@ const history = { _pageRefresh(route: string) { window.location.href = this.ensureBaseUrl(route); }, + + /** + * getEntryRoute returns a base ensured redirect URL value that is safe + * for redirect. + * @returns base ensured URL string. + */ + getEntryRoute() { + let entryUrl = this.getRedirectParam(); + if (entryUrl) { + entryUrl = this.ensureKnownRoute(entryUrl); + } else { + entryUrl = cfg.routes.root; + } + + return this.ensureBaseUrl(entryUrl); + }, }; interface RouteRecord { diff --git a/web/packages/teleport/src/services/history/index.ts b/web/packages/teleport/src/services/history/index.ts index f54c53cf04bf..652a2925ab7e 100644 --- a/web/packages/teleport/src/services/history/index.ts +++ b/web/packages/teleport/src/services/history/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import service, { getUrlParameter } from './history'; +import service, { getUrlParameter, LoginOptions } from './history'; export default service; -export { getUrlParameter }; +export { getUrlParameter, type LoginOptions }; diff --git a/web/packages/teleport/src/services/storageService/storageService.ts b/web/packages/teleport/src/services/storageService/storageService.ts index 05b3001dd30b..a46b34cf877b 100644 --- a/web/packages/teleport/src/services/storageService/storageService.ts +++ b/web/packages/teleport/src/services/storageService/storageService.ts @@ -383,4 +383,18 @@ export const storageService = { getUseLoginScopePicker(): boolean { return this.getParsedJSONValue(KeysEnum.USE_LOGIN_SCOPE_PICKER, false); }, + + // Returns true if the user has already picked a scope for this session or + // explicitly made the session unscoped. This allows us to tell the + // difference between a session that is "implicitly unscoped" (where the user + // has not yet interacted with the scope picker) and an "explicitly unscoped" + // one (where the user already made their choice). In both cases, the scope + // is an empty string, hence this additional flag. + getScopeSelected(): boolean { + return this.getParsedJSONValue(KeysEnum.SCOPE_SELECTED, false); + }, + + setScopeSelected(s: boolean) { + window.localStorage.setItem(KeysEnum.SCOPE_SELECTED, JSON.stringify(s)); + }, }; diff --git a/web/packages/teleport/src/services/storageService/types.ts b/web/packages/teleport/src/services/storageService/types.ts index 360d8257dfd6..91eee5a9e3c4 100644 --- a/web/packages/teleport/src/services/storageService/types.ts +++ b/web/packages/teleport/src/services/storageService/types.ts @@ -71,6 +71,7 @@ export const KeysEnum = { DESKTOP_HIDPI: 'grv_teleport_desktop_hidpi', // TODO(bl-nero): Remove this once the login scope picker is operational. USE_LOGIN_SCOPE_PICKER: 'grv_teleport_use_login_scope_picker', + SCOPE_SELECTED: 'grv_teleport_scope_selected', }; // SurveyRequest is the request for sending data to the back end diff --git a/web/packages/teleport/src/services/user/makeUserContext.ts b/web/packages/teleport/src/services/user/makeUserContext.ts index 27a6f14ce708..00898e398ba1 100644 --- a/web/packages/teleport/src/services/user/makeUserContext.ts +++ b/web/packages/teleport/src/services/user/makeUserContext.ts @@ -34,6 +34,7 @@ export default function makeUserContext(json: any): UserContext { const passwordState = json.passwordState || PasswordState.PASSWORD_STATE_UNSPECIFIED; const availableScopes = json.availableScopes || []; + const scope = json.scope || ''; return { username, @@ -48,6 +49,7 @@ export default function makeUserContext(json: any): UserContext { allowedSearchAsRoles, passwordState, availableScopes, + scope, }; } diff --git a/web/packages/teleport/src/services/user/types.ts b/web/packages/teleport/src/services/user/types.ts index ac347b6b97d8..c1283b7ac731 100644 --- a/web/packages/teleport/src/services/user/types.ts +++ b/web/packages/teleport/src/services/user/types.ts @@ -58,6 +58,8 @@ export interface UserContext { * scoped role assignments. */ availableScopes: string[]; + /** Scope is the scope of current session. Empty if unscoped. */ + scope: string; } /** diff --git a/web/packages/teleport/src/services/user/user.test.ts b/web/packages/teleport/src/services/user/user.test.ts index 93ec7040fb2f..53f6f4626ef9 100644 --- a/web/packages/teleport/src/services/user/user.test.ts +++ b/web/packages/teleport/src/services/user/user.test.ts @@ -435,6 +435,7 @@ test('undefined values in context response gives proper default values', async ( allowedSearchAsRoles: [], passwordState: PasswordState.PASSWORD_STATE_UNSPECIFIED, availableScopes: [], + scope: '', } as UserContext); }); diff --git a/web/packages/teleport/src/services/websession/websession.ts b/web/packages/teleport/src/services/websession/websession.ts index 45aeeed928cf..7105385261ef 100644 --- a/web/packages/teleport/src/services/websession/websession.ts +++ b/web/packages/teleport/src/services/websession/websession.ts @@ -39,29 +39,37 @@ const session = { _isDeviceTrustRequired: false, _isDeviceTrusted: false, - logout(rememberLocation = false) { + async logout(rememberLocation = false) { let samlSloUrl = ''; + try { + const response = await this._plainLogout(); + samlSloUrl = response?.samlSloUrl; + } finally { + if (samlSloUrl) { + window.open(samlSloUrl, '_self'); + } else { + history.goToLogin({ rememberLocation }); + } + } + }, - api - .delete(cfg.api.webSessionPath) - .then(response => { - samlSloUrl = response?.samlSloUrl; - }) - .catch(err => { - // This request can fail if the session is already expired, which isn't an issue, but we should still catch the error. - logger.error( - 'Failed to delete session. This can happen if the session has already expired.', - err - ); - }) - .finally(() => { - this.clear(); - if (samlSloUrl) { - window.open(samlSloUrl, '_self'); - } else { - history.goToLogin({ rememberLocation }); - } - }); + async _plainLogout(): Promise<{ samlSloUrl: string } | undefined> { + try { + return await api.deleteWithOptions(cfg.api.webSessionPath, {}); + } catch (err) { + // This request can fail if the session is already expired, which isn't an issue, but we should still catch the error. + logger.error( + 'Failed to delete session. This can happen if the session has already expired.', + err + ); + } finally { + this.clear(); + } + }, + + async switchScope(scope: string) { + await this._plainLogout(); + history.goToLogin({ scope }); }, logoutWithoutSlo({ diff --git a/web/packages/teleport/src/stores/storeUserContext.ts b/web/packages/teleport/src/stores/storeUserContext.ts index 993a9a631f76..459323e5c71f 100644 --- a/web/packages/teleport/src/stores/storeUserContext.ts +++ b/web/packages/teleport/src/stores/storeUserContext.ts @@ -330,4 +330,8 @@ export default class StoreUserContext extends Store { getAvailableScopes() { return this.state.availableScopes; } + + getScope() { + return this.state?.scope ?? ''; + } } diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts index 65c43a318e69..74351c5c0478 100644 --- a/web/packages/teleport/src/types.ts +++ b/web/packages/teleport/src/types.ts @@ -164,6 +164,11 @@ export interface TeleportFeature { showInDashboard?: boolean; /** isHyperLink is whether this subsection is merely a hyperlink/shortcut to another subsection. */ isHyperLink?: boolean; + /** + * supportsScopes indicates whether this feature should be enabled in scoped + * sessions. + */ + supportsScopes?: boolean; } export type StickyCluster = { diff --git a/web/packages/teleterm/src/ui/DocumentCluster/UnifiedResources.tsx b/web/packages/teleterm/src/ui/DocumentCluster/UnifiedResources.tsx index b65a484c187b..f6143c6bd2ef 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/UnifiedResources.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/UnifiedResources.tsx @@ -156,7 +156,8 @@ export function UnifiedResources(props: { supported: 'yes', availabilityFilter: getResourceAvailabilityFilter( userPreferences.unifiedResourcePreferences.availableResourceMode, - rootCluster.showResources === ShowResources.REQUESTABLE + rootCluster.showResources === ShowResources.REQUESTABLE, + false /* scopedSession */ ), }; }, [ From 2f0c4b6b7a0ac8b295a154ed533fb4fea5a7b82f Mon Sep 17 00:00:00 2001 From: mica Date: Thu, 30 Jul 2026 12:43:52 -0700 Subject: [PATCH 069/347] docs: critical and high severity agent blockers (#69079) * docs: critical and high sev agent blockers * Fix high-severity documentation issues found during audit: Rewrite awsoidc-integration.mdx verification section to show expected output and clarify that tctl get does not validate the AWS trust relationship. Correct EKS SSO claim to distinguish IAM role ARN from STS session ARN. Revert redis-create-users.mdx to original secure guidance (unsubstantiated claim, broken ACL). Add eice deprecation note to ec2-config.mdx. Fix numbered list in kubernetes-oidc-join-token.mdx. * Removed the false claim that TLS routing 'routes traffic locally without the reverse tunnel', removed the blanket 'replace port 443' instruction that would break tsh login, and replaced the misleading admonition that presented elastic as a working alias with a clear note that elasticsearch is the only valid value and the CLI doesn't validate input * Added the requirement for iam:GetRole when using SSO roles, with an explanation of what happens without it, and Split the CA trust instruction into self-hosted vs Cloud paths. * linter casing * linter casing * post-review feedback for ptgott - removed admonition tags, added tls info to prereq/rephrased for user or admin * post-feedback from zmb3: cut down tls routing info, clarify the labeling of the CA bundle * fold CA admonition into prose, clarify server.cas naming * feedback: db entries for single-node deployments and NAT hairpinning limitations * single note include addition for mysql and cockroach dbs * removing duplicate note for headless environments * feedback: Expanded tsh proxy db command with full working command, reduced admonition clutter, and in edition-prereqs I split the guidance into two bullets for tctl and tctl commands * Documents the correct critical endpoint and distinguishes it from optional metadata access in mysql-postgres-mariadb * corrected the China region hostname per verified Codex feedback * added identity.pem to connection string * update tbot instance re-creation tip * edit hostname to appease linter. users are already instructed to replace host with their own (line 15) so this is sufficent * edit hostname to appease linter. plain placeholder is clear enough * casing linter appeasement * moved mysql-postgres-mariadb admonition to troubleshooting, style changes --- docs/cspell.json | 3 + .../kubernetes-oidc-join-token.mdx | 22 +++++++ .../terraform-provider/dedicated-server.mdx | 5 ++ .../terraform-provider/local.mdx | 13 +++++ .../database-access.mdx | 5 +- .../model-context-protocol/kube-access.mdx | 6 ++ .../model-context-protocol/mcp-access.mdx | 6 ++ .../third-party/ansible.mdx | 6 ++ .../cloud-apis/awsoidc-integration.mdx | 58 ++++++++++++++++++- .../eks-discovery/eks-discovery-manual.mdx | 12 ++++ .../aws/rds/mysql-postgres-mariadb.mdx | 23 ++++++++ .../self-hosted/cockroachdb-self-hosted.mdx | 3 +- .../enrollment/self-hosted/elastic.mdx | 21 ++++++- .../self-hosted/mongodb-self-hosted.mdx | 2 + .../self-hosted/mysql-self-hosted.mdx | 21 ++++++- .../enrollment/self-hosted/redis-cluster.mdx | 2 + .../enrollment/self-hosted/redis.mdx | 2 + .../includes/auto-discovery/ec2-config.mdx | 9 +++ .../database-access/redis-create-users.mdx | 6 +- .../database-access/single-node-note.mdx | 20 +++++++ .../tctl-auth-sign-3-files.mdx | 16 ++++- docs/pages/includes/edition-prereqs-tabs.mdx | 23 ++++++++ docs/pages/includes/tls-routing-note.mdx | 16 +++++ .../access-guides/ansible.mdx | 2 + 24 files changed, 288 insertions(+), 14 deletions(-) create mode 100644 docs/pages/includes/database-access/single-node-note.mdx create mode 100644 docs/pages/includes/tls-routing-note.mdx diff --git a/docs/cspell.json b/docs/cspell.json index be9df597d77c..8a3fb3479670 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -513,6 +513,7 @@ "editbasicsaml", "efgh", "efghijk", + "eice", "ekontsevoy", "eksbuild", "eksctl", @@ -600,6 +601,7 @@ "gslb", "gsutil", "gworkspace", + "hairpinning", "hashfile", "hashicorp", "headerv", @@ -674,6 +676,7 @@ "knownhosts", "ktutil", "kube", + "kubeadm", "kubeclustername", "kubeconfig", "kubeconfigs", diff --git a/docs/pages/configuration/resource-guides/kubernetes-oidc-join-token.mdx b/docs/pages/configuration/resource-guides/kubernetes-oidc-join-token.mdx index c2da8d26ed4e..6d068e02ce49 100644 --- a/docs/pages/configuration/resource-guides/kubernetes-oidc-join-token.mdx +++ b/docs/pages/configuration/resource-guides/kubernetes-oidc-join-token.mdx @@ -130,6 +130,28 @@ $ curl .well-known/openid- } ``` + +Cloud-managed clusters (EKS, GKE) have publicly trusted TLS certificates and +public OIDC endpoints by default. Self-managed clusters (k3s, kubeadm, k0s) +require two additional steps: + +1. **Make the OIDC endpoint public.** Enable anonymous authentication on the + API server (e.g., `--anonymous-auth=true` for k3s) and create a + ClusterRoleBinding granting `system:unauthenticated` the + `system:service-account-issuer-discovery` ClusterRole. + +2. **Trust the API server's CA.** Teleport's Auth Service must trust the TLS + certificate of the OIDC endpoint. + + - **Self-hosted Teleport:** Copy the Kubernetes API server CA certificate to + the system trust store (e.g., `/usr/local/share/ca-certificates/`) and run + `update-ca-certificates`, then restart Teleport. + - **Teleport Cloud:** You cannot modify the managed Auth Service's trust + store. Instead, configure your self-managed Kubernetes cluster's OIDC + endpoint to use a publicly trusted TLS certificate (e.g., via Let's + Encrypt or a commercial CA). + + ## Step 2/3. Create the Join Token In this step we will configure Teleport to trust tokens signed by the Kubernetes OIDC provider. diff --git a/docs/pages/configuration/terraform-provider/dedicated-server.mdx b/docs/pages/configuration/terraform-provider/dedicated-server.mdx index 20738a994fef..a059be118f86 100644 --- a/docs/pages/configuration/terraform-provider/dedicated-server.mdx +++ b/docs/pages/configuration/terraform-provider/dedicated-server.mdx @@ -47,6 +47,11 @@ Terraform provider. The daemon stores its identity on the disk and refresh the t [the `tbot` deployment guide for Linux](../../machine-workload-identity/deployment/linux.mdx). + +If `tbot start` fails after consuming the one-time join token (e.g., due to +network issues), the token cannot be reused. Recreate the bot with `tctl bots instances add ` to generate credentials for another instance and recreate it to get a new token. + + ## Step 1/3. Configure RBAC At this point, `tbot` is installed and configured on the machine that will run Terraform. diff --git a/docs/pages/configuration/terraform-provider/local.mdx b/docs/pages/configuration/terraform-provider/local.mdx index 717794771712..92b8c555f96b 100644 --- a/docs/pages/configuration/terraform-provider/local.mdx +++ b/docs/pages/configuration/terraform-provider/local.mdx @@ -30,6 +30,8 @@ connect to Teleport as the temporary bot. - Being locally logged in Teleport with a role that allows creating Bot and Token resources. You can use the default `editor` role for this. +(!docs/pages/includes/tls-routing-note.mdx!) + To check that you can connect to your Teleport cluster, sign in with `tsh login`, then verify that you can run `tctl` commands using your current credentials. @@ -75,6 +77,17 @@ Detected security key tap 🚀 Certificates obtained, you can now use Terraform in this terminal for 1h0m0s ``` + +If you are running `tctl terraform env` directly on the Auth Service host (e.g., +via SSH), the output `TF_TELEPORT_ADDR` will be `127.0.0.1:3025`, which is not +reachable from a remote machine. Override it with the Teleport Proxy Service's public address. +{/* lint ignore page-structure remark-lint */} + +```code +$ export TF_TELEPORT_ADDR="" +``` + + ## Step 2/2. Run the Terraform provider diff --git a/docs/pages/connect-your-client/model-context-protocol/database-access.mdx b/docs/pages/connect-your-client/model-context-protocol/database-access.mdx index 954b314a3545..082f82ed0a20 100644 --- a/docs/pages/connect-your-client/model-context-protocol/database-access.mdx +++ b/docs/pages/connect-your-client/model-context-protocol/database-access.mdx @@ -21,8 +21,9 @@ page_type: how-to }} /> -This guide shows you how to connect to your **PostgreSQL** Teleport databases -with MCP clients. +This guide shows you how to connect to your PostgreSQL Teleport databases with MCP clients using interactive tsh login. +For headless environments, CI/CD pipelines, or automation, see the Machine ID guide (tbot) to issue short-lived +credentials without interactive authentication. {/* lint ignore page-structure remark-lint */} diff --git a/docs/pages/connect-your-client/model-context-protocol/kube-access.mdx b/docs/pages/connect-your-client/model-context-protocol/kube-access.mdx index f468c143ab77..6e2b47ac46e3 100644 --- a/docs/pages/connect-your-client/model-context-protocol/kube-access.mdx +++ b/docs/pages/connect-your-client/model-context-protocol/kube-access.mdx @@ -30,6 +30,12 @@ This guide shows you how to connect to Teleport Kubernetes clusters with MCP cli (!docs/pages/includes/edition-prereqs-tabs.mdx clients="\`tsh\` client"!) - Kubernetes Clusters enrolled with Teleport. See our [guides](../../enroll-resources/kubernetes-access/getting-started.mdx). + +This guide uses interactive `tsh login`. For headless environments, CI/CD +pipelines, or automation, use [Machine ID](../../machine-workload-identity/getting-started.mdx) +(`tbot`) to issue short-lived credentials without interactive authentication. + + ## Step 1/2. List available MCP servers First, sign in into your Teleport cluster using `tsh login`: diff --git a/docs/pages/connect-your-client/model-context-protocol/mcp-access.mdx b/docs/pages/connect-your-client/model-context-protocol/mcp-access.mdx index 8cb311a3b2fc..c2208a228c39 100644 --- a/docs/pages/connect-your-client/model-context-protocol/mcp-access.mdx +++ b/docs/pages/connect-your-client/model-context-protocol/mcp-access.mdx @@ -40,6 +40,12 @@ by Teleport. - [MCP Access with Stdio MCP Server](../../enroll-resources/mcp-access/enrolling-mcp-servers/stdio.mdx) + +This guide uses interactive `tsh login`. For headless environments, CI/CD +pipelines, or automation, use [Machine ID](../../machine-workload-identity/getting-started.mdx) +(`tbot`) to issue short-lived credentials without interactive authentication. + + ## Step 1/2. List available MCP servers First, sign in into your Teleport cluster using `tsh login`, assigning diff --git a/docs/pages/connect-your-client/third-party/ansible.mdx b/docs/pages/connect-your-client/third-party/ansible.mdx index 7ada69eebdf8..7ecbe6373f31 100644 --- a/docs/pages/connect-your-client/third-party/ansible.mdx +++ b/docs/pages/connect-your-client/third-party/ansible.mdx @@ -31,6 +31,12 @@ servers. - `ansible` >= (=ansible.min_version=) - Optional tool `jq` to process `JSON` output. + +This guide uses interactive `tsh login`. For headless environments, CI/CD +pipelines, or automation, use [Machine ID](../../machine-workload-identity/access-guides/ansible.mdx) +(`tbot`) to issue short-lived credentials without interactive authentication. + + ## Step 1/3. Login and configure SSH Log into Teleport with `tsh`: diff --git a/docs/pages/enroll-resources/application-access/cloud-apis/awsoidc-integration.mdx b/docs/pages/enroll-resources/application-access/cloud-apis/awsoidc-integration.mdx index 0cc5c706f150..31c44dab137e 100644 --- a/docs/pages/enroll-resources/application-access/cloud-apis/awsoidc-integration.mdx +++ b/docs/pages/enroll-resources/application-access/cloud-apis/awsoidc-integration.mdx @@ -149,13 +149,65 @@ $ tctl create -f awsoidc-integration.yaml integration '' has been created ``` -To validate the integration without making a mutating AWS change, run: +### Validate the integration + +First, confirm the integration resource was created: + +```code +$ tctl get integrations/ +``` + +A successful response shows the stored resource definition: + +```yaml +kind: integration +metadata: + name: my-integration + revision: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +spec: + aws_oidc: + role_arn: arn:aws:iam::123456789012:role/my-iam-role +sub_kind: aws-oidc +version: v1 +``` + + +The `tctl get` command only confirms that the integration resource exists in +Teleport. It does not verify that the OIDC trust relationship with AWS is +functional. + + +Next, verify that the OIDC discovery endpoint is publicly accessible (this is +what AWS uses to validate the trust relationship): ```code -$ tctl integrations test +$ curl https:///.well-known/openid-configuration +``` + +A successful response returns a JSON document: + +```json +{ + "issuer": "https://teleport.example.com", + "jwks_uri": "https://teleport.example.com/.well-known/jwks-oidc", + "claims": ["iss", "sub", "obo", "aud", "jti", "iat", "exp", "nbf"], + "id_token_signing_alg_values_supported": ["RS256"], + "response_types_supported": ["id_token"], + "scopes_supported": ["openid"], + "subject_types_supported": ["public"] +} ``` -If the integration is working, the command returns the AWS account ID and caller ARN from AWS STS `GetCallerIdentity`. +If this endpoint is not reachable or does not return valid JSON, AWS will not be +able to validate tokens issued by Teleport and the integration will not work. + + +Starting in a future Teleport release, the `tctl integrations test` command +will provide end-to-end validation of the AWS OIDC trust relationship. It +calls AWS STS `GetCallerIdentity` using the OIDC token exchange, confirming +that AWS trusts Teleport's identity provider and can assume the configured IAM +role. A successful result returns the AWS account ID and assumed role ARN. + After the set up is complete, you can now use the "Enroll New Resource" flow in Teleport Web UI, or other integration dependent features. diff --git a/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-manual.mdx b/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-manual.mdx index 287faa8e6445..a0035e1bcd99 100644 --- a/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-manual.mdx +++ b/docs/pages/enroll-resources/auto-discovery/kubernetes/eks-discovery/eks-discovery-manual.mdx @@ -61,6 +61,18 @@ If you are running Teleport Discovery v15.3.8 or later and the IAM role used by the Discovery Service has the necessary permissions to create and update Access Entries, you can skip this section. The service can self-bootstrap the required permissions automatically. + +Note: When using AWS SSO/Identity Center, you must use the underlying IAM role +ARN as the Access Entry principal (e.g., +`arn:aws:iam::ACCOUNT:role/aws-reserved/sso.amazonaws.com/REGION/AWSReservedSSO_RoleName_ID`), +not the STS assumed-role session ARN (e.g., +`arn:aws:sts::ACCOUNT:assumed-role/AWSReservedSSO_RoleName_ID/user@example.com`). +EKS does not support STS session principals for access entries. If the +Discovery Service runs under an SSO role, add `iam:GetRole` (scoped to the +service's own role) to the IAM policy so auto-bootstrap can resolve the full +role ARN path. Without this permission, the path is dropped and the access +entry will reference an incorrect ARN. The IAM policy should also include +`eks:ListAccessEntries` for the auto-bootstrap to verify existing entries. When the Kubernetes Service uses an IAM role that is different from the one that diff --git a/docs/pages/enroll-resources/database-access/enrollment/aws/rds/mysql-postgres-mariadb.mdx b/docs/pages/enroll-resources/database-access/enrollment/aws/rds/mysql-postgres-mariadb.mdx index 29a215c7edad..e219aaee9aeb 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/aws/rds/mysql-postgres-mariadb.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/aws/rds/mysql-postgres-mariadb.mdx @@ -327,6 +327,29 @@ $ tsh db logout rds-example ## Troubleshooting +### Databases not appearing + +The Database Service downloads the RDS CA certificate bundle on startup from +the following endpoint (required for mTLS connections to your database): + +```text +https://truststore.pki.rds.amazonaws.com//-bundle.pem +``` + +For GovCloud regions, the host is `truststore.pki.us-gov-west-1.rds.amazonaws.com`. +For China regions, it is `rds-truststore.s3.cn-north-1.amazonaws.com.cn`. + +If databases do not appear in `tctl db ls` within 60 seconds of starting the +service: + +1. Ensure this endpoint is reachable from the host running the Database Service. +1. Enable DEBUG logging by setting `teleport.log.severity: DEBUG` in + `teleport.yaml`, then check the logs for download failures. + +Access to the RDS API (`rds..amazonaws.com`) is used on a best-effort +basis to fetch additional metadata (tags, engine versions), but it is not +required for database registration. + (!docs/pages/includes/database-access/aws-troubleshooting.mdx!) (!docs/pages/includes/database-access/aws-troubleshooting-max-policy-size.mdx!) diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/cockroachdb-self-hosted.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/cockroachdb-self-hosted.mdx index 762e7f402839..092e43467e00 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/cockroachdb-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/cockroachdb-self-hosted.mdx @@ -31,8 +31,7 @@ page_type: how-to - CockroachDB cluster. -- A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database - Service. +- (!docs/pages/includes/database-access/single-node-note.mdx!) - (!docs/pages/includes/tctl.mdx!) diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/elastic.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/elastic.mdx index b693c81ae7a0..eb562282b542 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/elastic.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/elastic.mdx @@ -30,6 +30,8 @@ page_type: how-to - (!docs/pages/includes/tctl.mdx!) +(!docs/pages/includes/database-access/single-node-note.mdx!) + ## Step 1/5. Set up the Teleport Database Service (!docs/pages/includes/tctl-token.mdx serviceName="Database" tokenType="db" tokenFile="/tmp/token"!) @@ -41,7 +43,7 @@ Install and configure Teleport where you will run the Teleport Database Service: (!docs/pages/includes/install-linux.mdx!) -(!docs/pages/includes/database-access/self-hosted-config-start.mdx dbName="myelastic" dbProtocol="elastic" databaseAddress="elasticsearch.example.com:9200" !) +(!docs/pages/includes/database-access/self-hosted-config-start.mdx dbName="myelastic" dbProtocol="elasticsearch" databaseAddress="elasticsearch.example.com:9200" !) @@ -49,12 +51,19 @@ Install and configure Teleport where you will run the Teleport Database Service: (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) - (!docs/pages/includes/database-access/self-hosted-db-helm-install.mdx dbName="myelastic" dbProtocol="elastic" databaseAddress="elasticsearch.example.com:9200" !) + (!docs/pages/includes/database-access/self-hosted-db-helm-install.mdx dbName="myelastic" dbProtocol="elasticsearch" databaseAddress="elasticsearch.example.com:9200" !) (!docs/pages/includes/database-access/multiple-instances-tip.mdx !) + +The protocol name for Elasticsearch is `elasticsearch`. While `teleport db +configure create` accepts arbitrary protocol strings without validation, the +Database Service rejects any protocol not in the supported list at startup. +Always use `elasticsearch` as the protocol value. + + ## Step 2/5. Create a Teleport user (!docs/pages/includes/database-access/create-user.mdx!) @@ -128,6 +137,14 @@ longer be able to connect to the cluster without providing a valid client certif `xpack.security.http.ssl.client_authentication` to `optional` to allow connections from clients that do not present a certificate, using other methods like username and password. + +Complete Step 3 (role mapping creation) before setting `client_authentication: +required`. Once strict client authentication is enabled, you cannot use +username/password to manage Elasticsearch. If you need to create or update role +mappings after enabling mutual TLS, temporarily set `client_authentication: +optional`, make your changes, then set it back to `required`. + + ## Step 5/5. Connect Log into your Teleport cluster and see available databases: diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mongodb-self-hosted.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mongodb-self-hosted.mdx index bc94ca6dfdba..4c64887240b9 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mongodb-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mongodb-self-hosted.mdx @@ -47,6 +47,8 @@ page_type: how-to (!docs/pages/includes/database-access/split-db-ca-details.mdx db="MongoDB Replica Set"!) +(!docs/pages/includes/database-access/single-node-note.mdx!) + ## Step 1/3. Install and configure Teleport ### Set up the Teleport Database service diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mysql-self-hosted.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mysql-self-hosted.mdx index b7adac1406f6..36bd85869af5 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mysql-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/mysql-self-hosted.mdx @@ -30,10 +30,9 @@ page_type: how-to (!docs/pages/includes/edition-prereqs-tabs.mdx!) - A self-hosted MySQL or MariaDB instance. -- A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database - Service. - Optional: a certificate authority that issues certificates for your self-hosted database. +- (!docs/pages/includes/database-access/single-node-note.mdx!) - (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Create the Teleport Database Token @@ -199,6 +198,24 @@ $ tsh db connect --db-user=alice --db-name=mysql example-mysql able to connect. `mariadb` is a default command-line client for MySQL and MariaDB. +
    +Using identity files + +If you are using an identity file (`tsh -i identity.pem`) instead of interactive +`tsh login`, use `tsh proxy db` instead of `tsh db connect`. The `tsh db connect` +command does not work with identity files because it references certificate paths +that do not exist on disk. + +You must supply `--proxy`, the database name, and the required `--db-user`/`--db-name` +flags: + +```code +$ tsh -i identity.pem proxy db --tunnel --proxy= --db-user=alice --db-name=mysql -p 3307 example-mysql +``` + +Then connect your MySQL client to `localhost:3307`. +
    + (!docs/pages/includes/database-access/proxy-db-tunnel.mdx db="example-mysql" dbArgs="--db-user=alice --db-name=mysql"!) (!docs/pages/includes/database-access/db-access-webui-ad.mdx dbType="MySQL or MariaDB"!) diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis-cluster.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis-cluster.mdx index f0714dd2ca9e..57c317ff932f 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis-cluster.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis-cluster.mdx @@ -50,6 +50,8 @@ If you want to configure Redis Standalone, please read [Database Access with Red - (!docs/pages/includes/tctl.mdx!) +(!docs/pages/includes/database-access/single-node-note.mdx!) + ## Step 1/6. Set up the Teleport Database Service (!docs/pages/includes/tctl-token.mdx serviceName="Database" tokenType="db" tokenFile="/tmp/token"!) diff --git a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis.mdx b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis.mdx index a2cf03408f4b..ac7c43c25d7e 100644 --- a/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis.mdx +++ b/docs/pages/enroll-resources/database-access/enrollment/self-hosted/redis.mdx @@ -50,6 +50,8 @@ If you want to configure Redis Cluster, please read [Database Access with Redis - (!docs/pages/includes/tctl.mdx!) +(!docs/pages/includes/database-access/single-node-note.mdx!) + ## Step 1/5. Set up the Teleport Database Service (!docs/pages/includes/tctl-token.mdx serviceName="Database" tokenType="db" tokenFile="/tmp/token"!) diff --git a/docs/pages/includes/auto-discovery/ec2-config.mdx b/docs/pages/includes/auto-discovery/ec2-config.mdx index f0985d5de703..414318418e0f 100644 --- a/docs/pages/includes/auto-discovery/ec2-config.mdx +++ b/docs/pages/includes/auto-discovery/ec2-config.mdx @@ -67,6 +67,15 @@ Static configuration while simpler at first, has less flexibility because enroll join_method: iam join_token: aws-discovery-iam-token ``` + + + Dynamic `discovery_config` resources use numeric values for `enroll_mode`: + `1` corresponds to `script` (install via shell script) and `2` corresponds to + `eice` (EC2 Instance Connect Endpoint). The static `teleport.yaml` configuration + uses the string names (`script`, `eice`). Note that `eice` mode is deprecated; + use `script` mode for new deployments. + + Adjust the keys under `spec.aws` to match your EC2 environment, specifically the regions and tags you want to associate with the Discovery Service. diff --git a/docs/pages/includes/database-access/redis-create-users.mdx b/docs/pages/includes/database-access/redis-create-users.mdx index 5644b2e8f0ad..6afb6244cd20 100644 --- a/docs/pages/includes/database-access/redis-create-users.mdx +++ b/docs/pages/includes/database-access/redis-create-users.mdx @@ -29,6 +29,8 @@ user default off For more ACL examples refer to the [Redis documentation](https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/). - It's very important to either disable or protect with a password the `default` user. Otherwise, everyone with access - to the database can log in as the `default` user, which by default has administrator privileges. + It's important to protect the `default` user. Otherwise, everyone with access + to the database can log in as the `default` user, which by default has + administrator privileges. You can either disable it (`user default off`) or + protect it with a strong password. diff --git a/docs/pages/includes/database-access/single-node-note.mdx b/docs/pages/includes/database-access/single-node-note.mdx new file mode 100644 index 000000000000..476e3c1cdcee --- /dev/null +++ b/docs/pages/includes/database-access/single-node-note.mdx @@ -0,0 +1,20 @@ +- A host, e.g., an Amazon EC2 instance, where you will run the Teleport + Database Service, separate from the host(s) running the Teleport Auth + Service and Proxy Service. + +
    + Single-node Teleport clusters + + If you run the Database Service as a separate process on the same host as + the Proxy Service, the reverse tunnel may fail to connect through the + proxy's public address due to NAT hairpinning limitations in some cloud + environments (notably GCP and certain on-prem setups). To resolve this, add + your proxy domain to `/etc/hosts` pointing to `127.0.0.1`: + + ```code + $ echo "127.0.0.1 teleport.example.com" | sudo tee -a /etc/hosts + ``` + + Alternatively, run all Teleport services in a single process (Auth Service, Proxy Service, + and Database Service in one `teleport.yaml`) to avoid the reverse tunnel entirely. +
    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 d0bb3049ce7a..f07bb28f6d1d 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 @@ -4,6 +4,15 @@ databases must be able to verify certificates presented by the Teleport Database Service. Self-hosted databases also need a certificate/key pair that Teleport can verify. +Teleport maintains two separate certificate authorities (CAs) for database +access: the **Database CA** (`db`), which signs certificates that databases +present to the Teleport Database Service, and the **Database Client CA** +(`db_client`), which signs certificates that the Teleport Database Service +presents to databases. You can export either CA directly with +`tctl auth export --type=db` or `tctl auth export --type=db-client`, but in +most cases you won't need to: `tctl auth sign`, described below, retrieves +the certificates for you and writes them to local files. + By default, the Teleport Database Service trusts certificates issued by a certificate authority managed by the Teleport Auth Service. You can either: - Configure your self-hosted database to trust this CA, and instruct Teleport to @@ -40,7 +49,12 @@ the database, follow these instructions on your workstation: (!docs/pages/includes/database-access/ttl-note.mdx!) - The command creates 3 files: `server.cas`, `server.crt` and `server.key`. + The command creates 3 files: + - `server.cas`: The Database Client CA certificate, which your database + uses to verify connections from the Teleport Database Service. + - `server.crt`: A certificate for your database server, signed by the + Database CA. + - `server.key`: The private key for the server certificate. diff --git a/docs/pages/includes/edition-prereqs-tabs.mdx b/docs/pages/includes/edition-prereqs-tabs.mdx index 050a4fc504cf..caf16b11b45a 100644 --- a/docs/pages/includes/edition-prereqs-tabs.mdx +++ b/docs/pages/includes/edition-prereqs-tabs.mdx @@ -80,3 +80,26 @@ + +
    + Connecting with TLS routing disabled + + This guide's commands assume your Teleport cluster uses TLS routing + (`proxy_listener_mode: multiplex`), where the {{ clients }} reach every + Teleport service through the Proxy Service's web address on port `443`. If + you're not sure whether this applies to your cluster, check with whoever + manages it. + + If your cluster uses separate listener ports instead, adjust ports as follows: + + - **`tsh` commands** (e.g., `tsh login --proxy=...`): continue using the + Proxy Service web address on port `3080` (or `443` if behind a load + balancer). Do not change these to port `3025`. + - **Direct `tctl` or Auth Service API commands**: use port `3025` for the + Auth Service gRPC listener: + + ```code + $ tctl status --auth-server=teleport.example.com:3025 + ``` + +
    diff --git a/docs/pages/includes/tls-routing-note.mdx b/docs/pages/includes/tls-routing-note.mdx new file mode 100644 index 000000000000..b39a8be5bd0a --- /dev/null +++ b/docs/pages/includes/tls-routing-note.mdx @@ -0,0 +1,16 @@ +
    +Connecting with TLS routing disabled + +This guide's commands assume your Teleport cluster uses TLS routing +(`proxy_listener_mode: multiplex`), where all client traffic goes through the +proxy web address on port `443`. If you're not sure whether this applies to +your cluster, check with whoever manages it. + +If your cluster uses separate listener ports instead, replace port `443` in +the commands below with the port for the Auth Service: + +```code +$ tctl status --auth-server=teleport.example.com:3025 +``` + +
    diff --git a/docs/pages/machine-workload-identity/access-guides/ansible.mdx b/docs/pages/machine-workload-identity/access-guides/ansible.mdx index 1cf1bb63218f..945b54f1485c 100644 --- a/docs/pages/machine-workload-identity/access-guides/ansible.mdx +++ b/docs/pages/machine-workload-identity/access-guides/ansible.mdx @@ -50,6 +50,8 @@ You will need the following tools to use Teleport with Ansible. - (!docs/pages/includes/tctl.mdx!) +(!docs/pages/includes/tls-routing-note.mdx!) + ## Step 1/4. Configure RBAC As Ansible will use the credentials produced by `tbot` to connect to the SSH From 82289903fd39372436dc87c4d7b707ef6b2670c7 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Thu, 30 Jul 2026 16:19:16 -0400 Subject: [PATCH 070/347] Prevent demo cluster missteps for AI agents (#68894) Fix issues that hinder AI agents that attempt to set up a demo cluster in order to follow other docs guides. - **Add resource guidelines to the Deploy Community guide:** An agent failed to follow many of our how-to guides because it ran all demo infrastructure resources on the same host as the Teleport cluster. This caused OOMs. Sizing guidance is based on the single-instance Terraform guide, `installation/self-hosted/deployments/aws-starter-cluster-terraform.mdx`. - **Edit the Community Edition guide to add further preventions against using self-signed certificates:** Agents see the Prerequisites requirement for DNS records as a blocker. If records don't exist, the agent doesn't try to create them. Instead, add a "Step" section with instructions to create DNS records. While an earlier change removed an existing "Step" section for creating records, this change includes commands in the section to reduce the context needed to follow the guide and give agents an easy course of action to reach for. (If it's not easy, they'll reach for self-signed certificates instead.) Also strengthen the Admonition against using self-signed certificates, which tends to be an AI agent's first choice. - **Mark the private CA alternative as advanced in the Deploy Community guide:** This way, it's not presented as equally viable for a demo environment, and agents won't be as tempted to follow it. - **Require a valid TLS certificate** in the partial we use in Prerequisites sections throughout our how-to guides, `edition-prereqs-tabs.mdx`. While agents tend to set up demo clusters successully, they often use self-signed certificates and fail to connect Teleport Agents to it, and for `tctl` commands, end up running the commands over a direct SSH connection to the Auth Service. Include a persistent reminder in how-to guides that we expect the Proxy Service to be reachable with a valid TLS certificate. - **Edit add-role-to-user.mdx:** Add a `TabItem` in this partial for non-interactive users, since agents had difficulty with `tsh login`. --- docs/pages/get-started/deploy-community.mdx | 198 ++++++++++++++----- docs/pages/includes/add-role-to-user.mdx | 64 +++++- docs/pages/includes/edition-prereqs-tabs.mdx | 7 +- 3 files changed, 216 insertions(+), 53 deletions(-) diff --git a/docs/pages/get-started/deploy-community.mdx b/docs/pages/get-started/deploy-community.mdx index 8756e094cb60..ef59ac64ad7b 100644 --- a/docs/pages/get-started/deploy-community.mdx +++ b/docs/pages/get-started/deploy-community.mdx @@ -58,22 +58,25 @@ instance approach** for simplicity. Once you have completed this guide, having a dedicated host for your self-hosted Teleport cluster reduces the complexity of following other guides in the Teleport documentation. -Make sure you have met the -following requirements for your platform: +Make sure you have met the following requirements for your platform: - A Linux host with only port `443` open to ingress traffic, and no existing - Teleport installation. You must be able to install and run software on the - host. Either configure access to the host via SSH for the initial setup (and - open an SSH port in addition to port `443`) or enter the commands in this - guide into an Amazon EC2 [user data - script](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html), - Google Compute Engine [startup - script](https://cloud.google.com/compute/docs/instances/startup-scripts), or - similar. - + Teleport installation. + - You must be able to install and run software on the host. Either configure + access to the host via SSH for the initial setup (and open an SSH port in + addition to port `443`) or enter the commands in this guide into an Amazon + EC2 [user data + script](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html), + Google Compute Engine [startup + script](https://cloud.google.com/compute/docs/instances/startup-scripts), or + similar. + - The host can start with 2 vCPU and 1GB RAM for testing, though for + additional users and resources, we recommend a larger instance size. For the + most scalable performance, run Teleport-protected resources on separate + hosts from the Teleport cluster. - **One** of the following: - A registered domain name or access to a service like Amazon Route53 that allows you to register domain names. @@ -81,14 +84,6 @@ following requirements for your platform: existing certificate authority. If using this approach, ensure that your browser is configured to use your organization's nameserver. -- Two DNS `A` records, each pointing to the IP address of your Linux host. - Assuming `teleport.example.com` is your domain name, set up records for: - - |Domain|Reason| - |---|---| - |`teleport.example.com`|Traffic to the Proxy Service from users and services.| - |`*.teleport.example.com`|Traffic to web applications registered with Teleport. Teleport issues a subdomain of your cluster's domain name to each application.| - @@ -154,14 +149,118 @@ Authenticator](https://www.google.com/landing/2step/), or -While it is possible to configure the Teleport Auth Service to use a self-signed -certificate, this adds significant complexity and is not recommended. See -[Self-Signed Certificates](../installation/self-hosted/self-signed-certs.mdx) -for how to run a Teleport cluster with self-signed certificates. +For your Teleport cluster to be functional, the Proxy Service must be +addressable from outside the cluster with a valid TLS certificate. In most +cases, this requires a domain name. Only run your Teleport cluster with +self-signed certificates if you understand the implications. Other guides in the +documentation assume that your cluster has a domain name. + +See [Self-Signed +Certificates](../installation/self-hosted/self-signed-certs.mdx) for how to run +a Teleport cluster with self-signed certificates. -## Step 1/3. Set up Teleport on your Linux host +## Step 1/4. Create DNS records + +If you are following this guide with a local Docker container for testing, skip +to Step 2. + +Create two DNS `A` records, each pointing to the IP address of your Linux host. +Assuming `teleport.example.com` is your domain name, set up records for: + +|Domain|Reason| +|---|---| +|`teleport.example.com`|Traffic to the Proxy Service from users and services.| +|`*.teleport.example.com`|Traffic to web applications registered with Teleport. Teleport issues a subdomain of your cluster's domain name to each application.| + +Here are example commands for creating these records on AWS and Google Cloud. If +you are using a DNS name server managed by your organization, use your internal +tooling to create the two records and skip to Step 2. + +1. Assign the following variables: + - : An IP address for the instance where you want to + run Teleport (mentioned in the Prerequisites). It must be reachable by cluster + users as well as any infrastructure resources you want to protect. + - : The subdomain for your Teleport cluster. + +1. Run the appropriate commands for your cloud provider: + + + + + Assign to the ID of your Route 53 hosted zone and run + the following command: + + ```code + $ MYDNS='' + $ MYIP='' + $ MYZONE='' + + # Create a JSON file changeset for AWS. + $ jq -n --arg dns "${MYDNS?}" --arg ip "${MYIP?}" \ + '{ + "Comment": "Create records", + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": $dns, + "Type": "A", + "TTL": 300, + "ResourceRecords": [ + { "Value": $ip } + ] + } + }, + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": ("*." + $dns), + "Type": "A", + "TTL": 300, + "ResourceRecords": [ + { "Value": $ip } + ] + } + } + ] + }' > myrecords.json + + # Review records before applying. + $ cat myrecords.json | jq + # Apply the records and capture change id + $ CHANGEID="$(aws route53 change-resource-record-sets --hosted-zone-id "${MYZONE?}" --change-batch file://myrecords.json | jq -r '.ChangeInfo.Id')" + + # Verify that change has been applied + $ aws route53 get-change --id "${CHANGEID?}" | jq '.ChangeInfo.Status' + # "INSYNC" + ``` + + + + + Assign to the name of the Cloud DNS zone in which you will + create records. + + Run the following commands: + + ```code + $ MYZONE='' + $ MYIP='' + $ MYDNS='' + + $ gcloud dns record-sets transaction start --zone="${MYZONE?}" + $ gcloud dns record-sets transaction add ${MYIP?} --name="${MYDNS?}" --ttl="300" --type="A" --zone="${MYZONE?}" + $ gcloud dns record-sets transaction add ${MYIP?} --name="*.${MYDNS?}" --ttl="300" --type="A" --zone="${MYZONE?}" + $ gcloud dns record-sets transaction describe --zone="${MYZONE?}" + $ gcloud dns record-sets transaction execute --zone="${MYZONE?}" + ``` + + + + +## Step 2/4. Set up Teleport on your Linux host In this step, you will log into your Linux host, download the Teleport binary, generate a Teleport configuration file, and start the Teleport Auth Service, @@ -228,24 +327,6 @@ infrastructure in your cluster. --key-file=/etc/teleport-tls/localhost-key.pem ``` - - On your Teleport host, place a valid private key and a certificate chain in `/var/lib/teleport/privkey.pem` - and `/var/lib/teleport/fullchain.pem` respectively. - - The leaf certificate must have a subject that corresponds to the domain of your Teleport host, e.g., `*.teleport.example.com`. - - On the host where you will start the Teleport Auth Service and Proxy Service, - run the following `teleport configure` command. Assign to the domain name of your Teleport cluster. - - ```code - $ sudo teleport configure -o file \ - --cluster-name= \ - --public-addr=:443 \ - --cert-file=/var/lib/teleport/fullchain.pem \ - --key-file=/var/lib/teleport/privkey.pem - ``` - ### Start Teleport @@ -296,7 +377,7 @@ infrastructure in your cluster. - Check the Teleport logs for errors: `sudo journalctl -u teleport` on a VM, or review the terminal output in your container. -## Step 2/3. Create a Teleport user and set up multi-factor authentication +## Step 3/4. Create a Teleport user and set up multi-factor authentication In this step, we'll create a new Teleport user, `teleport-admin`, which is allowed to log into SSH hosts as any of the principals `root`, `ubuntu`, or @@ -402,7 +483,7 @@ $ tsh login --proxy= --user=teleport-admin -## Step 3/3. Access your server with Teleport +## Step 4/4. Access your server with Teleport Now that you have Teleport running and a user configured, you can access your Linux server through the Teleport Web UI (it will be automatically enrolled since Teleport is running on it). @@ -432,3 +513,32 @@ At this point, you've launched your own Teleport Community Edition cluster and c +## Advanced: Bring your own certificate + +It is possible to deploy Teleport Community Edition in environments that are +air-gapped or require a private certificate authority. You can configure the +Teleport Proxy Service to present a custom certificate and CA. + +On your Teleport host, place a valid private key and a certificate chain in +`/var/lib/teleport/privkey.pem` and `/var/lib/teleport/fullchain.pem` +respectively. + +The leaf certificate must have a subject that corresponds to the domain of your +Teleport host, e.g., `*.teleport.example.com`. + +On the host where you will start the Teleport Auth Service and Proxy Service, +run the following `teleport configure` command. Assign + to the domain name of your Teleport cluster. + +```code +$ sudo teleport configure -o file \ + --cluster-name= \ + --public-addr=:443 \ + --cert-file=/var/lib/teleport/fullchain.pem \ + --key-file=/var/lib/teleport/privkey.pem +``` + +All clients and Teleport Agents connecting to this cluster must trust the +certificate authority that signed `fullchain.pem`. See [Running Teleport with +Self-Signed Certificates]( ../installation/self-hosted/self-signed-certs.mdx) +for details. diff --git a/docs/pages/includes/add-role-to-user.mdx b/docs/pages/includes/add-role-to-user.mdx index b935cfc4139f..6fe83b678702 100644 --- a/docs/pages/includes/add-role-to-user.mdx +++ b/docs/pages/includes/add-role-to-user.mdx @@ -1,6 +1,7 @@ {{ role="myrole" user="your Teleport user" }} -Assign the `{{ role }}` role to {{ user }} by running the appropriate -commands for your authentication provider: +Assign the `{{ role }}` role to {{ user }}. The commands to run depend on how +you authenticate to Teleport, that is, whether you have a local Teleport user, a +single sign-on authentication connector, or a non-interactive user: @@ -102,13 +103,13 @@ commands for your authentication provider: oidc.yaml file immediately after updating the resource. 1. Edit `oidc.yaml`, adding `{{ role }}` to the `claims_to_roles` section. - + The claim you should map to this role depends on how you have designed your organization's role-based access controls (RBAC). However, the group must include your user account and should be the smallest group possible within your organization. - + Here is an example: - + ```diff claims_to_roles: - name: "groups" @@ -119,13 +120,64 @@ commands for your authentication provider: ``` 1. Apply your changes: - + ```code $ tctl create -f oidc.yaml ``` 1. Sign out of the Teleport cluster and sign in again to assume the new role. + + + +For non-interactive users such as AI agents and CI/CD runners, follow these +steps. + +If you are using Machine ID to issue identity files to your user: + +1. Add the new role to your bot. Assign to the name of the + Machine ID bot that issues your certificate: + + ```code + $ tctl bots update --add-roles {{ role }} + ``` + +1. Force `tbot` to regenerate the certificate: + + ```code + $ pkill -HUP tbot + ``` + + The bot will also generate a certificate with the new role on its next + renewal. + +Otherwise, update your user and regenerate your identity file: + +1. Retrieve your local user's roles as a comma-separated list. Make sure + is the Teleport user you want to assign the new role + to. + + ```code + $ ROLES=$(tctl get user/ --format=json | jq -r '.[].spec.roles | join(",")') + ``` + +1. Edit your local user to add the new role: + + ```code + $ tctl users update \ + --set-roles "${ROLES?},{{ role }}" + ``` + +1. Regenerate the identity file. Make sure + corresponds to the correct output path: + + ```code + $ tctl auth sign \ + --user= \ + --out= \ + --overwrite + ``` + diff --git a/docs/pages/includes/edition-prereqs-tabs.mdx b/docs/pages/includes/edition-prereqs-tabs.mdx index caf16b11b45a..b407fd2e1eff 100644 --- a/docs/pages/includes/edition-prereqs-tabs.mdx +++ b/docs/pages/includes/edition-prereqs-tabs.mdx @@ -1,8 +1,9 @@ {{ edition="Teleport" clients="`tctl` and `tsh` clients" optionalClientNote="" }} -- A running {{ edition }} cluster. If you want to get started with Teleport, - [sign up](https://goteleport.com/signup) for a free trial or [set up a demo - environment](../get-started/deploy-community.mdx). +- A running {{ edition }} cluster accessible at a hostname with a valid TLS + certificate. If you want to get started with Teleport, + [sign up](https://goteleport.com/signup) for a free trial or + [set up a demo environment](../get-started/deploy-community.mdx). - The {{ clients }}{{ optionalClientNote }}. From 9e93d7e512dbe7308ae32842e19aab73cc942abe Mon Sep 17 00:00:00 2001 From: Luke Okraszewski Date: Thu, 30 Jul 2026 21:38:50 +0100 Subject: [PATCH 071/347] Add unit test for GetMetrics (#69191) * Add unit test for GetMetrics * swap to require.InDelta --- lib/client/debug/debug_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/client/debug/debug_test.go b/lib/client/debug/debug_test.go index c82b945a57fe..3460e080f46e 100644 --- a/lib/client/debug/debug_test.go +++ b/lib/client/debug/debug_test.go @@ -18,6 +18,7 @@ package debug import ( "context" + "io" "net" "net/http" "os" @@ -119,6 +120,36 @@ func TestGetReadiness(t *testing.T) { }) } +func TestGetMetrics(t *testing.T) { + ctx := t.Context() + clt := &Client{ + clt: &http.Client{ + Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { + require.Equal(t, "http://debug/metrics", req.URL.String()) + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`# HELP test_metric Test metric. +# TYPE test_metric gauge +test_metric 1 +`)), + }, nil + }), + }, + } + + metrics, err := clt.GetMetrics(ctx) + require.NoError(t, err) + require.Contains(t, metrics, "test_metric") + require.Len(t, metrics["test_metric"].GetMetric(), 1) + require.InDelta(t, 1.0, metrics["test_metric"].GetMetric()[0].GetGauge().GetValue(), 0.0001) +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + func TestCollectProfile(t *testing.T) { ctx := context.Background() From a185131412b5a136602231050513e37668f9a5f3 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:39:31 -0400 Subject: [PATCH 072/347] Update Help & Support page to use new `design-system` (#69165) * migrate help & support page to use design-system components * remove redundant nesting * CR * fix header sizing --- web/packages/teleport/src/Support/Support.tsx | 459 +++++++++--------- 1 file changed, 226 insertions(+), 233 deletions(-) diff --git a/web/packages/teleport/src/Support/Support.tsx b/web/packages/teleport/src/Support/Support.tsx index 03835ea6ca57..1148e1eb4059 100644 --- a/web/packages/teleport/src/Support/Support.tsx +++ b/web/packages/teleport/src/Support/Support.tsx @@ -16,20 +16,24 @@ * along with this program. If not, see . */ +import { + ButtonLink, + Card, + Flex, + GraphIcon, + H1, + H2, + H3, + Subtitle2, + QuestionIcon, + SimpleGrid, + Stack, + styled, +} from '@gravitational/design-system'; import React from 'react'; import { Link } from 'react-router'; -import styled from 'styled-components'; - -import { Box, Card, Flex, H2, H3, Text } from 'design'; -import * as Icons from 'design/Icon'; -import { P } from 'design/Text/Text'; import { ButtonLockedFeature } from 'teleport/components/ButtonLockedFeature'; -import { - FeatureBox, - FeatureHeader, - FeatureHeaderTitle, -} from 'teleport/components/Layout'; import cfg from 'teleport/config'; import { useNoMinWidth } from 'teleport/Main'; import { CtaEvent } from 'teleport/services/userEvent'; @@ -71,211 +75,188 @@ export const Support = ({ const docs = getDocUrls(authVersion, isEnterprise); return ( - - - Help & Support - - - - props.theme.breakpoints.small}) { - grid-column: span 2; - } - `} + +

    + Help & Support +

    + } + titleAction={ + showPremiumSupportCta && ( + + Unlock Premium Support with Enterprise + + ) + } + display="block" + gridColumn="1 / -1" + > + - - - - - -

    Support and Resource Pages

    -
    - - {showPremiumSupportCta && ( - - Unlock Premium Support with Enterprise - - )} - -
    - - -

    - Contact Support -

    - {isEnterprise && !showPremiumSupportCta && ( - - )} + + {isEnterprise && !showPremiumSupportCta && ( - - - - -

    - Resources -

    - - - - - - -
    - -

    - Updates -

    - - - -
    -
    -
    - props.theme.breakpoints.mobile}) { - grid-column: auto; - } - ` - } - > - - - - -

    Cluster Information

    -
    - -

    Cluster Name: {clusterId}

    -

    Teleport Version: {authVersion}

    -

    Public Address: {publicURL}

    - {tunnelPublicAddress && ( -

    Public SSH Tunnel: {tunnelPublicAddress}

    )} - {isEnterprise && !cfg.isCloud && !!licenseExpiryDateText && ( -

    License Expiry: {licenseExpiryDateText}

    - )} -
    -
    + + + + + + + + + + + + + + + + + +
    + + } + gridColumn={isCloud ? undefined : '1 / -1'} + > + + Cluster Name: {clusterId} + Teleport Version: {authVersion} + Public Address: {publicURL} + {tunnelPublicAddress && ( + Public SSH Tunnel: {tunnelPublicAddress} + )} + {isEnterprise && !cfg.isCloud && !!licenseExpiryDateText && ( + License Expiry: {licenseExpiryDateText} + )} + + - {children} - - + {children} + ); }; -const SupportSectionsWrapper = styled(Box)<{ isCloud?: boolean }>` - display: grid; - gap: ${props => props.theme.space[3]}px; - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-auto-rows: auto; - width: 100%; - - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - grid-template-columns: 1fr !important; - gap: ${props => props.theme.space[2]}px; - } -`; - -export const SupportSectionCard = styled(Card)` - padding: ${props => props.theme.space[4]}px; - box-shadow: ${props => props.theme.boxShadow[0]}; - - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - padding: ${props => props.theme.space[3]}px; - } -`; - -export const IconBox = styled(Box)` - line-height: 0; - padding: ${props => props.theme.space[2]}px; - border-radius: ${props => props.theme.radii[3]}px; - margin-right: ${props => props.theme.space[3]}px; - background: ${props => props.theme.colors.interactive.tonal.neutral[0]}; - border: ${props => props.theme.borders[1]}; - border-color: ${props => props.theme.colors.interactive.tonal.neutral[2]}; - - .icon { - height: 16px; - width: 16px; - } - - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - background: transparent; - margin-right: ${props => props.theme.space[1]}px; - } -`; - -const SupportLinkCategory = styled(Flex)` - flex-direction: column; - gap: ${props => props.theme.space[1]}px; -`; +type SupportSectionCardProps = Omit & { + title: string; + icon: React.ReactNode; + titleAction?: React.ReactNode; +}; -const SupportButtonBox = styled(Box)` - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - width: 100%; - } -`; +export const SupportSectionCard = ({ + title, + icon, + titleAction, + children, + ...cardProps +}: SupportSectionCardProps) => { + const heading = ( +

    + {icon} + {title} +

    + ); -const SupportLinksFlex = styled(Flex)` - justify-content: space-between; - flex-wrap: wrap; - max-width: 70%; - @media screen and (max-width: ${props => props.theme.breakpoints.medium}) { - max-width: 100%; - } - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - flex-direction: column; - gap: ${props => props.theme.space[3]}px; - margin-bottom: ${props => props.theme.space[3]}px; - } -`; + return ( + + {titleAction ? ( + + {heading} + {titleAction} + + ) : ( + heading + )} + {children} + + ); +}; -const DataItemFlex = styled(Flex)` - margin-bottom: ${props => props.theme.space[3]}px; - @media screen and (max-width: ${props => props.theme.breakpoints.small}) { - flex-direction: column; - padding-left: ${props => props.theme.space[2]}px; - } -`; +const IconBox = styled('span', { + base: { + lineHeight: 0, + padding: 2, + borderRadius: 'md', + marginRight: { base: 1, sm: 4 }, + border: 'sm', + borderColor: 'interactive.tonal.neutral.2', + background: { base: 'transparent', sm: 'interactive.tonal.neutral.0' }, + + '& .icon': { + height: '16px', + width: '16px', + }, + }, +}); + +const SupportLinkCategory = ({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) => ( + +

    + {title} +

    + {children} +
    +); /** * getDocUrls returns an object of URLs appended with @@ -322,9 +303,11 @@ const DownloadLink = ({ }) => { if (isCloud) { return ( - - Download Page - + + + Download Page + + ); } @@ -346,34 +329,44 @@ const DownloadLink = ({ }; const ExternalSupportLink = ({ title = '', url = '' }) => ( - - {title} - + + + {title} + + ); -const StyledSupportLink = styled.a.attrs({ - rel: 'noreferrer', -})` - display: block; - color: ${props => props.theme.colors.text.main}; - border-radius: 4px; - text-decoration: none; - padding: 4px 8px; - transition: all 0.3s; - - ${props => props.theme.typography.body2} - &:hover, &:focus { - background: ${props => props.theme.colors.spotBackground[0]}; - } -`; - -export const DataItem = ({ title = '', data = null }) => ( - - - {title}: - - {data} - +const SupportLink = ({ children }: { children: React.ReactElement }) => ( + + {children} + ); export type Props = { From beeb9066fb7757bc258be0d639979b057d1b7544 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:54:51 -0400 Subject: [PATCH 073/347] add fallback for oss checkout ref (#69208) --- .github/workflows/e2e-tests-base.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests-base.yaml b/.github/workflows/e2e-tests-base.yaml index da78ed270d2a..8333d1f277a6 100644 --- a/.github/workflows/e2e-tests-base.yaml +++ b/.github/workflows/e2e-tests-base.yaml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v6 with: repository: ${{ inputs.oss-repository || github.repository }} - ref: ${{ inputs.oss-repository && (github.event.pull_request.base.ref || github.event.merge_group.base_ref) || '' }} + ref: ${{ inputs.oss-repository && (github.event.pull_request.base.ref || github.event.merge_group.base_ref || github.ref_name) || '' }} fetch-depth: 1 - name: Checkout Enterprise @@ -201,7 +201,7 @@ jobs: uses: actions/checkout@v6 with: repository: ${{ inputs.oss-repository || github.repository }} - ref: ${{ inputs.oss-repository && (github.event.pull_request.base.ref || github.event.merge_group.base_ref) || '' }} + ref: ${{ inputs.oss-repository && (github.event.pull_request.base.ref || github.event.merge_group.base_ref || github.ref_name) || '' }} fetch-depth: 1 persist-credentials: false From 60d859b511262149101d056cb9f13659b4bbfb0b Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:51:14 -0400 Subject: [PATCH 074/347] fix featurehiding (#68770) --- web/packages/teleport/src/Main/Main.tsx | 3 +- .../teleport/src/Navigation/Navigation.tsx | 5 +- .../src/Navigation/ResourcesSection.test.tsx | 47 +++- .../src/Navigation/ResourcesSection.tsx | 200 ++++++++++++------ .../src/UnifiedResources/UnifiedResources.tsx | 10 +- web/packages/teleport/src/config.ts | 4 +- web/packages/teleport/src/features.tsx | 89 ++++---- web/packages/teleport/src/teleportContext.tsx | 32 ++- web/packages/teleport/src/types.ts | 13 ++ 9 files changed, 271 insertions(+), 132 deletions(-) diff --git a/web/packages/teleport/src/Main/Main.tsx b/web/packages/teleport/src/Main/Main.tsx index 6fd43d4ccf72..0fcebcf06d57 100644 --- a/web/packages/teleport/src/Main/Main.tsx +++ b/web/packages/teleport/src/Main/Main.tsx @@ -47,6 +47,7 @@ import { CatchError } from 'teleport/components/CatchError'; import { Redirect, Route, Switch } from 'teleport/components/Router'; import { InfoGuideSidePanel } from 'teleport/components/SlidingSidePanel/InfoGuideSidePanel'; import cfg from 'teleport/config'; +import { canShowFeature } from 'teleport/features'; import { FeaturesContextProvider, useFeatures } from 'teleport/FeaturesContext'; import { Navigation } from 'teleport/Navigation'; import { @@ -96,7 +97,7 @@ export function Main(props: MainProps) { () => props.features.filter( feature => - feature.hasAccess(featureFlags) && + canShowFeature(feature, featureFlags) && supportsCurrentScope(feature, scope) ), [featureFlags, props.features, scope] diff --git a/web/packages/teleport/src/Navigation/Navigation.tsx b/web/packages/teleport/src/Navigation/Navigation.tsx index a9f93da8574e..b070ba176e89 100644 --- a/web/packages/teleport/src/Navigation/Navigation.tsx +++ b/web/packages/teleport/src/Navigation/Navigation.tsx @@ -42,6 +42,7 @@ import { useFeatures } from 'teleport/FeaturesContext'; import type { TeleportFeature } from 'teleport/types'; import { useUser } from 'teleport/User/UserContext'; import useStickyClusterId from 'teleport/useStickyClusterId'; +import useTeleport from 'teleport/useTeleport'; import { CustomNavigationSubcategory, @@ -343,6 +344,7 @@ export function Navigation({ }: { showPoweredByLogo?: boolean; }) { + const ctx = useTeleport(); const features = useFeatures(); const location = useLocation(); const { clusterId } = useStickyClusterId(); @@ -408,8 +410,9 @@ export function Navigation({ preferences, updatePreferences, searchParams, + flags: ctx.getFeatureFlags(), }); - }, [clusterId, preferences, updatePreferences]); + }, [clusterId, preferences, updatePreferences, ctx]); const handleSetExpandedSection = useCallback( (section: NavigationSection) => { diff --git a/web/packages/teleport/src/Navigation/ResourcesSection.test.tsx b/web/packages/teleport/src/Navigation/ResourcesSection.test.tsx index f816e10eabbd..56e9bba65aad 100644 --- a/web/packages/teleport/src/Navigation/ResourcesSection.test.tsx +++ b/web/packages/teleport/src/Navigation/ResourcesSection.test.tsx @@ -16,23 +16,26 @@ * along with this program. If not, see . */ +import cfg from 'teleport/config'; +import { createTeleportContext, getAcl } from 'teleport/mocks/contexts'; import { getResourcesSection } from 'teleport/Navigation/ResourcesSection'; import { makeDefaultUserPreferences } from 'teleport/services/userPreferences/userPreferences'; describe('getResourcesSection', () => { + const fixed = ['All Resources', 'Pinned Resources']; + test('titles for subsections by kind are sorted', () => { + const ctx = createTeleportContext(); const navSections = getResourcesSection({ clusterId: 'cluster', preferences: makeDefaultUserPreferences(), updatePreferences: async () => {}, searchParams: new URLSearchParams(), + flags: ctx.getFeatureFlags(), }); const titles = navSections.subsections.map(s => s.title); - // First two sections are fixed. - const fixed = ['All Resources', 'Pinned Resources']; - // Kind filters that should be sorted alphabetically. const kindFilters = [ 'Applications', @@ -51,4 +54,42 @@ describe('getResourcesSection', () => { expect(titles).toEqual(expected); }); + + test('all kind shortcuts are shown when feature hiding is off', () => { + const ctx = createTeleportContext({ + customAcl: getAcl({ noAccess: true }), + }); + const navSections = getResourcesSection({ + clusterId: 'cluster', + preferences: makeDefaultUserPreferences(), + updatePreferences: async () => {}, + searchParams: new URLSearchParams(), + flags: ctx.getFeatureFlags(), + }); + + const titles = navSections.subsections.map(s => s.title); + expect(titles).toHaveLength(fixed.length + 7); + }); + + test('inaccessible kind shortcuts are hidden when feature hiding is on', () => { + const defaultFeatureHiding = cfg.entitlements.FeatureHiding.enabled; + cfg.entitlements.FeatureHiding.enabled = true; + try { + const ctx = createTeleportContext({ + customAcl: getAcl({ noAccess: true }), + }); + const navSections = getResourcesSection({ + clusterId: 'cluster', + preferences: makeDefaultUserPreferences(), + updatePreferences: async () => {}, + searchParams: new URLSearchParams(), + flags: ctx.getFeatureFlags(), + }); + + const titles = navSections.subsections.map(s => s.title); + expect(titles).toEqual(fixed); + } finally { + cfg.entitlements.FeatureHiding.enabled = defaultFeatureHiding; + } + }); }); diff --git a/web/packages/teleport/src/Navigation/ResourcesSection.tsx b/web/packages/teleport/src/Navigation/ResourcesSection.tsx index 8973bc71640a..139384b42517 100644 --- a/web/packages/teleport/src/Navigation/ResourcesSection.tsx +++ b/web/packages/teleport/src/Navigation/ResourcesSection.tsx @@ -31,8 +31,11 @@ import { import { encodeUrlQueryParams } from 'teleport/components/hooks/useUrlFiltering'; import { EncodeUrlQueryParamsProps } from 'teleport/components/hooks/useUrlFiltering/encodeUrlQueryParams'; import cfg from 'teleport/config'; +import { shouldHideInaccessibleFeatures } from 'teleport/features'; +import { FeatureFlags } from 'teleport/types'; import { useUser } from 'teleport/User/UserContext'; import useStickyClusterId from 'teleport/useStickyClusterId'; +import useTeleport from 'teleport/useTeleport'; import { CustomNavigationSubcategory, NavigationCategory } from './categories'; import { @@ -68,8 +71,36 @@ type GetSubsectionProps = { preferences: UserPreferences; updatePreferences: (preferences: Partial) => Promise; searchParams: URLSearchParams; + flags: FeatureFlags; }; +// hasAccessToResourceKind returns whether the user has RBAC access to the given +// resource kind. +function hasAccessToResourceKind( + kind: ResourceFilterKind, + flags: FeatureFlags +): boolean { + switch (kind) { + case 'app': + case 'mcp': + // MCP servers are app resources, so they share the same RBAC rule + return flags.applications; + case 'db': + return flags.databases; + case 'windows_desktop': + case 'linux_desktop': + return flags.desktops; + case 'kube_cluster': + return flags.kubernetes; + case 'git_server': + return flags.gitServers; + case 'node': + return flags.nodes; + default: + return true; + } +} + function encodeUrlQueryParamsWithTypedKinds( params: Omit & { kinds?: ResourceFilterKind[]; @@ -83,6 +114,7 @@ function getResourcesSubsections({ preferences, updatePreferences, searchParams, + flags, }: GetSubsectionProps): NavigationSubsection[] { const baseRoute = cfg.getUnifiedResourcesRoute(clusterId); @@ -168,7 +200,7 @@ function getResourcesSubsections({ pinnedOnly: false, }); - return [ + const baseSubsections: NavigationSubsection[] = [ { title: 'All Resources', icon: Icons.Server, @@ -203,85 +235,121 @@ function getResourcesSubsections({ currentKinds.length !== 1, onClick: () => setPinnedUserPreference(true), }, + ]; + + const filteredViews: { + kind: ResourceFilterKind; + subsection: NavigationSubsection; + }[] = [ { - title: getFilterKindName('app'), - icon: Icons.Application, - route: applicationsOnlyRoute, - searchableTags: ['resources', 'apps', 'applications'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('app'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'app', + subsection: { + title: getFilterKindName('app'), + icon: Icons.Application, + route: applicationsOnlyRoute, + searchableTags: ['resources', 'apps', 'applications'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('app'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('db'), - icon: Icons.Database, - route: databasesOnlyRoute, - searchableTags: ['resources', 'dbs', 'databases'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('db'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'db', + subsection: { + title: getFilterKindName('db'), + icon: Icons.Database, + route: databasesOnlyRoute, + searchableTags: ['resources', 'dbs', 'databases'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('db'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('windows_desktop'), - icon: Icons.Desktop, - route: desktopsOnlyRoute, - searchableTags: ['resources', 'desktops', 'rdp', 'windows'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => - isKindActive('windows_desktop') || isKindActive('linux_desktop'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'windows_desktop', + subsection: { + title: getFilterKindName('windows_desktop'), + icon: Icons.Desktop, + route: desktopsOnlyRoute, + searchableTags: ['resources', 'desktops', 'rdp', 'windows'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => + isKindActive('windows_desktop') || isKindActive('linux_desktop'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('git_server'), - icon: Icons.GitHub, - route: gitOnlyRoute, - searchableTags: ['resources', 'git', 'github', 'git servers'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('git_server'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'git_server', + subsection: { + title: getFilterKindName('git_server'), + icon: Icons.GitHub, + route: gitOnlyRoute, + searchableTags: ['resources', 'git', 'github', 'git servers'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('git_server'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('kube_cluster'), - icon: Icons.Kubernetes, - route: kubesOnlyRoute, - searchableTags: ['resources', 'k8s', 'kubes', 'kubernetes'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('kube_cluster'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'kube_cluster', + subsection: { + title: getFilterKindName('kube_cluster'), + icon: Icons.Kubernetes, + route: kubesOnlyRoute, + searchableTags: ['resources', 'k8s', 'kubes', 'kubernetes'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('kube_cluster'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('mcp'), - icon: Icons.ModelContextProtocol, - route: mcpOnlyRoute, - searchableTags: ['resources', 'mcp', 'mcp servers'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('mcp'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'mcp', + subsection: { + title: getFilterKindName('mcp'), + icon: Icons.ModelContextProtocol, + route: mcpOnlyRoute, + searchableTags: ['resources', 'mcp', 'mcp servers'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('mcp'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, { - title: getFilterKindName('node'), - icon: Icons.Server, - route: nodesOnlyRoute, - searchableTags: ['resources', 'servers', 'nodes', 'ssh resources'], - category: NavigationCategory.Resources, - exact: false, - customRouteMatchFn: () => isKindActive('node'), - onClick: () => setPinnedUserPreference(false), - subCategory: CustomNavigationSubcategory.FilteredViews, + kind: 'node', + subsection: { + title: getFilterKindName('node'), + icon: Icons.Server, + route: nodesOnlyRoute, + searchableTags: ['resources', 'servers', 'nodes', 'ssh resources'], + category: NavigationCategory.Resources, + exact: false, + customRouteMatchFn: () => isKindActive('node'), + onClick: () => setPinnedUserPreference(false), + subCategory: CustomNavigationSubcategory.FilteredViews, + }, }, ]; + + const hideInaccessible = shouldHideInaccessibleFeatures(cfg); + const visibleFilteredViews = filteredViews + .filter( + ({ kind }) => !hideInaccessible || hasAccessToResourceKind(kind, flags) + ) + .map(({ subsection }) => subsection); + + return [...baseSubsections, ...visibleFilteredViews]; } export function ResourcesSection({ @@ -303,6 +371,7 @@ export function ResourcesSection({ canToggleStickyMode: boolean; showPoweredByLogo: boolean; }) { + const ctx = useTeleport(); const { clusterId } = useStickyClusterId(); const { preferences, updatePreferences } = useUser(); const section: NavigationSection = { @@ -320,6 +389,7 @@ export function ResourcesSection({ preferences, updatePreferences, searchParams, + flags: ctx.getFeatureFlags(), }); const currentViewRoute = currentView?.route; diff --git a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx index 62108bb47563..46438c53f31c 100644 --- a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx +++ b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx @@ -60,6 +60,7 @@ import { import { ServersideSearchPanel } from 'teleport/components/ServersideSearchPanel'; import cfg from 'teleport/config'; import { SearchResource } from 'teleport/Discover/SelectResource'; +import { shouldHideInaccessibleFeatures } from 'teleport/features'; import { useNoMinWidth } from 'teleport/Main'; import { SamlAppActionProvider, @@ -113,7 +114,7 @@ function expandDesktopKinds(kinds?: string[]): string[] | undefined { } const getAvailableKindsWithAccess = (flags: FeatureFlags): FilterKind[] => { - return [ + const kinds: FilterKind[] = [ { kind: 'node', disabled: !flags.nodes, @@ -143,6 +144,13 @@ const getAvailableKindsWithAccess = (flags: FeatureFlags): FilterKind[] => { disabled: !flags.applications, }, ]; + + // When feature hiding is enabled, kinds the user can't access are removed from the filter entirely. + if (shouldHideInaccessibleFeatures(cfg)) { + return kinds.filter(kind => !kind.disabled); + } + + return kinds; }; export function ClusterResources({ diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 9b3f56e4a55b..d002e8c036e1 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -243,7 +243,9 @@ const cfg = { // If you have no other options, use the `isStripeManaged` config flag to determine if product used is Team. // EUB can be determined from a combination of existing config flags eg: `isUsageBasedBilling && !isStripeManaged`. isUsageBasedBilling: false, - hideInaccessibleFeatures: false, + get hideInaccessibleFeatures(): boolean { + return this.entitlements.FeatureHiding.enabled; + }, customTheme: '', isStripeManaged: false, hasQuestionnaire: false, diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index afd4e8f06128..b7b28b61bf2d 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -79,13 +79,23 @@ import { UnifiedResources } from './UnifiedResources'; import { Users } from './Users'; import { WorkloadIdentities } from './WorkloadIdentity/WorkloadIdentities'; -// to promote feature discoverability, most features should be visible in the navigation even if a user doesnt have access. -// However, there are some cases where hiding the feature is explicitly requested. Use this as a backdoor to hide the features that -// are usually "always visible" -export function shouldHideFromNavigation(cfg: Cfg) { +// shouldHideInaccessibleFeatures returns whether features the user lacks access to +// should be hidden from the navigation entirely, rather than shown for +// discoverability. This is the case for dashboard tenants, and when feature +// hiding is enabled in the license. +export function shouldHideInaccessibleFeatures(cfg: Cfg) { return cfg.isDashboard || cfg.hideInaccessibleFeatures; } +// canShowFeature returns whether the user can see a given feature based on their RBAC permissions, +// whether the feature is discoverable and whether featurehiding is enabled in the license. +export function canShowFeature(feature: TeleportFeature, flags: FeatureFlags) { + if (feature.hasAccess(flags)) { + return true; + } + return !!feature.discoverable && !shouldHideInaccessibleFeatures(cfg); +} + class AccessRequests implements TeleportFeature { category = NavigationCategory.IdentityGovernance; @@ -252,13 +262,10 @@ export class FeatureBots implements TeleportFeature { component: Bots, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - // if feature hiding is enabled, only show - // if the user has access - if (shouldHideFromNavigation(cfg)) { - return flags.listBots; - } - return true; + return flags.listBots; } navigationItem = { @@ -286,13 +293,10 @@ export class FeatureBotInstances implements TeleportFeature { component: BotInstances, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - // if feature hiding is enabled, only show - // if the user has access - if (shouldHideFromNavigation(cfg)) { - return flags.listBotInstances; - } - return true; + return flags.listBotInstances; } navigationItem = { @@ -328,13 +332,10 @@ export class FeatureInstances implements TeleportFeature { component: Instances, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - // if feature hiding is enabled, only show - // if the user has access - if (shouldHideFromNavigation(cfg)) { - return flags.listInstances || flags.listBotInstances; - } - return true; + return flags.listInstances || flags.listBotInstances; } navigationItem = { @@ -585,13 +586,10 @@ export class FeatureDiscover implements TeleportFeature { export class FeatureIntegrations implements TeleportFeature { category = NavigationCategory.ZeroTrustAccess; + discoverable = true; + hasAccess(flags: FeatureFlags) { - // if feature hiding is enabled, only show - // if the user has access - if (shouldHideFromNavigation(cfg)) { - return flags.integrations; - } - return true; + return flags.integrations; } route = { @@ -626,11 +624,10 @@ export class FeatureIntegrationEnroll implements TeleportFeature { component: IntegrationEnroll, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - if (shouldHideFromNavigation(cfg)) { - return flags.enrollIntegrations; - } - return true; + return flags.enrollIntegrations; } navigationItem = { @@ -659,16 +656,14 @@ export class FeatureManagedUpdates implements TeleportFeature { component: ManagedUpdates, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - const canViewPage = + return ( flags.readAutoUpdateConfig || flags.readAutoUpdateVersion || - flags.readAutoUpdateAgentRollout; - - if (shouldHideFromNavigation(cfg)) { - return canViewPage; - } - return true; + flags.readAutoUpdateAgentRollout + ); } navigationItem = { @@ -803,13 +798,10 @@ export class FeatureWorkloadIdentity implements TeleportFeature { component: WorkloadIdentities, }; + discoverable = true; + hasAccess(flags: FeatureFlags): boolean { - // if feature hiding is enabled, only show - // if the user has access - if (shouldHideFromNavigation(cfg)) { - return flags.listWorkloadIdentities; - } - return true; + return flags.listWorkloadIdentities; } navigationItem = { title: NavTitle.WorkloadIdentity, @@ -830,11 +822,10 @@ class FeatureDeviceTrust implements TeleportFeature { component: DeviceTrustLocked, }; + discoverable = true; + hasAccess(flags: FeatureFlags) { - if (shouldHideFromNavigation(cfg)) { - return flags.deviceTrust; - } - return true; + return flags.deviceTrust; } navigationItem = { diff --git a/web/packages/teleport/src/teleportContext.tsx b/web/packages/teleport/src/teleportContext.tsx index 13c65cb0d84d..9f72f222023e 100644 --- a/web/packages/teleport/src/teleportContext.tsx +++ b/web/packages/teleport/src/teleportContext.tsx @@ -138,19 +138,16 @@ class TeleportContext implements types.Context { } function hasAccessRequestsAccess() { - // If feature hiding is enabled in the license, only allow access to access requests if the user has permission to access them, either by - // having list access, requestable roles, or allowed search_as_roles. - if (cfg.hideInaccessibleFeatures) { - return !!( - userContext.getReviewRequests() || - userContext.getAccessRequestAccess().list || - userContext.getRequestableRoles().length || - userContext.getAllowedSearchAsRoles().length - ); + if (cfg.isDashboard) { + return false; } - // Return true if this isn't a Cloud dashboard cluster. - return !cfg.isDashboard; + return !!( + userContext.getReviewRequests() || + userContext.getAccessRequestAccess().list || + userContext.getRequestableRoles().length || + userContext.getAllowedSearchAsRoles().length + ); } function hasAccessMonitoringAccess() { @@ -212,6 +209,16 @@ class TeleportContext implements types.Context { userContext.getLockAccess().create && userContext.getLockAccess().edit, // Presumably because this is an upsert operation so needs both create and edit permissions removeLocks: userContext.getLockAccess().remove, accessMonitoring: hasAccessMonitoringAccess(), + accessAutomations: + userContext.getAccessMonitoringRuleAccess().list && + userContext.getAccessMonitoringRuleAccess().read, + accessLists: + userContext.getAccessListAccess().list && + userContext.getAccessListAccess().read, + addAccessList: + userContext.getAccessListAccess().create && + userContext.getAccessListAccess().list && + userContext.getAccessListAccess().read, accessGraph: userContext.getAccessGraphAccess().list, accessGraphIntegrations: hasAccessGraphIntegrationsAccess(), createTokens: userContext.getTokenAccess().create, @@ -276,6 +283,9 @@ export const disabledFeatureFlags: types.FeatureFlags = { addLocks: false, removeLocks: false, accessMonitoring: false, + accessAutomations: false, + accessLists: false, + addAccessList: false, accessGraph: false, accessGraphIntegrations: false, externalAuditStorage: false, diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts index 74351c5c0478..2ce4a0133984 100644 --- a/web/packages/teleport/src/types.ts +++ b/web/packages/teleport/src/types.ts @@ -134,7 +134,17 @@ export interface TeleportFeature { category?: NavigationCategory; /** standalone is whether this feature has no subsections */ standalone?: boolean; + /** + * hasAccess returns whether the user has RBAC access to this feature. + */ hasAccess(flags: FeatureFlags): boolean; + /** + * discoverable, when true, keeps this feature visible in the navigation (and + * routable) even if the user doesn't have access to it. This is ignored when feature + * hiding is enabled (or on dashboard tenants), where inaccessible features are + * always hidden. Defaults to false. + */ + discoverable?: boolean; // logoOnlyTopbar is used to optionally hide the elements in the topbar from view except for the logo. // The features that use this are supposed to be "full page" features where navigation // is either blocked, or done explicitly through the page (such as device trust authorize) @@ -219,6 +229,9 @@ export interface FeatureFlags { createTokens: boolean; listTokens: boolean; accessMonitoring: boolean; + accessAutomations: boolean; + accessLists: boolean; + addAccessList: boolean; accessGraph: boolean; accessGraphIntegrations: boolean; externalAuditStorage: boolean; From b624d1a09c28bb0674842f2bd4c4ec987b18b83b Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 31 Jul 2026 09:30:22 +0100 Subject: [PATCH 075/347] MWI: Scope Namespace Bot Instances (1/2) - Storage and API (#68390) * MWI: Scope Namespace Bot Instances (1/2) - Storage and API (#68390) * Address review feedback Squash of: - Tighten validation of FilterBotScope - Address review feedback --- .../v1/bot_instance_service_protohybrid.pb.go | 89 +- .../v1/bot_instance_service_protoopaque.pb.go | 71 +- .../machineid/v1/bot_instance_service.proto | 15 + lib/auth/authclient/api.go | 6 +- lib/auth/authtest/authtest.go | 2 +- lib/auth/bot.go | 53 +- lib/auth/bot_test.go | 6 +- .../machineidv1/bot_instance_service.go | 97 ++- .../machineidv1/bot_instance_service_test.go | 30 +- .../machineid/machineidv1/machineidv1_test.go | 63 +- lib/cache/bot_instance.go | 34 +- lib/cache/bot_instance_test.go | 816 ++++++++---------- lib/cache/inventory/inventory_cache_test.go | 6 +- lib/join/join_bound_keypair_test.go | 5 +- lib/service/service.go | 2 +- lib/services/bot_instance.go | 59 +- lib/services/local/bot_instance.go | 146 +++- lib/services/local/bot_instance_test.go | 152 +++- lib/services/local/events.go | 63 +- .../local/generic/scopeaware_wrapper.go | 98 ++- .../local/generic/scopeaware_wrapper_test.go | 176 ++++ lib/web/machineid_test.go | 4 +- 22 files changed, 1359 insertions(+), 634 deletions(-) diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protohybrid.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protohybrid.pb.go index 025e96731ae9..5df7ba5a0fd5 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protohybrid.pb.go @@ -44,7 +44,11 @@ type GetBotInstanceRequest struct { // The name of the bot associated with the instance. BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3" json:"bot_name,omitempty"` // The unique identifier of the bot instance to retrieve. - InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string `protobuf:"bytes,3,opt,name=bot_scope,json=botScope,proto3" json:"bot_scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -88,6 +92,13 @@ func (x *GetBotInstanceRequest) GetInstanceId() string { return "" } +func (x *GetBotInstanceRequest) GetBotScope() string { + if x != nil { + return x.BotScope + } + return "" +} + func (x *GetBotInstanceRequest) SetBotName(v string) { x.BotName = v } @@ -96,6 +107,10 @@ func (x *GetBotInstanceRequest) SetInstanceId(v string) { x.InstanceId = v } +func (x *GetBotInstanceRequest) SetBotScope(v string) { + x.BotScope = v +} + type GetBotInstanceRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -103,6 +118,10 @@ type GetBotInstanceRequest_builder struct { BotName string // The unique identifier of the bot instance to retrieve. InstanceId string + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string } func (b0 GetBotInstanceRequest_builder) Build() *GetBotInstanceRequest { @@ -111,6 +130,7 @@ func (b0 GetBotInstanceRequest_builder) Build() *GetBotInstanceRequest { _, _ = b, x x.BotName = b.BotName x.InstanceId = b.InstanceId + x.BotScope = b.BotScope return m0 } @@ -488,7 +508,11 @@ type DeleteBotInstanceRequest struct { // The name of the BotInstance to delete. BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3" json:"bot_name,omitempty"` // The unique identifier of the bot instance to delete. - InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string `protobuf:"bytes,3,opt,name=bot_scope,json=botScope,proto3" json:"bot_scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -532,6 +556,13 @@ func (x *DeleteBotInstanceRequest) GetInstanceId() string { return "" } +func (x *DeleteBotInstanceRequest) GetBotScope() string { + if x != nil { + return x.BotScope + } + return "" +} + func (x *DeleteBotInstanceRequest) SetBotName(v string) { x.BotName = v } @@ -540,6 +571,10 @@ func (x *DeleteBotInstanceRequest) SetInstanceId(v string) { x.InstanceId = v } +func (x *DeleteBotInstanceRequest) SetBotScope(v string) { + x.BotScope = v +} + type DeleteBotInstanceRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -547,6 +582,10 @@ type DeleteBotInstanceRequest_builder struct { BotName string // The unique identifier of the bot instance to delete. InstanceId string + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string } func (b0 DeleteBotInstanceRequest_builder) Build() *DeleteBotInstanceRequest { @@ -555,6 +594,7 @@ func (b0 DeleteBotInstanceRequest_builder) Build() *DeleteBotInstanceRequest { _, _ = b, x x.BotName = b.BotName x.InstanceId = b.InstanceId + x.BotScope = b.BotScope return m0 } @@ -699,7 +739,14 @@ type ListBotInstancesV2Request_Filters struct { // match against supported fields. SearchTerm string `protobuf:"bytes,2,opt,name=search_term,json=searchTerm,proto3" json:"search_term,omitempty"` // A Teleport predicate language query used to filter the results. - Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + // The scope of the bot to list BotInstances for. Only valid alongside a + // non-empty bot_name, which it qualifies to uniquely identify a scoped bot + // (a scoped bot is identified by the pair (scope, name)); setting bot_scope + // without bot_name is an error. An empty bot_scope with a non-empty bot_name + // selects the unscoped bot with that name. This is not a standalone scope + // filter for listing all of a scope's instances. + BotScope string `protobuf:"bytes,4,opt,name=bot_scope,json=botScope,proto3" json:"bot_scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -750,6 +797,13 @@ func (x *ListBotInstancesV2Request_Filters) GetQuery() string { return "" } +func (x *ListBotInstancesV2Request_Filters) GetBotScope() string { + if x != nil { + return x.BotScope + } + return "" +} + func (x *ListBotInstancesV2Request_Filters) SetBotName(v string) { x.BotName = v } @@ -762,6 +816,10 @@ func (x *ListBotInstancesV2Request_Filters) SetQuery(v string) { x.Query = v } +func (x *ListBotInstancesV2Request_Filters) SetBotScope(v string) { + x.BotScope = v +} + type ListBotInstancesV2Request_Filters_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -773,6 +831,13 @@ type ListBotInstancesV2Request_Filters_builder struct { SearchTerm string // A Teleport predicate language query used to filter the results. Query string + // The scope of the bot to list BotInstances for. Only valid alongside a + // non-empty bot_name, which it qualifies to uniquely identify a scoped bot + // (a scoped bot is identified by the pair (scope, name)); setting bot_scope + // without bot_name is an error. An empty bot_scope with a non-empty bot_name + // selects the unscoped bot with that name. This is not a standalone scope + // filter for listing all of a scope's instances. + BotScope string } func (b0 ListBotInstancesV2Request_Filters_builder) Build() *ListBotInstancesV2Request_Filters { @@ -782,6 +847,7 @@ func (b0 ListBotInstancesV2Request_Filters_builder) Build() *ListBotInstancesV2R x.BotName = b.BotName x.SearchTerm = b.SearchTerm x.Query = b.Query + x.BotScope = b.BotScope return m0 } @@ -789,18 +855,19 @@ var File_teleport_machineid_v1_bot_instance_service_proto protoreflect.FileDescr const file_teleport_machineid_v1_bot_instance_service_proto_rawDesc = "" + "\n" + - "0teleport/machineid/v1/bot_instance_service.proto\x12\x15teleport.machineid.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a!teleport/legacy/types/types.proto\x1a(teleport/machineid/v1/bot_instance.proto\"S\n" + + "0teleport/machineid/v1/bot_instance_service.proto\x12\x15teleport.machineid.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a!teleport/legacy/types/types.proto\x1a(teleport/machineid/v1/bot_instance.proto\"p\n" + "\x15GetBotInstanceRequest\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vinstance_id\x18\x02 \x01(\tR\n" + - "instanceId\"\xce\x01\n" + + "instanceId\x12\x1b\n" + + "\tbot_scope\x18\x03 \x01(\tR\bbotScope\"\xce\x01\n" + "\x17ListBotInstancesRequest\x12&\n" + "\x0ffilter_bot_name\x18\x01 \x01(\tR\rfilterBotName\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" + "\n" + "page_token\x18\x03 \x01(\tR\tpageToken\x12,\n" + "\x12filter_search_term\x18\x04 \x01(\tR\x10filterSearchTerm\x12!\n" + - "\x04sort\x18\x05 \x01(\v2\r.types.SortByR\x04sort\"\xc2\x02\n" + + "\x04sort\x18\x05 \x01(\v2\r.types.SortByR\x04sort\"\xdf\x02\n" + "\x19ListBotInstancesV2Request\x12\x1b\n" + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + "\n" + @@ -808,19 +875,21 @@ const file_teleport_machineid_v1_bot_instance_service_proto_rawDesc = "" + "\n" + "sort_field\x18\x03 \x01(\tR\tsortField\x12\x1b\n" + "\tsort_desc\x18\x04 \x01(\bR\bsortDesc\x12P\n" + - "\x06filter\x18\x05 \x01(\v28.teleport.machineid.v1.ListBotInstancesV2Request.FiltersR\x06filter\x1a[\n" + + "\x06filter\x18\x05 \x01(\v28.teleport.machineid.v1.ListBotInstancesV2Request.FiltersR\x06filter\x1ax\n" + "\aFilters\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vsearch_term\x18\x02 \x01(\tR\n" + "searchTerm\x12\x14\n" + - "\x05query\x18\x03 \x01(\tR\x05query\"\x8b\x01\n" + + "\x05query\x18\x03 \x01(\tR\x05query\x12\x1b\n" + + "\tbot_scope\x18\x04 \x01(\tR\bbotScope\"\x8b\x01\n" + "\x18ListBotInstancesResponse\x12G\n" + "\rbot_instances\x18\x01 \x03(\v2\".teleport.machineid.v1.BotInstanceR\fbotInstances\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"V\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"s\n" + "\x18DeleteBotInstanceRequest\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vinstance_id\x18\x02 \x01(\tR\n" + - "instanceId\"\xc1\x01\n" + + "instanceId\x12\x1b\n" + + "\tbot_scope\x18\x03 \x01(\tR\bbotScope\"\xc1\x01\n" + "\x16SubmitHeartbeatRequest\x12O\n" + "\theartbeat\x18\x01 \x01(\v21.teleport.machineid.v1.BotInstanceStatusHeartbeatR\theartbeat\x12V\n" + "\x0eservice_health\x18\x02 \x03(\v2/.teleport.machineid.v1.BotInstanceServiceHealthR\rserviceHealth\"\x19\n" + diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protoopaque.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protoopaque.pb.go index 907cf46f6acd..380e2e6cae77 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance_service_protoopaque.pb.go @@ -43,6 +43,7 @@ type GetBotInstanceRequest struct { state protoimpl.MessageState `protogen:"opaque.v1"` xxx_hidden_BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3"` xxx_hidden_InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3"` + xxx_hidden_BotScope string `protobuf:"bytes,3,opt,name=bot_scope,json=botScope,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -86,6 +87,13 @@ func (x *GetBotInstanceRequest) GetInstanceId() string { return "" } +func (x *GetBotInstanceRequest) GetBotScope() string { + if x != nil { + return x.xxx_hidden_BotScope + } + return "" +} + func (x *GetBotInstanceRequest) SetBotName(v string) { x.xxx_hidden_BotName = v } @@ -94,6 +102,10 @@ func (x *GetBotInstanceRequest) SetInstanceId(v string) { x.xxx_hidden_InstanceId = v } +func (x *GetBotInstanceRequest) SetBotScope(v string) { + x.xxx_hidden_BotScope = v +} + type GetBotInstanceRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -101,6 +113,10 @@ type GetBotInstanceRequest_builder struct { BotName string // The unique identifier of the bot instance to retrieve. InstanceId string + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string } func (b0 GetBotInstanceRequest_builder) Build() *GetBotInstanceRequest { @@ -109,6 +125,7 @@ func (b0 GetBotInstanceRequest_builder) Build() *GetBotInstanceRequest { _, _ = b, x x.xxx_hidden_BotName = b.BotName x.xxx_hidden_InstanceId = b.InstanceId + x.xxx_hidden_BotScope = b.BotScope return m0 } @@ -467,6 +484,7 @@ type DeleteBotInstanceRequest struct { state protoimpl.MessageState `protogen:"opaque.v1"` xxx_hidden_BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3"` xxx_hidden_InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3"` + xxx_hidden_BotScope string `protobuf:"bytes,3,opt,name=bot_scope,json=botScope,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -510,6 +528,13 @@ func (x *DeleteBotInstanceRequest) GetInstanceId() string { return "" } +func (x *DeleteBotInstanceRequest) GetBotScope() string { + if x != nil { + return x.xxx_hidden_BotScope + } + return "" +} + func (x *DeleteBotInstanceRequest) SetBotName(v string) { x.xxx_hidden_BotName = v } @@ -518,6 +543,10 @@ func (x *DeleteBotInstanceRequest) SetInstanceId(v string) { x.xxx_hidden_InstanceId = v } +func (x *DeleteBotInstanceRequest) SetBotScope(v string) { + x.xxx_hidden_BotScope = v +} + type DeleteBotInstanceRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -525,6 +554,10 @@ type DeleteBotInstanceRequest_builder struct { BotName string // The unique identifier of the bot instance to delete. InstanceId string + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + BotScope string } func (b0 DeleteBotInstanceRequest_builder) Build() *DeleteBotInstanceRequest { @@ -533,6 +566,7 @@ func (b0 DeleteBotInstanceRequest_builder) Build() *DeleteBotInstanceRequest { _, _ = b, x x.xxx_hidden_BotName = b.BotName x.xxx_hidden_InstanceId = b.InstanceId + x.xxx_hidden_BotScope = b.BotScope return m0 } @@ -673,6 +707,7 @@ type ListBotInstancesV2Request_Filters struct { xxx_hidden_BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3"` xxx_hidden_SearchTerm string `protobuf:"bytes,2,opt,name=search_term,json=searchTerm,proto3"` xxx_hidden_Query string `protobuf:"bytes,3,opt,name=query,proto3"` + xxx_hidden_BotScope string `protobuf:"bytes,4,opt,name=bot_scope,json=botScope,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -723,6 +758,13 @@ func (x *ListBotInstancesV2Request_Filters) GetQuery() string { return "" } +func (x *ListBotInstancesV2Request_Filters) GetBotScope() string { + if x != nil { + return x.xxx_hidden_BotScope + } + return "" +} + func (x *ListBotInstancesV2Request_Filters) SetBotName(v string) { x.xxx_hidden_BotName = v } @@ -735,6 +777,10 @@ func (x *ListBotInstancesV2Request_Filters) SetQuery(v string) { x.xxx_hidden_Query = v } +func (x *ListBotInstancesV2Request_Filters) SetBotScope(v string) { + x.xxx_hidden_BotScope = v +} + type ListBotInstancesV2Request_Filters_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -746,6 +792,13 @@ type ListBotInstancesV2Request_Filters_builder struct { SearchTerm string // A Teleport predicate language query used to filter the results. Query string + // The scope of the bot to list BotInstances for. Only valid alongside a + // non-empty bot_name, which it qualifies to uniquely identify a scoped bot + // (a scoped bot is identified by the pair (scope, name)); setting bot_scope + // without bot_name is an error. An empty bot_scope with a non-empty bot_name + // selects the unscoped bot with that name. This is not a standalone scope + // filter for listing all of a scope's instances. + BotScope string } func (b0 ListBotInstancesV2Request_Filters_builder) Build() *ListBotInstancesV2Request_Filters { @@ -755,6 +808,7 @@ func (b0 ListBotInstancesV2Request_Filters_builder) Build() *ListBotInstancesV2R x.xxx_hidden_BotName = b.BotName x.xxx_hidden_SearchTerm = b.SearchTerm x.xxx_hidden_Query = b.Query + x.xxx_hidden_BotScope = b.BotScope return m0 } @@ -762,18 +816,19 @@ var File_teleport_machineid_v1_bot_instance_service_proto protoreflect.FileDescr const file_teleport_machineid_v1_bot_instance_service_proto_rawDesc = "" + "\n" + - "0teleport/machineid/v1/bot_instance_service.proto\x12\x15teleport.machineid.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a!teleport/legacy/types/types.proto\x1a(teleport/machineid/v1/bot_instance.proto\"S\n" + + "0teleport/machineid/v1/bot_instance_service.proto\x12\x15teleport.machineid.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a!teleport/legacy/types/types.proto\x1a(teleport/machineid/v1/bot_instance.proto\"p\n" + "\x15GetBotInstanceRequest\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vinstance_id\x18\x02 \x01(\tR\n" + - "instanceId\"\xce\x01\n" + + "instanceId\x12\x1b\n" + + "\tbot_scope\x18\x03 \x01(\tR\bbotScope\"\xce\x01\n" + "\x17ListBotInstancesRequest\x12&\n" + "\x0ffilter_bot_name\x18\x01 \x01(\tR\rfilterBotName\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" + "\n" + "page_token\x18\x03 \x01(\tR\tpageToken\x12,\n" + "\x12filter_search_term\x18\x04 \x01(\tR\x10filterSearchTerm\x12!\n" + - "\x04sort\x18\x05 \x01(\v2\r.types.SortByR\x04sort\"\xc2\x02\n" + + "\x04sort\x18\x05 \x01(\v2\r.types.SortByR\x04sort\"\xdf\x02\n" + "\x19ListBotInstancesV2Request\x12\x1b\n" + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + "\n" + @@ -781,19 +836,21 @@ const file_teleport_machineid_v1_bot_instance_service_proto_rawDesc = "" + "\n" + "sort_field\x18\x03 \x01(\tR\tsortField\x12\x1b\n" + "\tsort_desc\x18\x04 \x01(\bR\bsortDesc\x12P\n" + - "\x06filter\x18\x05 \x01(\v28.teleport.machineid.v1.ListBotInstancesV2Request.FiltersR\x06filter\x1a[\n" + + "\x06filter\x18\x05 \x01(\v28.teleport.machineid.v1.ListBotInstancesV2Request.FiltersR\x06filter\x1ax\n" + "\aFilters\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vsearch_term\x18\x02 \x01(\tR\n" + "searchTerm\x12\x14\n" + - "\x05query\x18\x03 \x01(\tR\x05query\"\x8b\x01\n" + + "\x05query\x18\x03 \x01(\tR\x05query\x12\x1b\n" + + "\tbot_scope\x18\x04 \x01(\tR\bbotScope\"\x8b\x01\n" + "\x18ListBotInstancesResponse\x12G\n" + "\rbot_instances\x18\x01 \x03(\v2\".teleport.machineid.v1.BotInstanceR\fbotInstances\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"V\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"s\n" + "\x18DeleteBotInstanceRequest\x12\x19\n" + "\bbot_name\x18\x01 \x01(\tR\abotName\x12\x1f\n" + "\vinstance_id\x18\x02 \x01(\tR\n" + - "instanceId\"\xc1\x01\n" + + "instanceId\x12\x1b\n" + + "\tbot_scope\x18\x03 \x01(\tR\bbotScope\"\xc1\x01\n" + "\x16SubmitHeartbeatRequest\x12O\n" + "\theartbeat\x18\x01 \x01(\v21.teleport.machineid.v1.BotInstanceStatusHeartbeatR\theartbeat\x12V\n" + "\x0eservice_health\x18\x02 \x03(\v2/.teleport.machineid.v1.BotInstanceServiceHealthR\rserviceHealth\"\x19\n" + diff --git a/api/proto/teleport/machineid/v1/bot_instance_service.proto b/api/proto/teleport/machineid/v1/bot_instance_service.proto index bc3469f895ab..ec048872edb6 100644 --- a/api/proto/teleport/machineid/v1/bot_instance_service.proto +++ b/api/proto/teleport/machineid/v1/bot_instance_service.proto @@ -28,6 +28,10 @@ message GetBotInstanceRequest { string bot_name = 1; // The unique identifier of the bot instance to retrieve. string instance_id = 2; + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + string bot_scope = 3; } // Request for ListBotInstances. @@ -80,6 +84,13 @@ message ListBotInstancesV2Request { string search_term = 2; // A Teleport predicate language query used to filter the results. string query = 3; + // The scope of the bot to list BotInstances for. Only valid alongside a + // non-empty bot_name, which it qualifies to uniquely identify a scoped bot + // (a scoped bot is identified by the pair (scope, name)); setting bot_scope + // without bot_name is an error. An empty bot_scope with a non-empty bot_name + // selects the unscoped bot with that name. This is not a standalone scope + // filter for listing all of a scope's instances. + string bot_scope = 4; } } @@ -98,6 +109,10 @@ message DeleteBotInstanceRequest { string bot_name = 1; // The unique identifier of the bot instance to delete. string instance_id = 2; + // The scope of the bot associated with the instance. Combined with bot_name + // to uniquely identify a scoped bot. Leave empty if the associated bot is + // unscoped. + string bot_scope = 3; } // The request for SubmitHeartbeat. diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index 0fb32505d5aa..e5dcba853dd6 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -1556,8 +1556,10 @@ type Cache interface { // ListRelayServers returns a paginated list of relay server heartbeats. ListRelayServers(ctx context.Context, pageSize int, pageToken string) (_ []*presencev1.RelayServer, nextPageToken string, _ error) - // GetBotInstance returns the specified BotInstance resource. - GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) + // GetBotInstance returns the specified BotInstance resource. A bot is + // identified by the request's (bot_scope, bot_name); bot_scope is empty + // for instances of unscoped bots. + GetBotInstance(ctx context.Context, req *machineidv1.GetBotInstanceRequest) (*machineidv1.BotInstance, error) // ListBotInstances returns a page of BotInstance resources. ListBotInstances(ctx context.Context, pageSize int, lastToken string, options *services.ListBotInstancesRequestOptions) ([]*machineidv1.BotInstance, string, error) diff --git a/lib/auth/authtest/authtest.go b/lib/auth/authtest/authtest.go index 19a1b8ca631c..682bda8f6f0c 100644 --- a/lib/auth/authtest/authtest.go +++ b/lib/auth/authtest/authtest.go @@ -671,7 +671,7 @@ func InitAuthCache(p AuthCacheParams) error { PrimaryCache: c, Events: p.AuthServer.Services, Inventory: p.AuthServer.Services, - BotInstanceBackend: p.AuthServer.Services, + BotInstanceBackend: p.AuthServer.Services.BotInstance, }) if err != nil { return trace.Wrap(err) diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 81df89c44b3b..0f65369eb7d3 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -41,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshutils" logutils "github.com/gravitational/teleport/lib/utils/log" @@ -329,7 +330,11 @@ func (a *Server) updateBotInstance( var instanceGeneration int32 instanceNotFound := false if botInstanceID != "" { - existingInstance, err := a.BotInstance.GetBotInstance(ctx, botName, botInstanceID) + existingInstance, err := a.BotInstance.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ + BotScope: scope, + BotName: botName, + InstanceId: botInstanceID, + }.Build()) if trace.IsNotFound(err) { instanceNotFound = true } else if err != nil { @@ -489,32 +494,36 @@ func (a *Server) updateBotInstance( } } - _, err := a.BotInstance.PatchBotInstance(ctx, botName, botInstanceID, func(bi *machineidv1pb.BotInstance) (*machineidv1pb.BotInstance, error) { - if !bi.HasStatus() { - bi.SetStatus(&machineidv1pb.BotInstanceStatus{}) - } + _, err := a.BotInstance.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Scope: scope, Name: botName}, + InstanceID: botInstanceID, + UpdateFn: func(bi *machineidv1pb.BotInstance) (*machineidv1pb.BotInstance, error) { + if !bi.HasStatus() { + bi.SetStatus(&machineidv1pb.BotInstanceStatus{}) + } - // Update the record's expiration timestamp based on the request TTL - // plus an expiry margin. - bi.GetMetadata().SetExpires(timestamppb.New(a.GetClock().Now().Add(req.TTL + machineidv1.ExpiryMargin))) + // Update the record's expiration timestamp based on the request TTL + // plus an expiry margin. + bi.GetMetadata().SetExpires(timestamppb.New(a.GetClock().Now().Add(req.TTL + machineidv1.ExpiryMargin))) - // If we're at or above the limit, remove enough of the front elements - // to make room for the new one at the end. - if len(bi.GetStatus().GetLatestAuthentications()) >= machineidv1.AuthenticationHistoryLimit { - toRemove := len(bi.GetStatus().GetLatestAuthentications()) - machineidv1.AuthenticationHistoryLimit + 1 - bi.GetStatus().SetLatestAuthentications(bi.GetStatus().GetLatestAuthentications()[toRemove:]) - } + // If we're at or above the limit, remove enough of the front elements + // to make room for the new one at the end. + if len(bi.GetStatus().GetLatestAuthentications()) >= machineidv1.AuthenticationHistoryLimit { + toRemove := len(bi.GetStatus().GetLatestAuthentications()) - machineidv1.AuthenticationHistoryLimit + 1 + bi.GetStatus().SetLatestAuthentications(bi.GetStatus().GetLatestAuthentications()[toRemove:]) + } - // An initial auth record should have been added during initial join, - // but if not, add it now. - if !bi.GetStatus().HasInitialAuthentication() { - log.WarnContext(ctx, "bot instance is missing its initial authentication record, a new one will be added") - bi.GetStatus().SetInitialAuthentication(authRecord) - } + // An initial auth record should have been added during initial join, + // but if not, add it now. + if !bi.GetStatus().HasInitialAuthentication() { + log.WarnContext(ctx, "bot instance is missing its initial authentication record, a new one will be added") + bi.GetStatus().SetInitialAuthentication(authRecord) + } - bi.GetStatus().SetLatestAuthentications(append(bi.GetStatus().GetLatestAuthentications(), authRecord)) + bi.GetStatus().SetLatestAuthentications(append(bi.GetStatus().GetLatestAuthentications(), authRecord)) - return bi, nil + return bi, nil + }, }) return trace.Wrap(err) diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 8655b661af24..08edaa7fd7f3 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -185,7 +185,7 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { // Renew the cert a bunch of times. for i := range 10 { // Ensure the state of the bot instance before renewal is sane. - bi, err := srv.Auth().BotInstance.GetBotInstance(ctx, initialIdent.BotName, initialIdent.BotInstanceID) + bi, err := srv.Auth().BotInstance.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{BotScope: "", BotName: initialIdent.BotName, InstanceId: initialIdent.BotInstanceID}.Build()) require.NoError(t, err) // There should always be at least 1 entry as the initial join is @@ -221,7 +221,7 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { require.Equal(t, uint64(i+2), renewedIdent.Generation) // Ensure the bot instance after renewal is sane. - bi, err = srv.Auth().BotInstance.GetBotInstance(ctx, initialIdent.BotName, initialIdent.BotInstanceID) + bi, err = srv.Auth().BotInstance.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{BotScope: "", BotName: initialIdent.BotName, InstanceId: initialIdent.BotInstanceID}.Build()) require.NoError(t, err) require.Len(t, bi.GetStatus().GetLatestAuthentications(), min(i+2, machineidv1.AuthenticationHistoryLimit)) @@ -432,7 +432,7 @@ func TestRegisterBotInstance(t *testing.T) { require.NotEmpty(t, ident.BotInstanceID) // The instance ID should match a bot instance record. - botInstance, err := srv.Auth().BotInstance.GetBotInstance(ctx, ident.BotName, ident.BotInstanceID) + botInstance, err := srv.Auth().BotInstance.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{BotScope: "", BotName: ident.BotName, InstanceId: ident.BotInstanceID}.Build()) require.NoError(t, err) require.Equal(t, ident.BotName, botInstance.GetSpec().GetBotName()) diff --git a/lib/auth/machineid/machineidv1/bot_instance_service.go b/lib/auth/machineid/machineidv1/bot_instance_service.go index 585d696f2d72..5d05c29f9078 100644 --- a/lib/auth/machineid/machineidv1/bot_instance_service.go +++ b/lib/auth/machineid/machineidv1/bot_instance_service.go @@ -33,6 +33,7 @@ import ( pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" logutils "github.com/gravitational/teleport/lib/utils/log" ) @@ -58,8 +59,10 @@ const ( // BotInstancesCache is the subset of the cached resources that the Service queries. type BotInstancesCache interface { - // GetBotInstance returns the specified BotInstance resource. - GetBotInstance(ctx context.Context, botName, instanceID string) (*pb.BotInstance, error) + // GetBotInstance returns the specified BotInstance resource. A bot is + // identified by the request's (bot_scope, bot_name); bot_scope is empty + // for instances of unscoped bots. + GetBotInstance(ctx context.Context, req *pb.GetBotInstanceRequest) (*pb.BotInstance, error) // ListBotInstances returns a page of BotInstance resources. ListBotInstances(ctx context.Context, pageSize int, lastToken string, options *services.ListBotInstancesRequestOptions) ([]*pb.BotInstance, string, error) @@ -129,7 +132,11 @@ func (b *BotInstanceService) DeleteBotInstance(ctx context.Context, req *pb.Dele return nil, trace.Wrap(err) } - instance, err := b.backend.GetBotInstance(ctx, req.GetBotName(), req.GetInstanceId()) + instance, err := b.backend.GetBotInstance(ctx, pb.GetBotInstanceRequest_builder{ + BotScope: req.GetBotScope(), + BotName: req.GetBotName(), + InstanceId: req.GetInstanceId(), + }.Build()) if err != nil { return nil, trace.Wrap(err) } @@ -157,9 +164,7 @@ func (b *BotInstanceService) DeleteBotInstance(ctx context.Context, req *pb.Dele } } - if err := b.backend.DeleteBotInstance( - ctx, instance.GetSpec().GetBotName(), instance.GetSpec().GetInstanceId(), - ); err != nil { + if err := b.backend.DeleteBotInstance(ctx, req); err != nil { return nil, trace.Wrap(err) } @@ -182,7 +187,7 @@ func (b *BotInstanceService) GetBotInstance(ctx context.Context, req *pb.GetBotI return nil, trace.Wrap(err) } - res, err := b.cache.GetBotInstance(ctx, req.GetBotName(), req.GetInstanceId()) + res, err := b.cache.GetBotInstance(ctx, req) if err != nil { return nil, trace.Wrap(err) } @@ -241,6 +246,21 @@ func (b *BotInstanceService) ListBotInstancesV2(ctx context.Context, req *pb.Lis return nil, trace.Wrap(err) } + if filterBotScope := req.GetFilter().GetBotScope(); filterBotScope != "" { + // bot_scope is only designed to scope-qualify bot_name. If we want to + // introduce actual scope-filtering down the line, we'll introduce a + // new field with more control (i.e. exact, descendent) + if req.GetFilter().GetBotName() == "" { + return nil, trace.BadParameter( + "bot_scope filter requires bot_name", + ) + } + + if err := scopes.StrongValidate(filterBotScope); err != nil { + return nil, trace.Wrap(err) + } + } + botInstances, nextToken, err := b.cache.ListBotInstances( ctx, int(req.GetPageSize()), @@ -249,6 +269,7 @@ func (b *BotInstanceService) ListBotInstancesV2(ctx context.Context, req *pb.Lis SortField: req.GetSortField(), SortDesc: req.GetSortDesc(), FilterBotName: req.GetFilter().GetBotName(), + FilterBotScope: req.GetFilter().GetBotScope(), FilterSearchTerm: req.GetFilter().GetSearchTerm(), FilterQuery: req.GetFilter().GetQuery(), FilterFn: func(botInstance *pb.BotInstance) bool { @@ -322,6 +343,18 @@ func (b *BotInstanceService) SubmitHeartbeat(ctx context.Context, req *pb.Submit } } + // A scoped bot's instances are stored namespaced by the bot's scope, which + // is encoded into the identity as BotScope. Certificates issued before the + // BotScope field existed lack it, so fall back to the scope pin - correct + // for those certs because bots are always pinned to their scope of origin. + // TODO(strideynet): remove the ScopePin fallback once sufficient time has + // passed that all bot certs carry BotScope. It must be removed before bots + // can be pinned to a scope other than their scope of origin. + botScope := ident.BotScope + if botScope == "" && ident.ScopePin != nil { + botScope = ident.ScopePin.GetScope() + } + b.logger.DebugContext( ctx, "Received bot instance heartbeat", @@ -329,29 +362,33 @@ func (b *BotInstanceService) SubmitHeartbeat(ctx context.Context, req *pb.Submit "bot_instance", botInstanceID, "heartbeat", logutils.StringerAttr(req.GetHeartbeat()), ) - _, err = b.backend.PatchBotInstance(ctx, botName, botInstanceID, func(instance *pb.BotInstance) (*pb.BotInstance, error) { - if !instance.HasStatus() { - instance.SetStatus(&pb.BotInstanceStatus{}) - } - // Set initial heartbeat if not set. - if !instance.GetStatus().HasInitialHeartbeat() { - instance.GetStatus().SetInitialHeartbeat(req.GetHeartbeat()) - } - // If we're at or above the limit, remove enough of the front - // elements to make room for the new one at the end. - if len(instance.GetStatus().GetLatestHeartbeats()) >= heartbeatHistoryLimit { - toRemove := len(instance.GetStatus().GetLatestHeartbeats()) - heartbeatHistoryLimit + 1 - instance.GetStatus().SetLatestHeartbeats(instance.GetStatus().GetLatestHeartbeats()[toRemove:]) - } - // Append the new heartbeat to the end. - instance.GetStatus().SetLatestHeartbeats(append(instance.GetStatus().GetLatestHeartbeats(), req.GetHeartbeat())) - - if storeHeartbeatExtras() { - // Overwrite the service health. - instance.GetStatus().SetServiceHealth(req.GetServiceHealth()) - } - - return instance, nil + _, err = b.backend.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Scope: botScope, Name: botName}, + InstanceID: botInstanceID, + UpdateFn: func(instance *pb.BotInstance) (*pb.BotInstance, error) { + if !instance.HasStatus() { + instance.SetStatus(&pb.BotInstanceStatus{}) + } + // Set initial heartbeat if not set. + if !instance.GetStatus().HasInitialHeartbeat() { + instance.GetStatus().SetInitialHeartbeat(req.GetHeartbeat()) + } + // If we're at or above the limit, remove enough of the front + // elements to make room for the new one at the end. + if len(instance.GetStatus().GetLatestHeartbeats()) >= heartbeatHistoryLimit { + toRemove := len(instance.GetStatus().GetLatestHeartbeats()) - heartbeatHistoryLimit + 1 + instance.GetStatus().SetLatestHeartbeats(instance.GetStatus().GetLatestHeartbeats()[toRemove:]) + } + // Append the new heartbeat to the end. + instance.GetStatus().SetLatestHeartbeats(append(instance.GetStatus().GetLatestHeartbeats(), req.GetHeartbeat())) + + if storeHeartbeatExtras() { + // Overwrite the service health. + instance.GetStatus().SetServiceHealth(req.GetServiceHealth()) + } + + return instance, nil + }, }) if err != nil { return nil, trace.Wrap(err, "patching bot instance") diff --git a/lib/auth/machineid/machineidv1/bot_instance_service_test.go b/lib/auth/machineid/machineidv1/bot_instance_service_test.go index 61b5da565297..de0612d3996e 100644 --- a/lib/auth/machineid/machineidv1/bot_instance_service_test.go +++ b/lib/auth/machineid/machineidv1/bot_instance_service_test.go @@ -454,6 +454,24 @@ func TestBotInstanceServiceSubmitHeartbeat(t *testing.T) { assertErr: assert.NoError, wantHeartbeat: true, }, + { + // BotScope determines the storage scope even when the identity is + // pinned to a different (narrower) scope. + name: "scoped identity with BotScope preferred over pin", + createBotInstance: true, + req: machineidv1.SubmitHeartbeatRequest_builder{ + Heartbeat: machineidv1.BotInstanceStatusHeartbeat_builder{Hostname: "llama"}.Build(), + }.Build(), + identity: tlsca.Identity{ + BotName: botName, + BotInstanceID: botInstanceID, + BotScope: "/scopes/test", + ScopePin: scopesv1.Pin_builder{Kind: scopesv1.PinKind_PIN_KIND_USER, Scope: "/scopes/test/narrowed"}.Build(), + BotInternal: true, + }, + assertErr: assert.NoError, + wantHeartbeat: true, + }, } for _, tt := range tests { @@ -473,9 +491,17 @@ func TestBotInstanceServiceSubmitHeartbeat(t *testing.T) { }) require.NoError(t, err) + // A scoped bot's instances are stored namespaced by the identity's + // BotScope, falling back to the pinned scope for older certs. + botScope := tt.identity.BotScope + if botScope == "" { + botScope = tt.identity.ScopePin.GetScope() + } + if tt.createBotInstance { bi := newBotInstance(botName) bi.GetSpec().SetInstanceId(botInstanceID) + bi.SetScope(botScope) _, err := backend.CreateBotInstance(ctx, bi) require.NoError(t, err) } @@ -483,7 +509,7 @@ func TestBotInstanceServiceSubmitHeartbeat(t *testing.T) { _, err = service.SubmitHeartbeat(ctx, tt.req) tt.assertErr(t, err) if tt.createBotInstance { - bi, err := backend.GetBotInstance(ctx, botName, botInstanceID) + bi, err := backend.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: botScope, BotName: botName, InstanceId: botInstanceID}.Build()) require.NoError(t, err) if tt.wantHeartbeat { assert.Empty( @@ -557,7 +583,7 @@ func TestBotInstanceServiceSubmitHeartbeat_HeartbeatLimit(t *testing.T) { require.NoError(t, err) } - bi, err = backend.GetBotInstance(ctx, botName, botInstanceID) + bi, err = backend.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: botName, InstanceId: botInstanceID}.Build()) require.NoError(t, err) assert.Len(t, bi.GetStatus().GetLatestHeartbeats(), heartbeatHistoryLimit) assert.Equal(t, "0", bi.GetStatus().GetInitialHeartbeat().GetHostname()) diff --git a/lib/auth/machineid/machineidv1/machineidv1_test.go b/lib/auth/machineid/machineidv1/machineidv1_test.go index 5aca835e31a6..2bb3320cff5a 100644 --- a/lib/auth/machineid/machineidv1/machineidv1_test.go +++ b/lib/auth/machineid/machineidv1/machineidv1_test.go @@ -3259,6 +3259,7 @@ func TestBotInstanceService_DeleteBotInstance(t *testing.T) { _, err = client.BotInstanceServiceClient().DeleteBotInstance(ctx, machineidv1pb.DeleteBotInstanceRequest_builder{ BotName: tt.instance.GetSpec().GetBotName(), InstanceId: tt.instance.GetSpec().GetInstanceId(), + BotScope: tt.instance.GetScope(), }.Build()) tt.assertError(t, err) }) @@ -3386,6 +3387,7 @@ func TestBotInstanceService_GetBotInstance(t *testing.T) { got, err := client.BotInstanceServiceClient().GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ BotName: tt.instance.GetSpec().GetBotName(), InstanceId: tt.instance.GetSpec().GetInstanceId(), + BotScope: tt.instance.GetScope(), }.Build()) tt.assertError(t, err) if err == nil { @@ -3558,6 +3560,52 @@ func TestBotInstanceService_ListBotInstancesV2(t *testing.T) { got := listAll(t, client.BotInstanceServiceClient()) require.Empty(t, got) }) + + t.Run("scope filter with bot name returns that bot's instances", func(t *testing.T) { + client, err := srv.NewClient(authtest.TestUser(unscopedUser.GetName())) + require.NoError(t, err) + + want := grantedInstances[0] + res, err := client.BotInstanceServiceClient().ListBotInstancesV2(t.Context(), machineidv1pb.ListBotInstancesV2Request_builder{ + PageSize: 100, + Filter: machineidv1pb.ListBotInstancesV2Request_Filters_builder{ + BotName: want.GetSpec().GetBotName(), + BotScope: "/scopes/granted", + }.Build(), + }.Build()) + require.NoError(t, err) + require.Len(t, res.GetBotInstances(), 1) + require.Equal(t, want.GetSpec().GetInstanceId(), res.GetBotInstances()[0].GetSpec().GetInstanceId()) + }) + + t.Run("scope filter without bot name is rejected", func(t *testing.T) { + client, err := srv.NewClient(authtest.TestUser(unscopedUser.GetName())) + require.NoError(t, err) + + // bot_scope only qualifies bot_name; it cannot be used as a standalone + // scope filter. + _, err = client.BotInstanceServiceClient().ListBotInstancesV2(t.Context(), machineidv1pb.ListBotInstancesV2Request_builder{ + PageSize: 100, + Filter: machineidv1pb.ListBotInstancesV2Request_Filters_builder{ + BotScope: "/scopes/granted", + }.Build(), + }.Build()) + require.True(t, trace.IsBadParameter(err), "expected bad parameter, got: %v", err) + }) + + t.Run("non-canonical scope filter is rejected", func(t *testing.T) { + client, err := srv.NewClient(authtest.TestUser(unscopedUser.GetName())) + require.NoError(t, err) + + _, err = client.BotInstanceServiceClient().ListBotInstancesV2(t.Context(), machineidv1pb.ListBotInstancesV2Request_builder{ + PageSize: 100, + Filter: machineidv1pb.ListBotInstancesV2Request_Filters_builder{ + BotName: grantedInstances[0].GetSpec().GetBotName(), + BotScope: "/scopes/granted/", + }.Build(), + }.Build()) + require.True(t, trace.IsBadParameter(err), "expected bad parameter, got: %v", err) + }) } func TestBotInstanceService_SubmitHeartbeat(t *testing.T) { @@ -3677,13 +3725,14 @@ func TestBotInstanceService_SubmitHeartbeat(t *testing.T) { }.Build()) require.NoError(t, err) - got, err := adminClient.BotInstanceServiceClient().GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ - BotName: botName, - InstanceId: instanceID, - }.Build()) - require.NoError(t, err) - require.NotNil(t, got.GetStatus().GetInitialHeartbeat()) - require.Equal(t, "scoped-host", got.GetStatus().GetInitialHeartbeat().GetHostname()) + // The heartbeat lands in the backend and replicates to cache-backed + // reads through the scoped event watch, so allow for propagation. + require.EventuallyWithT(t, func(t *assert.CollectT) { + got, err := srv.Auth().Cache.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{BotScope: "/scopes/test", BotName: botName, InstanceId: instanceID}.Build()) + require.NoError(t, err) + require.NotNil(t, got.GetStatus().GetInitialHeartbeat()) + require.Equal(t, "scoped-host", got.GetStatus().GetInitialHeartbeat().GetHostname()) + }, 5*time.Second, 100*time.Millisecond) }) } diff --git a/lib/cache/bot_instance.go b/lib/cache/bot_instance.go index 7c5f6eaf1482..c2cf3027bdf8 100644 --- a/lib/cache/bot_instance.go +++ b/lib/cache/bot_instance.go @@ -32,6 +32,7 @@ import ( "github.com/gravitational/teleport/api/utils/clientutils" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/expression" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils/typical" ) @@ -76,8 +77,11 @@ func newBotInstanceCollection(upstream services.BotInstance, w types.WatchKind) }, nil } -// GetBotInstance returns the specified BotInstance resource. -func (c *Cache) GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) { +// GetBotInstance returns the specified BotInstance resource. A bot is +// identified by the request's (bot_scope, bot_name): a lookup with the wrong +// scope misses, just as it would against the backend's scope-namespaced key +// ranges. +func (c *Cache) GetBotInstance(ctx context.Context, req *machineidv1.GetBotInstanceRequest) (*machineidv1.BotInstance, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetBotInstance") defer span.End() @@ -86,11 +90,11 @@ func (c *Cache) GetBotInstance(ctx context.Context, botName, instanceID string) collection: c.collections.botInstances, index: botInstanceNameIndex, upstreamGet: func(ctx context.Context, _ string) (*machineidv1.BotInstance, error) { - return c.Config.BotInstanceService.GetBotInstance(ctx, botName, instanceID) + return c.Config.BotInstanceService.GetBotInstance(ctx, req) }, } - out, err := getter.get(ctx, makeBotInstanceNameIndexKey(botName, instanceID)) + out, err := getter.get(ctx, makeBotInstanceNameIndexKey(req.GetBotScope(), req.GetBotName(), req.GetInstanceId())) return out, trace.Wrap(err) } @@ -100,6 +104,13 @@ func (c *Cache) ListBotInstances(ctx context.Context, pageSize int, lastToken st ctx, span := c.Tracer.Start(ctx, "cache/ListBotInstances") defer span.End() + // A bot is identified by the pair (scope, name), so the scope filter only + // ever qualifies the name filter. Listing a whole scope will be a separate + // filter with explicit exact/descendant control. + if options.GetFilterBotScope() != "" && options.GetFilterBotName() == "" { + return nil, "", trace.BadParameter("bot scope filter requires a bot name filter") + } + index := botInstanceNameIndex keyFn := keyForBotInstanceNameIndex isDesc := options.GetSortDesc() @@ -144,6 +155,14 @@ func (c *Cache) ListBotInstances(ctx context.Context, pageSize int, lastToken st return c.Config.BotInstanceService.ListBotInstances(ctx, limit, start, options) }, filter: func(b *machineidv1.BotInstance) bool { + // A bot is identified by (scope, name), so a by-bot filter also + // constrains the scope: a name without a scope means the unscoped + // bot. Only the unfiltered listing spans all scopes. This mirrors + // the backend's range routing in + // local.BotInstanceService.ListBotInstances. + if options.GetFilterBotName() != "" && b.GetScope() != options.GetFilterBotScope() { + return false + } if !services.MatchBotInstance(b, options.GetFilterBotName(), options.GetFilterSearchTerm(), exp) { return false } @@ -165,13 +184,16 @@ func (c *Cache) ListBotInstances(ctx context.Context, pageSize int, lastToken st func keyForBotInstanceNameIndex(botInstance *machineidv1.BotInstance) string { return makeBotInstanceNameIndexKey( + botInstance.GetScope(), botInstance.GetSpec().GetBotName(), botInstance.GetMetadata().GetName(), ) } -func makeBotInstanceNameIndexKey(botName string, instanceID string) string { - return botName + "/" + instanceID +// makeBotInstanceNameIndexKey builds the primary index key for a bot +// instance. +func makeBotInstanceNameIndexKey(botScope, botName, instanceID string) string { + return scopes.MakeResourceCursor(botScope, botName+"/"+instanceID) } func keyForBotInstanceActiveAtIndex(botInstance *machineidv1.BotInstance) string { diff --git a/lib/cache/bot_instance_test.go b/lib/cache/bot_instance_test.go index 3c4813eb68ff..a3272a1f86c9 100644 --- a/lib/cache/bot_instance_test.go +++ b/lib/cache/bot_instance_test.go @@ -18,8 +18,9 @@ package cache import ( "context" - "strconv" + "fmt" "testing" + "testing/synctest" "time" "github.com/google/uuid" @@ -30,507 +31,394 @@ import ( headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/clientutils" + "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" ) // TestBotInstanceCache tests that CRUD operations on bot instances resources are -// replicated from the backend to the cache. +// replicated from the backend to the cache. Instances of scoped bots live in a +// scope-namespaced backend range, so create/update/delete events for them must +// round-trip through the cache's event watcher just like unscoped instances. func TestBotInstanceCache(t *testing.T) { t.Parallel() - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) + for _, botScope := range []string{"", "/scopes/test"} { + t.Run(fmt.Sprintf("scope=%q", botScope), func(t *testing.T) { + t.Parallel() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + testResources153(t, p, testFuncs[*machineidv1.BotInstance]{ + newResource: func(key string) (*machineidv1.BotInstance, error) { + return machineidv1.BotInstance_builder{ + Kind: types.KindBotInstance, + Version: types.V1, + Scope: botScope, + Metadata: &headerv1.Metadata{}, + Spec: machineidv1.BotInstanceSpec_builder{ + BotName: "bot-1", + InstanceId: key, + }.Build(), + Status: &machineidv1.BotInstanceStatus{}, + }.Build(), nil + }, + cacheGet: func(ctx context.Context, key string) (*machineidv1.BotInstance, error) { + return p.cache.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: botScope, BotName: "bot-1", InstanceId: key}.Build()) + }, + cacheList: func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1.BotInstance, string, error) { + return p.cache.ListBotInstances(ctx, pageSize, pageToken, nil) + }, + create: func(ctx context.Context, resource *machineidv1.BotInstance) error { + _, err := p.botInstanceService.CreateBotInstance(ctx, resource) + return err + }, + list: func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1.BotInstance, string, error) { + return p.botInstanceService.ListBotInstances(ctx, pageSize, pageToken, nil) + }, + update: func(ctx context.Context, bi *machineidv1.BotInstance) error { + _, err := p.botInstanceService.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Scope: botScope, Name: "bot-1"}, + InstanceID: bi.GetMetadata().GetName(), + UpdateFn: func(_ *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { + return bi, nil + }, + }) + return err + }, + delete: func(ctx context.Context, key string) error { + return p.botInstanceService.DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: botScope, BotName: "bot-1", InstanceId: key}.Build()) + }, + deleteAll: func(ctx context.Context) error { + return p.botInstanceService.DeleteAllBotInstances(ctx) + }, + }) + }) + } +} - testResources153(t, p, testFuncs[*machineidv1.BotInstance]{ - newResource: func(key string) (*machineidv1.BotInstance, error) { - return machineidv1.BotInstance_builder{ +// TestBotInstanceCacheBackendTokenParity verifies that the cache lister and +// the backend lister agree on the ordering of the unified scoped+unscoped +// listing and mint interchangeable page tokens. genericLister falls back to +// the backend lister with the caller's token verbatim when the cache is +// unhealthy, so a token minted by one side must resume correctly against the +// other. +func TestBotInstanceCacheBackendTokenParity(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ctx := t.Context() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + instances := []struct { + botScope string + botName string + }{ + {botScope: "", botName: "bot-a"}, + {botScope: "", botName: "bot-b"}, + {botScope: "/bar", botName: "bot-c"}, + {botScope: "/foo", botName: "bot-d"}, + } + for _, i := range instances { + _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ Kind: types.KindBotInstance, Version: types.V1, + Scope: i.botScope, Metadata: &headerv1.Metadata{}, Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-1", - InstanceId: key, + BotName: i.botName, + InstanceId: uuid.New().String(), }.Build(), Status: &machineidv1.BotInstanceStatus{}, - }.Build(), nil - }, - cacheGet: func(ctx context.Context, key string) (*machineidv1.BotInstance, error) { - return p.cache.GetBotInstance(ctx, "bot-1", key) - }, - cacheList: func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1.BotInstance, string, error) { - return p.cache.ListBotInstances(ctx, pageSize, pageToken, nil) - }, - create: func(ctx context.Context, resource *machineidv1.BotInstance) error { - _, err := p.botInstanceService.CreateBotInstance(ctx, resource) - return err - }, - list: func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1.BotInstance, string, error) { - return p.botInstanceService.ListBotInstances(ctx, pageSize, pageToken, nil) - }, - update: func(ctx context.Context, bi *machineidv1.BotInstance) error { - _, err := p.botInstanceService.PatchBotInstance(ctx, "bot-1", bi.GetMetadata().GetName(), func(_ *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { - return bi, nil - }) - return err - }, - delete: func(ctx context.Context, key string) error { - return p.botInstanceService.DeleteBotInstance(ctx, "bot-1", key) - }, - deleteAll: func(ctx context.Context) error { - return p.botInstanceService.DeleteAllBotInstances(ctx) - }, - }) -} - -// TestBotInstanceCachePaging tests that items from the cache are paginated. -func TestBotInstanceCachePaging(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) - - for _, n := range []int{5, 1, 3, 4, 2} { - _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-1", - InstanceId: "instance-" + strconv.Itoa(n), - }.Build(), - Status: &machineidv1.BotInstanceStatus{}, - }.Build()) + }.Build()) + require.NoError(t, err) + } + + synctest.Wait() + + names := func(instances []*machineidv1.BotInstance) []string { + out := make([]string, 0, len(instances)) + for _, b := range instances { + out = append(out, b.GetSpec().GetBotName()) + } + return out + } + + // Both listings order unscoped instances first, then scoped grouped by + // scope. + wantOrder := []string{"bot-a", "bot-b", "bot-c", "bot-d"} + fromCache, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) require.NoError(t, err) - } - - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) + require.Equal(t, wantOrder, names(fromCache)) + fromBackend, _, err := p.botInstanceService.ListBotInstances(ctx, 0, "", nil) require.NoError(t, err) - require.Len(t, results, 5) - }, 10*time.Second, 100*time.Millisecond) - - // page size equal to total items - results, nextPageToken, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Empty(t, nextPageToken) - require.Len(t, results, 5) - require.Equal(t, "instance-1", results[0].GetMetadata().GetName()) - require.Equal(t, "instance-2", results[1].GetMetadata().GetName()) - require.Equal(t, "instance-3", results[2].GetMetadata().GetName()) - require.Equal(t, "instance-4", results[3].GetMetadata().GetName()) - require.Equal(t, "instance-5", results[4].GetMetadata().GetName()) - - // page size smaller than total items - results, nextPageToken, err = p.cache.ListBotInstances(ctx, 3, "", nil) - require.NoError(t, err) - require.Equal(t, "bot-1/instance-4", nextPageToken) - require.Len(t, results, 3) - require.Equal(t, "instance-1", results[0].GetMetadata().GetName()) - require.Equal(t, "instance-2", results[1].GetMetadata().GetName()) - require.Equal(t, "instance-3", results[2].GetMetadata().GetName()) - - // next page - results, nextPageToken, err = p.cache.ListBotInstances(ctx, 3, nextPageToken, nil) - require.NoError(t, err) - require.Empty(t, nextPageToken) - require.Len(t, results, 2) - require.Equal(t, "instance-4", results[0].GetMetadata().GetName()) - require.Equal(t, "instance-5", results[1].GetMetadata().GetName()) -} - -// TestBotInstanceCacheBotFilter tests that cache items are filtered by bot name. -func TestBotInstanceCacheBotFilter(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) - - for n := range 10 { - _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-" + strconv.Itoa(n%2), - InstanceId: "instance-" + strconv.Itoa(n), - }.Build(), - Status: &machineidv1.BotInstanceStatus{}, - }.Build()) - require.NoError(t, err) - } - - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Len(t, results, 10) - }, 10*time.Second, 100*time.Millisecond) - - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterBotName: "bot-1", + require.Equal(t, wantOrder, names(fromBackend)) + + // Page tokens are interchangeable whether the next item is unscoped + // (pageSize 1) or scoped (pageSize 3). + for _, pageSize := range []int{1, 3} { + cachePage, cacheToken, err := p.cache.ListBotInstances(ctx, pageSize, "", nil) + require.NoError(t, err) + require.Equal(t, wantOrder[:pageSize], names(cachePage)) + backendPage, backendToken, err := p.botInstanceService.ListBotInstances(ctx, pageSize, "", nil) + require.NoError(t, err) + require.Equal(t, wantOrder[:pageSize], names(backendPage)) + require.Equal(t, backendToken, cacheToken) + + rest, _, err := p.botInstanceService.ListBotInstances(ctx, 0, cacheToken, nil) + require.NoError(t, err) + require.Equal(t, wantOrder[pageSize:], names(rest)) + rest, _, err = p.cache.ListBotInstances(ctx, 0, backendToken, nil) + require.NoError(t, err) + require.Equal(t, wantOrder[pageSize:], names(rest)) + } }) - require.NoError(t, err) - require.Len(t, results, 5) - - for _, b := range results { - require.Equal(t, "bot-1", b.GetSpec().GetBotName()) - } } -// TestBotInstanceCacheSearchFilter tests that cache items are filtered by -// search term. -func TestBotInstanceCacheSearchFilter(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) +type botInstanceSpec struct { + scope, botName, id string + hostname, version string + activeAt int64 +} - for n := range 10 { - instance := machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-1", - InstanceId: "instance-" + strconv.Itoa(n+1), - }.Build(), - Status: machineidv1.BotInstanceStatus_builder{ - LatestHeartbeats: []*machineidv1.BotInstanceStatusHeartbeat{ - machineidv1.BotInstanceStatusHeartbeat_builder{ - Hostname: "host-" + strconv.Itoa(n%2), - }.Build(), - }, - }.Build(), +func buildBotInstance(s botInstanceSpec) *machineidv1.BotInstance { + status := &machineidv1.BotInstanceStatus{} + if s.hostname != "" || s.version != "" || s.activeAt != 0 { + status = machineidv1.BotInstanceStatus_builder{ + LatestHeartbeats: []*machineidv1.BotInstanceStatusHeartbeat{ + machineidv1.BotInstanceStatusHeartbeat_builder{ + Hostname: s.hostname, + Version: s.version, + RecordedAt: ×tamppb.Timestamp{Seconds: s.activeAt}, + }.Build(), + }, }.Build() - - _, err := p.botInstanceService.CreateBotInstance(ctx, instance) - require.NoError(t, err) } - - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Len(t, results, 10) - }, 10*time.Second, 100*time.Millisecond) - - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterSearchTerm: "host-1", - }) - require.NoError(t, err) - require.Len(t, results, 5) + return machineidv1.BotInstance_builder{ + Kind: types.KindBotInstance, + Version: types.V1, + Scope: s.scope, + Metadata: &headerv1.Metadata{}, + Spec: machineidv1.BotInstanceSpec_builder{ + BotName: s.botName, + InstanceId: s.id, + }.Build(), + Status: status, + }.Build() } -// TestBotInstanceCacheQueryFilter tests that cache items are filtered by query. -func TestBotInstanceCacheQueryFilter(t *testing.T) { +// TestBotInstanceCacheList exercises Cache.ListBotInstances across filtering and +// sorting. +func TestBotInstanceCacheList(t *testing.T) { t.Parallel() - ctx := t.Context() - - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) - - { - _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-1", - InstanceId: "00000000-0000-0000-0000-000000000000", - }.Build(), - Status: machineidv1.BotInstanceStatus_builder{ - LatestHeartbeats: []*machineidv1.BotInstanceStatusHeartbeat{ - machineidv1.BotInstanceStatusHeartbeat_builder{ - Hostname: "host-1", - }.Build(), - }, - }.Build(), - }.Build()) - require.NoError(t, err) + botInstanceIDs := func(instances []*machineidv1.BotInstance) []string { + out := make([]string, 0, len(instances)) + for _, b := range instances { + out = append(out, b.GetSpec().GetInstanceId()) + } + return out } - { - _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-1", - InstanceId: uuid.New().String(), - }.Build(), - Status: machineidv1.BotInstanceStatus_builder{ - LatestHeartbeats: []*machineidv1.BotInstanceStatusHeartbeat{ - machineidv1.BotInstanceStatusHeartbeat_builder{ - Hostname: "host-2", - }.Build(), - }, - }.Build(), - }.Build()) - require.NoError(t, err) + sortInstances := []botInstanceSpec{ + {botName: "alpha", id: "i-a", activeAt: 200, version: "2.0.0", hostname: "host-2"}, + {botName: "beta", id: "i-b", activeAt: 100, version: "2.0.0-rc1", hostname: "host-3"}, + {scope: "/east", botName: "alpha", id: "i-c", activeAt: 300, version: "1.0.0", hostname: "host-1"}, } - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Len(t, results, 2) - }, 10*time.Second, 100*time.Millisecond) - - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterQuery: `status.latest_heartbeat.hostname == "host-1"`, - }) - require.NoError(t, err) - require.Len(t, results, 1) - require.Equal(t, "00000000-0000-0000-0000-000000000000", results[0].GetSpec().GetInstanceId()) -} - -// TestBotInstanceCacheSorting tests that cache items are sorted. -func TestBotInstanceCacheSorting(t *testing.T) { - t.Parallel() - - ctx := t.Context() + evenFilter := func(b *machineidv1.BotInstance) bool { + id := b.GetSpec().GetInstanceId() + return (id[len(id)-1]-'0')%2 == 0 + } - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) + filterFnInstances := []botInstanceSpec{ + {botName: "bot", id: "i-0"}, + {botName: "bot", id: "i-1"}, + {botName: "bot", id: "i-2"}, + {botName: "bot", id: "i-3"}, + {scope: "/s", botName: "bot", id: "i-4"}, + {scope: "/s", botName: "bot", id: "i-5"}, + } - items := []struct { - botName string - instanceId string - recordedAtSeconds int64 - version string - hostname string + tests := []struct { + name string + instances []botInstanceSpec + opts *services.ListBotInstancesRequestOptions + want []string + wantErr string }{ - {"bot-1", "instance-1", 2, "2.0.0", "hostname-2"}, - {"bot-1", "instance-3", 1, "2.0.0-rc1", "hostname-3"}, - {"bot-2", "instance-2", 3, "1.0.0", "hostname-1"}, + { + name: "no filter spans all scopes, unscoped first", + instances: []botInstanceSpec{ + {botName: "bot-a", id: "u1"}, + {scope: "/foo", botName: "bot-b", id: "s1"}, + {botName: "bot-c", id: "u2"}, + }, + want: []string{"u1", "u2", "s1"}, + }, + { + name: "bot name filter matches only the unscoped bot of that name", + instances: []botInstanceSpec{ + {botName: "web", id: "web-a"}, + {botName: "web", id: "web-b"}, + {scope: "/prod", botName: "web", id: "web-c"}, + {botName: "db", id: "db-a"}, + }, + opts: &services.ListBotInstancesRequestOptions{FilterBotName: "web"}, + want: []string{"web-a", "web-b"}, + }, + { + name: "bot name and scope filter matches the scoped bot", + instances: []botInstanceSpec{ + {botName: "web", id: "web-u"}, + {scope: "/prod", botName: "web", id: "web-p1"}, + {scope: "/prod", botName: "web", id: "web-p2"}, + {scope: "/prod", botName: "api", id: "api-p"}, + }, + opts: &services.ListBotInstancesRequestOptions{FilterBotName: "web", FilterBotScope: "/prod"}, + want: []string{"web-p1", "web-p2"}, + }, + { + // A scope filter only qualifies a bot name, so it is rejected on its + // own rather than listing every instance in the scope. + name: "scope filter without a bot name filter is rejected", + instances: []botInstanceSpec{ + {botName: "web", id: "web-u"}, + {scope: "/prod", botName: "web", id: "web-p1"}, + }, + opts: &services.ListBotInstancesRequestOptions{FilterBotScope: "/prod"}, + wantErr: "bot scope filter requires a bot name filter", + }, + { + name: "search term matches across scopes", + instances: []botInstanceSpec{ + {botName: "runner", id: "r1", hostname: "host-match"}, + {scope: "/foo", botName: "runner", id: "r2", hostname: "host-match"}, + {botName: "runner", id: "r3", hostname: "host-other"}, + }, + opts: &services.ListBotInstancesRequestOptions{FilterSearchTerm: "host-match"}, + want: []string{"r1", "r2"}, + }, + { + name: "predicate query matches across scopes", + instances: []botInstanceSpec{ + {botName: "q", id: "q1", hostname: "host-1"}, + {scope: "/foo", botName: "q", id: "q2", hostname: "host-1"}, + {botName: "q", id: "q3", hostname: "host-2"}, + }, + opts: &services.ListBotInstancesRequestOptions{FilterQuery: `status.latest_heartbeat.hostname == "host-1"`}, + want: []string{"q1", "q2"}, + }, + { + name: "sort by bot_name ascending groups unscoped before scoped", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "bot_name"}, + want: []string{"i-a", "i-b", "i-c"}, + }, + { + name: "sort by bot_name descending", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "bot_name", SortDesc: true}, + want: []string{"i-c", "i-b", "i-a"}, + }, + { + name: "sort by active_at ascending is global across scopes", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "active_at_latest"}, + want: []string{"i-b", "i-a", "i-c"}, + }, + { + name: "sort by active_at descending", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "active_at_latest", SortDesc: true}, + want: []string{"i-c", "i-a", "i-b"}, + }, + { + name: "sort by version ascending is global across scopes", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "version_latest"}, + want: []string{"i-c", "i-b", "i-a"}, + }, + { + name: "sort by version descending", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "version_latest", SortDesc: true}, + want: []string{"i-a", "i-b", "i-c"}, + }, + { + name: "sort by host_name ascending is global across scopes", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "host_name_latest"}, + want: []string{"i-c", "i-a", "i-b"}, + }, + { + name: "sort by host_name descending", + instances: sortInstances, + opts: &services.ListBotInstancesRequestOptions{SortField: "host_name_latest", SortDesc: true}, + want: []string{"i-b", "i-a", "i-c"}, + }, + { + name: "unsupported sort field is rejected", + instances: []botInstanceSpec{{botName: "bot", id: "x1"}}, + opts: &services.ListBotInstancesRequestOptions{SortField: "nope"}, + wantErr: `unsupported sort "nope"`, + }, + { + name: "filter fn spans scopes", + instances: filterFnInstances, + opts: &services.ListBotInstancesRequestOptions{FilterFn: evenFilter}, + want: []string{"i-0", "i-2", "i-4"}, + }, + { + name: "filter fn combined with bot name filter", + instances: filterFnInstances, + opts: &services.ListBotInstancesRequestOptions{FilterBotName: "bot", FilterFn: evenFilter}, + want: []string{"i-0", "i-2"}, + }, } - for _, b := range items { - instance := machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: b.botName, - InstanceId: b.instanceId, - }.Build(), - Status: machineidv1.BotInstanceStatus_builder{ - LatestHeartbeats: []*machineidv1.BotInstanceStatusHeartbeat{ - machineidv1.BotInstanceStatusHeartbeat_builder{ - RecordedAt: ×tamppb.Timestamp{ - Seconds: b.recordedAtSeconds, + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ctx := t.Context() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + for _, s := range tc.instances { + _, err := p.botInstanceService.CreateBotInstance(ctx, buildBotInstance(s)) + require.NoError(t, err) + } + + synctest.Wait() + + got, _, err := p.cache.ListBotInstances(ctx, 0, "", tc.opts) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.want, botInstanceIDs(got)) + + // Also assert that pagination produces the same results (in + // particular, we're looking for cursor keys not lining up + // leading to skipped entries) + paged, err := stream.Collect( + clientutils.ResourcesWithPageSize( + ctx, + func(ctx context.Context, pageSize int, token string) ([]*machineidv1.BotInstance, string, error) { + return p.cache.ListBotInstances(ctx, pageSize, token, tc.opts) }, - Version: b.version, - Hostname: b.hostname, - }.Build(), - }, - }.Build(), - }.Build() - - _, err := p.botInstanceService.CreateBotInstance(ctx, instance) - require.NoError(t, err) - } - - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Len(t, results, 3) - }, 10*time.Second, 100*time.Millisecond) - - t.Run("sort ascending by active_at_latest", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "active_at_latest", - SortDesc: false, - }) - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-3", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-1", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-2", results[2].GetMetadata().GetName()) - }) - - t.Run("sort descending by active_at_latest", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "active_at_latest", - SortDesc: true, - }) - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-2", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-1", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-3", results[2].GetMetadata().GetName()) - }) - - t.Run("sort ascending by bot_name", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) // empty sort should default to `bot_name:asc` - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-1", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-3", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-2", results[2].GetMetadata().GetName()) - }) - - t.Run("sort descending by bot_name", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "bot_name", - SortDesc: true, - }) - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-2", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-3", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-1", results[2].GetMetadata().GetName()) - }) - - t.Run("sort ascending by version", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "version_latest", - SortDesc: false, - }) - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-2", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-3", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-1", results[2].GetMetadata().GetName()) - }) - - t.Run("sort descending by version", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "version_latest", - SortDesc: true, - }) - require.NoError(t, err) - require.Len(t, results, 3) - assert.Equal(t, "instance-1", results[0].GetMetadata().GetName()) - assert.Equal(t, "instance-3", results[1].GetMetadata().GetName()) - assert.Equal(t, "instance-2", results[2].GetMetadata().GetName()) - }) - - t.Run("sort ascending by hostname", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "host_name_latest", - SortDesc: false, - }) - require.NoError(t, err) - require.Len(t, results, 3) - require.Equal(t, "instance-2", results[0].GetMetadata().GetName()) - require.Equal(t, "instance-1", results[1].GetMetadata().GetName()) - require.Equal(t, "instance-3", results[2].GetMetadata().GetName()) - }) - - t.Run("sort descending by hostname", func(t *testing.T) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "host_name_latest", - SortDesc: true, - }) - require.NoError(t, err) - require.Len(t, results, 3) - require.Equal(t, "instance-3", results[0].GetMetadata().GetName()) - require.Equal(t, "instance-1", results[1].GetMetadata().GetName()) - require.Equal(t, "instance-2", results[2].GetMetadata().GetName()) - }) - - t.Run("sort invalid field", func(t *testing.T) { - _, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - SortField: "blah", + 2, + ), + ) + require.NoError(t, err) + assert.Equal(t, tc.want, botInstanceIDs(paged)) + }) }) - require.Error(t, err) - assert.ErrorContains(t, err, `unsupported sort "blah" but expected bot_name, active_at_latest, version_latest or host_name_latest`) - }) -} - -// TestBotInstanceCacheFilterFn tests that FilterFn is applied during cache -// iteration and that pages are filled correctly even when some items are -// filtered out. -func TestBotInstanceCacheFilterFn(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - p := newTestPack(t, ForAuth) - t.Cleanup(p.Close) - - // Create 10 instances across two bots: bot-0 and bot-1. - for n := range 10 { - _, err := p.botInstanceService.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ - Kind: types.KindBotInstance, - Version: types.V1, - Metadata: &headerv1.Metadata{}, - Spec: machineidv1.BotInstanceSpec_builder{ - BotName: "bot-" + strconv.Itoa(n%2), - InstanceId: "instance-" + strconv.Itoa(n), - }.Build(), - Status: &machineidv1.BotInstanceStatus{}, - }.Build()) - require.NoError(t, err) } - - // Let the cache catch up - require.EventuallyWithT(t, func(t *assert.CollectT) { - results, _, err := p.cache.ListBotInstances(ctx, 0, "", nil) - require.NoError(t, err) - require.Len(t, results, 10) - }, 10*time.Second, 100*time.Millisecond) - - // FilterFn that only accepts even-numbered instances. - filterFn := func(b *machineidv1.BotInstance) bool { - id := b.GetSpec().GetInstanceId() - n := id[len(id)-1] - return n == '0' || n == '2' || n == '4' || n == '6' || n == '8' - } - - // FilterFn alone: all even instances across both bots. - results, _, err := p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterFn: filterFn, - }) - require.NoError(t, err) - require.Len(t, results, 5) - - // Pages should be full: request 3 items with a filter that accepts half. - results, nextPageToken, err := p.cache.ListBotInstances(ctx, 3, "", &services.ListBotInstancesRequestOptions{ - FilterFn: filterFn, - }) - require.NoError(t, err) - require.Len(t, results, 3) - require.NotEmpty(t, nextPageToken) - - // Next page should contain the remaining 2 items. - results, nextPageToken, err = p.cache.ListBotInstances(ctx, 3, nextPageToken, &services.ListBotInstancesRequestOptions{ - FilterFn: filterFn, - }) - require.NoError(t, err) - require.Len(t, results, 2) - require.Empty(t, nextPageToken) - - // FilterFn composed with FilterBotName: bot-0 has instances 0,2,4,6,8 - // (even-numbered), all of which pass the FilterFn. bot-1 has instances - // 1,3,5,7,9 (odd-numbered), none of which pass the FilterFn. - results, _, err = p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterBotName: "bot-0", - FilterFn: filterFn, - }) - require.NoError(t, err) - require.Len(t, results, 5) - for _, b := range results { - require.Equal(t, "bot-0", b.GetSpec().GetBotName()) - } - - // bot-1 instances are all odd-numbered, so FilterFn rejects them all. - results, _, err = p.cache.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ - FilterBotName: "bot-1", - FilterFn: filterFn, - }) - require.NoError(t, err) - require.Empty(t, results) } // TestBotInstanceCacheFallback tests that requests fallback to the upstream when the cache is unhealthy. diff --git a/lib/cache/inventory/inventory_cache_test.go b/lib/cache/inventory/inventory_cache_test.go index f245def037ae..ebe58fbb7a67 100644 --- a/lib/cache/inventory/inventory_cache_test.go +++ b/lib/cache/inventory/inventory_cache_test.go @@ -1751,15 +1751,15 @@ func (m *mockBotInstanceCache) ListBotInstances(ctx context.Context, pageSize in return m.bots, "", nil } -func (m *mockBotInstanceCache) GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) { +func (m *mockBotInstanceCache) GetBotInstance(ctx context.Context, req *machineidv1.GetBotInstanceRequest) (*machineidv1.BotInstance, error) { return nil, nil } -func (m *mockBotInstanceCache) DeleteBotInstance(ctx context.Context, botName, instanceID string) error { +func (m *mockBotInstanceCache) DeleteBotInstance(ctx context.Context, req *machineidv1.DeleteBotInstanceRequest) error { return nil } -func (m *mockBotInstanceCache) PatchBotInstance(ctx context.Context, botName, instanceID string, update func(*machineidv1.BotInstance) (*machineidv1.BotInstance, error)) (*machineidv1.BotInstance, error) { +func (m *mockBotInstanceCache) PatchBotInstance(ctx context.Context, opts services.PatchBotInstanceOpts) (*machineidv1.BotInstance, error) { return nil, nil } diff --git a/lib/join/join_bound_keypair_test.go b/lib/join/join_bound_keypair_test.go index ceb8245b4ee3..fda1f5d0df15 100644 --- a/lib/join/join_bound_keypair_test.go +++ b/lib/join/join_bound_keypair_test.go @@ -1983,8 +1983,11 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { firstInstance, generation := testExtractBotParamsFromCerts(t, joinResult.Certs) require.Equal(t, uint64(1), generation) - // The BotInstance should have the scope set. + // The BotInstance should have the scope set. A bot is identified by + // (scope, name), so the read must declare the bot's scope to address the + // scoped instance. botInstance, err := adminClient.BotInstanceServiceClient().GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ + BotScope: "/test", BotName: "test-scoped", InstanceId: firstInstance, }.Build()) diff --git a/lib/service/service.go b/lib/service/service.go index 28c25ebf8168..e16296e68109 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2563,7 +2563,7 @@ func (process *TeleportProcess) initAuthService() error { PrimaryCache: cache, Events: as.Services, Inventory: as.Services, - BotInstanceBackend: as.Services, + BotInstanceBackend: as.Services.BotInstance, Logger: process.logger.With(teleport.ComponentKey, "inventory.cache"), MetricsRegistry: process.metricsRegistry.Wrap("inventory_cache"), }) diff --git a/lib/services/bot_instance.go b/lib/services/bot_instance.go index 7de4d2e2f0a2..bc3d55203b94 100644 --- a/lib/services/bot_instance.go +++ b/lib/services/bot_instance.go @@ -26,6 +26,7 @@ import ( machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/expression" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/utils/typical" ) @@ -36,27 +37,45 @@ import ( const BotUserPrefix = "bot-" // BotInstance is an interface for the BotInstance service. +// +// Bot instances belong to a bot, and bots are identified by their scope and +// name: instances of scoped bots are stored in a scope-namespaced key range, +// separate from instances of unscoped bots. Methods addressing an individual +// instance take the owning bot's scope, which must be empty if the bot is +// unscoped. type BotInstance interface { - // CreateBotInstance + // CreateBotInstance creates a new bot instance. It is stored in the key + // range determined by the scope set on the instance itself. CreateBotInstance(ctx context.Context, botInstance *machineidv1.BotInstance) (*machineidv1.BotInstance, error) - // GetBotInstance - GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) + // GetBotInstance returns the bot instance owned by the bot identified by + // the request's (bot_scope, bot_name) with the given instance ID. + GetBotInstance(ctx context.Context, req *machineidv1.GetBotInstanceRequest) (*machineidv1.BotInstance, error) // ListBotInstances ListBotInstances(ctx context.Context, pageSize int, lastToken string, options *ListBotInstancesRequestOptions) ([]*machineidv1.BotInstance, string, error) - // DeleteBotInstance - DeleteBotInstance(ctx context.Context, botName, instanceID string) error + // DeleteBotInstance deletes the bot instance owned by the bot identified + // by the request's (bot_scope, bot_name) with the given instance ID. + DeleteBotInstance(ctx context.Context, req *machineidv1.DeleteBotInstanceRequest) error + + // PatchBotInstance fetches the existing bot instance identified by the + // given options, then calls the options' UpdateFn to apply any changes + // before persisting the resource. + PatchBotInstance(ctx context.Context, opts PatchBotInstanceOpts) (*machineidv1.BotInstance, error) +} - // PatchBotInstance fetches an existing bot instance by bot name and ID, - // then calls `updateFn` to apply any changes before persisting the - // resource. - PatchBotInstance( - ctx context.Context, - botName, instanceID string, - updateFn func(*machineidv1.BotInstance) (*machineidv1.BotInstance, error), - ) (*machineidv1.BotInstance, error) +// PatchBotInstanceOpts identifies the bot instance to be patched by +// [BotInstance.PatchBotInstance] and holds the patch to apply to it. +type PatchBotInstanceOpts struct { + // Bot is the scope-qualified name of the bot that owns the instance. The + // scope must be empty if the bot is unscoped. + Bot scopes.QualifiedName + // InstanceID is the ID of the instance to patch. + InstanceID string + // UpdateFn is applied to the fetched instance to produce the instance to + // persist. It may be called more than once if the write is retried. + UpdateFn func(*machineidv1.BotInstance) (*machineidv1.BotInstance, error) } // ValidateBotInstance verifies that required fields for a new BotInstance are present @@ -159,6 +178,13 @@ type ListBotInstancesRequestOptions struct { // The name of the Bot to list BotInstances for. If empty, all BotInstances // will be listed. FilterBotName string + // The scope of the Bot to list BotInstances for. A bot is identified by the + // pair (scope, name), so this only ever qualifies FilterBotName and must be + // set alongside it; setting it without FilterBotName is an error. Leave + // empty if the bot is unscoped. This is deliberately not a scope filter for + // listing every BotInstance in a scope - that will be a separate field with + // explicit exact/descendant control. + FilterBotScope string // A search term used to filter the results. If non-empty, it's used to // match against supported fields. FilterSearchTerm string @@ -189,6 +215,13 @@ func (o *ListBotInstancesRequestOptions) GetFilterBotName() string { return o.FilterBotName } +func (o *ListBotInstancesRequestOptions) GetFilterBotScope() string { + if o == nil { + return "" + } + return o.FilterBotScope +} + func (o *ListBotInstancesRequestOptions) GetFilterSearchTerm() string { if o == nil { return "" diff --git a/lib/services/local/bot_instance.go b/lib/services/local/bot_instance.go index ee9c9e38419e..97045b2563fa 100644 --- a/lib/services/local/bot_instance.go +++ b/lib/services/local/bot_instance.go @@ -28,32 +28,49 @@ import ( "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/expression" "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/local/generic" "github.com/gravitational/teleport/lib/utils/typical" ) -const ( - botInstancePrefix = "bot_instance" -) +// botInstancePrefix is the backend prefix for bot instances. Instances of +// scoped bots are namespaced by their bot's scope under the shared +// scopedPrefix, keyed as +// scoped/bot_instance///. Instances +// of unscoped bots remain keyed as bot_instance//. +const botInstancePrefix = "bot_instance" + +// botInstanceUnscopedWatchPrefix returns the backend key range containing +// instances of unscoped bots. +func botInstanceUnscopedWatchPrefix() backend.Key { + return backend.NewKey(botInstancePrefix) +} + +// botInstanceScopedWatchPrefix returns the backend key range containing +// instances of scoped bots. +func botInstanceScopedWatchPrefix() backend.Key { + return backend.NewKey(scopedPrefix, botInstancePrefix) +} // BotInstanceService exposes backend functionality for storing bot instances. type BotInstanceService struct { - service *generic.ServiceWrapper[*machineidv1.BotInstance] + service *generic.ScopeAwareServiceWrapper[*machineidv1.BotInstance] clock clockwork.Clock } // NewBotInstanceService creates a new BotInstanceService with the given backend. func NewBotInstanceService(b backend.Backend, clock clockwork.Clock) (*BotInstanceService, error) { - service, err := generic.NewServiceWrapper( - generic.ServiceConfig[*machineidv1.BotInstance]{ - Backend: b, - ResourceKind: types.KindBotInstance, - BackendPrefix: backend.NewKey(botInstancePrefix), - MarshalFunc: services.MarshalBotInstance, - UnmarshalFunc: services.UnmarshalBotInstance, - ValidateFunc: services.ValidateBotInstance, + service, err := generic.NewScopeAwareServiceWrapper( + generic.ScopeAwareServiceWrapperConfig[*machineidv1.BotInstance]{ + Backend: b, + ResourceKind: types.KindBotInstance, + UnscopedBackendPrefix: backend.NewKey(botInstancePrefix), + ScopedBackendPrefix: backend.NewKey(scopedPrefix, botInstancePrefix), + MarshalFunc: services.MarshalBotInstance, + UnmarshalFunc: services.UnmarshalBotInstance, + ValidateFunc: services.ValidateBotInstance, }) if err != nil { return nil, trace.Wrap(err) @@ -64,7 +81,16 @@ func NewBotInstanceService(b backend.Backend, clock clockwork.Clock) (*BotInstan }, nil } -// CreateBotInstance inserts a new BotInstance into the backend. +// serviceForBot returns a single-range service addressing the instances of the +// bot identified by (botScope, botName): the bot's sub-range of the scoped key +// range when botScope is non-empty, else its sub-range of the unscoped range. +func (b *BotInstanceService) serviceForBot(botScope, botName string) (*generic.ServiceWrapper[*machineidv1.BotInstance], error) { + service, err := b.service.WithScopedResourcePrefix(scopes.QualifiedName{Scope: botScope, Name: botName}) + return service, trace.Wrap(err) +} + +// CreateBotInstance inserts a new BotInstance into the backend. It is stored +// in the key range determined by the scope set on the instance itself. // // Note that new BotInstances will have their .Metadata.Name overwritten by the // instance UUID. @@ -78,21 +104,30 @@ func (b *BotInstanceService) CreateBotInstance(ctx context.Context, instance *ma instance.GetMetadata().SetName(instance.GetSpec().GetInstanceId()) - serviceWithPrefix := b.service.WithPrefix(instance.GetSpec().GetBotName()) + serviceWithPrefix, err := b.serviceForBot(instance.GetScope(), instance.GetSpec().GetBotName()) + if err != nil { + return nil, trace.Wrap(err) + } created, err := serviceWithPrefix.CreateResource(ctx, instance) return created, trace.Wrap(err) } -// GetBotInstance retreives a specific bot instance given a bot name and -// instance ID. -func (b *BotInstanceService) GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) { - serviceWithPrefix := b.service.WithPrefix(botName) - instance, err := serviceWithPrefix.GetResource(ctx, instanceID) +// GetBotInstance retreives a specific bot instance given a bot scope, bot name +// and instance ID. The scope must be empty if the owning bot is unscoped. +func (b *BotInstanceService) GetBotInstance(ctx context.Context, req *machineidv1.GetBotInstanceRequest) (*machineidv1.BotInstance, error) { + serviceWithPrefix, err := b.serviceForBot(req.GetBotScope(), req.GetBotName()) + if err != nil { + return nil, trace.Wrap(err) + } + instance, err := serviceWithPrefix.GetResource(ctx, req.GetInstanceId()) return instance, trace.Wrap(err) } -// ListBotInstances lists all matching bot instances. A bot name and/or search terms can be optionally provided. -// If an non-empty bot name is provided, only instances for that bot will be fetched. +// ListBotInstances lists all matching bot instances. A bot (scope, name) and/or search terms can be optionally provided. +// If an non-empty bot name is provided, only instances for that bot will be fetched. The bot scope must be +// provided alongside the name for a scoped bot's instances, and only ever qualifies the name - providing a +// scope without a name is an error rather than a request for every instance in that scope. With no bot filter, +// instances for all bots are listed, unscoped bots' instances first. // If an non-empty search term is provided, only instances with a value containing the term in supported fields are fetched. // Supported search fields include; bot name, instance id, hostname (latest), tbot version (latest), join method (latest). // Sorting by bot name in ascending order is supported - an error is returned for any other sort type. @@ -103,13 +138,32 @@ func (b *BotInstanceService) ListBotInstances(ctx context.Context, pageSize int, if options.GetSortDesc() { return nil, "", trace.CompareFailed("unsupported sort, only ascending order is supported") } + // A bot is identified by the pair (scope, name), so the scope filter only + // ever qualifies the name filter. Listing a whole scope will be a separate + // filter with explicit exact/descendant control. + if options.GetFilterBotScope() != "" && options.GetFilterBotName() == "" { + return nil, "", trace.BadParameter("bot scope filter requires a bot name filter") + } - var service *generic.ServiceWrapper[*machineidv1.BotInstance] + // Satisfied by both the scope-aware wrapper (unified listing across the + // unscoped and scoped key ranges) and a single-range service routed by the + // bot filter. + var service interface { + ListResources(ctx context.Context, pageSize int, nextToken string) ([]*machineidv1.BotInstance, string, error) + ListResourcesWithFilter(ctx context.Context, pageSize int, nextToken string, matcher func(*machineidv1.BotInstance) bool) ([]*machineidv1.BotInstance, string, error) + } if options.GetFilterBotName() == "" { - // If botName is empty, return instances for all bots by not using a service prefix + // If no bot filter is set, return instances for all bots across both + // the unscoped and scoped key ranges. service = b.service } else { - service = b.service.WithPrefix(options.GetFilterBotName()) + // The filter identifies exactly one bot, so read only that bot's + // sub-range. + routed, err := b.serviceForBot(options.GetFilterBotScope(), options.GetFilterBotName()) + if err != nil { + return nil, "", trace.Wrap(err) + } + service = routed } var exp typical.Expression[*expression.Environment, bool] @@ -143,11 +197,15 @@ func (b *BotInstanceService) ListBotInstances(ctx context.Context, pageSize int, return r, nextToken, trace.Wrap(err) } -// DeleteBotInstance deletes a specific bot instance matching the given bot name -// and instance ID. -func (b *BotInstanceService) DeleteBotInstance(ctx context.Context, botName, instanceID string) error { - serviceWithPrefix := b.service.WithPrefix(botName) - return trace.Wrap(serviceWithPrefix.DeleteResource(ctx, instanceID)) +// DeleteBotInstance deletes a specific bot instance matching the given bot +// scope, bot name and instance ID. The scope must be empty if the owning bot +// is unscoped. +func (b *BotInstanceService) DeleteBotInstance(ctx context.Context, req *machineidv1.DeleteBotInstanceRequest) error { + serviceWithPrefix, err := b.serviceForBot(req.GetBotScope(), req.GetBotName()) + if err != nil { + return trace.Wrap(err) + } + return trace.Wrap(serviceWithPrefix.DeleteResource(ctx, req.GetInstanceId())) } // DeleteAllBotInstances deletes all bot instances for all bots @@ -155,24 +213,31 @@ func (b *BotInstanceService) DeleteAllBotInstances(ctx context.Context) error { return trace.Wrap(b.service.DeleteAllResources(ctx)) } -// PatchBotInstance uses the supplied function to patch the bot instance -// matching the given (botName, instanceID) key and persists the patched -// resource. It will make multiple attempts if a `CompareFailed` error is -// raised, automatically re-applying `updateFn()`. +// PatchBotInstance uses the options' UpdateFn to patch the bot instance +// identified by those same options and persists the patched resource. It will +// make multiple attempts if a `CompareFailed` error is raised, automatically +// re-applying UpdateFn. func (b *BotInstanceService) PatchBotInstance( ctx context.Context, - botName, instanceID string, - updateFn func(*machineidv1.BotInstance) (*machineidv1.BotInstance, error), + opts services.PatchBotInstanceOpts, ) (*machineidv1.BotInstance, error) { + if opts.UpdateFn == nil { + return nil, trace.BadParameter("opts.UpdateFn is required") + } + const iterLimit = 3 for range iterLimit { - existing, err := b.GetBotInstance(ctx, botName, instanceID) + existing, err := b.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{ + BotScope: opts.Bot.Scope, + BotName: opts.Bot.Name, + InstanceId: opts.InstanceID, + }.Build()) if err != nil { return nil, trace.Wrap(err) } - updated, err := updateFn(utils.CloneProtoMsg(existing)) + updated, err := opts.UpdateFn(utils.CloneProtoMsg(existing)) if err != nil { return nil, trace.Wrap(err) } @@ -186,9 +251,14 @@ func (b *BotInstanceService) PatchBotInstance( return nil, trace.BadParameter("spec.instance_id: cannot be patched") case updated.GetSpec().GetBotName() != existing.GetSpec().GetBotName(): return nil, trace.BadParameter("spec.bot_name: cannot be patched") + case updated.GetScope() != existing.GetScope(): + return nil, trace.BadParameter("scope: cannot be patched") } - serviceWithPrefix := b.service.WithPrefix(botName) + serviceWithPrefix, err := b.serviceForBot(opts.Bot.Scope, opts.Bot.Name) + if err != nil { + return nil, trace.Wrap(err) + } lease, err := serviceWithPrefix.ConditionalUpdateResource(ctx, updated) if err != nil { if trace.IsCompareFailed(err) { diff --git a/lib/services/local/bot_instance_test.go b/lib/services/local/bot_instance_test.go index 8847db1b96cf..6f321e0cf70e 100644 --- a/lib/services/local/bot_instance_test.go +++ b/lib/services/local/bot_instance_test.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/memory" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" ) @@ -82,6 +83,13 @@ func withBotInstanceExpiry(expiry time.Time) func(*machineidv1.BotInstance) { } } +// withBotInstanceScope sets the scope of a bot instance to the given value. +func withBotInstanceScope(scope string) func(*machineidv1.BotInstance) { + return func(bi *machineidv1.BotInstance) { + bi.SetScope(scope) + } +} + // withBotInstanceId sets the .Spec.InstanceId field of a bot instance to // the given value. func withBotInstanceId(value string) func(*machineidv1.BotInstance) { @@ -143,13 +151,13 @@ func withBotInstanceHeartbeatHostname(value string) func(*machineidv1.BotInstanc } // createInstances creates new bot instances for the named bot with random UUIDs -func createInstances(t *testing.T, ctx context.Context, service *BotInstanceService, botName string, count int) map[string]struct{} { +func createInstances(t *testing.T, ctx context.Context, service *BotInstanceService, botName string, count int, fns ...func(*machineidv1.BotInstance)) map[string]struct{} { t.Helper() ids := map[string]struct{}{} for range count { - bi := newBotInstance(botName) + bi := newBotInstance(botName, fns...) _, err := service.CreateBotInstance(ctx, bi) require.NoError(t, err) @@ -274,7 +282,7 @@ func TestBotInstanceInvalidGetters(t *testing.T) { _, err = service.CreateBotInstance(ctx, newBotInstance("example")) require.NoError(t, err) - _, err = service.GetBotInstance(ctx, "example", "invalid") + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: "example", InstanceId: "invalid"}.Build()) require.True(t, trace.IsNotFound(err)) } @@ -303,7 +311,7 @@ func TestBotInstanceCRUD(t *testing.T) { require.Equal(t, bi.GetSpec().GetInstanceId(), patched.GetMetadata().GetName()) // we should be able to retrieve a matching instance - bi2, err := service.GetBotInstance(ctx, bi.GetSpec().GetBotName(), bi.GetSpec().GetInstanceId()) + bi2, err := service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: bi.GetSpec().GetBotName(), InstanceId: bi.GetSpec().GetInstanceId()}.Build()) require.NoError(t, err) require.EqualExportedValues(t, patched, bi2) require.Equal(t, bi.GetMetadata().GetName(), bi2.GetMetadata().GetName()) @@ -320,9 +328,13 @@ func TestBotInstanceCRUD(t *testing.T) { Hostname: "foo", }.Build() - patched, err = service.PatchBotInstance(ctx, bi.GetSpec().GetBotName(), bi.GetSpec().GetInstanceId(), func(bi *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { - bi.GetStatus().SetLatestHeartbeats(append([]*machineidv1.BotInstanceStatusHeartbeat{heartbeat}, bi.GetStatus().GetLatestHeartbeats()...)) - return bi, nil + patched, err = service.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Name: bi.GetSpec().GetBotName()}, + InstanceID: bi.GetSpec().GetInstanceId(), + UpdateFn: func(bi *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { + bi.GetStatus().SetLatestHeartbeats(append([]*machineidv1.BotInstanceStatusHeartbeat{heartbeat}, bi.GetStatus().GetLatestHeartbeats()...)) + return bi, nil + }, }) require.NoError(t, err) @@ -330,10 +342,10 @@ func TestBotInstanceCRUD(t *testing.T) { require.EqualExportedValues(t, heartbeat, patched.GetStatus().GetLatestHeartbeats()[0]) // delete the stored instance - require.NoError(t, service.DeleteBotInstance(ctx, bi.GetSpec().GetBotName(), bi.GetSpec().GetInstanceId())) + require.NoError(t, service.DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "", BotName: bi.GetSpec().GetBotName(), InstanceId: bi.GetSpec().GetInstanceId()}.Build())) // subsequent delete attempts should fail - require.Error(t, service.DeleteBotInstance(ctx, bi.GetSpec().GetBotName(), bi.GetSpec().GetInstanceId())) + require.Error(t, service.DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "", BotName: bi.GetSpec().GetBotName(), InstanceId: bi.GetSpec().GetInstanceId()}.Build())) } // TestBotInstanceList verifies list and filtering by bot functionality for bot @@ -355,8 +367,11 @@ func TestBotInstanceList(t *testing.T) { aIds := createInstances(t, ctx, service, "a", 3) bIds := createInstances(t, ctx, service, "b", 4) + // "a" is also the name of a scoped bot in /foo; its instances live in the + // scoped key range. + scopedAIds := createInstances(t, ctx, service, "a", 2, withBotInstanceScope("/foo")) - // listing "a" should only return known "a" instances + // listing "a" should only return known instances of the unscoped bot "a" aInstances := listInstances(t, ctx, service, &services.ListBotInstancesRequestOptions{ FilterBotName: "a", }) @@ -374,6 +389,23 @@ func TestBotInstanceList(t *testing.T) { require.Contains(t, bIds, ins.GetSpec().GetInstanceId()) } + // listing "a" in scope /foo should only return the scoped bot's instances + scopedAInstances := listInstances(t, ctx, service, &services.ListBotInstancesRequestOptions{ + FilterBotName: "a", + FilterBotScope: "/foo", + }) + require.Len(t, scopedAInstances, 2) + for _, ins := range scopedAInstances { + require.Contains(t, scopedAIds, ins.GetSpec().GetInstanceId()) + } + + // a scope filter only qualifies a bot name, so listing scope /foo without a + // bot name is rejected rather than listing the whole scope + _, _, err = service.ListBotInstances(ctx, 0, "", &services.ListBotInstancesRequestOptions{ + FilterBotScope: "/foo", + }) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got %v", err) + allIds := map[string]struct{}{} for i := range aIds { allIds[i] = struct{}{} @@ -381,17 +413,113 @@ func TestBotInstanceList(t *testing.T) { for i := range bIds { allIds[i] = struct{}{} } + for i := range scopedAIds { + allIds[i] = struct{}{} + } - // Listing an empty bot name ("") should return all instances. + // Listing with no bot filter should return all instances, unscoped and + // scoped. allInstances := listInstances(t, ctx, service, &services.ListBotInstancesRequestOptions{ FilterBotName: "", }) - require.Len(t, allInstances, 7) + require.Len(t, allInstances, 9) for _, ins := range allInstances { require.Contains(t, allIds, ins.GetSpec().GetInstanceId()) } } +// TestBotInstanceScopedCoexistence verifies that instances of same-named bots +// in different scopes (and unscoped) are stored disjointly and are only +// addressable under their own bot's scope. +func TestBotInstanceScopedCoexistence(t *testing.T) { + t.Parallel() + + ctx := t.Context() + clock := clockwork.NewFakeClock() + + mem, err := memory.New(memory.Config{ + Context: ctx, + Clock: clock, + }) + require.NoError(t, err) + + service, err := NewBotInstanceService(backend.NewSanitizer(mem), clock) + require.NoError(t, err) + + // Three bots named "x": unscoped, in /foo, and in /bar. + unscoped := newBotInstance("x") + foo := newBotInstance("x", withBotInstanceScope("/foo")) + bar := newBotInstance("x", withBotInstanceScope("/bar")) + for _, bi := range []*machineidv1.BotInstance{unscoped, foo, bar} { + _, err := service.CreateBotInstance(ctx, bi) + require.NoError(t, err) + } + + // The scoped instance is stored in the scope-namespaced key range. + encodedScope, err := scopes.EncodeForKey("/foo") + require.NoError(t, err) + _, err = mem.Get(ctx, backend.NewKey(scopedPrefix, botInstancePrefix, encodedScope, "x", foo.GetSpec().GetInstanceId())) + require.NoError(t, err) + + // Each instance resolves only under its own bot's scope. + got, err := service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: "x", InstanceId: unscoped.GetSpec().GetInstanceId()}.Build()) + require.NoError(t, err) + require.Empty(t, got.GetScope()) + got, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "/foo", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build()) + require.NoError(t, err) + require.Equal(t, "/foo", got.GetScope()) + + // The wrong (or missing) scope does not resolve. + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build()) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "/bar", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build()) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "/foo", BotName: "x", InstanceId: unscoped.GetSpec().GetInstanceId()}.Build()) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + + // Patching routes by scope, and the scope itself cannot be patched. + patched, err := service.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Scope: "/foo", Name: "x"}, + InstanceID: foo.GetSpec().GetInstanceId(), + UpdateFn: func(bi *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { + bi.GetStatus().SetLatestHeartbeats([]*machineidv1.BotInstanceStatusHeartbeat{ + machineidv1.BotInstanceStatusHeartbeat_builder{Hostname: "scoped-host"}.Build(), + }) + return bi, nil + }, + }) + require.NoError(t, err) + require.Equal(t, "/foo", patched.GetScope()) + + _, err = service.PatchBotInstance(ctx, services.PatchBotInstanceOpts{ + Bot: scopes.QualifiedName{Scope: "/foo", Name: "x"}, + InstanceID: foo.GetSpec().GetInstanceId(), + UpdateFn: func(bi *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { + bi.SetScope("/other") + return bi, nil + }, + }) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got %v", err) + require.ErrorContains(t, err, "scope: cannot be patched") + + // Deleting under the wrong scope does not delete; the right scope deletes + // only that bot's instance. + err = service.DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "/bar", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build()) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + require.NoError(t, service.DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "/foo", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build())) + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "/foo", BotName: "x", InstanceId: foo.GetSpec().GetInstanceId()}.Build()) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "", BotName: "x", InstanceId: unscoped.GetSpec().GetInstanceId()}.Build()) + require.NoError(t, err) + _, err = service.GetBotInstance(ctx, machineidv1.GetBotInstanceRequest_builder{BotScope: "/bar", BotName: "x", InstanceId: bar.GetSpec().GetInstanceId()}.Build()) + require.NoError(t, err) + + // DeleteAllBotInstances empties both the unscoped and scoped ranges. + require.NoError(t, service.DeleteAllBotInstances(ctx)) + remaining := listInstances(t, ctx, service, nil) + require.Empty(t, remaining) +} + // TestBotInstanceListWithSearchFilter verifies list and filtering with search // term functionality for bot instances. func TestBotInstanceListWithSearchFilter(t *testing.T) { diff --git a/lib/services/local/events.go b/lib/services/local/events.go index 3cbf24161de1..374bf5916280 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -3278,7 +3278,10 @@ func (p *globalNotificationParser) parse(event backend.Event) (types.Resource, e func newBotInstanceParser() *botInstanceParser { return &botInstanceParser{ - baseParser: newBaseParser(backend.NewKey(botInstancePrefix)), + baseParser: newBaseParser( + botInstanceUnscopedWatchPrefix(), + botInstanceScopedWatchPrefix(), + ), } } @@ -3289,23 +3292,22 @@ type botInstanceParser struct { func (p *botInstanceParser) parse(event backend.Event) (types.Resource, error) { switch event.Type { case types.OpDelete: - parts := event.Item.Key.Components() - if len(parts) != 3 { - return nil, trace.BadParameter("malformed key for %s event: %s", types.KindBotInstance, event.Item.Key) + bot, instanceID, err := botInstanceNameFromKey(event.Item.Key) + if err != nil { + return nil, trace.Wrap(err) } - botInstance := machineidv1.BotInstance_builder{ Kind: types.KindBotInstance, Version: types.V1, + Scope: bot.Scope, Spec: machineidv1.BotInstanceSpec_builder{ - BotName: parts[1], - InstanceId: parts[2], + BotName: bot.Name, + InstanceId: instanceID, }.Build(), Metadata: headerv1.Metadata_builder{ - Name: parts[2], + Name: instanceID, }.Build(), }.Build() - return types.Resource153ToLegacy(botInstance), nil case types.OpPut: botInstance, err := services.UnmarshalBotInstance( @@ -3321,6 +3323,49 @@ func (p *botInstanceParser) parse(event backend.Event) (types.Resource, error) { } } +// botInstanceNameFromKey parses a bot instance backend key into the qualified +// name of the owning bot and the instance ID. The scope must be carried on +// delete events: consumers (e.g. the cache) key their stores by +// (scope, bot name, instance id). +func botInstanceNameFromKey(key backend.Key) (scopes.QualifiedName, string, error) { + switch { + case key.HasPrefix(botInstanceScopedWatchPrefix()): + components := key.TrimPrefix(botInstanceScopedWatchPrefix()).Components() + if len(components) != 3 { + return scopes.QualifiedName{}, "", trace.BadParameter( + "expected 3 components, got %d parsing backend key %v", + len(components), + key.String(), + ) + } + encodedScope, botName, instanceID := components[0], components[1], components[2] + scope, err := scopes.DecodeFromKey(encodedScope) + if err != nil { + return scopes.QualifiedName{}, "", trace.Wrap(err) + } + return scopes.QualifiedName{ + Scope: scope, + Name: botName, + }, instanceID, nil + case key.HasPrefix(botInstanceUnscopedWatchPrefix()): + components := key.TrimPrefix(botInstanceUnscopedWatchPrefix()).Components() + if len(components) != 2 { + return scopes.QualifiedName{}, "", trace.BadParameter( + "expected 2 components, got %d parsing backend key %v", + len(components), + key.String(), + ) + } + return scopes.QualifiedName{ + Name: components[0], + }, components[1], nil + default: + return scopes.QualifiedName{}, "", trace.BadParameter( + "unexpected prefix parsing backend key %v", key.String(), + ) + } +} + func newInstanceParser() *instanceParser { return &instanceParser{ baseParser: newBaseParser(backend.NewKey(instancePrefix)), diff --git a/lib/services/local/generic/scopeaware_wrapper.go b/lib/services/local/generic/scopeaware_wrapper.go index cacb763360bc..c0d190527529 100644 --- a/lib/services/local/generic/scopeaware_wrapper.go +++ b/lib/services/local/generic/scopeaware_wrapper.go @@ -71,7 +71,59 @@ func NewScopeAwareServiceWrapper[T ScopedResourceMetadata](cfg ScopeAwareService if err != nil { return nil, trace.Wrap(err) } - return &ScopeAwareServiceWrapper[T]{service: service}, nil + + // TODO(strideynet): I'm not super keen on the way we instantiate a scoped + // and unscoped ServiceWrapper here. We already instantiate a scoped and + // unscoped service within ScopeAwareService. It feels pretty awkward that + // we then have a bunch of instantiated services floating around and that + // each one has to be used very carefully. There's just too many layers of + // indirection and it's not particularly cleanly layered. + // + // I see a few alternatives: + // + // 1. Re-architect ScopedAwareServiceWrapper to no longer depend on + // ScopeAwareService and instead directly use the scoped and unscoped + // ServiceWrappers. This effectively leaves us with ScopeAwareService + // implemented twice (so a bit of duplication), but removes a layer of + // indirection. + // 2. Remove scopedResourceMetadataAdapter and instead add GetScope directly + // to resourceMetadataAdapter. This lets us directly wrap the Service + // returned by ScopeAwareService.WithScopePrefix with a ServiceWrapper? + // It keeps the indirection thru ScopeAwareService but avoids us forking + // and instantiating ServiceWrappers here... + // 3. Introduce /another/ wrapper designed to wrap the Service[T] returned + // by ScopeAwareService.WithScopePrefix? + newSingleRangeWrapper := func(prefix backend.Key) (*ServiceWrapper[T], error) { + return NewServiceWrapper(ServiceConfig[T]{ + Backend: cfg.Backend, + ResourceKind: cfg.ResourceKind, + PageLimit: cfg.PageLimit, + BackendPrefix: prefix, + MarshalFunc: cfg.MarshalFunc, + UnmarshalFunc: cfg.UnmarshalFunc, + ValidateFunc: cfg.ValidateFunc, + RunWhileLockedRetryInterval: cfg.RunWhileLockedRetryInterval, + }) + } + + scopedWrapper, err := newSingleRangeWrapper(cfg.ScopedBackendPrefix) + if err != nil { + return nil, trace.Wrap(err) + } + + var unscopedWrapper *ServiceWrapper[T] + if !cfg.ScopedOnly { + unscopedWrapper, err = newSingleRangeWrapper(cfg.UnscopedBackendPrefix) + if err != nil { + return nil, trace.Wrap(err) + } + } + + return &ScopeAwareServiceWrapper[T]{ + service: service, + unscopedWrapper: unscopedWrapper, + scopedWrapper: scopedWrapper, + }, nil } // ScopeAwareServiceWrapperConfig holds configuration options for a @@ -111,6 +163,50 @@ type ScopeAwareServiceWrapperConfig[T ScopedResourceMetadata] struct { // exported; additional methods may be exported in the future as needed. type ScopeAwareServiceWrapper[T ScopedResourceMetadata] struct { service *ScopeAwareService[scopedResourceMetadataAdapter[T]] + + // unscopedWrapper and scopedWrapper are single-range ServiceWrapper views + // over the same key ranges as service's unscoped and scoped halves. They + // exist because WithScopePrefix must return a *ServiceWrapper[T], and + // service's inner services are generic over the scoped adapter type rather + // than T. unscopedWrapper is nil when the service is scoped-only. + unscopedWrapper *ServiceWrapper[T] + scopedWrapper *ServiceWrapper[T] +} + +// WithScopePrefix returns a single-range ServiceWrapper routed by the given +// scope: the unscoped range when the scope is empty, otherwise the scoped +// range namespaced by the encoded scope. It is the ServiceWrapper analog of +// ScopeAwareService.WithScopePrefix, for callers that need to address one +// scope's key range directly — e.g. to key dependent resources under a +// sub-prefix via WithPrefix on the returned wrapper. +func (s *ScopeAwareServiceWrapper[T]) WithScopePrefix(scope string) (*ServiceWrapper[T], error) { + if scope == "" { + if s.unscopedWrapper == nil { + return nil, trace.BadParameter("scoped-only storage service received an empty scope") + } + return s.unscopedWrapper, nil + } + encodedScope, err := scopes.EncodeForKey(scope) + if err != nil { + return nil, trace.Wrap(err) + } + return s.scopedWrapper.WithPrefix(encodedScope), nil +} + +// WithScopedResourcePrefix returns a single-range ServiceWrapper with a prefix +// for the given scope-qualified name appended to the backend prefix: the +// unscoped range when the scope is empty, otherwise the scoped range +// namespaced by the encoded scope. It is the ServiceWrapper analog of +// ScopeAwareService.WithScopedResourcePrefix. +// +// This may be appropriate for dependent resources keyed by a unique scoped +// resource, i.e. the instances of a scoped bot. +func (s *ScopeAwareServiceWrapper[T]) WithScopedResourcePrefix(scopedName scopes.QualifiedName) (*ServiceWrapper[T], error) { + service, err := s.WithScopePrefix(scopedName.Scope) + if err != nil { + return nil, trace.Wrap(err) + } + return service.WithPrefix(scopedName.Name), nil } // CreateResource creates the given resource if it doesn't already exist. The diff --git a/lib/services/local/generic/scopeaware_wrapper_test.go b/lib/services/local/generic/scopeaware_wrapper_test.go index 459374c08d05..daa567da2a81 100644 --- a/lib/services/local/generic/scopeaware_wrapper_test.go +++ b/lib/services/local/generic/scopeaware_wrapper_test.go @@ -576,6 +576,182 @@ func TestScopeAwareServiceWrapper_MakeBackendItem(t *testing.T) { } } +func TestScopeAwareServiceWrapper_WithScopePrefix(t *testing.T) { + t.Parallel() + + t.Run("empty scope routes to the unscoped range", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + ctx := t.Context() + + qn := scopes.QualifiedName{Name: "foo"} + _, err := svc.CreateResource(ctx, newScopedTestResource153(qn)) + require.NoError(t, err) + + routed, err := svc.WithScopePrefix("") + require.NoError(t, err) + require.Equal(t, + backend.NewKey(testUnscopedPrefix, "foo").String(), + routed.BackendKey("foo").String(), + ) + + got, err := routed.GetResource(ctx, "foo") + require.NoError(t, err) + requireResourceBody(t, qn, got) + }) + + t.Run("scope routes to its scoped range", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + ctx := t.Context() + + qn := scopes.QualifiedName{Scope: "/security", Name: "foo"} + _, err := svc.CreateResource(ctx, newScopedTestResource153(qn)) + require.NoError(t, err) + + routed, err := svc.WithScopePrefix("/security") + require.NoError(t, err) + encodedScope, err := scopes.EncodeForKey("/security") + require.NoError(t, err) + require.Equal(t, + backend.NewKey(testScopedTopPrefix, testUnscopedPrefix, encodedScope, "foo").String(), + routed.BackendKey("foo").String(), + ) + + got, err := routed.GetResource(ctx, "foo") + require.NoError(t, err) + requireResourceBody(t, qn, got) + + // A different scope's routed wrapper does not see it. + other, err := svc.WithScopePrefix("/other") + require.NoError(t, err) + _, err = other.GetResource(ctx, "foo") + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + }) + + t.Run("sub-prefix keys dependent resources under the scope", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + ctx := t.Context() + + qn := scopes.QualifiedName{Scope: "/security", Name: "child"} + routed, err := svc.WithScopePrefix(qn.Scope) + require.NoError(t, err) + sub := routed.WithPrefix("parent") + + _, err = sub.CreateResource(ctx, newScopedTestResource153(qn)) + require.NoError(t, err) + + encodedScope, err := scopes.EncodeForKey(qn.Scope) + require.NoError(t, err) + require.Equal(t, + backend.NewKey(testScopedTopPrefix, testUnscopedPrefix, encodedScope, "parent", "child").String(), + sub.BackendKey("child").String(), + ) + + got, err := sub.GetResource(ctx, "child") + require.NoError(t, err) + requireResourceBody(t, qn, got) + + // The resource is not addressable at the scope's top level... + _, err = svc.GetResource(ctx, qn) + require.True(t, trace.IsNotFound(err), "expected NotFound, got %v", err) + + // ...but the unified list still ranges over it. + page, next, err := svc.ListResources(ctx, 100, "") + require.NoError(t, err) + require.Empty(t, next) + require.Equal(t, []scopes.QualifiedName{qn}, names(page)) + }) + + t.Run("invalid scope errors", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + _, err := svc.WithScopePrefix("/not valid") + require.Error(t, err) + }) + + t.Run("scoped-only rejects the empty scope", func(t *testing.T) { + t.Parallel() + memBackend, err := memory.New(memory.Config{ + Context: t.Context(), + Clock: clockwork.NewFakeClock(), + }) + require.NoError(t, err) + svc, err := NewScopeAwareServiceWrapper(ScopeAwareServiceWrapperConfig[*scopedTestResource153]{ + ScopedOnly: true, + Backend: memBackend, + ResourceKind: "scoped_generic_resource", + ScopedBackendPrefix: backend.NewKey(testScopedTopPrefix, testUnscopedPrefix), + PageLimit: 200, + MarshalFunc: marshalScopedResource153, + UnmarshalFunc: unmarshalScopedResource153, + }) + require.NoError(t, err) + + _, err = svc.WithScopePrefix("") + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got %v", err) + + routed, err := svc.WithScopePrefix("/security") + require.NoError(t, err) + require.NotNil(t, routed) + }) +} + +func TestScopeAwareServiceWrapper_WithScopedResourcePrefix(t *testing.T) { + t.Parallel() + + t.Run("empty scope keys under the unscoped range", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + + routed, err := svc.WithScopedResourcePrefix(scopes.QualifiedName{Name: "parent"}) + require.NoError(t, err) + require.Equal(t, + backend.NewKey(testUnscopedPrefix, "parent", "child").String(), + routed.BackendKey("child").String(), + ) + }) + + t.Run("scope keys under its scoped range", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + ctx := t.Context() + + qn := scopes.QualifiedName{Scope: "/security", Name: "child"} + routed, err := svc.WithScopedResourcePrefix(scopes.QualifiedName{Scope: qn.Scope, Name: "parent"}) + require.NoError(t, err) + + encodedScope, err := scopes.EncodeForKey(qn.Scope) + require.NoError(t, err) + require.Equal(t, + backend.NewKey(testScopedTopPrefix, testUnscopedPrefix, encodedScope, "parent", "child").String(), + routed.BackendKey("child").String(), + ) + + _, err = routed.CreateResource(ctx, newScopedTestResource153(qn)) + require.NoError(t, err) + got, err := routed.GetResource(ctx, "child") + require.NoError(t, err) + requireResourceBody(t, qn, got) + + // Equivalent to routing by scope and adding the name prefix separately. + twoStep, err := svc.WithScopePrefix(qn.Scope) + require.NoError(t, err) + require.Equal(t, + twoStep.WithPrefix("parent").BackendKey("child").String(), + routed.BackendKey("child").String(), + ) + }) + + t.Run("invalid scope errors", func(t *testing.T) { + t.Parallel() + svc := newScopeAwareWrapperForTest(t) + _, err := svc.WithScopedResourcePrefix(scopes.QualifiedName{Scope: "/not valid", Name: "parent"}) + require.Error(t, err) + }) +} + func TestScopeAwareServiceWrapper_BackendKey(t *testing.T) { t.Parallel() svc := newScopeAwareWrapperForTest(t) diff --git a/lib/web/machineid_test.go b/lib/web/machineid_test.go index fe106a17d98e..80b71cf5edcd 100644 --- a/lib/web/machineid_test.go +++ b/lib/web/machineid_test.go @@ -883,7 +883,7 @@ func TestListBotInstancesPaging(t *testing.T) { // remove instances before next test for n := range tc.numInstances { - err = env.server.Auth().DeleteBotInstance(ctx, "bot-1", "instance-"+strconv.Itoa(n)) + err = env.server.Auth().DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "", BotName: "bot-1", InstanceId: "instance-" + strconv.Itoa(n)}.Build()) require.NoError(t, err) } }) @@ -1130,7 +1130,7 @@ func TestListBotInstancesWithSearchTermFilter(t *testing.T) { assert.Equal(t, "00000000-0000-0000-0000-000000000000", instances.BotInstances[0].InstanceId) // remove before next test - err = env.server.Auth().DeleteBotInstance(ctx, spec.GetBotName(), spec.GetInstanceId()) + err = env.server.Auth().DeleteBotInstance(ctx, machineidv1.DeleteBotInstanceRequest_builder{BotScope: "", BotName: spec.GetBotName(), InstanceId: spec.GetInstanceId()}.Build()) require.NoError(t, err) }) } From 3ddc9b53cc6ddda444ca1915e37ffaea335c2191 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 31 Jul 2026 10:31:07 +0200 Subject: [PATCH 076/347] Update `e` (#69219) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 00680295366d..8303b4cdbf09 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 00680295366d3acd802959ce432cb98449b4a199 +Subproject commit 8303b4cdbf09304a23e37d814cb2868987f1431c From d1381cab2342d821b5677bcefb68e093c25d6bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:36:00 +0200 Subject: [PATCH 077/347] `tctl devices add`: Derive `--os` flag from `devicepb.OSType` (#69136) * `tctl devices add`: Derive `--os` flag from `devicepb.OSType` * Base accepted `--os` values on the api layer --- docs/pages/reference/cli/tctl.mdx | 2 +- tool/tctl/common/devices.go | 40 +++++++++++++++-------------- tool/tctl/common/devices_test.go | 42 +++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/docs/pages/reference/cli/tctl.mdx b/docs/pages/reference/cli/tctl.mdx index 58dcb42d9f38..f9df1be4b209 100644 --- a/docs/pages/reference/cli/tctl.mdx +++ b/docs/pages/reference/cli/tctl.mdx @@ -1469,7 +1469,7 @@ Flags: |`--format`|`text` (one of: `text`, `json`, `yaml`)|Output format.| |`--[no-]current-device`|`false`|Registers the current device. Overrides --os and --asset-tag.| |`--[no-]enroll`|`false`|If set, creates a device enrollment token| -|`--os`|*no default* (one of: `linux`, `macos`, `windows`)|Operating system| +|`--os`|*no default* (one of: `ios`, `ipados`, `linux`, `macos`, `windows`)|Operating system| ## tctl devices enroll diff --git a/tool/tctl/common/devices.go b/tool/tctl/common/devices.go index 8e5a5355834a..312036e5cbfb 100644 --- a/tool/tctl/common/devices.go +++ b/tool/tctl/common/devices.go @@ -62,20 +62,19 @@ type DevicesCommand struct { Stdout io.Writer } -type osType = string - -const ( - linuxType osType = "linux" - macosType osType = "macos" - windowsType osType = "windows" -) - -var osTypes = []string{linuxType, macosType, windowsType} - -var osTypeToEnum = map[osType]devicepb.OSType{ - linuxType: devicepb.OSType_OS_TYPE_LINUX, - macosType: devicepb.OSType_OS_TYPE_MACOS, - windowsType: devicepb.OSType_OS_TYPE_WINDOWS, +// osTypeFlagValues returns the accepted --os values: every [devicepb.OSType] +// except UNSPECIFIED, spelled the way api spells it. +func osTypeFlagValues() []string { + values := make([]string, 0, len(devicepb.OSType_name)-1) + for num := range devicepb.OSType_name { + osType := devicepb.OSType(num) + if osType == devicepb.OSType_OS_TYPE_UNSPECIFIED { + continue + } + values = append(values, types.ResourceOSTypeToString(osType)) + } + sort.Strings(values) + return values } func (c *DevicesCommand) Initialize(app *kingpin.Application, _ *tctlcfg.GlobalCLIFlags, cfg *servicecfg.Config) { @@ -85,7 +84,7 @@ func (c *DevicesCommand) Initialize(app *kingpin.Application, _ *tctlcfg.GlobalC addCmd := devicesCmd.Command("add", "Register managed devices.") addCmd.Flag("os", "Operating system"). - EnumVar(&c.add.os, osTypes...) + EnumVar(&c.add.os, osTypeFlagValues()...) addCmd.Flag("asset-tag", "Inventory identifier for the device (e.g., Mac serial number)"). StringVar(&c.add.assetTag) addCmd.Flag("current-device", "Registers the current device. Overrides --os and --asset-tag."). @@ -193,10 +192,13 @@ func (c *deviceAddCommand) Run(ctx context.Context, authClient *authclient.Clien } if c.os != "" { - var ok bool - c.osType, ok = osTypeToEnum[c.os] - if !ok { - return trace.BadParameter("invalid --os: %v", c.os) + var err error + // kingpin makes sure that c.os is set to one of the values returned from + // [osTypeFlagValues], so we don't have to worry about "unspecified" here. + if c.osType, err = types.ResourceOSTypeFromString(c.os); err != nil { + // No need to re-wrap err into something specific to the --os flag, as at + // this point we know that c.os points to a valid OSType. + return trace.Wrap(err) } } diff --git a/tool/tctl/common/devices_test.go b/tool/tctl/common/devices_test.go index 852d1530d2da..35f7be2640e8 100644 --- a/tool/tctl/common/devices_test.go +++ b/tool/tctl/common/devices_test.go @@ -22,12 +22,16 @@ import ( "time" "github.com/gravitational/trace" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/service/servicecfg" + "github.com/gravitational/teleport/lib/utils" + tctlcfg "github.com/gravitational/teleport/tool/tctl/common/config" ) func TestDeviceSourceToString(t *testing.T) { @@ -148,3 +152,41 @@ func TestWriteCreatedLock(t *testing.T) { require.True(t, trace.IsBadParameter(err), "expected BadParameter, got %v", err) }) } + +// TestOSTypeFlagValues covers the --os values offered by `tctl devices add`. +// They come from devicepb.OSType, so an OSType that ResourceOSTypeToString +// doesn't know about would reach the CLI as its bare proto name. +func TestOSTypeFlagValues(t *testing.T) { + t.Parallel() + + require.Equal(t, []string{"ios", "ipados", "linux", "macos", "windows"}, + osTypeFlagValues(), "osTypeFlagValues mismatch") +} + +// TestDeviceAddOSFlag covers the --os values `tctl devices add` accepts. +// The flag is the only thing keeping UNSPECIFIED out, as +// [types.ResourceOSTypeFromString] resolves "unspecified" without an error. +func TestDeviceAddOSFlag(t *testing.T) { + t.Parallel() + + parse := func(t *testing.T, os string) error { + t.Helper() + c := DevicesCommand{} + app := utils.InitCLIParser("tctl", GlobalHelpString) + c.Initialize(app, &tctlcfg.GlobalCLIFlags{}, servicecfg.MakeDefaultConfig()) + _, err := app.Parse([]string{"devices", "add", "--os", os, "--asset-tag", "C00AA0AAAA0A"}) + return err + } + + for _, os := range osTypeFlagValues() { + t.Run(os, func(t *testing.T) { + assert.NoError(t, parse(t, os), "--os %v rejected", os) + }) + } + + for _, os := range []string{"unspecified", "OS_TYPE_MACOS", "bogus"} { + t.Run("rejects "+os, func(t *testing.T) { + assert.ErrorContains(t, parse(t, os), "enum value must be one of ios,", "--os %v accepted", os) + }) + } +} From 3d82a6efe4a0c9eca0ff514041832c327a6728b1 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 31 Jul 2026 11:01:36 +0200 Subject: [PATCH 078/347] Enable `useUnknownInCatchVariables` in TypeScript (#68996) * Enable `useUnknownInCatchVariables` * Add `isErrnoException` * Switch from unsafe `error.message` access to `getErrorMessage`/`ensureError` * Make `serializeError` idempotent * `TypeError` -> `unknown` * Simplify `isErrnoException`, require `code` * Create `isPermissionDeniedError` utility * Make `isPermissionDeniedError` inline helper --- tsconfig.base.json | 1 + .../RequestReview/RequestReview.tsx | 28 ++++++- .../RequestView/RequestView.test.tsx | 20 +++-- .../DesktopSession/useDesktopSession.tsx | 6 +- .../createFileTransferEventsEmitter.ts | 4 +- web/packages/shared/hooks/useAsync.ts | 8 +- web/packages/shared/hooks/useAttempt.ts | 9 ++- web/packages/shared/hooks/useAttemptNext.ts | 5 +- .../useKeyBasedPagination.ts | 7 +- web/packages/shared/libs/tdp/client.ts | 6 +- web/packages/shared/utils/error.test.ts | 77 ++++++++++++++++++- web/packages/shared/utils/error.ts | 17 ++++ .../teleport/src/AppLauncher/AppLauncher.tsx | 7 +- .../Add/GitHubActionsSsh/ConnectGitHub.tsx | 11 ++- .../teleport/src/Bots/Add/Shared/github.ts | 3 +- .../teleport/src/BrowserMFA/BrowserMFA.tsx | 3 +- .../CreateAppAccess/CreateAppAccess.tsx | 3 +- .../CreateDatabase/useCreateDatabase.ts | 2 +- .../DiscoveryConfigSsm/DiscoveryConfigSsm.tsx | 3 +- .../useConnectionDiagnostic.ts | 3 +- .../src/Roles/RoleEditor/RoleEditor.tsx | 15 ++-- .../view/player/tty/TtyPlayer.ts | 2 +- .../ClusterDropdown/ClusterDropdown.tsx | 5 +- .../ReAuthenticate/useReAuthenticate.ts | 10 ++- .../teleport/src/lib/term/terminal.ts | 2 +- web/packages/teleport/src/lib/useMfa.ts | 3 +- web/packages/teleport/src/services/api/api.ts | 8 +- web/packages/teleport/src/services/bot/bot.ts | 3 +- .../src/services/version/unsupported.ts | 13 ++-- .../agentCleanupDaemon.test.ts | 4 +- web/packages/teleterm/src/deepLinks.ts | 10 ++- web/packages/teleterm/src/main.ts | 10 ++- .../agentDownloader/agentDownloader.ts | 3 +- .../clusterLifecycleManager.ts | 3 +- .../src/mainProcess/createAgentConfigFile.ts | 3 +- .../src/mainProcess/ipcSerializer.test.ts | 14 ++++ .../teleterm/src/mainProcess/ipcSerializer.ts | 37 +++++++-- .../loadInstallationId/loadInstallationId.ts | 4 +- .../src/mainProcess/mainProcessClient.ts | 4 +- .../profileWatcher/profileWatcher.test.ts | 5 +- .../profileWatcher/profileWatcher.ts | 5 +- .../src/mainProcess/windowsManager.ts | 3 +- .../appUpdater/nsisDualModeUpdater.ts | 4 +- .../src/services/config/configService.ts | 2 +- .../src/services/fileStorage/fileStorage.ts | 9 ++- .../src/services/grpcCredentials/files.ts | 3 +- .../src/services/tshd/cloneableClient.ts | 4 +- .../src/sharedProcess/ptyHost/ptyProcess.ts | 5 +- .../src/ui/AppInitializer/AppInitializer.tsx | 5 +- .../DocumentConnectMyComputer/Setup.tsx | 5 +- .../DocumentConnectMyComputer/Status.tsx | 3 +- .../src/ui/DeepLinks/launchDeepLink.ts | 3 +- .../src/ui/DocumentTerminal/Terminal/ctrl.ts | 7 +- .../DocumentTerminal/useDocumentTerminal.ts | 2 +- .../src/ui/Vnet/DocumentVnetDiagReport.tsx | 3 +- .../teleterm/src/ui/commandLauncher.ts | 6 +- .../ui/services/clusters/clustersService.ts | 12 +-- .../src/ui/services/usage/usageService.ts | 5 +- .../teleterm/src/ui/utils/retryWithRelogin.ts | 2 +- 59 files changed, 347 insertions(+), 122 deletions(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index d0c636d02eca..9980edc99f83 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,6 +15,7 @@ "strict": false, "strictBindCallApply": true, "noImplicitThis": true, + "useUnknownInCatchVariables": true, "noEmitHelpers": true, "resolveJsonModule": true, "skipLibCheck": true, diff --git a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx index 714d2d69c5e4..94a8a1be8e2f 100644 --- a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx +++ b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx @@ -310,10 +310,7 @@ function makeReviewStateOptions( // Connect) means the reviewer can't see the eligible Access Lists, which // is distinct from there being none. Detect it by status code rather than // message text so it survives across versions; surface other errors as-is. - const err = fetchSuggestedAccessListsAttempt.error; - const isPermissionError = - err?.response?.status === 403 || err?.code === 'PERMISSION_DENIED'; - msg = isPermissionError + msg = isPermissionDeniedError(fetchSuggestedAccessListsAttempt.error) ? "You don't have permission to view the Access Lists eligible for long-term approval of this request. You can still reject it." : fetchSuggestedAccessListsAttempt.statusText; } else if (request.resources.length === 0) { @@ -395,3 +392,26 @@ const HorizontalLine = styled.div<{ height?: number }>` function makeTraitLabel(traitKey: string, traitVals: string[]) { return `${traitKey}: ${traitVals.sort().join(', ')}`; } + +/** + * Checks whether an error represents a permission denial from either the Web + * API or a gRPC service. + * + * TODO(gzdunek): Consider passing a permission-error predicate so that Web UI and Connect + * can identify their own native error type. + */ +function isPermissionDeniedError(error: unknown): boolean { + if (typeof error !== 'object' || error === null) { + return false; + } + if ('code' in error && error.code === 'PERMISSION_DENIED') { + return true; + } + return ( + 'response' in error && + typeof error.response === 'object' && + error.response !== null && + 'status' in error.response && + error.response.status === 403 + ); +} diff --git a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx index 22009a19d545..9089adab211a 100644 --- a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx +++ b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx @@ -238,12 +238,22 @@ test('disables long-term approval and explains why when no Access List is sugges // A permission failure (the reviewer can't read the eligible Access Lists) is a // distinct state from there being none, and gets its own message. -test('shows a permission-specific message when the reviewer cannot view eligible Access Lists', async () => { +test.each([ + [ + 'HTTP 403', + Object.assign( + new Error('access denied to perform action "read" on access list'), + { response: { status: 403 } } + ), + ], + [ + 'gRPC PERMISSION_DENIED', + Object.assign(new Error('permission denied'), { + code: 'PERMISSION_DENIED', + }), + ], +])('shows a permission-specific message for %s', async (_, permissionError) => { const user = userEvent.setup(); - const permissionError = Object.assign( - new Error('access denied to perform action "read" on access list'), - { response: { status: 403 } } - ); render( = status: 'error'; data: null; statusText: string; - error: any; + error: unknown; }; export function hasFinished(attempt: Attempt): boolean { @@ -240,12 +242,12 @@ export function makeProcessingAttempt(): Attempt { }; } -export function makeErrorAttempt(error: Error): Attempt { +export function makeErrorAttempt(error: unknown): Attempt { return { data: null, status: 'error', error: error, - statusText: error.message, + statusText: getErrorMessage(error), }; } diff --git a/web/packages/shared/hooks/useAttempt.ts b/web/packages/shared/hooks/useAttempt.ts index 3ef8ff271550..27e14b46bff5 100644 --- a/web/packages/shared/hooks/useAttempt.ts +++ b/web/packages/shared/hooks/useAttempt.ts @@ -19,6 +19,7 @@ import { useMemo, useState } from 'react'; import Logger from 'shared/libs/logger'; +import { getErrorMessage } from 'shared/utils/error'; const logger = Logger.create('shared/hooks/useAttempt'); @@ -56,9 +57,13 @@ function makeActions(setState) { setState({ ...defaultState }); } - function error(err: Error) { + function error(err: unknown) { logger.error('attempt', err); - setState({ ...defaultState, isFailed: true, message: err.message }); + setState({ + ...defaultState, + isFailed: true, + message: getErrorMessage(err), + }); } function run(fn: Callback) { diff --git a/web/packages/shared/hooks/useAttemptNext.ts b/web/packages/shared/hooks/useAttemptNext.ts index f20851497a18..9b7e0ecdca82 100644 --- a/web/packages/shared/hooks/useAttemptNext.ts +++ b/web/packages/shared/hooks/useAttemptNext.ts @@ -19,6 +19,7 @@ import { useCallback, useState } from 'react'; import Logger from 'shared/libs/logger'; +import { getErrorMessage } from 'shared/utils/error'; const logger = Logger.create('shared/hooks/useAttempt'); @@ -33,9 +34,9 @@ export default function useAttemptNext(status = '' as Attempt['status']) { statusText: '', })); - const handleError = useCallback((err: Error) => { + const handleError = useCallback((err: unknown) => { logger.error('attempt', err); - setAttempt({ status: 'failed', statusText: err.message }); + setAttempt({ status: 'failed', statusText: getErrorMessage(err) }); }, []); const run = useCallback((fn: Callback) => { diff --git a/web/packages/shared/hooks/useInfiniteScroll/useKeyBasedPagination.ts b/web/packages/shared/hooks/useInfiniteScroll/useKeyBasedPagination.ts index 89424c68e19a..08064cf5abf5 100644 --- a/web/packages/shared/hooks/useInfiniteScroll/useKeyBasedPagination.ts +++ b/web/packages/shared/hooks/useInfiniteScroll/useKeyBasedPagination.ts @@ -26,6 +26,7 @@ import { import { Attempt } from 'shared/hooks/useAttemptNext'; import { isAbortError } from 'shared/utils/abortError'; +import { getErrorMessage } from 'shared/utils/error'; // eslint-disable-next-line no-restricted-imports -- FIXME import { ResourcesResponse } from 'teleport/services/agents'; @@ -147,7 +148,11 @@ export function useKeyBasedPagination({ } setState({ ...stateRef.current, - attempt: { status: 'failed', statusText: err.message, statusCode }, + attempt: { + status: 'failed', + statusText: getErrorMessage(err), + statusCode, + }, }); } }, diff --git a/web/packages/shared/libs/tdp/client.ts b/web/packages/shared/libs/tdp/client.ts index 8be66c0e73af..ba16bfdf5e6a 100644 --- a/web/packages/shared/libs/tdp/client.ts +++ b/web/packages/shared/libs/tdp/client.ts @@ -26,7 +26,7 @@ import init, { } from 'shared/libs/ironrdp/pkg/ironrdp'; // Inlines the wasm module as a static asset bundled with our app. import wasmUrl from 'shared/libs/ironrdp/pkg/ironrdp_bg.wasm?inline'; -import { ensureError, isAbortError } from 'shared/utils/error'; +import { ensureError, getErrorMessage, isAbortError } from 'shared/utils/error'; import { Alert, @@ -737,7 +737,7 @@ export class TdpClient extends EventEmitter { path: req.path, }, }); - this.handleWarning(e.message, TdpClientEvent.CLIENT_WARNING); + this.handleWarning(getErrorMessage(e), TdpClientEvent.CLIENT_WARNING); } } @@ -762,7 +762,7 @@ export class TdpClient extends EventEmitter { directoryId: req.directoryId, errCode: SharedDirectoryErrCode.Failed, }); - this.handleWarning(e.message, TdpClientEvent.CLIENT_WARNING); + this.handleWarning(getErrorMessage(e), TdpClientEvent.CLIENT_WARNING); } } diff --git a/web/packages/shared/utils/error.test.ts b/web/packages/shared/utils/error.test.ts index 71bbc6f363fd..382218207bdb 100644 --- a/web/packages/shared/utils/error.test.ts +++ b/web/packages/shared/utils/error.test.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { ensureError, isAbortError } from './error'; +import { ensureError, isAbortError, isErrnoException } from './error'; class CustomErrorClass extends Error { constructor(message: string) { @@ -115,6 +115,81 @@ describe('isAbortError', () => { }); }); +describe('isErrnoException', () => { + const cases: { + name: string; + input: unknown; + code: string; + expected: boolean; + }[] = [ + { + name: 'Error with a matching code', + input: Object.assign(new Error('failed'), { code: 'ENOENT' }), + code: 'ENOENT', + expected: true, + }, + { + name: 'AbortError with a matching code', + input: Object.assign(new Error('aborted'), { + name: 'AbortError', + code: 'ABORT_ERR', + }), + code: 'ABORT_ERR', + expected: true, + }, + { + name: 'Error with a non-matching code', + input: Object.assign(new Error('failed'), { code: 'EPERM' }), + code: 'ENOENT', + expected: false, + }, + { + name: 'Error without a code, expected code', + input: new Error('failed'), + code: 'ENOENT', + expected: false, + }, + { + name: 'serialized error with a code', + input: { + name: 'Error', + message: 'failed', + code: 'ENOENT', + }, + code: 'ENOENT', + expected: true, + }, + { + name: 'object with a non-string code', + input: { code: 123 }, + code: 'ENOENT', + expected: false, + }, + { + name: 'string', + input: 'ENOENT', + code: 'ENOENT', + expected: false, + }, + { + name: 'null', + input: null, + code: 'ENOENT', + expected: false, + }, + { + name: 'undefined', + input: undefined, + code: 'ENOENT', + expected: false, + }, + ]; + + test.each(cases)('$name', ({ input, code, expected }) => { + expect(isErrnoException(input, code)).toBe(expected); + }); +}); + function newDOMAbortError() { return new DOMException('Aborted', 'AbortError'); } diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts index fa28612a6499..121eb0d9ea98 100644 --- a/web/packages/shared/utils/error.ts +++ b/web/packages/shared/utils/error.ts @@ -83,3 +83,20 @@ export class AbortError extends DOMException { super(message, 'AbortError'); } } + +/** + * Checks whether an error has the expected Node.js system error code (errno exception). + * Works with both Error instances and serialized errors. + * + * This uses the conventional `error.code` check rather than attempting to + * identify the error's runtime class. Callers should pass a known system error + * code such as `ENOENT`, `EPERM`, or `ESRCH`. + */ +export function isErrnoException(error: unknown, code: string): boolean { + return ( + !!error && + typeof error === 'object' && + 'code' in error && + error.code === code + ); +} diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx index 5f352d461eb6..81fc1ee36ef2 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx @@ -22,6 +22,7 @@ import { useLocation, useParams } from 'react-router'; import { Flex, Indicator } from 'design'; import { AccessDenied } from 'design/CardError'; import useAttempt from 'shared/hooks/useAttemptNext'; +import { getErrorMessage } from 'shared/utils/error'; import AuthnDialog from 'teleport/components/AuthnDialog'; import { CreateAppSessionParams, UrlLauncherParams } from 'teleport/config'; @@ -207,7 +208,7 @@ export function AppLauncher({ // `fetch` returns `TypeError` when there is a network error. statusText = `Unable to access "${fqdn}". This may happen if your Teleport Proxy is using an untrusted or self-signed certificate. Please ensure Teleport Proxy service uses a valid certificate or access the application domain directly (https://${fqdn}${port}) and accept the certificate exception from your browser.`; } else if (isRedirectFlow) { - statusText = `Error while authenticating a required app: ${err.message}`; + statusText = `Error while authenticating a required app: ${getErrorMessage(err)}`; } else if (err instanceof Error) { statusText = err.message; } @@ -354,6 +355,6 @@ function getNewAuthExchangeUrl({ return url; } -function throwFailedToParseUrlError(err: TypeError) { - throw Error(`Failed to parse URL: ${err.message}`); +function throwFailedToParseUrlError(err: unknown) { + throw Error(`Failed to parse URL: ${getErrorMessage(err)}`); } diff --git a/web/packages/teleport/src/Bots/Add/GitHubActionsSsh/ConnectGitHub.tsx b/web/packages/teleport/src/Bots/Add/GitHubActionsSsh/ConnectGitHub.tsx index bdc51348e2c7..c9dba8abc114 100644 --- a/web/packages/teleport/src/Bots/Add/GitHubActionsSsh/ConnectGitHub.tsx +++ b/web/packages/teleport/src/Bots/Add/GitHubActionsSsh/ConnectGitHub.tsx @@ -32,6 +32,7 @@ import Link from 'design/Link'; import FieldInput from 'shared/components/FieldInput'; import Select from 'shared/components/Select'; import Validation, { Validator } from 'shared/components/Validation'; +import { getErrorMessage } from 'shared/utils/error'; import cfg from 'teleport/config'; @@ -308,10 +309,16 @@ const EnterpriseHostError = () => { ); }; -const InvalidHostError = ({ rule, error }: { rule: string; error: string }) => { +const InvalidHostError = ({ + rule, + error, +}: { + rule: string; + error: unknown; +}) => { return ( - Invalid address {rule}: {error} + Invalid address {rule}: {getErrorMessage(error)} ); }; diff --git a/web/packages/teleport/src/Bots/Add/Shared/github.ts b/web/packages/teleport/src/Bots/Add/Shared/github.ts index 1912a19bd373..612d33625e7c 100644 --- a/web/packages/teleport/src/Bots/Add/Shared/github.ts +++ b/web/packages/teleport/src/Bots/Add/Shared/github.ts @@ -18,6 +18,7 @@ import { Option } from 'shared/components/Select'; import { Rule } from 'shared/components/Validation/rules'; +import { getErrorMessage } from 'shared/utils/error'; import { RefType } from 'teleport/services/bot/types'; @@ -113,6 +114,6 @@ export const requireValidRepository: Rule = value => () => { return { valid: true }; } catch (e) { - return { valid: false, message: e?.message }; + return { valid: false, message: getErrorMessage(e) }; } }; diff --git a/web/packages/teleport/src/BrowserMFA/BrowserMFA.tsx b/web/packages/teleport/src/BrowserMFA/BrowserMFA.tsx index 07df330f9ddc..b1f46a18ede7 100644 --- a/web/packages/teleport/src/BrowserMFA/BrowserMFA.tsx +++ b/web/packages/teleport/src/BrowserMFA/BrowserMFA.tsx @@ -22,6 +22,7 @@ import { useParams } from 'react-router'; import { Flex, Indicator } from 'design'; import { AccessDenied, BadRequest } from 'design/CardError'; import useAttempt from 'shared/hooks/useAttemptNext'; +import { getErrorMessage } from 'shared/utils/error'; import AuthnDialog from 'teleport/components/AuthnDialog'; import { useMfa, shouldShowMfaPrompt } from 'teleport/lib/useMfa'; @@ -74,7 +75,7 @@ export function BrowserMfa({ onRedirect = redirectTo }: BrowserMfaProps) { } catch (err) { setAttempt({ status: 'failed', - statusText: err.message, + statusText: getErrorMessage(err), }); } } diff --git a/web/packages/teleport/src/Discover/AwsMangementConsole/CreateAppAccess/CreateAppAccess.tsx b/web/packages/teleport/src/Discover/AwsMangementConsole/CreateAppAccess/CreateAppAccess.tsx index 14ad8555dbfb..a7cbc8f26ae7 100644 --- a/web/packages/teleport/src/Discover/AwsMangementConsole/CreateAppAccess/CreateAppAccess.tsx +++ b/web/packages/teleport/src/Discover/AwsMangementConsole/CreateAppAccess/CreateAppAccess.tsx @@ -26,6 +26,7 @@ import { IconTooltip } from 'design/Tooltip'; import TextEditor from 'shared/components/TextEditor'; import Validation, { Validator } from 'shared/components/Validation'; import { useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy'; import cfg from 'teleport/config'; @@ -79,7 +80,7 @@ export function CreateAppAccess() { resourceName: app.name, }); } catch (err) { - emitErrorEvent(err.message); + emitErrorEvent(getErrorMessage(err)); throw err; } }); diff --git a/web/packages/teleport/src/Discover/Database/CreateDatabase/useCreateDatabase.ts b/web/packages/teleport/src/Discover/Database/CreateDatabase/useCreateDatabase.ts index aa896a41d7d8..f029a795049e 100644 --- a/web/packages/teleport/src/Discover/Database/CreateDatabase/useCreateDatabase.ts +++ b/web/packages/teleport/src/Discover/Database/CreateDatabase/useCreateDatabase.ts @@ -330,7 +330,7 @@ export function useCreateDatabase() { setAttempt({ status: '' }); } - function handleRequestError(err: Error, preErrMsg = '') { + function handleRequestError(err: unknown, preErrMsg = '') { const message = getErrMessage(err); setAttempt({ status: 'failed', statusText: `${preErrMsg}${message}` }); emitErrorEvent(`${preErrMsg}${message}`); diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx index d48821fc2245..8189576a9108 100644 --- a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx @@ -38,6 +38,7 @@ import { TextSelectCopyMulti } from 'shared/components/TextSelectCopy'; import Validation, { Validator } from 'shared/components/Validation'; import { Rule } from 'shared/components/Validation/rules'; import { makeEmptyAttempt, useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { LabelsInput } from 'teleport/components/LabelsInput'; import cfg from 'teleport/config'; @@ -176,7 +177,7 @@ export function DiscoveryConfigSsm() { }, }); } catch (err) { - emitErrorEvent(err.message); + emitErrorEvent(getErrorMessage(err)); throw err; } } diff --git a/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts b/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts index ba3dca5e5e55..8642c9f2a5ab 100644 --- a/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts +++ b/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts @@ -19,6 +19,7 @@ import { useState } from 'react'; import useAttempt from 'shared/hooks/useAttemptNext'; +import { getErrorMessage } from 'shared/utils/error'; import { getDatabaseProtocol, @@ -107,7 +108,7 @@ export function useConnectionDiagnostic() { } } catch (err) { handleError(err); - emitErrorEvent(err.message); + emitErrorEvent(getErrorMessage(err)); } return { mfaRequired: false }; diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx index 0f53efc5743d..6a5e3943c83d 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx @@ -28,6 +28,7 @@ import Dialog, { } from 'design/Dialog'; import Validation, { Validator } from 'shared/components/Validation'; import { Attempt, useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { CatchError } from 'teleport/components/CatchError'; import cfg from 'teleport/config'; @@ -417,12 +418,16 @@ const AttemptAlert = ({ attempt }: { attempt?: Attempt }) => { }; /** Renders an alert if there is an error. */ -const ErrorAlert = ({ error }: { error: Error }) => - error && ( - - {error.message} - +const ErrorAlert = ({ error }: { error: unknown }) => { + const details = error instanceof Error ? error.cause?.toString() : ''; + return ( + error && ( + + {getErrorMessage(error)} + + ) ); +}; const ShowHide = styled(Flex)<{ hidden: boolean }>` display: ${props => (props.hidden ? 'none' : '')}; diff --git a/web/packages/teleport/src/SessionRecordings/view/player/tty/TtyPlayer.ts b/web/packages/teleport/src/SessionRecordings/view/player/tty/TtyPlayer.ts index 88f3921b2ddf..c946a442e286 100644 --- a/web/packages/teleport/src/SessionRecordings/view/player/tty/TtyPlayer.ts +++ b/web/packages/teleport/src/SessionRecordings/view/player/tty/TtyPlayer.ts @@ -85,7 +85,7 @@ export class TtyPlayer extends Player { this.terminal.loadAddon(imageAddon); this.addons.push(imageAddon); } catch (e) { - this.logger.error(`Failed to load image addon: ${e.message}`); + this.logger.error('Failed to load image addon', e); } let webglAddon: WebglAddon | undefined; diff --git a/web/packages/teleport/src/components/ClusterDropdown/ClusterDropdown.tsx b/web/packages/teleport/src/components/ClusterDropdown/ClusterDropdown.tsx index 7ac064102013..d210e5f2ce69 100644 --- a/web/packages/teleport/src/components/ClusterDropdown/ClusterDropdown.tsx +++ b/web/packages/teleport/src/components/ClusterDropdown/ClusterDropdown.tsx @@ -23,6 +23,7 @@ import styled from 'styled-components'; import { Box, ButtonSecondary, Flex, Menu, MenuItem, Text } from 'design'; import { ChevronDown } from 'design/Icon'; import { HoverTooltip } from 'design/Tooltip'; +import { getErrorMessage } from 'shared/utils/error'; import cfg from 'teleport/config'; import { Cluster } from 'teleport/services/clusters'; @@ -84,7 +85,7 @@ export function ClusterDropdown({ try { return clusterLoader.fetchClusters(signal); } catch (err) { - onError(err.message); + onError(getErrorMessage(err)); } } @@ -115,7 +116,7 @@ export function ClusterDropdown({ const res = await loadClusters(signal.signal); setOptions(createOptions(res)); } catch (err) { - onError(err.message); + onError(getErrorMessage(err)); } } diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index 09e44e1b745a..807c0e074ed8 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -19,6 +19,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Attempt, makeEmptyAttempt, useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import auth from 'teleport/services/auth'; import { MfaChallengeScope } from 'teleport/services/auth/auth'; @@ -145,17 +146,18 @@ type challengeState = { deviceUsage: DeviceUsage; }; -function getReAuthenticationErrorMessage(err: Error): string { - if (err.message.includes('attempt was made to use an object that is not')) { +function getReAuthenticationErrorMessage(err: unknown): string { + const message = getErrorMessage(err); + if (message.includes('attempt was made to use an object that is not')) { // Catch a webauthn frontend error that occurs on Firefox and replace it with a more helpful error message. return 'The two-factor device you used is not registered on this account. You must verify using a device that has already been registered.'; } - if (err.message === 'invalid totp token') { + if (message === 'invalid totp token') { // This message relies on the status message produced by the auth server in // lib/auth/Server.checkOTP function. Please keep these in sync. return 'Invalid authenticator code'; } - return err.message; + return message; } diff --git a/web/packages/teleport/src/lib/term/terminal.ts b/web/packages/teleport/src/lib/term/terminal.ts index b62c604f4d04..c5927d6558e1 100644 --- a/web/packages/teleport/src/lib/term/terminal.ts +++ b/web/packages/teleport/src/lib/term/terminal.ts @@ -132,7 +132,7 @@ export default class TtyTerminal implements TerminalSearcher { this._imageAddon = new ImageAddon(); this.term.loadAddon(this._imageAddon); } catch (e) { - logger.error('Failed to load image addon:', e.message); + logger.error('Failed to load image addon', e); } try { diff --git a/web/packages/teleport/src/lib/useMfa.ts b/web/packages/teleport/src/lib/useMfa.ts index 82484ae73193..ded4f5b44ecd 100644 --- a/web/packages/teleport/src/lib/useMfa.ts +++ b/web/packages/teleport/src/lib/useMfa.ts @@ -24,6 +24,7 @@ import { makeEmptyAttempt, useAsync, } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; import { TermEvent } from 'teleport/lib/term/enums'; @@ -163,7 +164,7 @@ export function useMfa(props?: MfaProps): MfaState { setMfaAttempt({ data: null, status: 'error', - statusText: err.message, + statusText: getErrorMessage(err), error: err, }); } diff --git a/web/packages/teleport/src/services/api/api.ts b/web/packages/teleport/src/services/api/api.ts index 2c939bf5bf9d..01b7c8bd7a6d 100644 --- a/web/packages/teleport/src/services/api/api.ts +++ b/web/packages/teleport/src/services/api/api.ts @@ -17,6 +17,8 @@ */ import 'whatwg-fetch'; +import { getErrorMessage } from 'shared/utils/error'; + import auth, { MfaChallengeScope } from 'teleport/services/auth/auth'; import websession from 'teleport/services/websession'; @@ -293,7 +295,7 @@ const api = { } catch (err) { // error reading JSON const message = response.ok - ? err.message + ? getErrorMessage(err) : `${response.status} - ${response.url}`; throw new ApiError({ message, response, opts: { cause: err } }); } @@ -455,8 +457,8 @@ export function getHostName() { return location.hostname + (location.port ? ':' + location.port : ''); } -export function isAdminActionRequiresMfaError(err: Error) { - return err.message.includes( +export function isAdminActionRequiresMfaError(err: unknown) { + return getErrorMessage(err).includes( 'admin-level API request requires MFA verification' ); } diff --git a/web/packages/teleport/src/services/bot/bot.ts b/web/packages/teleport/src/services/bot/bot.ts index 1fc26133aebf..7aecab21857d 100644 --- a/web/packages/teleport/src/services/bot/bot.ts +++ b/web/packages/teleport/src/services/bot/bot.ts @@ -20,6 +20,7 @@ import { MutationFunction } from '@tanstack/react-query'; import cfg from 'teleport/config'; import api from 'teleport/services/api'; +import { ApiError } from 'teleport/services/api/parseError'; import { canUseV1Edit, canUseV2Edit, @@ -67,7 +68,7 @@ export async function getBot( .then(makeBot); } catch (err) { // capture the not found error response and return null instead of throwing - if (err?.response?.status === 404) { + if (err instanceof ApiError && err.response.status === 404) { return null; } throw err; diff --git a/web/packages/teleport/src/services/version/unsupported.ts b/web/packages/teleport/src/services/version/unsupported.ts index f62b8a2df217..fed160384433 100644 --- a/web/packages/teleport/src/services/version/unsupported.ts +++ b/web/packages/teleport/src/services/version/unsupported.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { getErrMessage } from 'shared/utils/errorType'; +import { getErrorMessage } from 'shared/utils/error'; import { App } from 'teleport/services/apps/types'; import { @@ -124,7 +124,7 @@ export const withUnsupportedOktaPluginUpdateErrorConversion = ( err: unknown ) => { if (err instanceof ApiError && err.response.status === 404) { - const msg = getErrMessage(err); + const msg = getErrorMessage(err); throw new Error( `Could not update Okta plugin: ${msg}. Your proxy may be behind the minimum required version (v17.3.0) to support Okta plugin updates with this web client.` ); @@ -136,7 +136,7 @@ export const withUnsupportedOktaPluginCreateErrorConversion = ( err: unknown ) => { if (err instanceof ApiError) { - const msg = getErrMessage(err); + const msg = getErrorMessage(err); if (msg.match(/missing okta (?:organization url|api token)/gi)) { throw new Error( `Could not create Okta plugin: ${msg}. Your proxy may be behind the minimum required version (v17.3.0) to support Okta plugin creation with this web client.` @@ -147,7 +147,7 @@ export const withUnsupportedOktaPluginCreateErrorConversion = ( }; type Base = { - err: Error; + err: unknown; }; type CreateJoinToken = Base & { @@ -212,7 +212,10 @@ export function useV1Fallback() { async function tryV1Fallback(props: CreateJoinToken): Promise; async function tryV1Fallback(props: FallbackProps) { - if (!props.err.message.includes(ProxyRequiresUpgrade) || hasLabels(props)) { + if ( + !getErrorMessage(props.err).includes(ProxyRequiresUpgrade) || + hasLabels(props) + ) { throw props.err; } diff --git a/web/packages/teleterm/src/agentCleanupDaemon/agentCleanupDaemon.test.ts b/web/packages/teleterm/src/agentCleanupDaemon/agentCleanupDaemon.test.ts index d62d6e602e64..9e0288bfb35a 100644 --- a/web/packages/teleterm/src/agentCleanupDaemon/agentCleanupDaemon.test.ts +++ b/web/packages/teleterm/src/agentCleanupDaemon/agentCleanupDaemon.test.ts @@ -25,6 +25,8 @@ import { tmpdir } from 'node:os'; import path from 'node:path'; import process from 'node:process'; +import { isErrnoException } from 'shared/utils/error'; + const stdio = 'pipe'; // Change to 'inherit' for easier debugging. let logsDir: string; @@ -197,7 +199,7 @@ const isRunning = (pid: number) => { try { return process.kill(pid, 0); } catch (error) { - if (error.code === 'ESRCH') { + if (isErrnoException(error, 'ESRCH')) { return false; } diff --git a/web/packages/teleterm/src/deepLinks.ts b/web/packages/teleterm/src/deepLinks.ts index e88c72683bc8..f4254f305fcc 100644 --- a/web/packages/teleterm/src/deepLinks.ts +++ b/web/packages/teleterm/src/deepLinks.ts @@ -30,7 +30,7 @@ export type DeepLinkParseResult = // ergonomic. Unfortunately, `if (!result.ok)` doesn't narrow down the type properly with // strictNullChecks off. https://github.com/microsoft/TypeScript/issues/10564 | DeepLinkParseResultSuccess - | ParseError<'malformed-url', { error: TypeError }> + | ParseError<'malformed-url', { error: unknown }> | ParseError<'unknown-protocol', { protocol: string }> | ParseError<'unsupported-url'>; @@ -69,9 +69,11 @@ export function parseDeepLink(rawUrl: string): DeepLinkParseResult { try { parsedURL = new URL(rawUrl); } catch (error) { - // `error instanceof TypeError` doesn't work in tests. The URL constructor shouldn't throw other - // errors anyway. - return { status: 'error', reason: 'malformed-url', error }; + return { + status: 'error', + reason: 'malformed-url', + error, + }; } if (parsedURL.protocol !== `${CUSTOM_PROTOCOL}:`) { diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index 1bba5945e4b4..8572b55fb95e 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -23,7 +23,11 @@ import path from 'node:path'; import { app, dialog, nativeTheme } from 'electron'; import { CUSTOM_PROTOCOL } from 'shared/deepLinks'; -import { ensureError } from 'shared/utils/error'; +import { + ensureError, + getErrorMessage, + isErrnoException, +} from 'shared/utils/error'; import { parseDeepLink } from 'teleterm/deepLinks'; import Logger from 'teleterm/logger'; @@ -366,7 +370,7 @@ function launchDeepLink( break; } case 'malformed-url': { - reason = `malformed URL (${result.error.message})`; + reason = `malformed URL (${getErrorMessage(result.error)})`; break; } default: { @@ -429,7 +433,7 @@ async function migrateOldTshHomeOnce( try { await fs.stat(oldTshHome); } catch (err) { - if (err.code === 'ENOENT') { + if (isErrnoException(err, 'ENOENT')) { logger.info( 'Old tsh directory does not exist, marking migration as processed' ); diff --git a/web/packages/teleterm/src/mainProcess/agentDownloader/agentDownloader.ts b/web/packages/teleterm/src/mainProcess/agentDownloader/agentDownloader.ts index 23d989669c00..9d39d9014eb4 100644 --- a/web/packages/teleterm/src/mainProcess/agentDownloader/agentDownloader.ts +++ b/web/packages/teleterm/src/mainProcess/agentDownloader/agentDownloader.ts @@ -26,6 +26,7 @@ import { createUnzip } from 'node:zlib'; import { extract } from 'tar-fs'; +import { isErrnoException } from 'shared/utils/error'; import { compareSemVers } from 'shared/utils/semVer'; import Logger from 'teleterm/logger'; @@ -149,7 +150,7 @@ async function isCorrectAgentVersionAlreadyDownloaded( return agentVersion.stdout.trim() === neededVersion; } catch (e) { // When the agent is being downloaded for the first time, the binary does not yet exist. - if (e.code !== 'ENOENT') { + if (!isErrnoException(e, 'ENOENT')) { throw e; } return false; diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts index 29c39b4fd6b9..5f044c100a4f 100644 --- a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts @@ -20,7 +20,6 @@ import { Cluster, LoggedInUser, } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; -import { ensureError } from 'shared/utils/error'; import Logger from 'teleterm/logger'; import type { IAwaitableSender } from 'teleterm/mainProcess/awaitableSender'; @@ -381,7 +380,7 @@ export class ClusterLifecycleManager { ); const serialized: ProfileWatcherError = { reason: watcherError.reason, - error: serializeError(ensureError(watcherError.error)), + error: serializeError(watcherError.error), }; this.windowsManager .getWindow() diff --git a/web/packages/teleterm/src/mainProcess/createAgentConfigFile.ts b/web/packages/teleterm/src/mainProcess/createAgentConfigFile.ts index 92e010ae81db..598d94ecc9c4 100644 --- a/web/packages/teleterm/src/mainProcess/createAgentConfigFile.ts +++ b/web/packages/teleterm/src/mainProcess/createAgentConfigFile.ts @@ -22,6 +22,7 @@ import path from 'node:path'; import { promisify } from 'node:util'; import * as connectMyComputer from 'shared/connectMyComputer'; +import { isErrnoException } from 'shared/utils/error'; import { RuntimeSettings } from 'teleterm/mainProcess/types'; import { RootClusterUri, routing } from 'teleterm/ui/uri'; @@ -127,7 +128,7 @@ export async function isAgentConfigFileCreated( await fs.access(configFile); return true; } catch (e) { - if (e.code === 'ENOENT') { + if (isErrnoException(e, 'ENOENT')) { return false; } throw e; diff --git a/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts b/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts index 83127d503bf0..3df28479e70b 100644 --- a/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts +++ b/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts @@ -64,3 +64,17 @@ test('serializes and deserializes RPC error', () => { 'is-resolvable-with-relogin': ['1'], }); }); + +test('serializing an already serialized RPC error preserves custom fields', () => { + const err = new RpcError('Session expired', 'UNAUTHENTICATED', { + 'is-resolvable-with-relogin': ['1'], + }); + + const serialized = serializeError(err); + const serializedAgain = serializeError(structuredClone(serialized)); + + expect(serializedAgain['code']).toBe('UNAUTHENTICATED'); + expect(serializedAgain['meta']).toEqual({ + 'is-resolvable-with-relogin': ['1'], + }); +}); diff --git a/web/packages/teleterm/src/mainProcess/ipcSerializer.ts b/web/packages/teleterm/src/mainProcess/ipcSerializer.ts index f73fde1147a8..0b0914f0fb7c 100644 --- a/web/packages/teleterm/src/mainProcess/ipcSerializer.ts +++ b/web/packages/teleterm/src/mainProcess/ipcSerializer.ts @@ -16,16 +16,34 @@ * along with this program. If not, see . */ +import { ensureError } from 'shared/utils/error'; + export type SerializedError = { name: string; message: string; stack?: string; cause?: unknown; toStringResult?: string; + $serializedError?: true; }; -/** Serializes an Error into a plain object for transport through Electron IPC. */ -export function serializeError(error: Error): SerializedError { +function isSerialized(error: unknown): error is SerializedError { + return ( + !!error && + typeof error === 'object' && + error['$serializedError'] === true && + typeof error['name'] === 'string' && + typeof error['message'] === 'string' + ); +} + +/** Serializes an error into a plain object for transport through Electron IPC. */ +export function serializeError(error: unknown): SerializedError { + if (isSerialized(error)) { + return error; + } + + const errorInstance = ensureError(error); const { name, cause, @@ -34,7 +52,7 @@ export function serializeError(error: Error): SerializedError { // functions must be skipped, otherwise structuredClone will fail to clone the object toString, ...enumerableFields - } = error; + } = errorInstance; return { name, message, @@ -42,14 +60,23 @@ export function serializeError(error: Error): SerializedError { stack, // Calling the destructured function directly could result in the following error: // Method Error.prototype.toString called on incompatible receiver undefined - toStringResult: error.toString?.(), + toStringResult: errorInstance.toString?.(), ...enumerableFields, + $serializedError: true, }; } /** Deserializes a plain object back into an Error instance. */ export function deserializeError(serialized: SerializedError): Error { - const { name, cause, stack, message, toStringResult, ...rest } = serialized; + const { + name, + cause, + stack, + message, + toStringResult, + $serializedError, + ...rest + } = serialized; const error = new Error(message); error.name = name; error.cause = cause; diff --git a/web/packages/teleterm/src/mainProcess/loadInstallationId/loadInstallationId.ts b/web/packages/teleterm/src/mainProcess/loadInstallationId/loadInstallationId.ts index 4f3b00bd3a8f..e292693b1f5a 100644 --- a/web/packages/teleterm/src/mainProcess/loadInstallationId/loadInstallationId.ts +++ b/web/packages/teleterm/src/mainProcess/loadInstallationId/loadInstallationId.ts @@ -19,6 +19,8 @@ import crypto from 'crypto'; import fs from 'fs'; +import { getErrorMessage } from 'shared/utils/error'; + const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; @@ -46,7 +48,7 @@ function writeInstallationId(filePath: string): string { fs.writeFileSync(filePath, newId); } catch (error) { throw new Error( - `Could not write installation_id to ${filePath}, ${error.message}`, + `Could not write installation_id to ${filePath}, ${getErrorMessage(error)}`, { cause: error } ); } diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index b01e7136d8f0..2d24673fd198 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -18,8 +18,6 @@ import { ipcRenderer } from 'electron'; -import { ensureError } from 'shared/utils/error'; - import Logger from 'teleterm/logger'; import type { Message, MessageAck } from 'teleterm/mainProcess/awaitableSender'; import { CreateAgentConfigFileArgs } from 'teleterm/mainProcess/createAgentConfigFile'; @@ -325,7 +323,7 @@ function startAwaitableSenderListener( try { await listener(msg.payload as T); } catch (e) { - ack.error = serializeError(ensureError(e)); + ack.error = serializeError(e); } try { diff --git a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts index be5937a29806..8e1936b43fae 100644 --- a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts +++ b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts @@ -23,6 +23,7 @@ import os from 'node:os'; import path from 'node:path'; import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { isErrnoException } from 'shared/utils/error'; import { wait } from 'shared/utils/wait'; import Logger, { NullService } from 'teleterm/logger'; @@ -65,7 +66,7 @@ async function mockTshClient(tshDir: string, initial: { clusters: Cluster[] }) { try { paths = await fs.readdir(tshDir); } catch (err) { - if (err.code === 'ENOENT') { + if (isErrnoException(err, 'ENOENT')) { throw { name: 'RpcError', code: 'NOT_FOUND', @@ -88,7 +89,7 @@ async function mockTshClient(tshDir: string, initial: { clusters: Cluster[] }) { // The file with the cluster disappeared between fs.readdir above and fs.readFile. // This is possible in tests where we call `void tshClientMock.removeCluster` without // awaiting. - if (err.code === 'ENOENT') { + if (isErrnoException(err, 'ENOENT')) { return null; } throw err; diff --git a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts index fed294d2bb02..254842dea9f8 100644 --- a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts +++ b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts @@ -20,6 +20,7 @@ import { watch, type WatchEventType } from 'node:fs'; import { access } from 'node:fs/promises'; import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { isErrnoException } from 'shared/utils/error'; import { debounce } from 'shared/utils/highbar'; import { wait } from 'shared/utils/wait'; @@ -129,7 +130,7 @@ export async function* watchProfiles({ if ( isTshdRpcError(error, 'NOT_FOUND') || error instanceof FileSystemEventsOverflowError || - error?.code === 'EPERM' + isErrnoException(error, 'EPERM') ) { const ok = await pathExists(tshDirectory); if (!ok) { @@ -161,7 +162,7 @@ async function pathExists(dirPath: string): Promise { await access(dirPath); return true; } catch (error) { - if (error.code === 'ENOENT') { + if (isErrnoException(error, 'ENOENT')) { return false; } throw error; diff --git a/web/packages/teleterm/src/mainProcess/windowsManager.ts b/web/packages/teleterm/src/mainProcess/windowsManager.ts index 5e11d1191175..6d6577a8c21b 100644 --- a/web/packages/teleterm/src/mainProcess/windowsManager.ts +++ b/web/packages/teleterm/src/mainProcess/windowsManager.ts @@ -246,7 +246,8 @@ export class WindowsManager { await this.whenFrontendAppIsReady(); } catch (error) { this.logger.error( - `Could not send the deep link to the frontend app: ${error.message}` + 'Could not send the deep link to the frontend app', + error ); return; } diff --git a/web/packages/teleterm/src/services/appUpdater/nsisDualModeUpdater.ts b/web/packages/teleterm/src/services/appUpdater/nsisDualModeUpdater.ts index ae9c4774e3ac..af0ce46a5684 100644 --- a/web/packages/teleterm/src/services/appUpdater/nsisDualModeUpdater.ts +++ b/web/packages/teleterm/src/services/appUpdater/nsisDualModeUpdater.ts @@ -24,7 +24,7 @@ import { NsisUpdater } from 'electron-updater'; import { DownloadUpdateOptions } from 'electron-updater/out/AppUpdater'; import { InstallOptions } from 'electron-updater/out/BaseUpdater'; -import { getErrorMessage } from 'shared/utils/error'; +import { ensureError, getErrorMessage } from 'shared/utils/error'; import { TSH_AUTOUPDATE_ENV_VAR, @@ -100,7 +100,7 @@ export class NsisDualModeUpdater extends NsisUpdater { [TSH_AUTOUPDATE_ENV_VAR]: TSH_AUTOUPDATE_OFF, }); } catch (error) { - this.dispatchError(error); + this.dispatchError(ensureError(error)); const errorMessage = getErrorMessage(error); if (!errorMessage.includes('failed to ensure service is running')) { // If not a problem with starting the service, keep the app open and surface the error in the UI. diff --git a/web/packages/teleterm/src/services/config/configService.ts b/web/packages/teleterm/src/services/config/configService.ts index ea097e882474..8bc5701e3749 100644 --- a/web/packages/teleterm/src/services/config/configService.ts +++ b/web/packages/teleterm/src/services/config/configService.ts @@ -33,7 +33,7 @@ const logger = new Logger('ConfigService'); export type FileLoadingError = { source: 'file-loading'; - error: Error; + error: unknown; }; export type ValidationError = { diff --git a/web/packages/teleterm/src/services/fileStorage/fileStorage.ts b/web/packages/teleterm/src/services/fileStorage/fileStorage.ts index 0332f29abd61..52297d937070 100644 --- a/web/packages/teleterm/src/services/fileStorage/fileStorage.ts +++ b/web/packages/teleterm/src/services/fileStorage/fileStorage.ts @@ -21,6 +21,7 @@ import fs from 'node:fs'; import fsAsync from 'node:fs/promises'; import path from 'node:path'; +import { isErrnoException } from 'shared/utils/error'; import { debounce } from 'shared/utils/highbar'; import Logger from 'teleterm/logger'; @@ -52,7 +53,7 @@ export interface FileStorage { getFileName(): string; /** Returns the error that could occur while reading and parsing the file. */ - getFileLoadingError(): Error | undefined; + getFileLoadingError(): unknown; } /** @@ -80,7 +81,7 @@ export function createFileStorage(opts: { const { filePath } = opts; - let state: any, error: Error | undefined; + let state: any, error: unknown; try { state = loadStateSync(filePath); } catch (e) { @@ -127,7 +128,7 @@ export function createFileStorage(opts: { return path.basename(opts.filePath); } - function getFileLoadingError(): Error | undefined { + function getFileLoadingError(): unknown { return error; } @@ -161,7 +162,7 @@ function readOrCreateFileSync(filePath: string): string { try { return fs.readFileSync(filePath, { encoding: 'utf-8' }); } catch (error) { - if (error?.code === 'ENOENT') { + if (isErrnoException(error, 'ENOENT')) { fs.writeFileSync(filePath, defaultValue); return defaultValue; } diff --git a/web/packages/teleterm/src/services/grpcCredentials/files.ts b/web/packages/teleterm/src/services/grpcCredentials/files.ts index f7d220417456..d0501e11d985 100644 --- a/web/packages/teleterm/src/services/grpcCredentials/files.ts +++ b/web/packages/teleterm/src/services/grpcCredentials/files.ts @@ -20,6 +20,7 @@ import { watch, type Stats } from 'fs'; import { readFile, rename, stat, writeFile } from 'fs/promises'; import path from 'path'; +import { isErrnoException } from 'shared/utils/error'; import { wait } from 'shared/utils/wait'; import { makeCert } from './makeCert'; @@ -68,7 +69,7 @@ export async function readGrpcCert( try { stats = await stat(fullPath); } catch (error) { - if (error?.code === 'ENOENT') { + if (isErrnoException(error, 'ENOENT')) { return false; } throw error; diff --git a/web/packages/teleterm/src/services/tshd/cloneableClient.ts b/web/packages/teleterm/src/services/tshd/cloneableClient.ts index 69a694eab7c6..e6fce75179dc 100644 --- a/web/packages/teleterm/src/services/tshd/cloneableClient.ts +++ b/web/packages/teleterm/src/services/tshd/cloneableClient.ts @@ -28,8 +28,6 @@ import { UnaryCall, } from '@protobuf-ts/runtime-rpc'; -import { ensureError } from 'shared/utils/error'; - import { serializeError, type SerializedError, @@ -314,7 +312,7 @@ export function isRpcErrorReloginResolvable(error: unknown): boolean { } function cloneError(error: unknown): SerializedError { - return serializeError(ensureError(error)); + return serializeError(error); } function cloneRequests( diff --git a/web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.ts b/web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.ts index 98cfc6137925..f89d2eaa5cf7 100644 --- a/web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.ts +++ b/web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.ts @@ -24,6 +24,7 @@ import { promisify } from 'node:util'; import * as nodePTY from 'node-pty'; import which from 'which'; +import { getErrorMessage } from 'shared/utils/error'; import { wait } from 'shared/utils/wait'; import Logger from 'teleterm/logger'; @@ -257,11 +258,11 @@ export class PtyProcess extends EventEmitter implements IPtyProcess { } } - private handleStartError(error: Error) { + private handleStartError(error: unknown) { const command = `${this.options.path} ${this.options.args.join(' ')}`; this.emit( TermEventEnum.StartError, - `Cannot execute ${command}: ${error.message}` + `Cannot execute ${command}: ${getErrorMessage(error)}` ); } diff --git a/web/packages/teleterm/src/ui/AppInitializer/AppInitializer.tsx b/web/packages/teleterm/src/ui/AppInitializer/AppInitializer.tsx index 7e4143b1c834..809c8e6e7844 100644 --- a/web/packages/teleterm/src/ui/AppInitializer/AppInitializer.tsx +++ b/web/packages/teleterm/src/ui/AppInitializer/AppInitializer.tsx @@ -20,6 +20,7 @@ import { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { Indicator } from 'design'; +import { getErrorMessage } from 'shared/utils/error'; import { useAppContext } from 'teleterm/ui/appContextProvider'; import { useLogger } from 'teleterm/ui/hooks/useLogger'; @@ -57,10 +58,10 @@ export const AppInitializer = () => { success: true, }); } catch (error) { - logger.error(error?.message); + logger.error('Failed to initialize app', error); setShouldShowUi(true); - appContext?.notificationsService.notifyError(error?.message); + appContext?.notificationsService.notifyError(getErrorMessage(error)); appContext?.mainProcessClient.signalUserInterfaceReadiness({ success: false, }); diff --git a/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Setup.tsx b/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Setup.tsx index 8704e07e0dc2..cf8a89e2a8b1 100644 --- a/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Setup.tsx +++ b/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Setup.tsx @@ -22,6 +22,7 @@ import styled from 'styled-components'; import { Alert, Box, ButtonPrimary, Flex, H1, Text } from 'design'; import * as Alerts from 'design/Alert'; import { Attempt, makeEmptyAttempt, useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { wait } from 'shared/utils/wait'; import { @@ -244,7 +245,7 @@ function AgentSetup() { !isRpcErrorReloginResolvable(error) ) { throw new Error( - `Cannot set up the role: ${error.message}. Contact your administrator for permissions to manage users and roles.`, + `Cannot set up the role: ${getErrorMessage(error)}. Contact your administrator for permissions to manage users and roles.`, { cause: error } ); } @@ -431,7 +432,7 @@ function AgentSetup() { const { agentBinaryPath } = mainProcessClient.getRuntimeSettings(); notificationsService.notifyError({ title: 'Could not remove the agent binary', - description: `Please try removing the binary manually to continue. The binary is at ${agentBinaryPath}. The error message was: ${error.message}`, + description: `Please try removing the binary manually to continue. The binary is at ${agentBinaryPath}. The error message was: ${getErrorMessage(error)}`, }); return; } diff --git a/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Status.tsx b/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Status.tsx index 1a6e38ddd6c0..fe2af7eb9306 100644 --- a/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Status.tsx +++ b/web/packages/teleterm/src/ui/ConnectMyComputer/DocumentConnectMyComputer/Status.tsx @@ -36,6 +36,7 @@ import * as icons from 'design/Icon'; import type { IconProps } from 'design/Icon/Icon'; import Indicator from 'design/Indicator'; import { MenuIcon } from 'shared/components/MenuAction'; +import { getErrorMessage } from 'shared/utils/error'; import type * as tsh from 'teleterm/services/tshd/types'; import { useAppContext } from 'teleterm/ui/appContextProvider'; @@ -112,7 +113,7 @@ export function Status(props: { closeDocument?: () => void }) { } catch (e) { ctx.notificationsService.notifyError({ title: 'Failed to open agent logs directory', - description: `${e.message}\n\nNote: the logs directory is created only after the agent process successfully spawns.`, + description: `${getErrorMessage(e)}\n\nNote: the logs directory is created only after the agent process successfully spawns.`, }); } } diff --git a/web/packages/teleterm/src/ui/DeepLinks/launchDeepLink.ts b/web/packages/teleterm/src/ui/DeepLinks/launchDeepLink.ts index b38eaf94c68f..1dfdc45976fa 100644 --- a/web/packages/teleterm/src/ui/DeepLinks/launchDeepLink.ts +++ b/web/packages/teleterm/src/ui/DeepLinks/launchDeepLink.ts @@ -22,6 +22,7 @@ import { DeepURL, VnetDeepURL, } from 'shared/deepLinks'; +import { getErrorMessage } from 'shared/utils/error'; import { DeepLinkParseResult } from 'teleterm/deepLinks'; import { IAppContext } from 'teleterm/ui/types'; @@ -59,7 +60,7 @@ export async function launchDeepLink( break; } case 'malformed-url': { - reason = `The URL of the link appears to be malformed. ${result.error.message}`; + reason = `The URL of the link appears to be malformed. ${getErrorMessage(result.error)}`; break; } default: { diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts index a021b04b19dd..a735a54272bf 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts @@ -238,14 +238,15 @@ export default class TtyTerminal implements TerminalSearcher { this.term.onData(data => { this.ptyProcess.write(data).catch(error => { - this.logger.error(`Failed to write to the PTY process: ${error}`); + this.logger.error('Failed to write to the PTY process', error); }); }); this.term.onResize(size => { this.ptyProcess.resize(size.cols, size.rows).catch(error => { this.logger.error( - `Failed to send resize request to the PTY process: ${error}` + 'Failed to send resize request to the PTY process', + error ); }); }); @@ -259,7 +260,7 @@ export default class TtyTerminal implements TerminalSearcher { // The shared process version of PtyProcess knows whether it was started or not (the status // field), so it's a matter of exposing this field through gRPC and reading it here. this.ptyProcess.start(this.term.cols, this.term.rows).catch(error => { - this.logger.error(`Failed to start the PTY process: ${error}`); + this.logger.error('Failed to start the PTY process', error); }); window.addEventListener('resize', this.debouncedResize); diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts index 08173969c619..44c9433e05e7 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts @@ -89,7 +89,7 @@ export function useDocumentTerminal(doc: types.DocumentTerminal) { return () => { if (attempt.status === 'success') { void attempt.data.ptyProcess.dispose().catch(error => { - logger.error(`Failed to dispose of the PTY process: ${error}`); + logger.error('Failed to dispose of the PTY process', error); }); } }; diff --git a/web/packages/teleterm/src/ui/Vnet/DocumentVnetDiagReport.tsx b/web/packages/teleterm/src/ui/Vnet/DocumentVnetDiagReport.tsx index 5c01209252e5..ab41c6b5bcf4 100644 --- a/web/packages/teleterm/src/ui/Vnet/DocumentVnetDiagReport.tsx +++ b/web/packages/teleterm/src/ui/Vnet/DocumentVnetDiagReport.tsx @@ -36,6 +36,7 @@ import { copyToClipboard } from 'design/utils/copyToClipboard'; import { Timestamp } from 'gen-proto-ts/google/protobuf/timestamp_pb'; import * as diag from 'gen-proto-ts/teleport/lib/vnet/diag/v1/diag_pb'; import { CanceledError, useAsync } from 'shared/hooks/useAsync'; +import { getErrorMessage } from 'shared/utils/error'; import { pluralize } from 'shared/utils/text'; import { @@ -122,7 +123,7 @@ export function DocumentVnetDiagReport(props: { previousSaveToFileNotificationIdRef.current = notificationsService.notifyError({ title: 'Could not save the report to a file.', - description: error?.message, + description: getErrorMessage(error), }); return; } diff --git a/web/packages/teleterm/src/ui/commandLauncher.ts b/web/packages/teleterm/src/ui/commandLauncher.ts index 6a2a117cc5ed..b36df7a2e626 100644 --- a/web/packages/teleterm/src/ui/commandLauncher.ts +++ b/web/packages/teleterm/src/ui/commandLauncher.ts @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import { getErrorMessage } from 'shared/utils/error'; + import { IAppContext } from 'teleterm/ui/types'; import { ClusterUri, RootClusterUri, routing } from 'teleterm/ui/uri'; @@ -35,7 +37,7 @@ const commands = { error => { ctx.notificationsService.notifyError({ title: 'Could not install tsh in PATH', - description: `Ran into an error: ${error}`, + description: `Ran into an error: ${getErrorMessage(error)}`, }); } ); @@ -57,7 +59,7 @@ const commands = { error => { ctx.notificationsService.notifyError({ title: 'Could not remove tsh from PATH', - description: `Ran into an error: ${error}`, + description: `Ran into an error: ${getErrorMessage(error)}`, }); } ); diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index d53c2151cfe2..818445023e2c 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -26,7 +26,7 @@ import { ReviewAccessRequestRequest, } from 'gen-proto-ts/teleport/lib/teleterm/v1/service_pb'; import { useStore } from 'shared/libs/stores'; -import { AbortError, isAbortError } from 'shared/utils/error'; +import { AbortError, getErrorMessage, isAbortError } from 'shared/utils/error'; import type { State as ClustersState } from 'teleterm/mainProcess/clusterStore'; import { MainProcessClient } from 'teleterm/mainProcess/types'; @@ -89,7 +89,7 @@ export class ClustersService extends ImmutableStore { const notificationId = this.notificationsService.notifyError({ title: `Could not synchronize cluster ${clusterName}`, - description: e.message, + description: getErrorMessage(e), action: { content: 'Retry', onClick: () => { @@ -112,7 +112,7 @@ export class ClustersService extends ImmutableStore { const notificationId = this.notificationsService.notifyError({ title: `Could not start headless requests watcher for ${clusterName}`, - description: e.message, + description: getErrorMessage(e), action: { content: 'Retry', onClick: () => { @@ -166,7 +166,7 @@ export class ClustersService extends ImmutableStore { } const notificationId = this.notificationsService.notifyError({ title: 'Could not fetch root clusters', - description: error.message, + description: getErrorMessage(error), action: { content: 'Retry', onClick: () => { @@ -193,7 +193,7 @@ export class ClustersService extends ImmutableStore { } catch (error) { const notificationId = this.notificationsService.notifyError({ title: 'Could not synchronize database connections', - description: error.message, + description: getErrorMessage(error), action: { content: 'Retry', onClick: () => { @@ -301,7 +301,7 @@ export class ClustersService extends ImmutableStore { const notificationId = this.notificationsService.notifyError({ title, - description: error.message, + description: getErrorMessage(error), action: { content: 'Retry', onClick: () => { diff --git a/web/packages/teleterm/src/ui/services/usage/usageService.ts b/web/packages/teleterm/src/ui/services/usage/usageService.ts index 339a25e915ba..69035ae092df 100644 --- a/web/packages/teleterm/src/ui/services/usage/usageService.ts +++ b/web/packages/teleterm/src/ui/services/usage/usageService.ts @@ -19,6 +19,7 @@ import { Timestamp } from 'gen-proto-ts/google/protobuf/timestamp_pb'; import { SubmitConnectEventRequest } from 'gen-proto-ts/prehog/v1alpha/connect_pb'; import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { getErrorMessage } from 'shared/utils/error'; import Logger from 'teleterm/logger'; import { RuntimeSettings } from 'teleterm/mainProcess/types'; @@ -304,9 +305,9 @@ export class UsageService { } catch (e) { this.notificationsService.notifyWarning({ title: 'Failed to report usage event', - description: e.message, + description: getErrorMessage(e), }); - this.logger.warn(`Failed to report usage event`, e.message); + this.logger.warn('Failed to report usage event', e); } } diff --git a/web/packages/teleterm/src/ui/utils/retryWithRelogin.ts b/web/packages/teleterm/src/ui/utils/retryWithRelogin.ts index 15469a629358..ec385e845263 100644 --- a/web/packages/teleterm/src/ui/utils/retryWithRelogin.ts +++ b/web/packages/teleterm/src/ui/utils/retryWithRelogin.ts @@ -54,7 +54,7 @@ export async function retryWithRelogin( resourceUri: ClusterOrResourceUri, actionToRetry: () => Promise ): Promise { - let retryableErrorFromActionToRetry: Error; + let retryableErrorFromActionToRetry: unknown; try { return await actionToRetry(); } catch (error) { From 4d3765c02d4bbfb377d9baeef168c78dbb7b794e Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Fri, 31 Jul 2026 10:02:59 +0100 Subject: [PATCH 079/347] Use split Identity Security entitlements (#68944) Gate Access Graph, Activity Center, and Session Summaries consumers on their dedicated entitlements. Treat legacy Policy as a fallback for missing split entitlements across module, proto, web config, tctl, and UI config paths so newer clients remain compatible with older clusters and proxies. Continues - https://github.com/gravitational/teleport/pull/66571 - https://github.com/gravitational/teleport/pull/64340 - https://github.com/gravitational/teleport.e/pull/8722 - https://github.com/gravitational/teleport.e/pull/8725 - https://github.com/gravitational/teleport.e/pull/8791 - https://github.com/gravitational/teleport.e/pull/8792 Signed-off-by: Tiago Silva --- api/client/webclient/webconfig.go | 8 +-- .../operator/controllers/reconciler.go | 4 +- .../resources/inference_model_controller.go | 2 +- .../resources/inference_policy_controller.go | 2 +- .../resources/inference_secret_controller.go | 2 +- .../resources/retrieval_model_controller.go | 2 +- .../controllers/resources/setup_test.go | 22 ++++++ lib/auth/auth_with_roles.go | 4 +- lib/auth/auth_with_roles_test.go | 6 +- .../clusterconfig/clusterconfigv1/service.go | 17 ++--- .../clusterconfigv1/service_test.go | 20 +++--- lib/auth/grpcserver_test.go | 6 +- lib/modules/modules.go | 65 ++++++++++++------ lib/modules/modules_test.go | 45 +++++++++++++ lib/srv/discovery/access_graph_aws.go | 15 +++-- lib/srv/discovery/access_graph_aws_test.go | 67 +++++++++++++++++++ lib/srv/discovery/access_graph_azure.go | 5 +- lib/web/apiserver.go | 20 +++--- lib/web/apiserver_test.go | 35 ++++++++++ skills/teleport-session-review/SKILL.md | 10 +-- .../references/SCHEMA.md | 6 +- tool/tctl/common/accessgraph/credentials.go | 11 +-- .../common/accessgraph/credentials_test.go | 34 +++++++--- .../teleport/src/Roles/PolicyPlaceholder.tsx | 5 +- .../src/Roles/RoleEditor/RoleEditor.story.tsx | 6 +- .../src/Roles/RoleEditor/RoleEditor.test.tsx | 14 ++-- .../src/Roles/RoleEditor/RoleEditor.tsx | 3 +- .../RoleEditor/RoleEditorVisualizer.test.tsx | 4 +- web/packages/teleport/src/config.test.ts | 67 +++++++++++++++++++ web/packages/teleport/src/config.ts | 11 +-- web/packages/teleport/src/entitlement.ts | 54 +++++++++++++-- 31 files changed, 449 insertions(+), 123 deletions(-) diff --git a/api/client/webclient/webconfig.go b/api/client/webclient/webconfig.go index 99017c557467..c93e487c5521 100644 --- a/api/client/webclient/webconfig.go +++ b/api/client/webclient/webconfig.go @@ -96,8 +96,8 @@ type WebConfig struct { // IdentitySecurity contains identity security features and settings. // The individual identity security entitlements should be read from the Entitlements field. IdentitySecurity IdentitySecurity `json:"identitySecurity"` - // IsPolicyEnabled is true if [Features.Policy] = true - // Deprecated, use entitlements + // IsPolicyEnabled is true if [Features.Policy] = true. + // Deprecated; use the feature-specific identity security entitlements. IsPolicyEnabled bool `json:"isPolicyEnabled"` // BeamsUI indicates whether the Beams lite-mode UI is enabled BeamsUI bool `json:"beamsUi"` @@ -107,8 +107,8 @@ type WebConfig struct { // IdentitySecurity contains identity security features and settings. type IdentitySecurity struct { - // IsClusterLicensed indicates whether identity security features are licensed - // for this cluster. + // IsClusterLicensed indicates whether any identity security feature is + // licensed for this cluster. Deprecated; use the individual entitlements. IsClusterLicensed bool `json:"licensed"` // AccessGraphConfigSet indicates whether access graph configuration is set in // Auth and/or Proxy. diff --git a/integrations/operator/controllers/reconciler.go b/integrations/operator/controllers/reconciler.go index 64f74a6cf7c6..4a7fe840b09f 100644 --- a/integrations/operator/controllers/reconciler.go +++ b/integrations/operator/controllers/reconciler.go @@ -52,6 +52,6 @@ func RequireEnterprise(features *proto.Features) bool { return features.GetAdvancedAccessWorkflows() } -func RequirePolicy(features *proto.Features) bool { - return modules.GetProtoEntitlement(features, entitlements.Policy).Enabled +func RequireSessionSummaries(features *proto.Features) bool { + return modules.GetProtoEntitlement(features, entitlements.SessionSummaries).Enabled } diff --git a/integrations/operator/controllers/resources/inference_model_controller.go b/integrations/operator/controllers/resources/inference_model_controller.go index df516c331150..2d32eebe0fa0 100644 --- a/integrations/operator/controllers/resources/inference_model_controller.go +++ b/integrations/operator/controllers/resources/inference_model_controller.go @@ -92,7 +92,7 @@ func NewInferenceModelReconciler( client, inferenceModelClient, reconcilers.Config{ - CheckFeatures: controllers.RequirePolicy, + CheckFeatures: controllers.RequireSessionSummaries, }, ) diff --git a/integrations/operator/controllers/resources/inference_policy_controller.go b/integrations/operator/controllers/resources/inference_policy_controller.go index e2cbf1178b13..c22091d84e6b 100644 --- a/integrations/operator/controllers/resources/inference_policy_controller.go +++ b/integrations/operator/controllers/resources/inference_policy_controller.go @@ -92,7 +92,7 @@ func NewInferencePolicyReconciler( client, inferencePolicyClient, reconcilers.Config{ - CheckFeatures: controllers.RequirePolicy, + CheckFeatures: controllers.RequireSessionSummaries, }, ) diff --git a/integrations/operator/controllers/resources/inference_secret_controller.go b/integrations/operator/controllers/resources/inference_secret_controller.go index 94b8a5a2a731..4f16d11ddcfc 100644 --- a/integrations/operator/controllers/resources/inference_secret_controller.go +++ b/integrations/operator/controllers/resources/inference_secret_controller.go @@ -102,7 +102,7 @@ func NewInferenceSecretReconciler( client, secretClient, reconcilers.Config{ - CheckFeatures: controllers.RequirePolicy, + CheckFeatures: controllers.RequireSessionSummaries, }, ) diff --git a/integrations/operator/controllers/resources/retrieval_model_controller.go b/integrations/operator/controllers/resources/retrieval_model_controller.go index 0d03d2e0f94c..b2a7d72a7576 100644 --- a/integrations/operator/controllers/resources/retrieval_model_controller.go +++ b/integrations/operator/controllers/resources/retrieval_model_controller.go @@ -94,7 +94,7 @@ func NewRetrievalModelV1Reconciler(client kclient.Client, tClient *client.Client client, rmClient, reconcilers.Config{ - CheckFeatures: controllers.RequirePolicy, + CheckFeatures: controllers.RequireSessionSummaries, }, ) diff --git a/integrations/operator/controllers/resources/setup_test.go b/integrations/operator/controllers/resources/setup_test.go index 1e2be35b021c..15a49e6af3cf 100644 --- a/integrations/operator/controllers/resources/setup_test.go +++ b/integrations/operator/controllers/resources/setup_test.go @@ -31,10 +31,32 @@ import ( "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/entitlements" "github.com/gravitational/teleport/integrations/operator/controllers" "github.com/gravitational/teleport/lib/utils/log/logtest" ) +func TestRequireSessionSummaries(t *testing.T) { + t.Parallel() + + require.True(t, controllers.RequireSessionSummaries(&proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + }, + })) + require.False(t, controllers.RequireSessionSummaries(&proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + string(entitlements.SessionSummaries): {Enabled: false}, + }, + })) + require.True(t, controllers.RequireSessionSummaries(&proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.SessionSummaries): {Enabled: true}, + }, + })) +} + type fakeReconciler struct { reconcile.Reconciler gvk schema.GroupVersionKind diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index df394d3430b3..42524e3e98a5 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -4288,8 +4288,8 @@ func (a *ScopedServerWithRoles) generateUserCerts(ctx context.Context, req proto } if req.Usage == proto.UserCertsRequest_AccessGraphAPI { - if !a.authServer.modules.Features().GetEntitlement(entitlements.Policy).Enabled { - return nil, trace.AccessDenied("access graph requires a Teleport Policy license") + if !a.authServer.modules.Features().GetEntitlement(entitlements.AccessGraph).Enabled { + return nil, trace.AccessDenied("access graph requires a Teleport Access Graph license") } if isScoped { return nil, trace.Wrap(services.ErrScopedIdentity, "access graph is not permitted") diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index 7956a73dff30..6c7591c024d4 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -14001,9 +14001,9 @@ func TestScopedUserCertGeneration(t *testing.T) { TestBuildType: modules.BuildEnterprise, TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, - entitlements.K8s: {Enabled: true}, - entitlements.App: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, + entitlements.K8s: {Enabled: true}, + entitlements.App: {Enabled: true}, }, }, }), withClock(clock)) diff --git a/lib/auth/clusterconfig/clusterconfigv1/service.go b/lib/auth/clusterconfig/clusterconfigv1/service.go index 2e03bb3802e7..2dd1ad32f8a6 100644 --- a/lib/auth/clusterconfig/clusterconfigv1/service.go +++ b/lib/auth/clusterconfig/clusterconfigv1/service.go @@ -982,8 +982,9 @@ func (s *Service) GetClusterAccessGraphConfig(ctx context.Context, _ *clustercon return nil, trace.AccessDenied("this request can be only executed by a Teleport service") } - // If the policy feature is disabled in the license, return a disabled response. if cloud, return the response to allow demo mode enabling - if !s.modules.Features().GetEntitlement(entitlements.Policy).Enabled && !s.modules.Features().AccessGraph && !s.modules.Features().Cloud { + // If Access Graph is disabled in the license, return a disabled response. If + // this is a Cloud cluster, return the response to allow demo mode enabling. + if !s.modules.Features().GetEntitlement(entitlements.AccessGraph).Enabled && !s.modules.Features().AccessGraph && !s.modules.Features().Cloud { return clusterconfigpb.GetClusterAccessGraphConfigResponse_builder{ AccessGraph: clusterconfigpb.AccessGraphConfig_builder{ Enabled: false, @@ -1084,8 +1085,8 @@ func (s *Service) UpdateAccessGraphSettings(ctx context.Context, req *clustercon return nil, trace.Wrap(err) } - if !s.modules.Features().GetEntitlement(entitlements.Policy).Enabled && !s.modules.Features().AccessGraph && !s.modules.Features().Cloud { - return nil, trace.AccessDenied("access graph is feature isn't enabled") + if !s.modules.Features().GetEntitlement(entitlements.AccessGraph).Enabled && !s.modules.Features().AccessGraph && !s.modules.Features().Cloud { + return nil, trace.AccessDenied("access graph feature isn't enabled") } cfg := req.GetAccessGraphSettings() @@ -1128,8 +1129,8 @@ func (s *Service) UpsertAccessGraphSettings(ctx context.Context, req *clustercon return nil, trace.Wrap(err) } - if !s.modules.Features().GetEntitlement(entitlements.Policy).Enabled && !s.modules.Features().AccessGraph { - return nil, trace.AccessDenied("access graph is feature isn't enabled") + if !s.modules.Features().GetEntitlement(entitlements.AccessGraph).Enabled && !s.modules.Features().AccessGraph { + return nil, trace.AccessDenied("access graph feature isn't enabled") } cfg := req.GetAccessGraphSettings() @@ -1172,8 +1173,8 @@ func (s *Service) ResetAccessGraphSettings(ctx context.Context, _ *clusterconfig return nil, trace.Wrap(err) } - if !s.modules.Features().GetEntitlement(entitlements.Policy).Enabled && !s.modules.Features().AccessGraph { - return nil, trace.AccessDenied("access graph is feature isn't enabled") + if !s.modules.Features().GetEntitlement(entitlements.AccessGraph).Enabled && !s.modules.Features().AccessGraph { + return nil, trace.AccessDenied("access graph feature isn't enabled") } obj, err := clusterconfig.NewAccessGraphSettings(clusterconfigpb.AccessGraphSettingsSpec_builder{ diff --git a/lib/auth/clusterconfig/clusterconfigv1/service_test.go b/lib/auth/clusterconfig/clusterconfigv1/service_test.go index 847d38ba0d6c..96c8f79afb26 100644 --- a/lib/auth/clusterconfig/clusterconfigv1/service_test.go +++ b/lib/auth/clusterconfig/clusterconfigv1/service_test.go @@ -2004,7 +2004,7 @@ func TestGetAccessGraphConfig(t *testing.T) { accessGraphSettings *clusterconfigpb.AccessGraphSettings }{ { - name: "authorized proxy with non empty access graph config; Policy module is disabled", + name: "authorized proxy with non empty access graph config; Access Graph entitlement is disabled", role: types.RoleProxy, accessGraphConfig: cfgEnabled, errorAssertion: require.NoError, @@ -2015,12 +2015,12 @@ func TestGetAccessGraphConfig(t *testing.T) { }.Build(), }, { - name: "authorized proxy with non empty access graph config; Policy module is enabled", + name: "authorized proxy with non empty access graph config; Access Graph entitlement is enabled", role: types.RoleProxy, modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, @@ -2037,12 +2037,12 @@ func TestGetAccessGraphConfig(t *testing.T) { }.Build(), }, { - name: "authorized discovery with non empty access graph config; Policy module is enabled", + name: "authorized discovery with non empty access graph config; Access Graph entitlement is enabled", role: types.RoleDiscovery, modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, @@ -2059,12 +2059,12 @@ func TestGetAccessGraphConfig(t *testing.T) { }.Build(), }, { - name: "Policy module is enabled with secrets scan option", + name: "Access Graph entitlement is enabled with secrets scan option", role: types.RoleDiscovery, modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, @@ -2222,7 +2222,7 @@ func TestUpdateAccessGraphSettings(t *testing.T) { modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, @@ -2345,7 +2345,7 @@ func TestUpsertAccessGraphSettings(t *testing.T) { modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, @@ -2437,7 +2437,7 @@ func TestResetAccessGraphSettings(t *testing.T) { modules: &modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }, diff --git a/lib/auth/grpcserver_test.go b/lib/auth/grpcserver_test.go index 6beda359b6be..6858a6d1248a 100644 --- a/lib/auth/grpcserver_test.go +++ b/lib/auth/grpcserver_test.go @@ -6131,7 +6131,7 @@ func TestGetAccessGraphConfig(t *testing.T) { withModules(&modulestest.Modules{ TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, }), @@ -7089,7 +7089,7 @@ func TestGenerateUserCerts_accessGraphUsage(t *testing.T) { TestBuildType: modules.BuildEnterprise, // required for Device Trust. TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, })) @@ -7213,7 +7213,7 @@ func TestGenerateUserCertsScopedBot(t *testing.T) { TestBuildType: modules.BuildEnterprise, // required for Device Trust. TestFeatures: modules.Features{ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ - entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: true}, }, }, })) diff --git a/lib/modules/modules.go b/lib/modules/modules.go index febb963c02eb..18e28592c806 100644 --- a/lib/modules/modules.go +++ b/lib/modules/modules.go @@ -81,7 +81,7 @@ type Features struct { // AccessGraph enables the usage of access graph. // NOTE: this is a legacy flag that is currently used to signal // that Access Graph integration is *enabled* on a cluster. - // *Access* to the feature is gated on the `Policy` flag. + // *Access* to the feature is gated on the `AccessGraph` entitlement. // TODO(justinas): remove this field once "TAG enabled" status is moved to a resource in the backend. AccessGraph bool // AccessMonitoringConfigured contributes to the enablement of access monitoring. @@ -147,19 +147,15 @@ func (f Features) ToProto() *proto.Features { } } -// EntitlementsToProto takes the features.Entitlements object and returns a proto version. If not present on Features, the -// proto entitlement will default to false +// EntitlementsToProto takes the features.Entitlements object and returns a +// proto version. Missing split identity security entitlements fall back to the +// legacy Policy entitlement. func (f Features) EntitlementsToProto() map[string]*proto.EntitlementInfo { all := entitlements.AllEntitlements result := make(map[string]*proto.EntitlementInfo, len(all)) for _, e := range all { - al, ok := f.Entitlements[e] - if !ok { - result[string(e)] = &proto.EntitlementInfo{} - continue - } - + al := f.GetEntitlement(e) result[string(e)] = &proto.EntitlementInfo{ Enabled: al.Enabled, Limit: al.Limit, @@ -169,31 +165,56 @@ func (f Features) EntitlementsToProto() map[string]*proto.EntitlementInfo { return result } -// GetEntitlement takes an entitlement and returns either the Features entitlement, or if not present, a false entitlement +// GetEntitlement returns the requested entitlement. For compatibility with +// licenses that predate the identity security entitlement split, a missing +// AccessGraph, ActivityCenter, or SessionSummaries entitlement falls back to an +// enabled legacy Policy entitlement. func (f Features) GetEntitlement(e entitlements.EntitlementKind) EntitlementInfo { al, ok := f.Entitlements[e] - if !ok { - return EntitlementInfo{} + if ok { + return EntitlementInfo{ + Enabled: al.Enabled, + Limit: al.Limit, + } } - return EntitlementInfo{ - Enabled: al.Enabled, - Limit: al.Limit, + if isLegacyPolicyFallbackEntitlement(e) && f.Entitlements[entitlements.Policy].Enabled { + return EntitlementInfo{Enabled: true} } + + return EntitlementInfo{} } -// GetProtoEntitlement takes a proto features set and an entitlement and returns either the proto features entitlement, -// or if not present, a false entitlement +// GetProtoEntitlement returns the requested proto entitlement. For +// compatibility with clusters that predate the identity security entitlement +// split, a missing AccessGraph, ActivityCenter, or SessionSummaries entitlement +// falls back to an enabled legacy Policy entitlement or Policy proto field. func GetProtoEntitlement(f *proto.Features, e entitlements.EntitlementKind) *proto.EntitlementInfo { fE := f.GetEntitlements() al, ok := fE[string(e)] - if !ok { - return &proto.EntitlementInfo{} + if ok { + return &proto.EntitlementInfo{ + Enabled: al.Enabled, + Limit: al.Limit, + } } - return &proto.EntitlementInfo{ - Enabled: al.Enabled, - Limit: al.Limit, + if isLegacyPolicyFallbackEntitlement(e) { + policy, hasPolicyEntitlement := fE[string(entitlements.Policy)] + if policy.GetEnabled() || !hasPolicyEntitlement && f.GetPolicy().GetEnabled() { + return &proto.EntitlementInfo{Enabled: true} + } + } + + return &proto.EntitlementInfo{} +} + +func isLegacyPolicyFallbackEntitlement(e entitlements.EntitlementKind) bool { + switch e { + case entitlements.AccessGraph, entitlements.ActivityCenter, entitlements.SessionSummaries: + return true + default: + return false } } diff --git a/lib/modules/modules_test.go b/lib/modules/modules_test.go index dcd7c3cb2ee9..5ea2c352eb69 100644 --- a/lib/modules/modules_test.go +++ b/lib/modules/modules_test.go @@ -244,6 +244,7 @@ func TestFeatures_GetEntitlement(t *testing.T) { Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ entitlements.AccessLists: {Enabled: true, Limit: 111}, entitlements.K8s: {Enabled: false}, + entitlements.Policy: {Enabled: true}, entitlements.SAML: {}, }, } @@ -259,6 +260,50 @@ func TestFeatures_GetEntitlement(t *testing.T) { actual = f.GetEntitlement(entitlements.UsageReporting) require.Equal(t, modules.EntitlementInfo{}, actual) + + actual = f.GetEntitlement(entitlements.AccessGraph) + require.Equal(t, modules.EntitlementInfo{Enabled: true}, actual) + + actual = f.GetEntitlement(entitlements.ActivityCenter) + require.Equal(t, modules.EntitlementInfo{Enabled: true}, actual) + + actual = f.GetEntitlement(entitlements.SessionSummaries) + require.Equal(t, modules.EntitlementInfo{Enabled: true}, actual) + + f.Entitlements[entitlements.AccessGraph] = modules.EntitlementInfo{Enabled: false} + actual = f.GetEntitlement(entitlements.AccessGraph) + require.Equal(t, modules.EntitlementInfo{Enabled: false}, actual) +} + +func TestGetProtoEntitlementLegacyPolicyFallback(t *testing.T) { + features := &proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + string(entitlements.AccessGraph): {Enabled: false}, + }, + } + + require.False(t, modules.GetProtoEntitlement(features, entitlements.AccessGraph).Enabled) + require.True(t, modules.GetProtoEntitlement(features, entitlements.ActivityCenter).Enabled) + require.True(t, modules.GetProtoEntitlement(features, entitlements.SessionSummaries).Enabled) + require.False(t, modules.GetProtoEntitlement(features, entitlements.AccessLists).Enabled) + + protoFeatures := modules.Features{ + Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ + entitlements.Policy: {Enabled: true}, + entitlements.AccessGraph: {Enabled: false}, + }, + }.ToProto() + require.False(t, protoFeatures.Entitlements[string(entitlements.AccessGraph)].Enabled) + require.True(t, protoFeatures.Entitlements[string(entitlements.ActivityCenter)].Enabled) + require.True(t, protoFeatures.Entitlements[string(entitlements.SessionSummaries)].Enabled) + + legacyFeatures := &proto.Features{ + Policy: &proto.PolicyFeature{Enabled: true}, + } + require.True(t, modules.GetProtoEntitlement(legacyFeatures, entitlements.AccessGraph).Enabled) + require.True(t, modules.GetProtoEntitlement(legacyFeatures, entitlements.ActivityCenter).Enabled) + require.True(t, modules.GetProtoEntitlement(legacyFeatures, entitlements.SessionSummaries).Enabled) } func TestEntitlementInfo_UnderLimit(t *testing.T) { diff --git a/lib/srv/discovery/access_graph_aws.go b/lib/srv/discovery/access_graph_aws.go index c5774f89a746..7f452641d595 100644 --- a/lib/srv/discovery/access_graph_aws.go +++ b/lib/srv/discovery/access_graph_aws.go @@ -333,6 +333,15 @@ func newAccessGraphClient(ctx context.Context, getCert func() (*tls.Certificate, // in the cluster features. var errTAGFeatureNotEnabled = errors.New("TAG feature is not enabled") +func accessGraphEntitlementEnabled(features *proto.Features) bool { + return features.GetAccessGraph() || + modules.GetProtoEntitlement(features, entitlements.AccessGraph).Enabled +} + +func activityCenterEntitlementEnabled(features *proto.Features) bool { + return modules.GetProtoEntitlement(features, entitlements.ActivityCenter).Enabled +} + // initializeAndWatchAccessGraph creates a new access graph service client and // watches the connection state. If the connection is closed, it will // automatically try to reconnect. @@ -343,8 +352,7 @@ func (s *Server) initializeAndWatchAccessGraph(ctx context.Context, reloadCh <-c ) clusterFeatures := s.Config.ClusterFeatures() - policy := modules.GetProtoEntitlement(&clusterFeatures, entitlements.Policy) - if !clusterFeatures.AccessGraph && !policy.Enabled { + if !accessGraphEntitlementEnabled(&clusterFeatures) { return trace.Wrap(errTAGFeatureNotEnabled) } @@ -645,8 +653,7 @@ func (s *Server) startCloudtrailPoller(ctx context.Context, reloadCh <-chan stru const semaphoreName = "access_graph_aws_cloudtrail_sync" clusterFeatures := s.Config.ClusterFeatures() - policy := modules.GetProtoEntitlement(&clusterFeatures, entitlements.Policy) - if !clusterFeatures.AccessGraph && !policy.Enabled { + if !activityCenterEntitlementEnabled(&clusterFeatures) { return trace.Wrap(errTAGFeatureNotEnabled) } diff --git a/lib/srv/discovery/access_graph_aws_test.go b/lib/srv/discovery/access_graph_aws_test.go index 7a612edb0196..902f8f20879a 100644 --- a/lib/srv/discovery/access_graph_aws_test.go +++ b/lib/srv/discovery/access_graph_aws_test.go @@ -39,9 +39,76 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/entitlements" ) +func TestIdentitySecurityEntitlementGates(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + features *proto.Features + wantAccessGraph bool + wantActivityCenter bool + }{ + { + name: "legacy AccessGraph enabled signal", + features: &proto.Features{ + AccessGraph: true, + }, + wantAccessGraph: true, + }, + { + name: "legacy Policy entitlement enables missing split features", + features: &proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + }, + }, + wantAccessGraph: true, + wantActivityCenter: true, + }, + { + name: "explicit split entitlements override legacy Policy fallback", + features: &proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + string(entitlements.AccessGraph): {Enabled: false}, + string(entitlements.ActivityCenter): {Enabled: false}, + }, + }, + }, + { + name: "Access Graph entitlement", + features: &proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.AccessGraph): {Enabled: true}, + }, + }, + wantAccessGraph: true, + }, + { + name: "Activity Center entitlement", + features: &proto.Features{ + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.ActivityCenter): {Enabled: true}, + }, + }, + wantActivityCenter: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.wantAccessGraph, accessGraphEntitlementEnabled(tt.features)) + require.Equal(t, tt.wantActivityCenter, activityCenterEntitlementEnabled(tt.features)) + }) + } +} + func TestSQSPollEvents(t *testing.T) { t.Parallel() diff --git a/lib/srv/discovery/access_graph_azure.go b/lib/srv/discovery/access_graph_azure.go index e2c0892348b3..d24da3e4718e 100644 --- a/lib/srv/discovery/access_graph_azure.go +++ b/lib/srv/discovery/access_graph_azure.go @@ -31,9 +31,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/retryutils" - "github.com/gravitational/teleport/entitlements" accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha" - "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/discovery/fetchers/azuresync" ) @@ -212,8 +210,7 @@ func (s *Server) getAllTAGSyncAzureFetchers() []*azuresync.Fetcher { func (s *Server) initializeAndWatchAzureAccessGraph(ctx context.Context, reloadCh chan struct{}) error { // Check if the access graph is enabled clusterFeatures := s.Config.ClusterFeatures() - policy := modules.GetProtoEntitlement(&clusterFeatures, entitlements.Policy) - if !clusterFeatures.AccessGraph && !policy.Enabled { + if !accessGraphEntitlementEnabled(&clusterFeatures) { return trace.Wrap(errTAGFeatureNotEnabled) } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 826c94fd8a0c..1199c170d6f7 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -2270,9 +2270,11 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou IsPolicyEnabled: modules.GetProtoEntitlement(&clusterFeatures, entitlements.Policy).Enabled, // if Entitlements are not present, GetWebCfgEntitlements will return a map of entitlement to {enabled:false} // if Entitlements are present, GetWebCfgEntitlements will populate the fields appropriately - Entitlements: GetWebCfgEntitlements(clusterFeatures.GetEntitlements()), + Entitlements: getWebCfgEntitlements(&clusterFeatures), IdentitySecurity: webclient.IdentitySecurity{ - IsClusterLicensed: modules.GetProtoEntitlement(&clusterFeatures, entitlements.Policy).Enabled, + IsClusterLicensed: modules.GetProtoEntitlement(&clusterFeatures, entitlements.AccessGraph).Enabled || + modules.GetProtoEntitlement(&clusterFeatures, entitlements.ActivityCenter).Enabled || + modules.GetProtoEntitlement(&clusterFeatures, entitlements.SessionSummaries).Enabled, AccessGraphConfigSet: accessGraphConfigSet, SessionSummarizationEnabled: sessionSummarizerEnabled, }, @@ -2441,18 +2443,18 @@ func (h *Handler) getUserMatchedAuthConnectors(w http.ResponseWriter, r *http.Re }, nil } -// GetWebCfgEntitlements takes a cloud entitlement set and returns a modules Entitlement set +// GetWebCfgEntitlements converts a proto entitlement map into the Web UI +// representation, including the legacy Policy fallback. func GetWebCfgEntitlements(protoEntitlements map[string]*proto.EntitlementInfo) map[string]webclient.EntitlementInfo { + return getWebCfgEntitlements(&proto.Features{Entitlements: protoEntitlements}) +} + +func getWebCfgEntitlements(features *proto.Features) map[string]webclient.EntitlementInfo { all := entitlements.AllEntitlements result := make(map[string]webclient.EntitlementInfo, len(all)) for _, e := range all { - al, ok := protoEntitlements[string(e)] - if !ok { - result[string(e)] = webclient.EntitlementInfo{} - continue - } - + al := modules.GetProtoEntitlement(features, e) result[string(e)] = webclient.EntitlementInfo{ Enabled: al.Enabled, Limit: al.Limit, diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 5ce640689817..cf105466ac3d 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -5370,6 +5370,34 @@ func (s *safeModules) SetFeatures(f modules.Features) { s.Modules.TestFeatures = f } +func TestGetWebCfgEntitlementsLegacyPolicyFallback(t *testing.T) { + t.Parallel() + + got := GetWebCfgEntitlements(map[string]*authproto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + }) + require.True(t, got[string(entitlements.AccessGraph)].Enabled) + require.True(t, got[string(entitlements.ActivityCenter)].Enabled) + require.True(t, got[string(entitlements.SessionSummaries)].Enabled) + + got = GetWebCfgEntitlements(map[string]*authproto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + string(entitlements.AccessGraph): {Enabled: false}, + string(entitlements.ActivityCenter): {Enabled: false}, + string(entitlements.SessionSummaries): {Enabled: false}, + }) + require.False(t, got[string(entitlements.AccessGraph)].Enabled) + require.False(t, got[string(entitlements.ActivityCenter)].Enabled) + require.False(t, got[string(entitlements.SessionSummaries)].Enabled) + + got = getWebCfgEntitlements(&authproto.Features{ + Policy: &authproto.PolicyFeature{Enabled: true}, + }) + require.True(t, got[string(entitlements.AccessGraph)].Enabled) + require.True(t, got[string(entitlements.ActivityCenter)].Enabled) + require.True(t, got[string(entitlements.SessionSummaries)].Enabled) +} + func TestGetWebConfig_WithEntitlements(t *testing.T) { synctest.Test(t, func(t *testing.T) { ctx := t.Context() @@ -5572,6 +5600,7 @@ func TestGetWebConfig_WithEntitlements(t *testing.T) { entitlements.DB: {Enabled: true, Limit: 22}, entitlements.DeviceTrust: {Enabled: true, Limit: 33}, entitlements.Desktop: {Enabled: true, Limit: 44}, + entitlements.Policy: {Enabled: true}, }, }) @@ -5590,12 +5619,18 @@ func TestGetWebConfig_WithEntitlements(t *testing.T) { expectedCfg.AutomaticUpgrades = true expectedCfg.AutomaticUpgradesTargetVersion = "v" + teleport.Version expectedCfg.Edition = testModules.BuildType() + expectedCfg.Entitlements[string(entitlements.AccessGraph)] = webclient.EntitlementInfo{Enabled: true} + expectedCfg.Entitlements[string(entitlements.ActivityCenter)] = webclient.EntitlementInfo{Enabled: true} expectedCfg.Entitlements[string(entitlements.App)] = webclient.EntitlementInfo{Enabled: false} expectedCfg.Entitlements[string(entitlements.DB)] = webclient.EntitlementInfo{Enabled: true, Limit: 22} expectedCfg.Entitlements[string(entitlements.DeviceTrust)] = webclient.EntitlementInfo{Enabled: true, Limit: 33} expectedCfg.Entitlements[string(entitlements.Desktop)] = webclient.EntitlementInfo{Enabled: true, Limit: 44} expectedCfg.Entitlements[string(entitlements.JoinActiveSessions)] = webclient.EntitlementInfo{Enabled: false} expectedCfg.Entitlements[string(entitlements.K8s)] = webclient.EntitlementInfo{Enabled: false} + expectedCfg.Entitlements[string(entitlements.Policy)] = webclient.EntitlementInfo{Enabled: true} + expectedCfg.Entitlements[string(entitlements.SessionSummaries)] = webclient.EntitlementInfo{Enabled: true} + expectedCfg.IdentitySecurity.IsClusterLicensed = true + expectedCfg.IsPolicyEnabled = true // Advance time to unblock the feature watcher. Wait until // the features have been retrieved and the feature watcher is blocked diff --git a/skills/teleport-session-review/SKILL.md b/skills/teleport-session-review/SKILL.md index 126d02094678..2729df90836b 100644 --- a/skills/teleport-session-review/SKILL.md +++ b/skills/teleport-session-review/SKILL.md @@ -109,7 +109,8 @@ answer instead of a raw error: curl -s https:///web/config.js \ | sed -E 's/^[^{]*//; s/;[[:space:]]*$//' \ | jq '{edition, - identitySecurityLicensed: .identitySecurity.licensed, + sessionSummariesLicensed: .entitlements.SessionSummaries.enabled, + accessGraphLicensed: .entitlements.AccessGraph.enabled, sessionSummarization: .identitySecurity.sessionSummarizationEnabled, accessGraphConfigSet: .identitySecurity.accessGraphConfigSet}' ``` @@ -117,9 +118,10 @@ answer instead of a raw error: Interpret (treat `null`/absent the same as `false` — on some clusters, including Enterprise **Cloud** tenants, the whole `identitySecurity` block is missing from `config.js` even when Identity Security is entitled): - - `edition` is `oss`/`community` **or** `identitySecurityLicensed` is not true → - this cluster cannot do summaries/search. Tell the user it requires - Enterprise + Identity Security, and use `recordings ls` instead. + - `edition` is `oss`/`community`, `sessionSummariesLicensed` is not true, or + `accessGraphLicensed` is not true → this cluster cannot do + summaries/search. Tell the user it requires Enterprise with the Session + Summaries and Access Graph entitlements, and use `recordings ls` instead. - `sessionSummarization` (i.e. `identitySecurity.sessionSummarizationEnabled`, the gate the user asked about) is not true, or `accessGraphConfigSet` is not true → licensed but not turned on; tell them to enable session summarization diff --git a/skills/teleport-session-review/references/SCHEMA.md b/skills/teleport-session-review/references/SCHEMA.md index 25d8c1657dfb..842f815c6543 100644 --- a/skills/teleport-session-review/references/SCHEMA.md +++ b/skills/teleport-session-review/references/SCHEMA.md @@ -155,7 +155,8 @@ Relevant fields (observed on a live v18.8 Enterprise cluster): | Field | Meaning | |---|---| | `edition` | `ent` = Enterprise; `oss`/`community` = no summaries/search. | -| `identitySecurity.licensed` | Identity Security is licensed. | +| `entitlements.SessionSummaries.enabled` | Session Summaries is licensed. | +| `entitlements.AccessGraph.enabled` | Access Graph is licensed. | | `identitySecurity.sessionSummarizationEnabled` | **The session-summarization gate.** Must be `true` for search/summaries. | | `identitySecurity.accessGraphConfigSet` | Access Graph is configured. | | `sessionSummarizerEnabled` | Top-level mirror of the summarization gate. | @@ -167,7 +168,8 @@ requires 18.8.0+). Capability summary: `recordings ls` / `download` and `tsh play` work on **every edition**; `recordings search` (+ AI summaries) needs `edition=ent` **and** -`identitySecurity.licensed=true` **and** +`entitlements.SessionSummaries.enabled=true` **and** +`entitlements.AccessGraph.enabled=true` **and** `identitySecurity.sessionSummarizationEnabled=true` on a **v18.8.0+** proxy. --- diff --git a/tool/tctl/common/accessgraph/credentials.go b/tool/tctl/common/accessgraph/credentials.go index 2a6fb9994cdd..e3282fb72832 100644 --- a/tool/tctl/common/accessgraph/credentials.go +++ b/tool/tctl/common/accessgraph/credentials.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/entitlements" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/modules" commonclient "github.com/gravitational/teleport/tool/tctl/common/client" tctlcfg "github.com/gravitational/teleport/tool/tctl/common/config" ) @@ -198,18 +199,12 @@ func ensureAccessGraphCert(ctx context.Context, creds *accessGraphCredentials, c return trace.Wrap(issueAndStoreAccessGraphCert(ctx, creds, authClient)) } -// checkAccessGraphSupported gates on the `Policy` entitlement. +// checkAccessGraphSupported gates on the `AccessGraph` entitlement. // Endpoint reachability is checked at AG call time, not here. func checkAccessGraphSupported(ctx context.Context, ping proto.PingResponse) error { features := ping.GetServerFeatures() - // This gate routes the "not licensed" error message and accepts the legacy - // `Policy` submessage from older clusters that predate the entitlements map. - // - // TODO(ghassan): when #66571's follow-ups split Identity Security out of Policy, - // gate on the more specific entitlement here and keep Policy as the legacy fallback. - policy := features.GetEntitlements()[string(entitlements.Policy)] - licensed := policy.GetEnabled() || features.GetPolicy().GetEnabled() + licensed := modules.GetProtoEntitlement(features, entitlements.AccessGraph).Enabled if !licensed { return trace.AccessDenied(unlicensedAccessGraphMessage) } diff --git a/tool/tctl/common/accessgraph/credentials_test.go b/tool/tctl/common/accessgraph/credentials_test.go index 246706a6d9da..b4ba5853d178 100644 --- a/tool/tctl/common/accessgraph/credentials_test.go +++ b/tool/tctl/common/accessgraph/credentials_test.go @@ -150,7 +150,7 @@ func pingResponseAccessGraphReady() proto.PingResponse { ServerFeatures: &proto.Features{ AccessGraph: true, Entitlements: map[string]*proto.EntitlementInfo{ - string(entitlements.Policy): {Enabled: true}, + string(entitlements.AccessGraph): {Enabled: true}, }, }, } @@ -658,12 +658,26 @@ func TestCheckAccessGraphSupported(t *testing.T) { wantSubstr []string // every substring must appear in err.Error() }{ { - name: "Policy entitlement enabled → no error", + name: "AccessGraph entitlement enabled → no error", ping: pingResponseAccessGraphReady(), wantErr: func(err error) bool { return err == nil }, }, + { + // Older clusters have an entitlements map but no dedicated + // AccessGraph key. + name: "legacy Policy entitlement enabled → no error", + ping: proto.PingResponse{ + ServerFeatures: &proto.Features{ + AccessGraph: true, + Entitlements: map[string]*proto.EntitlementInfo{ + string(entitlements.Policy): {Enabled: true}, + }, + }, + }, + wantErr: func(err error) bool { return err == nil }, + }, { // Older clusters set only the legacy Policy submessage. name: "legacy Policy submessage enabled → no error", @@ -681,7 +695,7 @@ func TestCheckAccessGraphSupported(t *testing.T) { ServerFeatures: &proto.Features{ AccessGraph: false, Entitlements: map[string]*proto.EntitlementInfo{ - string(entitlements.Policy): {Enabled: true}, + string(entitlements.AccessGraph): {Enabled: true}, }, }, }, @@ -694,12 +708,13 @@ func TestCheckAccessGraphSupported(t *testing.T) { }, }, { - name: "Policy entitlement explicitly disabled → AccessDenied", + name: "AccessGraph entitlement explicitly disabled does not fall back to Policy → AccessDenied", ping: proto.PingResponse{ ServerFeatures: &proto.Features{ AccessGraph: true, Entitlements: map[string]*proto.EntitlementInfo{ - string(entitlements.Policy): {Enabled: false}, + string(entitlements.AccessGraph): {Enabled: false}, + string(entitlements.Policy): {Enabled: true}, }, }, }, @@ -719,9 +734,7 @@ func TestCheckAccessGraphSupported(t *testing.T) { }, }, { - // Regression guard: the `AccessGraph` entitlement key is - // never populated by the modules code — only `Policy` is. - name: "AccessGraph-key entitlement on its own is NOT sufficient — Policy is the gate", + name: "AccessGraph entitlement on its own is sufficient", ping: proto.PingResponse{ ServerFeatures: &proto.Features{ AccessGraph: true, @@ -730,9 +743,8 @@ func TestCheckAccessGraphSupported(t *testing.T) { }, }, }, - wantErr: trace.IsAccessDenied, - wantSubstr: []string{ - "Identity Security", + wantErr: func(err error) bool { + return err == nil }, }, } diff --git a/web/packages/teleport/src/Roles/PolicyPlaceholder.tsx b/web/packages/teleport/src/Roles/PolicyPlaceholder.tsx index c6b0b5c76aa9..04e41652babf 100644 --- a/web/packages/teleport/src/Roles/PolicyPlaceholder.tsx +++ b/web/packages/teleport/src/Roles/PolicyPlaceholder.tsx @@ -72,7 +72,8 @@ export function PolicyPlaceholder({ // roleDiffProps can be undefined if not cloud and not role tester // enabled. - const hideSalesButton = (cfg.isPolicyEnabled || cfg.isCloud) && roleDiffProps; + const hideSalesButton = + (cfg.entitlements.AccessGraph.enabled || cfg.isCloud) && roleDiffProps; return ( @@ -90,7 +91,7 @@ export function PolicyPlaceholder({ {canUpdateAccessGraphSettings && - !cfg.isPolicyEnabled && + !cfg.entitlements.AccessGraph.enabled && cfg.isCloud && enableDemoMode && ( // cloud can enable a demo mode so show that button parameters.acl; } if (args.roleDiffEnabled) { - cfg.isPolicyEnabled = true; + cfg.entitlements.AccessGraph = { enabled: true, limit: 0 }; storageService.getAccessGraphRoleTesterEnabled = () => true; } useEffect(() => { // Clean up return () => { - cfg.isPolicyEnabled = defaultIsPolicyEnabled; + cfg.entitlements.AccessGraph = defaultAccessGraphEntitlement; storageService.getAccessGraphRoleTesterEnabled = defaultGetAccessGraphRoleTesterEnabled; }; diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx index 38d74da680a3..e514b67069da 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx @@ -48,7 +48,7 @@ import { defaultRoleVersion, newRole } from './StandardEditor/standardmodel'; import * as StandardModelModule from './StandardEditor/standardmodel'; import { defaultOptions, withDefaults } from './StandardEditor/withDefaults'; -const defaultIsPolicyEnabled = cfg.isPolicyEnabled; +const defaultAccessGraphEntitlement = cfg.entitlements.AccessGraph; let user: UserEvent; @@ -85,7 +85,7 @@ beforeEach(() => { afterEach(() => { jest.restoreAllMocks(); - cfg.isPolicyEnabled = defaultIsPolicyEnabled; + cfg.entitlements.AccessGraph = defaultAccessGraphEntitlement; }); test('rendering and switching tabs for new role', async () => { @@ -192,7 +192,7 @@ test('rendering and switching tabs for a non-standard role', async () => { }); it('calls onRoleUpdate on each modification in the standard editor', async () => { - cfg.isPolicyEnabled = true; + cfg.entitlements.AccessGraph = { enabled: true, limit: 0 }; const onRoleUpdate = jest.fn(); render(); expect(onRoleUpdate).toHaveBeenLastCalledWith( @@ -208,7 +208,7 @@ it('calls onRoleUpdate on each modification in the standard editor', async () => }); it('calls onRoleUpdate after the first rendering of a non-standard role', async () => { - cfg.isPolicyEnabled = true; + cfg.entitlements.AccessGraph = { enabled: true, limit: 0 }; const onRoleUpdate = jest.fn(); const nonStandardRole = withDefaults({ unsupportedField: true, @@ -423,7 +423,7 @@ test('saving a new role', async () => { }); describe('saving a new role after editing as YAML', () => { - test('with Policy disabled', async () => { + test('with Access Graph disabled', async () => { const onSave = jest.fn(); render(); @@ -455,8 +455,8 @@ describe('saving a new role after editing as YAML', () => { } as CreateNewRoleSaveClickEventData); }); - test('with Policy enabled', async () => { - cfg.isPolicyEnabled = true; + test('with Access Graph enabled', async () => { + cfg.entitlements.AccessGraph = { enabled: true, limit: 0 }; jest .spyOn(storageService, 'getAccessGraphRoleTesterEnabled') .mockReturnValue(true); diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx index 6a5e3943c83d..42fed64b968e 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx @@ -91,7 +91,8 @@ export const RoleEditor = ({ onMinimizedChange, }: RoleEditorProps) => { const roleTesterEnabled = - (cfg.isPolicyEnabled && storageService.getAccessGraphRoleTesterEnabled()) || + (cfg.entitlements.AccessGraph.enabled && + storageService.getAccessGraphRoleTesterEnabled()) || demoMode; const idPrefix = useId(); // These IDs are needed to connect accessibility attributes between the diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditorVisualizer.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditorVisualizer.test.tsx index c8c4f437be14..7d204ef158b8 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditorVisualizer.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditorVisualizer.test.tsx @@ -31,11 +31,11 @@ import { RoleDiffProps, RoleDiffState } from '../Roles'; import { RoleEditorVisualizer } from './RoleEditorVisualizer'; const defaultDemoMode = cfg.entitlements.AccessGraphDemoMode; -const defaultIsPolicyEnabled = cfg.isPolicyEnabled; +const defaultAccessGraphEntitlement = cfg.entitlements.AccessGraph; const defaultIsCloud = cfg.isCloud; afterEach(() => { cfg.entitlements.AccessGraphDemoMode = defaultDemoMode; - cfg.isPolicyEnabled = defaultIsPolicyEnabled; + cfg.entitlements.AccessGraph = defaultAccessGraphEntitlement; cfg.isCloud = defaultIsCloud; }); diff --git a/web/packages/teleport/src/config.test.ts b/web/packages/teleport/src/config.test.ts index 8ab3b84ff6ed..6a0e81eb4158 100644 --- a/web/packages/teleport/src/config.test.ts +++ b/web/packages/teleport/src/config.test.ts @@ -25,6 +25,73 @@ import cfg, { } from './config'; import { IntegrationTag } from './Integrations/Enroll/Shared'; +describe('legacy Policy entitlement fallback', () => { + const original = { + isPolicyEnabled: cfg.isPolicyEnabled, + Policy: { ...cfg.entitlements.Policy }, + AccessGraph: { ...cfg.entitlements.AccessGraph }, + ActivityCenter: { ...cfg.entitlements.ActivityCenter }, + SessionSummaries: { ...cfg.entitlements.SessionSummaries }, + }; + + afterEach(() => { + cfg.isPolicyEnabled = original.isPolicyEnabled; + cfg.entitlements.Policy = { ...original.Policy }; + cfg.entitlements.AccessGraph = { ...original.AccessGraph }; + cfg.entitlements.ActivityCenter = { ...original.ActivityCenter }; + cfg.entitlements.SessionSummaries = { ...original.SessionSummaries }; + }); + + test('enables missing split entitlements from the Policy entitlement', () => { + cfg.init({ + entitlements: { + Policy: { enabled: true, limit: 0 }, + }, + }); + + expect(cfg.entitlements.AccessGraph.enabled).toBe(true); + expect(cfg.entitlements.ActivityCenter.enabled).toBe(true); + expect(cfg.entitlements.SessionSummaries.enabled).toBe(true); + }); + + test('uses the old isPolicyEnabled flag when entitlements are absent', () => { + cfg.init({ + isPolicyEnabled: true, + }); + + expect(cfg.entitlements.Policy.enabled).toBe(true); + expect(cfg.entitlements.AccessGraph.enabled).toBe(true); + expect(cfg.entitlements.ActivityCenter.enabled).toBe(true); + expect(cfg.entitlements.SessionSummaries.enabled).toBe(true); + }); + + test('does not apply fallback when any split entitlement is present', () => { + cfg.init({ + isPolicyEnabled: true, + entitlements: { + AccessGraph: { enabled: false, limit: 0 }, + }, + }); + + expect(cfg.entitlements.AccessGraph.enabled).toBe(false); + expect(cfg.entitlements.ActivityCenter.enabled).toBe(false); + expect(cfg.entitlements.SessionSummaries.enabled).toBe(false); + }); + + test('an explicit Policy entitlement overrides the deprecated flag', () => { + cfg.init({ + isPolicyEnabled: true, + entitlements: { + Policy: { enabled: false, limit: 0 }, + }, + }); + + expect(cfg.entitlements.AccessGraph.enabled).toBe(false); + expect(cfg.entitlements.ActivityCenter.enabled).toBe(false); + expect(cfg.entitlements.SessionSummaries.enabled).toBe(false); + }); +}); + test('getDeployServiceIamConfigureScriptPath formatting', () => { const params: UrlDeployServiceIamConfigureScriptParams = { integrationName: 'int-name', diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index d002e8c036e1..b40cd492dc98 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -50,7 +50,10 @@ import type { RecordingType } from 'teleport/services/recordings'; import type { ParticipantMode } from 'teleport/services/session'; import type { YamlSupportedResourceKind } from 'teleport/services/yaml/types'; -import { defaultEntitlements } from './entitlement'; +import { + applyLegacyPolicyEntitlementFallback, + defaultEntitlements, +} from './entitlement'; import generateResourcePath from './generateResourcePath'; import { IntegrationTag } from './Integrations/Enroll/Shared'; import type { MfaChallengeResponse } from './services/mfa'; @@ -283,8 +286,8 @@ const cfg = { oidc: false, /** @deprecated Use entitlements instead; remove in v20 */ saml: false, - // isPolicyEnabled refers to the Teleport Policy product - /** @deprecated Use entitlements.Policy.enabled instead;*/ + // isPolicyEnabled refers to the legacy Teleport Policy product. + /** @deprecated Use the feature-specific identity security entitlements instead. */ isPolicyEnabled: false, ui: { @@ -2078,7 +2081,7 @@ const cfg = { }, init(backendConfig = {}) { - mergeDeep(this, backendConfig); + mergeDeep(this, applyLegacyPolicyEntitlementFallback(backendConfig)); }, }; diff --git a/web/packages/teleport/src/entitlement.ts b/web/packages/teleport/src/entitlement.ts index 8440bbdc3094..9e794edb7e98 100644 --- a/web/packages/teleport/src/entitlement.ts +++ b/web/packages/teleport/src/entitlement.ts @@ -49,10 +49,19 @@ type entitlement = | 'UpsellAlert' | 'UsageReporting'; -export const defaultEntitlements: Record< - entitlement, - { enabled: boolean; limit: number } -> = { +type EntitlementInfo = { enabled: boolean; limit: number }; +type LegacyPolicyConfig = { + isPolicyEnabled?: boolean; + entitlements?: Partial>; +}; + +const legacyPolicyFallbackEntitlements = [ + 'AccessGraph', + 'ActivityCenter', + 'SessionSummaries', +] as const satisfies readonly entitlement[]; + +export const defaultEntitlements: Record = { AccessGraph: { enabled: false, limit: 0 }, AccessGraphDemoMode: { enabled: false, limit: 0 }, AccessLists: { enabled: false, limit: 0 }, @@ -84,3 +93,40 @@ export const defaultEntitlements: Record< UpsellAlert: { enabled: false, limit: 0 }, UsageReporting: { enabled: false, limit: 0 }, }; + +/** + * Applies the Identity Security entitlement split for config payloads from + * older proxies. The fallback is applied only if all split entitlements are + * absent. + */ +export function applyLegacyPolicyEntitlementFallback< + T extends LegacyPolicyConfig, +>(config: T): T { + const incomingEntitlements = config.entitlements ?? {}; + const hasPolicyEntitlement = Object.prototype.hasOwnProperty.call( + incomingEntitlements, + 'Policy' + ); + const policyEnabled = hasPolicyEntitlement + ? incomingEntitlements?.Policy?.enabled === true + : config.isPolicyEnabled === true; + const hasSplitEntitlement = legacyPolicyFallbackEntitlements.some( + entitlement => + Object.prototype.hasOwnProperty.call(incomingEntitlements, entitlement) + ); + + if (!policyEnabled || hasSplitEntitlement) { + return config; + } + + const entitlements = { ...incomingEntitlements }; + if (!hasPolicyEntitlement) { + entitlements.Policy = { enabled: true, limit: 0 }; + } + + for (const entitlement of legacyPolicyFallbackEntitlements) { + entitlements[entitlement] = { enabled: true, limit: 0 }; + } + + return { ...config, entitlements }; +} From a5cde47a0b470980873dde1650ff38b092b0ace0 Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Fri, 31 Jul 2026 07:05:15 -0600 Subject: [PATCH 080/347] ui: update mwi guide link for generic linux (#69122) * ui: update mwi guide link for generic linux * ui: Change link for Generic Linux bot configuration Updated the link for Generic Linux to the new deployment guide. * ui: remove space Co-authored-by: Grzegorz Zdunek --------- Co-authored-by: Grzegorz Zdunek --- web/packages/teleport/src/Bots/Add/AddBotsPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/teleport/src/Bots/Add/AddBotsPicker.tsx b/web/packages/teleport/src/Bots/Add/AddBotsPicker.tsx index eb4a1c4e9476..9106a5e56563 100644 --- a/web/packages/teleport/src/Bots/Add/AddBotsPicker.tsx +++ b/web/packages/teleport/src/Bots/Add/AddBotsPicker.tsx @@ -182,7 +182,7 @@ export const integrations: BotIntegration[] = [ title: 'Generic Linux', description: 'Use Machine & Workload Identity (MWI) to eliminate long-lived credentials on Linux servers.', - link: 'https://goteleport.com/docs/enroll-resources/machine-id/getting-started/', + link: 'https://goteleport.com/docs/machine-workload-identity/deployment/linux/', icon: 'server', kind: IntegrationEnrollKind.MachineID, type: 'bot', From a463fd1fa121afaed8916d32f365870071091f03 Mon Sep 17 00:00:00 2001 From: Brendan O'Halloran Date: Fri, 31 Jul 2026 10:31:37 -0300 Subject: [PATCH 081/347] gha: use kvstore instead of secrets context (#68745) * gha: use kvstore instead of secrets context * fix: syntax and naming * limit exposure of secrets in pull_request triggered workflows * use shared-workflows/bot v0.0.6 * explicitly overwrite secrets in GITHUB_ENV * use env-kvstore v1.0.2 --- .github/workflows/assign.yaml | 13 ++++++-- .github/workflows/backport.yaml | 19 +++++------ .github/workflows/bloat.yaml | 6 ++-- .github/workflows/build-usage-image.yaml | 10 +++--- .github/workflows/changelog.yaml | 4 +-- .github/workflows/check.yaml | 20 +++++------- .github/workflows/cla-assistant.yaml | 21 +++++++++--- .github/workflows/dismiss.yaml | 4 +-- .github/workflows/doc-tests.yaml | 24 +++++++------- .github/workflows/docs-amplify.yaml | 4 +-- .github/workflows/flaky-tests.yaml | 38 ++++++++++++---------- .github/workflows/label.yaml | 4 +-- .github/workflows/manual-test-plan.yaml | 18 +++++++--- .github/workflows/post-release.yaml | 2 +- .github/workflows/renovate.yaml | 14 ++++++-- .github/workflows/update-ami-ids.yaml | 2 +- .github/workflows/update-docs-webhook.yaml | 2 +- 17 files changed, 122 insertions(+), 83 deletions(-) diff --git a/.github/workflows/assign.yaml b/.github/workflows/assign.yaml index 4fe33cc044e8..bc3352caf0cb 100644 --- a/.github/workflows/assign.yaml +++ b/.github/workflows/assign.yaml @@ -26,6 +26,7 @@ permissions: repository-projects: none security-events: none statuses: none + id-token: write jobs: auto-request-review: @@ -33,17 +34,25 @@ jobs: if: ${{ !github.event.pull_request.draft && !startsWith(github.head_ref, 'dependabot/') }} runs-on: ubuntu-latest steps: + - name: Get runtime values from kvstore + id: kvstore + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 + with: + cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" + cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" + values: | + REVIEWERS,secret # Checkout main branch of shared-workflow repository. - name: Checkout shared-workflow uses: actions/checkout@v6 with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' # Run "check" subcommand on bot. - name: Assigning reviewers - run: cd .github/shared-workflows/bot && go run main.go -workflow=assign -token="${{ secrets.GITHUB_TOKEN }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=assign -token="${{ secrets.GITHUB_TOKEN }}" -reviewers="${{ env.REVIEWERS }}" diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index cb2a51afc747..7f08dcb41925 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -23,21 +23,20 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" - existing-gh-secrets: "${{ toJSON(secrets) }}" - existing-gh-variables: "${{ toJSON(vars) }}" - # values: | - # BACKPORT_APP_ID,secret - # BACKPORT_PRIVATE_KEY,secret + values: | + BACKPORT_APP_ID,secret + BACKPORT_PRIVATE_KEY,secret + REVIEWERS,secret - name: Generate GitHub Token id: generate_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.BACKPORT_APP_ID }} - private-key: ${{ secrets.BACKPORT_PRIVATE_KEY }} + app-id: ${{ env.BACKPORT_APP_ID }} + private-key: ${{ env.BACKPORT_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@v6 with: @@ -49,11 +48,11 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' # Run "backport" subcommand on bot. - name: Backport PR - run: ( cd .github/shared-workflows/bot && go build ) && .github/shared-workflows/bot/bot -workflow=backport -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" + run: ( cd .github/shared-workflows/bot && go build ) && .github/shared-workflows/bot/bot -workflow=backport -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ env.REVIEWERS }}" diff --git a/.github/workflows/bloat.yaml b/.github/workflows/bloat.yaml index c9b885b642a5..2b72ac5ddcd5 100644 --- a/.github/workflows/bloat.yaml +++ b/.github/workflows/bloat.yaml @@ -40,7 +40,7 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" @@ -63,7 +63,7 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Setup base cache uses: actions/cache/restore@v5 @@ -114,7 +114,7 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Build Binaries id: build_branch diff --git a/.github/workflows/build-usage-image.yaml b/.github/workflows/build-usage-image.yaml index d736309c0674..50202204d916 100644 --- a/.github/workflows/build-usage-image.yaml +++ b/.github/workflows/build-usage-image.yaml @@ -11,14 +11,12 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" - existing-gh-secrets: "${{ toJSON(secrets) }}" - existing-gh-variables: "${{ toJSON(vars) }}" - # values: | - # TELEPORT_USAGE_IAM_ROLE_ARN,secret + values: | + TELEPORT_USAGE_IAM_ROLE_ARN,variable # This step is used to extract the version of the usage script. - name: Trim leading v in release id: version @@ -28,7 +26,7 @@ jobs: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0 with: - role-to-assume: ${{ secrets.TELEPORT_USAGE_IAM_ROLE_ARN }} + role-to-assume: ${{ env.TELEPORT_USAGE_IAM_ROLE_ARN }} aws-region: us-east-1 - uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 with: diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index dd27a9b02dad..1ff088b0aac2 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -29,10 +29,10 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' - name: Validate the changelog entry - run: cd .github/shared-workflows/bot && go run main.go -workflow=changelog -token="${{ secrets.GITHUB_TOKEN }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=changelog -token="${{ secrets.GITHUB_TOKEN }}" -reviewers='{}' diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index f7bbf4d59319..5b0c4f783065 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -38,33 +38,31 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" - existing-gh-secrets: "${{ toJSON(secrets) }}" - existing-gh-variables: "${{ toJSON(vars) }}" - # values: | - # REVIEWERS_APP_ID,secret - # REVIEWERS_PRIVATE_KEY,secret - # REVIEWERS,secret + values: | + REVIEWERS_APP_ID,variable + REVIEWERS_PRIVATE_KEY,secret + REVIEWERS,secret - name: Generate GitHub Token id: generate_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.REVIEWERS_APP_ID }} - private-key: ${{ secrets.REVIEWERS_PRIVATE_KEY }} + app-id: ${{ env.REVIEWERS_APP_ID }} + private-key: ${{ env.REVIEWERS_PRIVATE_KEY }} # Checkout main branch of shared-workflow repository. - name: Checkout shared-workflow uses: actions/checkout@v6 with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' # Run "check" subcommand on bot. - name: Checking reviewers - run: cd .github/shared-workflows/bot && go run main.go -workflow=check -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=check -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ env.REVIEWERS }}" diff --git a/.github/workflows/cla-assistant.yaml b/.github/workflows/cla-assistant.yaml index d8ddd31984cd..bda5b0e05623 100644 --- a/.github/workflows/cla-assistant.yaml +++ b/.github/workflows/cla-assistant.yaml @@ -8,7 +8,7 @@ on: - opened - synchronize # Run on any diff changes to the PR (e.g. code updates) # merge_group will allow this workflow to be triggered when in merge queue. - # The job will end up being skipped due to conditionals below. This will be considered a "Success" + # The job will end up being skipped due to conditionals below. This will be considered a "Success" # for the required check as the workflow was triggered but the job was skipped due to conditionals # See: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: @@ -17,6 +17,8 @@ permissions: actions: read contents: read # this can be 'read' if the signatures are in remote repository pull-requests: write + id-token: write + jobs: cla-assistant: # Only do job for pull requests. For issues this is skipped making workflow a no-op @@ -24,12 +26,21 @@ jobs: name: "Check Contributor License Agreement Signed" runs-on: ubuntu-latest steps: + - name: Get runtime values from kvstore + id: kvstore + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 + with: + cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" + cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" + values: | + CLA_ASSISTANT_APP_ID,variable + CLA_ASSISTANT_APP_PRIVATE_KEY,secret - name: Fetch installation token id: fetch-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.CLA_ASSISTANT_APP_ID }} - private-key: ${{ secrets.CLA_ASSISTANT_APP_PRIVATE_KEY }} + app-id: ${{ env.CLA_ASSISTANT_APP_ID }} + private-key: ${{ env.CLA_ASSISTANT_APP_PRIVATE_KEY }} repositories: cla-signatures - name: "Determine gravitational membership" id: get-membership @@ -47,14 +58,14 @@ jobs: # Otherwise for those out of the gravitational org: # * CLA Assistant gathers authors from commits made to the PR # * Will determine if authors have already signed the CLA - # + # # To sign the CLA a user can reply to the PR with the comment: # * 'I have read the CLA Document and I hereby sign the CLA' # # The workflow will be rerun again to perform a recheck on: # * Any code changes to the PR # * PR comment with the body being 'recheck' - In the event of a transient failure - # * PR comment with body 'I have read the CLA Document and I hereby sign the CLA' + # * PR comment with body 'I have read the CLA Document and I hereby sign the CLA' - name: "CLA Assistant" if: steps.get-membership.outcome != 'success' && ((github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target') uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 diff --git a/.github/workflows/dismiss.yaml b/.github/workflows/dismiss.yaml index a61cbc385b33..86922c744259 100644 --- a/.github/workflows/dismiss.yaml +++ b/.github/workflows/dismiss.yaml @@ -43,11 +43,11 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' # Run "dismiss" subcommand on bot. - name: Dismiss - run: cd .github/shared-workflows/bot && go run main.go -workflow=dismiss -token="${{ secrets.GITHUB_TOKEN }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=dismiss -token="${{ secrets.GITHUB_TOKEN }}" -reviewers='{}' diff --git a/.github/workflows/doc-tests.yaml b/.github/workflows/doc-tests.yaml index 6c10014a0afa..738800b96b7e 100644 --- a/.github/workflows/doc-tests.yaml +++ b/.github/workflows/doc-tests.yaml @@ -83,16 +83,13 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" - existing-gh-secrets: "${{ toJSON(secrets) }}" - existing-gh-variables: "${{ toJSON(vars) }}" - # values: | - # REVIEWERS_APP_ID,secret - # REVIEWERS_PRIVATE_KEY,secret - # REVIEWERS,secret + values: | + REVIEWERS_APP_ID,variable + REVIEWERS_PRIVATE_KEY,secret - name: Check out teleport uses: actions/checkout@v6 @@ -110,15 +107,19 @@ jobs: id: generate_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.REVIEWERS_APP_ID }} - private-key: ${{ secrets.REVIEWERS_PRIVATE_KEY }} + app-id: ${{ env.REVIEWERS_APP_ID }} + private-key: ${{ env.REVIEWERS_PRIVATE_KEY }} + + - name: Clear runtime values + id: clear_kvstore_env + run: echo "REVIEWERS_PRIVATE_KEY=" >> $GITHUB_ENV - name: Check out shared-workflows uses: actions/checkout@v6 with: repository: gravitational/shared-workflows path: shared-workflows - ref: 6f388765b8ce993721502083c74cb9af1fc5658f + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Install Go uses: actions/setup-go@faf52423ec0d44c58f68e83b614bfcd99dded66f # v6.0.0 @@ -129,8 +130,7 @@ jobs: - name: Ensure docs changes include redirects env: TOKEN: ${{ steps.generate_token.outputs.token }} - REVIEWERS: ${{ secrets.reviewers }} - run: cd shared-workflows/bot && go run main.go -workflow=docpaths -token="${TOKEN}" -teleport-path="${GITHUB_WORKSPACE}/teleport" -reviewers="${REVIEWERS}" + run: cd shared-workflows/bot && go run main.go -workflow=docpaths -token="${TOKEN}" -teleport-path="${GITHUB_WORKSPACE}/teleport" -reviewers='{}' - name: Install Node.js uses: actions/setup-node@dda4788290998366da86b6a4f497909644397bb2 # v6.0.0 diff --git a/.github/workflows/docs-amplify.yaml b/.github/workflows/docs-amplify.yaml index 7def2c61c7dc..49640ed85e41 100644 --- a/.github/workflows/docs-amplify.yaml +++ b/.github/workflows/docs-amplify.yaml @@ -31,7 +31,7 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" @@ -107,4 +107,4 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { run } = require('./.github/scripts/post-preview-links.js'); - await run({ github, context, core }); \ No newline at end of file + await run({ github, context, core }); diff --git a/.github/workflows/flaky-tests.yaml b/.github/workflows/flaky-tests.yaml index 30654fe51dae..c320e75951fb 100644 --- a/.github/workflows/flaky-tests.yaml +++ b/.github/workflows/flaky-tests.yaml @@ -49,19 +49,6 @@ jobs: - 3379:3379 steps: - - name: Get runtime values from kvstore - id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 - with: - cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" - cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" - existing-gh-secrets: "${{ toJSON(secrets) }}" - existing-gh-variables: "${{ toJSON(vars) }}" - # values: | - # REVIEWERS_APP_ID,secret - # REVIEWERS_PRIVATE_KEY,secret - # REVIEWERS,secret - - name: Checkout Teleport uses: actions/checkout@v6 with: @@ -76,23 +63,40 @@ jobs: - name: Prepare unit tests run: make test-go-prepare + - name: Get runtime values from kvstore + id: kvstore + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 + with: + cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" + cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" + values: | + REVIEWERS_APP_ID,variable + REVIEWERS_PRIVATE_KEY,secret + REVIEWERS,secret + - name: Generate GitHub Token id: generate_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.REVIEWERS_APP_ID }} - private-key: ${{ secrets.REVIEWERS_PRIVATE_KEY }} + app-id: ${{ env.REVIEWERS_APP_ID }} + private-key: ${{ env.REVIEWERS_PRIVATE_KEY }} - name: Checkout shared-workflows uses: actions/checkout@v6 with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Find excluded tests id: find_excluded - run: cd .github/shared-workflows/bot && go run main.go -workflow=exclude-flakes -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=exclude-flakes -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ env.REVIEWERS }}" + + - name: Clear runtime values + id: clear_kvstore_env + run: | + echo "REVIEWERS=" >> $GITHUB_ENV + echo "REVIEWERS_PRIVATE_KEY=" >> $GITHUB_ENV - name: Run base difftest uses: ./.github/actions/difftest diff --git a/.github/workflows/label.yaml b/.github/workflows/label.yaml index 6e90f98ad268..18723a83f19b 100644 --- a/.github/workflows/label.yaml +++ b/.github/workflows/label.yaml @@ -39,11 +39,11 @@ jobs: with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Installing Go uses: actions/setup-go@v6 with: go-version: 'stable' # Run "check" subcommand on bot. - name: Labeling PR - run: cd .github/shared-workflows/bot && go run main.go -workflow=label -token="${{ secrets.GITHUB_TOKEN }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=label -token="${{ secrets.GITHUB_TOKEN }}" -reviewers='{}' diff --git a/.github/workflows/manual-test-plan.yaml b/.github/workflows/manual-test-plan.yaml index 6ad6f5a7bb64..6b1963f59eca 100644 --- a/.github/workflows/manual-test-plan.yaml +++ b/.github/workflows/manual-test-plan.yaml @@ -43,25 +43,35 @@ jobs: permissions: contents: read + id-token: write container: image: ghcr.io/gravitational/teleport-buildbox:teleport19 steps: + - name: Get runtime values from kvstore + id: kvstore + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 + with: + cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" + cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" + values: | + REVIEWERS_APP_ID,variable + REVIEWERS_PRIVATE_KEY,secret - name: Checkout shared-workflows uses: actions/checkout@v6 with: repository: gravitational/shared-workflows path: .github/shared-workflows - ref: 02af65890b521ecdf79856c00f6d2c875bf40ea3 # workflows/v0.0.5 + ref: 986f92841a4f032e262477e5bb15f214d50b1015 # workflows/v0.0.6 - name: Generate GitHub Token id: generate_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.REVIEWERS_APP_ID }} - private-key: ${{ secrets.REVIEWERS_PRIVATE_KEY }} + app-id: ${{ env.REVIEWERS_APP_ID }} + private-key: ${{ env.REVIEWERS_PRIVATE_KEY }} - name: Validate Test Plan id: validate_test_plan - run: cd .github/shared-workflows/bot && go run main.go -workflow=manual-test-plan -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" + run: cd .github/shared-workflows/bot && go run main.go -workflow=manual-test-plan -token="${{ steps.generate_token.outputs.token }}" -reviewers='{}' diff --git a/.github/workflows/post-release.yaml b/.github/workflows/post-release.yaml index 81399b74708e..53e853bed048 100644 --- a/.github/workflows/post-release.yaml +++ b/.github/workflows/post-release.yaml @@ -59,7 +59,7 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" diff --git a/.github/workflows/renovate.yaml b/.github/workflows/renovate.yaml index e1428eda5f90..51215c0a2a17 100644 --- a/.github/workflows/renovate.yaml +++ b/.github/workflows/renovate.yaml @@ -10,18 +10,28 @@ on: # GitHub token is only used to read the repository contents for checkout. permissions: contents: read + id-token: write jobs: renovate: name: Renovate runs-on: ubuntu-latest steps: + - name: Get runtime values from kvstore + id: kvstore + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 + with: + cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" + cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" + values: | + PUBLIC_RENOVATE_GHA_APP_ID,variable + PUBLIC_RENOVATE_GHA_PRIVATE_KEY,secret - name: Generate GitHub App token id: generate_token uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: - app-id: ${{ secrets.PUBLIC_RENOVATE_GHA_APP_ID }} - private-key: ${{ secrets.PUBLIC_RENOVATE_GHA_PRIVATE_KEY }} + app-id: ${{ env.PUBLIC_RENOVATE_GHA_APP_ID }} + private-key: ${{ env.PUBLIC_RENOVATE_GHA_PRIVATE_KEY }} owner: ${{ github.repository_owner }} repositories: ${{ github.event.repository.name }} diff --git a/.github/workflows/update-ami-ids.yaml b/.github/workflows/update-ami-ids.yaml index ac40ad310b0f..177c9c27e4e2 100644 --- a/.github/workflows/update-ami-ids.yaml +++ b/.github/workflows/update-ami-ids.yaml @@ -30,7 +30,7 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" diff --git a/.github/workflows/update-docs-webhook.yaml b/.github/workflows/update-docs-webhook.yaml index ff1ac59fa2da..20a86c0359c6 100644 --- a/.github/workflows/update-docs-webhook.yaml +++ b/.github/workflows/update-docs-webhook.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get runtime values from kvstore id: kvstore - uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.0 + uses: gravitational/shared-workflows/tools/env-kvstore@tools/env-kvstore/v1.0.2 with: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" From f89b519a0f267cf93ac2fd7ce41adaa9a05b2d7c Mon Sep 17 00:00:00 2001 From: matheus Date: Fri, 31 Jul 2026 10:39:54 -0300 Subject: [PATCH 082/347] `client_ip_restriction`: add new fields (#68941) * Add new fields, update protos * Expose ClientIPRestrictionClient on authclient.ClientI so the web proxy can call the client_ip_restriction service. * Exclude new fields from terraform generation * Update docs to reflect that spec is not changed by the server --- .../v1/clientiprestriction_protohybrid.pb.go | 116 ++++++++++++++++-- .../v1/clientiprestriction_protoopaque.pb.go | 87 +++++++++++-- .../v1/clientiprestriction.proto | 32 ++++- ...toc-gen-terraform-clientiprestriction.yaml | 6 + .../v1/clientiprestriction_terraform.go | 1 + lib/auth/authclient/clt.go | 12 ++ .../common/resources/clientiprestriction.go | 15 ++- 7 files changed, 247 insertions(+), 22 deletions(-) diff --git a/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protohybrid.pb.go b/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protohybrid.pb.go index 5971e0c58979..ff6bf57de921 100644 --- a/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protohybrid.pb.go @@ -26,6 +26,7 @@ import ( v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" unsafe "unsafe" ) @@ -216,7 +217,32 @@ type ClientIPRestrictionSpec struct { state protoimpl.MessageState `protogen:"hybrid.v1"` // allowed_cidrs is the list of CIDR blocks permitted to connect to the cluster. // An empty list disables restrictions and allows all traffic. - AllowedCidrs []string `protobuf:"bytes,1,rep,name=allowed_cidrs,json=allowedCidrs,proto3" json:"allowed_cidrs,omitempty"` + AllowedCidrs []string `protobuf:"bytes,1,rep,name=allowed_cidrs,json=allowedCidrs,proto3" json:"allowed_cidrs,omitempty"` + // mode is the user-controlled operational mode of the restriction. It denotes + // intent; the actual enforcement state is reported by status.state. + // Possible values: "draft" (configured but not enforced) and "enforced" + // (should be enforced by Teleport Cloud). An empty value is treated as + // "enforced" for backward compatibility. + Mode string `protobuf:"bytes,2,opt,name=mode,proto3" json:"mode,omitempty"` + // expires is the deadline on enforcement: the restriction is enforced until + // this time and not after it. An unset value means enforcement never lapses. + // When set, it must be at least 20 minutes in the future. + // + // Once it elapses, enforcement stops and status.state becomes "expired". + // Neither mode nor expires is modified by Teleport Cloud, so a lapsed + // restriction still reads "enforced until "; enforcing again takes a new + // write. Whether the restriction is in effect is therefore derived from this + // field and mode, not stored on its own. + // + // Note: we do not use the `metadata.expires` field for this, because that + // conventionally denotes that the resource should be deleted once it elapses. + // Here expiry only stops enforcement on the Cloud side; the resource itself + // keeps existing rather than being removed. + // + // Writes fully replace the resource, so a client editing other fields must + // re-send a still-valid expiry or clear it. An expiry read earlier and passed + // back unchanged can be rejected once it is under 20 minutes away. + Expires *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires,proto3" json:"expires,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -253,16 +279,74 @@ func (x *ClientIPRestrictionSpec) GetAllowedCidrs() []string { return nil } +func (x *ClientIPRestrictionSpec) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *ClientIPRestrictionSpec) GetExpires() *timestamppb.Timestamp { + if x != nil { + return x.Expires + } + return nil +} + func (x *ClientIPRestrictionSpec) SetAllowedCidrs(v []string) { x.AllowedCidrs = v } +func (x *ClientIPRestrictionSpec) SetMode(v string) { + x.Mode = v +} + +func (x *ClientIPRestrictionSpec) SetExpires(v *timestamppb.Timestamp) { + x.Expires = v +} + +func (x *ClientIPRestrictionSpec) HasExpires() bool { + if x == nil { + return false + } + return x.Expires != nil +} + +func (x *ClientIPRestrictionSpec) ClearExpires() { + x.Expires = nil +} + type ClientIPRestrictionSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. // allowed_cidrs is the list of CIDR blocks permitted to connect to the cluster. // An empty list disables restrictions and allows all traffic. AllowedCidrs []string + // mode is the user-controlled operational mode of the restriction. It denotes + // intent; the actual enforcement state is reported by status.state. + // Possible values: "draft" (configured but not enforced) and "enforced" + // (should be enforced by Teleport Cloud). An empty value is treated as + // "enforced" for backward compatibility. + Mode string + // expires is the deadline on enforcement: the restriction is enforced until + // this time and not after it. An unset value means enforcement never lapses. + // When set, it must be at least 20 minutes in the future. + // + // Once it elapses, enforcement stops and status.state becomes "expired". + // Neither mode nor expires is modified by Teleport Cloud, so a lapsed + // restriction still reads "enforced until "; enforcing again takes a new + // write. Whether the restriction is in effect is therefore derived from this + // field and mode, not stored on its own. + // + // Note: we do not use the `metadata.expires` field for this, because that + // conventionally denotes that the resource should be deleted once it elapses. + // Here expiry only stops enforcement on the Cloud side; the resource itself + // keeps existing rather than being removed. + // + // Writes fully replace the resource, so a client editing other fields must + // re-send a still-valid expiry or clear it. An expiry read earlier and passed + // back unchanged can be rejected once it is under 20 minutes away. + Expires *timestamppb.Timestamp } func (b0 ClientIPRestrictionSpec_builder) Build() *ClientIPRestrictionSpec { @@ -270,6 +354,8 @@ func (b0 ClientIPRestrictionSpec_builder) Build() *ClientIPRestrictionSpec { b, x := &b0, m0 _, _ = b, x x.AllowedCidrs = b.AllowedCidrs + x.Mode = b.Mode + x.Expires = b.Expires return m0 } @@ -278,7 +364,9 @@ func (b0 ClientIPRestrictionSpec_builder) Build() *ClientIPRestrictionSpec { type ClientIPRestrictionStatus struct { state protoimpl.MessageState `protogen:"hybrid.v1"` // state is the current enforcement state of the restrictions at the ingress layer. - // Possible values: "pending", "active". + // Possible values: "pending" (written but not yet applied), "active" (applied + // and enforced), "draft" (saved but not enforced because mode is "draft"), and + // "expired" (saved but not enforced because spec.expires elapsed). State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -324,7 +412,9 @@ type ClientIPRestrictionStatus_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. // state is the current enforcement state of the restrictions at the ingress layer. - // Possible values: "pending", "active". + // Possible values: "pending" (written but not yet applied), "active" (applied + // and enforced), "draft" (saved but not enforced because mode is "draft"), and + // "expired" (saved but not enforced because spec.expires elapsed). State string } @@ -340,16 +430,18 @@ var File_teleport_clientiprestriction_v1_clientiprestriction_proto protoreflect. const file_teleport_clientiprestriction_v1_clientiprestriction_proto_rawDesc = "" + "\n" + - "9teleport/clientiprestriction/v1/clientiprestriction.proto\x12\x1fteleport.clientiprestriction.v1\x1a!teleport/header/v1/metadata.proto\"\xba\x02\n" + + "9teleport/clientiprestriction/v1/clientiprestriction.proto\x12\x1fteleport.clientiprestriction.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/header/v1/metadata.proto\"\xba\x02\n" + "\x13ClientIPRestriction\x12\x12\n" + "\x04kind\x18\x01 \x01(\tR\x04kind\x12\x19\n" + "\bsub_kind\x18\x02 \x01(\tR\asubKind\x12\x18\n" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12L\n" + "\x04spec\x18\x05 \x01(\v28.teleport.clientiprestriction.v1.ClientIPRestrictionSpecR\x04spec\x12R\n" + - "\x06status\x18\x06 \x01(\v2:.teleport.clientiprestriction.v1.ClientIPRestrictionStatusR\x06status\">\n" + + "\x06status\x18\x06 \x01(\v2:.teleport.clientiprestriction.v1.ClientIPRestrictionStatusR\x06status\"\x88\x01\n" + "\x17ClientIPRestrictionSpec\x12#\n" + - "\rallowed_cidrs\x18\x01 \x03(\tR\fallowedCidrs\"1\n" + + "\rallowed_cidrs\x18\x01 \x03(\tR\fallowedCidrs\x12\x12\n" + + "\x04mode\x18\x02 \x01(\tR\x04mode\x124\n" + + "\aexpires\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\"1\n" + "\x19ClientIPRestrictionStatus\x12\x14\n" + "\x05state\x18\x01 \x01(\tR\x05stateBjZhgithub.com/gravitational/teleport/api/gen/proto/go/teleport/clientiprestriction/v1;clientiprestrictionv1b\x06proto3" @@ -359,16 +451,18 @@ var file_teleport_clientiprestriction_v1_clientiprestriction_proto_goTypes = []a (*ClientIPRestrictionSpec)(nil), // 1: teleport.clientiprestriction.v1.ClientIPRestrictionSpec (*ClientIPRestrictionStatus)(nil), // 2: teleport.clientiprestriction.v1.ClientIPRestrictionStatus (*v1.Metadata)(nil), // 3: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp } var file_teleport_clientiprestriction_v1_clientiprestriction_proto_depIdxs = []int32{ 3, // 0: teleport.clientiprestriction.v1.ClientIPRestriction.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.clientiprestriction.v1.ClientIPRestriction.spec:type_name -> teleport.clientiprestriction.v1.ClientIPRestrictionSpec 2, // 2: teleport.clientiprestriction.v1.ClientIPRestriction.status:type_name -> teleport.clientiprestriction.v1.ClientIPRestrictionStatus - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 4, // 3: teleport.clientiprestriction.v1.ClientIPRestrictionSpec.expires:type_name -> google.protobuf.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_clientiprestriction_v1_clientiprestriction_proto_init() } diff --git a/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protoopaque.pb.go b/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protoopaque.pb.go index 79af863673c3..506ae7d65f77 100644 --- a/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/clientiprestriction/v1/clientiprestriction_protoopaque.pb.go @@ -26,6 +26,7 @@ import ( v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" unsafe "unsafe" ) @@ -209,6 +210,8 @@ func (b0 ClientIPRestriction_builder) Build() *ClientIPRestriction { type ClientIPRestrictionSpec struct { state protoimpl.MessageState `protogen:"opaque.v1"` xxx_hidden_AllowedCidrs []string `protobuf:"bytes,1,rep,name=allowed_cidrs,json=allowedCidrs,proto3"` + xxx_hidden_Mode string `protobuf:"bytes,2,opt,name=mode,proto3"` + xxx_hidden_Expires *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -245,16 +248,74 @@ func (x *ClientIPRestrictionSpec) GetAllowedCidrs() []string { return nil } +func (x *ClientIPRestrictionSpec) GetMode() string { + if x != nil { + return x.xxx_hidden_Mode + } + return "" +} + +func (x *ClientIPRestrictionSpec) GetExpires() *timestamppb.Timestamp { + if x != nil { + return x.xxx_hidden_Expires + } + return nil +} + func (x *ClientIPRestrictionSpec) SetAllowedCidrs(v []string) { x.xxx_hidden_AllowedCidrs = v } +func (x *ClientIPRestrictionSpec) SetMode(v string) { + x.xxx_hidden_Mode = v +} + +func (x *ClientIPRestrictionSpec) SetExpires(v *timestamppb.Timestamp) { + x.xxx_hidden_Expires = v +} + +func (x *ClientIPRestrictionSpec) HasExpires() bool { + if x == nil { + return false + } + return x.xxx_hidden_Expires != nil +} + +func (x *ClientIPRestrictionSpec) ClearExpires() { + x.xxx_hidden_Expires = nil +} + type ClientIPRestrictionSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. // allowed_cidrs is the list of CIDR blocks permitted to connect to the cluster. // An empty list disables restrictions and allows all traffic. AllowedCidrs []string + // mode is the user-controlled operational mode of the restriction. It denotes + // intent; the actual enforcement state is reported by status.state. + // Possible values: "draft" (configured but not enforced) and "enforced" + // (should be enforced by Teleport Cloud). An empty value is treated as + // "enforced" for backward compatibility. + Mode string + // expires is the deadline on enforcement: the restriction is enforced until + // this time and not after it. An unset value means enforcement never lapses. + // When set, it must be at least 20 minutes in the future. + // + // Once it elapses, enforcement stops and status.state becomes "expired". + // Neither mode nor expires is modified by Teleport Cloud, so a lapsed + // restriction still reads "enforced until "; enforcing again takes a new + // write. Whether the restriction is in effect is therefore derived from this + // field and mode, not stored on its own. + // + // Note: we do not use the `metadata.expires` field for this, because that + // conventionally denotes that the resource should be deleted once it elapses. + // Here expiry only stops enforcement on the Cloud side; the resource itself + // keeps existing rather than being removed. + // + // Writes fully replace the resource, so a client editing other fields must + // re-send a still-valid expiry or clear it. An expiry read earlier and passed + // back unchanged can be rejected once it is under 20 minutes away. + Expires *timestamppb.Timestamp } func (b0 ClientIPRestrictionSpec_builder) Build() *ClientIPRestrictionSpec { @@ -262,6 +323,8 @@ func (b0 ClientIPRestrictionSpec_builder) Build() *ClientIPRestrictionSpec { b, x := &b0, m0 _, _ = b, x x.xxx_hidden_AllowedCidrs = b.AllowedCidrs + x.xxx_hidden_Mode = b.Mode + x.xxx_hidden_Expires = b.Expires return m0 } @@ -314,7 +377,9 @@ type ClientIPRestrictionStatus_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. // state is the current enforcement state of the restrictions at the ingress layer. - // Possible values: "pending", "active". + // Possible values: "pending" (written but not yet applied), "active" (applied + // and enforced), "draft" (saved but not enforced because mode is "draft"), and + // "expired" (saved but not enforced because spec.expires elapsed). State string } @@ -330,16 +395,18 @@ var File_teleport_clientiprestriction_v1_clientiprestriction_proto protoreflect. const file_teleport_clientiprestriction_v1_clientiprestriction_proto_rawDesc = "" + "\n" + - "9teleport/clientiprestriction/v1/clientiprestriction.proto\x12\x1fteleport.clientiprestriction.v1\x1a!teleport/header/v1/metadata.proto\"\xba\x02\n" + + "9teleport/clientiprestriction/v1/clientiprestriction.proto\x12\x1fteleport.clientiprestriction.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/header/v1/metadata.proto\"\xba\x02\n" + "\x13ClientIPRestriction\x12\x12\n" + "\x04kind\x18\x01 \x01(\tR\x04kind\x12\x19\n" + "\bsub_kind\x18\x02 \x01(\tR\asubKind\x12\x18\n" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12L\n" + "\x04spec\x18\x05 \x01(\v28.teleport.clientiprestriction.v1.ClientIPRestrictionSpecR\x04spec\x12R\n" + - "\x06status\x18\x06 \x01(\v2:.teleport.clientiprestriction.v1.ClientIPRestrictionStatusR\x06status\">\n" + + "\x06status\x18\x06 \x01(\v2:.teleport.clientiprestriction.v1.ClientIPRestrictionStatusR\x06status\"\x88\x01\n" + "\x17ClientIPRestrictionSpec\x12#\n" + - "\rallowed_cidrs\x18\x01 \x03(\tR\fallowedCidrs\"1\n" + + "\rallowed_cidrs\x18\x01 \x03(\tR\fallowedCidrs\x12\x12\n" + + "\x04mode\x18\x02 \x01(\tR\x04mode\x124\n" + + "\aexpires\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\"1\n" + "\x19ClientIPRestrictionStatus\x12\x14\n" + "\x05state\x18\x01 \x01(\tR\x05stateBjZhgithub.com/gravitational/teleport/api/gen/proto/go/teleport/clientiprestriction/v1;clientiprestrictionv1b\x06proto3" @@ -349,16 +416,18 @@ var file_teleport_clientiprestriction_v1_clientiprestriction_proto_goTypes = []a (*ClientIPRestrictionSpec)(nil), // 1: teleport.clientiprestriction.v1.ClientIPRestrictionSpec (*ClientIPRestrictionStatus)(nil), // 2: teleport.clientiprestriction.v1.ClientIPRestrictionStatus (*v1.Metadata)(nil), // 3: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp } var file_teleport_clientiprestriction_v1_clientiprestriction_proto_depIdxs = []int32{ 3, // 0: teleport.clientiprestriction.v1.ClientIPRestriction.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.clientiprestriction.v1.ClientIPRestriction.spec:type_name -> teleport.clientiprestriction.v1.ClientIPRestrictionSpec 2, // 2: teleport.clientiprestriction.v1.ClientIPRestriction.status:type_name -> teleport.clientiprestriction.v1.ClientIPRestrictionStatus - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 4, // 3: teleport.clientiprestriction.v1.ClientIPRestrictionSpec.expires:type_name -> google.protobuf.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_clientiprestriction_v1_clientiprestriction_proto_init() } diff --git a/api/proto/teleport/clientiprestriction/v1/clientiprestriction.proto b/api/proto/teleport/clientiprestriction/v1/clientiprestriction.proto index 958bf8fbb88a..cf6235d0ea2e 100644 --- a/api/proto/teleport/clientiprestriction/v1/clientiprestriction.proto +++ b/api/proto/teleport/clientiprestriction/v1/clientiprestriction.proto @@ -16,6 +16,7 @@ syntax = "proto3"; package teleport.clientiprestriction.v1; +import "google/protobuf/timestamp.proto"; import "teleport/header/v1/metadata.proto"; option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/clientiprestriction/v1;clientiprestrictionv1"; @@ -47,12 +48,41 @@ message ClientIPRestrictionSpec { // allowed_cidrs is the list of CIDR blocks permitted to connect to the cluster. // An empty list disables restrictions and allows all traffic. repeated string allowed_cidrs = 1; + + // mode is the user-controlled operational mode of the restriction. It denotes + // intent; the actual enforcement state is reported by status.state. + // Possible values: "draft" (configured but not enforced) and "enforced" + // (should be enforced by Teleport Cloud). An empty value is treated as + // "enforced" for backward compatibility. + string mode = 2; + + // expires is the deadline on enforcement: the restriction is enforced until + // this time and not after it. An unset value means enforcement never lapses. + // When set, it must be at least 20 minutes in the future. + // + // Once it elapses, enforcement stops and status.state becomes "expired". + // Neither mode nor expires is modified by Teleport Cloud, so a lapsed + // restriction still reads "enforced until "; enforcing again takes a new + // write. Whether the restriction is in effect is therefore derived from this + // field and mode, not stored on its own. + // + // Note: we do not use the `metadata.expires` field for this, because that + // conventionally denotes that the resource should be deleted once it elapses. + // Here expiry only stops enforcement on the Cloud side; the resource itself + // keeps existing rather than being removed. + // + // Writes fully replace the resource, so a client editing other fields must + // re-send a still-valid expiry or clear it. An expiry read earlier and passed + // back unchanged can be rejected once it is under 20 minutes away. + google.protobuf.Timestamp expires = 3; } // ClientIPRestrictionStatus contains dynamic properties of a ClientIPRestriction. These properties are // modified during runtime of a Teleport process. They should not be modified by users. message ClientIPRestrictionStatus { // state is the current enforcement state of the restrictions at the ingress layer. - // Possible values: "pending", "active". + // Possible values: "pending" (written but not yet applied), "active" (applied + // and enforced), "draft" (saved but not enforced because mode is "draft"), and + // "expired" (saved but not enforced because spec.expires elapsed). string state = 1; } diff --git a/integrations/terraform/protoc-gen-terraform-clientiprestriction.yaml b/integrations/terraform/protoc-gen-terraform-clientiprestriction.yaml index 03bbec499b2b..d736253cd791 100644 --- a/integrations/terraform/protoc-gen-terraform-clientiprestriction.yaml +++ b/integrations/terraform/protoc-gen-terraform-clientiprestriction.yaml @@ -38,6 +38,12 @@ exclude_fields: # not track it. The Create/Update poll reads it from the live resource, not # from Terraform state. - "ClientIPRestriction.status" + # TODO: add Terraform support for mode and expires. Excluded for now so the + # provider surface stays unchanged until they can be wired up together; + # spec.expires additionally needs a `Timestamp` custom_types entry, without + # which codegen emits invalid Go. + - "ClientIPRestriction.spec.mode" + - "ClientIPRestriction.spec.expires" # These fields will be marked as Computed: true computed_fields: diff --git a/integrations/terraform/tfschema/clientiprestriction/v1/clientiprestriction_terraform.go b/integrations/terraform/tfschema/clientiprestriction/v1/clientiprestriction_terraform.go index a352fdac6d81..a93c952569f5 100644 --- a/integrations/terraform/tfschema/clientiprestriction/v1/clientiprestriction_terraform.go +++ b/integrations/terraform/tfschema/clientiprestriction/v1/clientiprestriction_terraform.go @@ -33,6 +33,7 @@ import ( 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" + _ "google.golang.org/protobuf/types/known/timestamppb" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 4687d897c865..a1a47ef4a690 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -44,6 +44,7 @@ import ( accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" beamsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/beams/v1" + clientiprestrictionv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/clientiprestriction/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" delegationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/delegation/v1" @@ -619,6 +620,11 @@ func (c *Client) AccessMonitoringRuleClient() services.AccessMonitoringRules { return c.APIClient.AccessMonitoringRulesClient() } +// ClientIPRestrictionClient returns the client IP restriction client. +func (c *Client) ClientIPRestrictionClient() clientiprestrictionv1.ClientIPRestrictionServiceClient { + return c.APIClient.ClientIPRestrictionClient() +} + func (c *Client) ExternalAuditStorageClient() *externalauditstorage.Client { return c.APIClient.ExternalAuditStorageClient() } @@ -1832,6 +1838,12 @@ type ClientI interface { // (as per the default gRPC behavior). AccessMonitoringRuleClient() services.AccessMonitoringRules + // ClientIPRestrictionClient returns a client IP restriction client. + // Clients connecting to older Teleport versions still get a client when calling + // this method, but all RPCs will return "not implemented" errors (as per the + // default gRPC behavior). + ClientIPRestrictionClient() clientiprestrictionv1.ClientIPRestrictionServiceClient + // DatabaseObjectImportRuleClient returns a database object import rule client. DatabaseObjectImportRuleClient() dbobjectimportrulev1.DatabaseObjectImportRuleServiceClient diff --git a/tool/tctl/common/resources/clientiprestriction.go b/tool/tctl/common/resources/clientiprestriction.go index 9914dab1939c..4f82d0113446 100644 --- a/tool/tctl/common/resources/clientiprestriction.go +++ b/tool/tctl/common/resources/clientiprestriction.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "strings" + "time" "github.com/gravitational/trace" @@ -113,10 +114,22 @@ func (c *clientIPRestrictionCollection) Resources() []types.Resource { } func (c *clientIPRestrictionCollection) WriteText(w io.Writer, verbose bool) error { - t := asciitable.MakeTable([]string{"Allowed CIDRs", "State"}) + mode := c.cir.GetSpec().GetMode() + if mode == "" { + // An empty mode is treated as enforced for backward compatibility. + mode = "enforced" + } + expires := "never" + if e := c.cir.GetSpec().GetExpires(); e != nil { + expires = e.AsTime().Format(time.RFC3339) + } + + t := asciitable.MakeTable([]string{"Allowed CIDRs", "Mode", "State", "Expires"}) t.AddRow([]string{ strings.Join(c.cir.GetSpec().GetAllowedCidrs(), ", "), + mode, c.cir.GetStatus().GetState(), + expires, }) _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) From b80a9d84a0100655149066a5e82371b7373a342a Mon Sep 17 00:00:00 2001 From: Brendan O'Halloran Date: Fri, 31 Jul 2026 10:59:30 -0300 Subject: [PATCH 083/347] gha: backport app id is not a secret (#69225) --- .github/workflows/backport.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 7f08dcb41925..bb30ceb51185 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -28,7 +28,7 @@ jobs: cognito-identity-pool-id: "us-west-2:da53389e-a459-4a28-bd54-43eeb8e4b579" cognito-role-arn: "arn:aws:iam::247230412011:role/github-actions-cognito-oidc" values: | - BACKPORT_APP_ID,secret + BACKPORT_APP_ID,variable BACKPORT_PRIVATE_KEY,secret REVIEWERS,secret - name: Generate GitHub Token From 663184b5fc23d5b51084cdc4e7446daa1b5276d4 Mon Sep 17 00:00:00 2001 From: Jake Alti Date: Fri, 31 Jul 2026 08:15:52 -0700 Subject: [PATCH 084/347] Fix kube requests using the proxy special-verb path prefix (#69098) * kube: parse the proxy special-verb URL prefix * kube: test the proxy special-verb URL prefix t * docs: document the kube proxy verb * kube: normalize proxy-prefix resource names for core proxyable kinds * kube: close idle connections in the test helper * kube: clarify the special-verb path test comment * kube: drop trailing path segments from proxy special-verb kinds * kube: add a TODO to reuse the upstream request parser * kube: fix the stale list-detection comment * kube: unify the special-verb length guard * kube: reject dot segments in kube request paths * kube: reject dot segments inside parseResourcePath * kube: move the dot-segment cases into the parser test * docs: widen the kube proxy verb description --- docs/pages/includes/role-spec.mdx | 1 + lib/kube/proxy/resource_rbac_test.go | 125 +++++++++++++++--- .../proxy/testing/kube_server/kube_mock.go | 1 + lib/kube/proxy/url.go | 72 ++++++++-- lib/kube/proxy/url_test.go | 91 ++++++++++++- 5 files changed, 256 insertions(+), 34 deletions(-) diff --git a/docs/pages/includes/role-spec.mdx b/docs/pages/includes/role-spec.mdx index 970f2cc58b7f..aac6a76fef6d 100644 --- a/docs/pages/includes/role-spec.mdx +++ b/docs/pages/includes/role-spec.mdx @@ -246,6 +246,7 @@ spec: # - deletecollection # - exec - allows users to execute commands in a pod # - portforward - allows users to forward ports from a pod + # - proxy - allows users to send HTTP requests to a resource verbs: ['*'] # Database account names this role can connect as. diff --git a/lib/kube/proxy/resource_rbac_test.go b/lib/kube/proxy/resource_rbac_test.go index 658450f730ca..b1da2995e916 100644 --- a/lib/kube/proxy/resource_rbac_test.go +++ b/lib/kube/proxy/resource_rbac_test.go @@ -4460,22 +4460,6 @@ func TestProxySubresourceRBAC(t *testing.T) { Verbs: []string{"get"}, APIGroup: types.Wildcard, }}, nil) - sendGet := func(t *testing.T, user types.User, urlPath string, opts ...GenTestKubeClientTLSCertOptions) (int, string) { - t.Helper() - _, cfg := testCtx.GenTestKubeClientTLSCert(t, user.GetName(), kubeCluster, opts...) - transport, err := rest.TransportFor(cfg) - require.NoError(t, err) - client := &http.Client{Transport: transport} - req, err := http.NewRequestWithContext(testCtx.Context, http.MethodGet, cfg.Host+urlPath, nil) - require.NoError(t, err) - resp, err := client.Do(req) - require.NoError(t, err) - t.Cleanup(func() { resp.Body.Close() }) - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - return resp.StatusCode, string(body) - } - tests := []struct { name string user types.User @@ -4532,7 +4516,7 @@ func TestProxySubresourceRBAC(t *testing.T) { opts = makeScopedOpts(t, testCtx, tt.user.GetName(), scope) } t.Run(fmt.Sprintf("%s scoped=%t", tt.name, scoped), func(t *testing.T) { - code, body := sendGet(t, tt.user, tt.urlPath, opts...) + code, body := sendKubeGet(t, testCtx, tt.user, tt.urlPath, opts...) require.Equal(t, tt.wantCode, code, "body: %s", body) require.Contains(t, body, tt.bodyContains) }) @@ -4562,3 +4546,110 @@ func toScopedKubeResource(resource types.KubernetesResource) *accessv1.KubeResou func scopedUsername(username string) string { return "scoped-" + username } + +// TestProxySpecialVerbPathRBAC verifies that the Kubernetes "special verb" URL form, +// where a proxy segment precedes the resource path +// (/apis/{group}/{version}/proxy/namespaces/{ns}/{kind}/{name}), +// is parsed as the resource it targets and enforced with the KubeVerbProxy verb, +// instead of being rejected as a resource kind the cluster doesn't serve. +func TestProxySpecialVerbPathRBAC(t *testing.T) { + const ( + basePath = "/apis/resources.teleport.dev/v6" + resourceKind = "teleportroles" + resourceName = "telerole-1" + ) + _, testCtx := newTestKubeCRDMock(t, "", tkm.WithTeleportRoleCRD) + + newUser := func(name string, resources []types.KubernetesResource) types.User { + u, _ := testCtx.CreateUserAndRole(testCtx.Context, t, name, RoleSpec{ + Name: name, + KubeUsers: roleKubeUsers, + KubeGroups: roleKubeGroups, + SetupRoleFunc: func(r types.Role) { r.SetKubeResources(types.Allow, resources) }, + }) + return u + } + + proxyUser := newUser("crd-proxy", []types.KubernetesResource{{ + Kind: types.Wildcard, APIGroup: "*.teleport.dev", Namespace: types.Wildcard, Name: types.Wildcard, + Verbs: []string{types.KubeVerbGet, types.KubeVerbList, types.KubeVerbProxy}, + }}) + // Same resources, no proxy verb. + getUser := newUser("crd-get", []types.KubernetesResource{{ + Kind: resourceKind, APIGroup: types.Wildcard, Namespace: types.Wildcard, Name: types.Wildcard, + Verbs: []string{types.KubeVerbGet, types.KubeVerbList}, + }}) + // Negative control: the proxy verb, but on a different kind. + otherKindUser := newUser("crd-other-kind", []types.KubernetesResource{{ + Kind: "pods", APIGroup: types.Wildcard, Namespace: types.Wildcard, Name: types.Wildcard, + Verbs: []string{types.KubeVerbProxy}, + }}) + + const proxyPath = basePath + "/proxy/namespaces/default/" + resourceKind + "/" + resourceName + + tests := []struct { + name string + user types.User + urlPath string + wantCode int + bodyContains string + }{ + { + // Before the fix this failed with a 404 no role could allow, + // because the verb segment was folded into the resource kind. + name: "allowed_with_proxy_verb", + user: proxyUser, + urlPath: proxyPath, + wantCode: http.StatusOK, + bodyContains: resourceName, + }, + { + name: "denied_without_proxy_verb", + user: getUser, + urlPath: proxyPath, + wantCode: http.StatusForbidden, + bodyContains: `User \"crd-get\" cannot proxy resource \"` + resourceKind, + }, + { + name: "denied_proxy_verb_on_other_kind", + user: otherKindUser, + urlPath: proxyPath, + wantCode: http.StatusForbidden, + bodyContains: "cannot proxy resource", + }, + { + // The plain resource path is unaffected. It still uses the method-derived verb. + name: "plain_get_still_allowed", + user: getUser, + urlPath: basePath + "/namespaces/default/" + resourceKind + "/" + resourceName, + wantCode: http.StatusOK, + bodyContains: resourceName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + code, body := sendKubeGet(t, testCtx, tt.user, tt.urlPath) + require.Equal(t, tt.wantCode, code, "body: %s", body) + require.Contains(t, body, tt.bodyContains) + }) + } +} + +// sendKubeGet issues a raw GET against the kube proxy as the given user and returns the status code and body. +// Raw HTTP rather than a typed client, so arbitrary URL paths can be exercised. +func sendKubeGet(t *testing.T, testCtx *TestContext, user types.User, urlPath string, opts ...GenTestKubeClientTLSCertOptions) (int, string) { + t.Helper() + _, cfg := testCtx.GenTestKubeClientTLSCert(t, user.GetName(), kubeCluster, opts...) + transport, err := rest.TransportFor(cfg) + require.NoError(t, err) + client := &http.Client{Transport: transport} + defer client.CloseIdleConnections() + req, err := http.NewRequestWithContext(testCtx.Context, http.MethodGet, cfg.Host+urlPath, nil) + require.NoError(t, err) + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + return resp.StatusCode, string(body) +} diff --git a/lib/kube/proxy/testing/kube_server/kube_mock.go b/lib/kube/proxy/testing/kube_server/kube_mock.go index 9d3ae6bca121..921a2bb1abc7 100644 --- a/lib/kube/proxy/testing/kube_server/kube_mock.go +++ b/lib/kube/proxy/testing/kube_server/kube_mock.go @@ -305,6 +305,7 @@ func (s *KubeMockServer) setup() { router.Handle("GET /apis/"+k.group+"/"+k.version+"/"+k.plural, s.withWriter(s.listCRDs(crd))) router.Handle("GET /apis/"+k.group+"/"+k.version+"/namespaces/{namespace}/"+k.plural+"/{name}", s.withWriter(s.getCRD(crd))) router.Handle("DELETE /apis/"+k.group+"/"+k.version+"/namespaces/{namespace}/"+k.plural+"/{name}", s.withWriter(s.deleteCRD(crd))) + router.Handle("GET /apis/"+k.group+"/"+k.version+"/proxy/namespaces/{namespace}/"+k.plural+"/{name}", s.withWriter(s.getCRD(crd))) } router.Handle("GET /version", s.withWriter(s.versionEndpoint)) diff --git a/lib/kube/proxy/url.go b/lib/kube/proxy/url.go index 06280c658209..e4e54650c0c1 100644 --- a/lib/kube/proxy/url.go +++ b/lib/kube/proxy/url.go @@ -23,6 +23,7 @@ import ( "io" "net/http" "path" + "slices" "strings" "github.com/gravitational/trace" @@ -100,11 +101,14 @@ type apiResource struct { resourceName string skipEvent bool isWatch bool + isProxyVerb bool } // parseResourcePath does best-effort parsing of a Kubernetes API request path. // All fields of the returned apiResource may be empty. -func parseResourcePath(p string) apiResource { +// +// TODO(jakealti): reuse k8s.io/apiserver request.RequestInfoFactory here instead of re-implementing it. +func parseResourcePath(p string) (apiResource, error) { // Kubernetes API reference: https://kubernetes.io/docs/reference/kubernetes-api/ // Let's try to parse this. Here be dragons! // @@ -130,6 +134,15 @@ func parseResourcePath(p string) apiResource { // for live updates on resources (specific resources or all of one kind) var r apiResource + // Cleaning below resolves "." and ".." segments, which changes which resource the path names. + // The raw path is what gets forwarded, so parsing one and forwarding the other + // would authorize a name the cluster never resolves. + for segment := range strings.SplitSeq(p, "/") { + if segment == "." || segment == ".." { + return r, trace.BadParameter("kubernetes request path must not contain %q segments", segment) + } + } + // Clean up the path and make it absolute. p = path.Clean(p) if !path.IsAbs(p) { @@ -152,17 +165,24 @@ func parseResourcePath(p string) apiResource { // This is part of API discovery. Don't emit to audit log to reduce // noise. r.skipEvent = true - return r + return r, nil default: // Doesn't look like a k8s API path, return empty result. - return r + return r, nil } - // Watch API endpoints have an extra /watch/ prefix. For now, silently - // strip it from our result. - if len(parts) > 0 && parts[0] == "watch" { - r.isWatch = true - parts = parts[1:] + // Special verb endpoints carry the verb in a segment ahead of the resource path. + // The API server consumes that segment and parses the rest as a normal resource path, so we do the same. + // See specialVerbs in https://github.com/kubernetes/apiserver/blob/master/pkg/endpoints/request/requestinfo.go + if len(parts) > 1 { + switch parts[0] { + case "watch": + r.isWatch = true + parts = parts[1:] + case "proxy": + r.isProxyVerb = true + parts = parts[1:] + } } switch len(parts) { @@ -171,7 +191,7 @@ func parseResourcePath(p string) apiResource { // This is part of API discovery. Don't emit to audit log to reduce // noise. r.skipEvent = true - return r + return r, nil case 1: // e.g. /api/v1/pods - list pods in all namespaces r.resourceKind = parts[0] @@ -212,7 +232,24 @@ func parseResourcePath(p string) apiResource { } } } - return r + + // The proxy special verb takes no subresource. + // Kubernetes apiserver stops parsing at the name and hands every segment after it to the proxied backend. + // Drop them so the kind we record and report is the one the API server resolved. + if r.isProxyVerb { + r.resourceKind = getResourceFromAPIResource(r.resourceKind) + } + + // The core API accepts [scheme:]name[:port] in the name segment of its pods/services/nodes proxy endpoints, + // so the special verb form has to be normalized the same way the subresource form above is. + // Otherwise a rule naming the resource stops matching once a scheme or port is supplied. + if r.isProxyVerb && r.apiGroup == "" { + switch r.resourceKind { + case "pods", "services", "nodes": + r.resourceName = stripProxyNamePortScheme(r.resourceName) + } + } + return r, nil } // stripProxyNamePortScheme extracts the bare resource name from the [scheme:]name[:port] segment that @@ -260,7 +297,10 @@ func (r rbacSupportedResources) getTeleportResourceKindFromAPIResource(api apiRe // getResourceFromRequest returns a KubernetesResource if the user tried to access // a specific endpoint that Teleport support resource filtering. Otherwise, returns nil. func getResourceFromRequest(req *http.Request, kubeDetails *kubeDetails) (metaResource, error) { - apiResource := parseResourcePath(req.URL.Path) + apiResource, err := parseResourcePath(req.URL.Path) + if err != nil { + return metaResource{}, trace.Wrap(err) + } out := metaResource{ requestedResource: apiResource, @@ -292,9 +332,9 @@ func getResourceFromRequest(req *http.Request, kubeDetails *kubeDetails) (metaRe } out.resourceDefinition = &resource - if apiResource.resourceName == "" && out.verb != types.KubeVerbCreate { - // if the resource is supported but the resource name is not present and not a create request, - // return nil because it's a list request. + if apiResource.resourceName == "" && !slices.Contains([]string{types.KubeVerbCreate, types.KubeVerbProxy}, out.verb) { + // A missing name means the request targets the whole collection: a list. + // Create and proxy are the exceptions. Create has the name in the body, proxy has none. out.isList = true return out, nil } @@ -410,6 +450,10 @@ func isKubeWatchRequest(req *http.Request, r apiResource) bool { } func (r apiResource) getVerb(req *http.Request) string { + if r.isProxyVerb { + return types.KubeVerbProxy + } + verb := "" isWatch := isKubeWatchRequest(req, r) switch r.resourceKind { diff --git a/lib/kube/proxy/url_test.go b/lib/kube/proxy/url_test.go index a1c6d3a6937a..bb0de1dec80a 100644 --- a/lib/kube/proxy/url_test.go +++ b/lib/kube/proxy/url_test.go @@ -29,6 +29,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -78,8 +79,9 @@ func TestExtractResourceNameFromPostRequest_Replayable(t *testing.T) { func TestParseResourcePath(t *testing.T) { tests := []struct { - path string - want apiResource + path string + want apiResource + wantErr bool }{ {path: "", want: apiResource{}}, {path: "/", want: apiResource{}}, @@ -119,11 +121,45 @@ func TestParseResourcePath(t *testing.T) { {path: "/api/v1/namespaces/default/services/https:svc:/proxy/path", want: apiResource{apiGroup: "", apiGroupVersion: "v1", namespace: "default", resourceKind: "services/proxy/path", resourceName: "svc"}}, {path: "/api/v1/namespaces/default/pods/https:foo:8080/proxy/healthz", want: apiResource{apiGroup: "", apiGroupVersion: "v1", namespace: "default", resourceKind: "pods/proxy/healthz", resourceName: "foo"}}, {path: "/api/v1/nodes/https:node-1:10250/proxy/pods", want: apiResource{apiGroup: "", apiGroupVersion: "v1", resourceKind: "nodes/proxy/pods", resourceName: "node-1"}}, + // The "special verb" form puts the verb ahead of the resource path. + // The verb segment is consumed and the remainder parses like any other resource path. + {path: "/apis/resources.teleport.dev/v6/proxy/namespaces/default/teleportroles/telerole-1", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", namespace: "default", resourceKind: "teleportroles", resourceName: "telerole-1", isProxyVerb: true}}, + {path: "/apis/resources.teleport.dev/v6/proxy/namespaces/default/teleportroles", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", namespace: "default", resourceKind: "teleportroles", isProxyVerb: true}}, + {path: "/apis/resources.teleport.dev/v6/proxy/teleportroles/telerole-1", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", resourceKind: "teleportroles", resourceName: "telerole-1", isProxyVerb: true}}, + {path: "/apis/resources.teleport.dev/v6/proxy/namespaces/default/teleportroles/telerole-1/extra/path", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", namespace: "default", resourceKind: "teleportroles", resourceName: "telerole-1", isProxyVerb: true}}, + {path: "/api/v1/proxy/namespaces/default/pods/foo", want: apiResource{apiGroup: "", apiGroupVersion: "v1", namespace: "default", resourceKind: "pods", resourceName: "foo", isProxyVerb: true}}, + // The core API's proxyable kinds accept [scheme:]name[:port] here too, + // so the name is reduced the same way as in the subresource form above. + {path: "/api/v1/proxy/namespaces/default/services/https:admin:443/healthz", want: apiResource{apiGroup: "", apiGroupVersion: "v1", namespace: "default", resourceKind: "services", resourceName: "admin", isProxyVerb: true}}, + {path: "/api/v1/proxy/nodes/https:node-1:10250/healthz", want: apiResource{apiGroup: "", apiGroupVersion: "v1", resourceKind: "nodes", resourceName: "node-1", isProxyVerb: true}}, + // Only those kinds: an aggregated API server gets the name segment verbatim, + // so reducing it would match a name the API server never sees. + {path: "/api/v1/proxy/namespaces/default/configmaps/https:admin:443", want: apiResource{apiGroup: "", apiGroupVersion: "v1", namespace: "default", resourceKind: "configmaps", resourceName: "https:admin:443", isProxyVerb: true}}, + {path: "/apis/resources.teleport.dev/v6/proxy/namespaces/default/teleportroles/https:admin:443", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", namespace: "default", resourceKind: "teleportroles", resourceName: "https:admin:443", isProxyVerb: true}}, + // A bare verb segment carries no resource path. + // The API server rejects it, so keep it as a kind the cluster doesn't serve rather than a resource-less request. + {path: "/apis/resources.teleport.dev/v6/proxy", want: apiResource{apiGroup: "resources.teleport.dev", apiGroupVersion: "v6", resourceKind: "proxy"}}, + // Cleaning resolves dot segments, but the raw path is what gets forwarded. + // Under the proxy verb the API server resolves "denied" while the cleaned path names "allowed", + // so reject rather than authorize a name it never resolves. + {wantErr: true, path: "/apis/apps/v1/proxy/namespaces/default/deployments/denied/./allowed"}, + {wantErr: true, path: "/apis/apps/v1/proxy/namespaces/default/deployments/denied/../allowed"}, + {wantErr: true, path: "/api/v1/namespaces/default/pods/denied/../allowed"}, + {wantErr: true, path: "/api/v1/namespaces/default/pods/./foo"}, + // Trailing and doubled slashes are normalized too, + // but they name the same resource either way, so they stay allowed. + {path: "/api/v1/pods/", want: apiResource{apiGroup: "", apiGroupVersion: "v1", resourceKind: "pods"}}, + {path: "/api/v1//pods", want: apiResource{apiGroup: "", apiGroupVersion: "v1", resourceKind: "pods"}}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { - got := parseResourcePath(tt.path) + got, err := parseResourcePath(tt.path) + if tt.wantErr { + require.True(t, trace.IsBadParameter(err), "want BadParameter, got %v", err) + return + } + require.NoError(t, err) diff := cmp.Diff(got, tt.want, cmp.AllowUnexported(apiResource{})) require.Empty(t, diff, "parsing path %q", tt.path) }) @@ -271,6 +307,13 @@ func Test_getResourceFromRequest(t *testing.T) { {path: "/apis/apps/v1/namespaces/default/deployments", body: bodyFunc("Deployment", "apps/v1"), want: &types.KubernetesResource{Kind: "deployments", Namespace: "default", Name: "foo-create", Verbs: []string{"create"}, APIGroup: "apps"}}, {path: "/apis/apps/v1beta2/namespaces/default/deployments", body: bodyFunc("Deployment", "apps/v1beta2"), want: &types.KubernetesResource{Kind: "deployments", Namespace: "default", Name: "foo-create", Verbs: []string{"create"}, APIGroup: "apps"}}, {path: "/apis/apps/v1/namespaces/default/deployments", body: bodyFuncWithoutGVK(), want: &types.KubernetesResource{Kind: "deployments", Namespace: "default", Name: "foo-create", Verbs: []string{"create"}, APIGroup: "apps"}}, + // The "special verb" form resolves to the kind the path targets and takes its + // verb from the path, not from the HTTP method. + {path: "/apis/apps/v1/proxy/namespaces/default/deployments/foo", want: &types.KubernetesResource{Kind: "deployments", Namespace: "default", Name: "foo", Verbs: []string{"proxy"}, APIGroup: "apps"}}, + {path: "/apis/apps/v1/proxy/namespaces/default/deployments/foo/extra/path", want: &types.KubernetesResource{Kind: "deployments", Namespace: "default", Name: "foo", Verbs: []string{"proxy"}, APIGroup: "apps"}}, + // A bare verb segment names no kind the cluster serves, so no RBAC resource + // is built and the forwarder denies the request. + {path: "/apis/apps/v1/proxy", want: nil}, // Statefulsets {path: "/apis/apps/v1/statefulsets", want: &types.KubernetesResource{Kind: "statefulsets", Verbs: []string{"list"}, APIGroup: "apps"}}, @@ -372,3 +415,45 @@ func Test_getResourceFromRequest(t *testing.T) { }) } } + +// TestGetResourceFromRequest_SpecialVerbProxyPath covers the two properties of the +// proxy special-verb form that RBAC relies on: +// +// - The verb comes from the path, not the HTTP method, so every method maps to KubeVerbProxy. +// - A name-less path is still a single resource, not a list. Lists bypass KubernetesResourceMatcher +// and are enforced by filtering the response instead, but a proxied response carries no list to filter. +func TestGetResourceFromRequest_SpecialVerbProxyPath(t *testing.T) { + t.Parallel() + details := &kubeDetails{kubeCodecs: &globalKubeCodecs, rbacSupportedTypes: getRBACSupportedTypes(t)} + + const ( + nameless = "/apis/apps/v1/proxy/namespaces/default/deployments" + named = "/apis/apps/v1/proxy/namespaces/default/deployments/foo" + trailing = "/apis/apps/v1/proxy/namespaces/default/deployments/foo/extra/path" + ) + tests := []struct { + method string + path string + }{ + {method: http.MethodGet, path: named}, + {method: http.MethodPost, path: named}, + {method: http.MethodPut, path: named}, + {method: http.MethodPatch, path: named}, + {method: http.MethodDelete, path: named}, + {method: http.MethodGet, path: nameless}, + {method: http.MethodDelete, path: nameless}, + {method: http.MethodGet, path: trailing}, + {method: http.MethodPost, path: trailing}, + } + + for _, tt := range tests { + t.Run(tt.method+" "+tt.path, func(t *testing.T) { + got, err := getResourceFromRequest(&http.Request{Method: tt.method, URL: &url.URL{Path: tt.path}}, details) + require.NoError(t, err) + require.False(t, got.unsupportedResource) + require.False(t, got.isList) + require.Equal(t, types.KubeVerbProxy, got.verb) + require.Equal(t, "deployments", got.requestedResource.resourceKind) + }) + } +} From ec168c7c549d45e7688d212f1ffb6f5a694af620 Mon Sep 17 00:00:00 2001 From: Michelle Bergquist <11967646+michellescripts@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:27:51 -0600 Subject: [PATCH 085/347] Restore Stripe CSP for stripe-managed tenants (#68824) --- lib/httplib/httpheaders.go | 56 +++++++++++++++++++++++++++++-------- lib/httplib/httplib_test.go | 34 +++++++++++++++++++++- lib/web/apiserver.go | 3 +- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/lib/httplib/httpheaders.go b/lib/httplib/httpheaders.go index b7e605ddc150..779ccc0633af 100644 --- a/lib/httplib/httpheaders.go +++ b/lib/httplib/httpheaders.go @@ -50,6 +50,16 @@ var ( defaultFontSrc = CSPMap{"font-src": {"'self'", "data:"}} defaultConnectSrc = CSPMap{"connect-src": {"'self'", "wss:"}} wasmSecurityPolicy = CSPMap{"script-src": {"'self'", "'wasm-unsafe-eval'"}} + + // stripeSecurityPolicy grants the origins Stripe.js and the Payment Element + // need to load and communicate with Stripe. Applied only to stripe-managed + // tenants — see getIndexContentSecurityPolicyString. + // https://docs.stripe.com/security/guide#content-security-policy + stripeSecurityPolicy = CSPMap{ + "script-src": {"https://js.stripe.com", "https://*.js.stripe.com"}, + "frame-src": {"https://js.stripe.com", "https://*.js.stripe.com", "https://hooks.stripe.com"}, + "connect-src": {"https://api.stripe.com"}, + } ) // combineCSPMaps combines multiple CSP maps into a single map. @@ -137,11 +147,22 @@ func SetDefaultSecurityHeaders(h http.Header) { h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") } -func getIndexContentSecurityPolicy(withWasm bool) CSPMap { - cspMaps := []CSPMap{defaultContentSecurityPolicy, defaultFontSrc, defaultConnectSrc} +type cspOption int + +const ( + withWASM cspOption = iota + withStripe +) - if withWasm { - cspMaps = append(cspMaps, wasmSecurityPolicy) +func getIndexContentSecurityPolicy(options ...cspOption) CSPMap { + cspMaps := []CSPMap{defaultContentSecurityPolicy, defaultFontSrc, defaultConnectSrc} + for _, option := range options { + switch option { + case withWASM: + cspMaps = append(cspMaps, wasmSecurityPolicy) + case withStripe: + cspMaps = append(cspMaps, stripeSecurityPolicy) + } } return combineCSPMaps(cspMaps...) @@ -172,23 +193,34 @@ const ( ) var ( - indexCSPWithWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(true)) - indexCSPWithoutWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(false)) + indexCSPWithWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(withWASM)) + indexCSPWithoutWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy()) + indexCSPStripeWithWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(withStripe, withWASM)) + indexCSPStripeWithoutWASM = GetContentSecurityPolicyString(getIndexContentSecurityPolicy(withStripe)) ) -func getIndexContentSecurityPolicyString(urlPath string) string { - if wasm := desktopSessionRe.MatchString(urlPath) || +func getIndexContentSecurityPolicyString(withStripe bool, urlPath string) string { + wasm := desktopSessionRe.MatchString(urlPath) || linuxDesktopSessionRe.MatchString(urlPath) || recordingRe.MatchString(urlPath) || - sshSessionRe.MatchString(urlPath); wasm { + sshSessionRe.MatchString(urlPath) + switch { + case wasm && withStripe: + return indexCSPStripeWithWASM + case wasm: return indexCSPWithWASM + case withStripe: + return indexCSPStripeWithoutWASM + default: + return indexCSPWithoutWASM } - return indexCSPWithoutWASM } // SetIndexContentSecurityPolicy sets the Content-Security-Policy header for main index.html page. -func SetIndexContentSecurityPolicy(h http.Header, urlPath string) { - h.Set(cspHeaderName, getIndexContentSecurityPolicyString(urlPath)) +// withStripe should be true for stripe-managed tenants so that Stripe.js and the Payment Element +// can load in the cloud panel. +func SetIndexContentSecurityPolicy(h http.Header, withStripe bool, urlPath string) { + h.Set(cspHeaderName, getIndexContentSecurityPolicyString(withStripe, urlPath)) } // SetRedirectPageContentSecurityPolicy sets the Content-Security-Policy header diff --git a/lib/httplib/httplib_test.go b/lib/httplib/httplib_test.go index d9c7b342bc40..063967c791b7 100644 --- a/lib/httplib/httplib_test.go +++ b/lib/httplib/httplib_test.go @@ -272,6 +272,7 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { for _, tt := range []struct { name string + withStripe bool urlPath string expectedCSPVals map[string]string }{ @@ -370,15 +371,46 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { "connect-src": "'self' wss:", }, }, + { + name: "for stripe-managed tenant (no wasm)", + withStripe: true, + urlPath: "/web/index.js", + expectedCSPVals: map[string]string{ + "default-src": "'self'", + "base-uri": "'self'", + "form-action": "'self'", + "frame-ancestors": "'none'", + "object-src": "'none'", + "script-src": "'self' https://js.stripe.com https://*.js.stripe.com", + "frame-src": "https://js.stripe.com https://*.js.stripe.com https://hooks.stripe.com", + "connect-src": "'self' wss: https://api.stripe.com", + "style-src": "'self' 'unsafe-inline'", + "img-src": "'self' data: blob:", + "font-src": "'self' data:", + }, + }, + { + name: "for stripe-managed tenant on desktop session (with wasm)", + withStripe: true, + urlPath: "/web/cluster/:clusterId/desktops/:desktopName/:username", + expectedCSPVals: map[string]string{ + "script-src": "'self' https://js.stripe.com https://*.js.stripe.com 'wasm-unsafe-eval'", + "frame-src": "https://js.stripe.com https://*.js.stripe.com https://hooks.stripe.com", + "connect-src": "'self' wss: https://api.stripe.com", + }, + }, } { t.Run(tt.name, func(t *testing.T) { h := make(http.Header) - SetIndexContentSecurityPolicy(h, tt.urlPath) + SetIndexContentSecurityPolicy(h, tt.withStripe, tt.urlPath) actualCSP := h.Get("Content-Security-Policy") for k, v := range tt.expectedCSPVals { expectedCSPSubstring := fmt.Sprintf("%s %s;", k, v) require.Contains(t, actualCSP, expectedCSPSubstring) } + if !tt.withStripe { + require.NotContains(t, actualCSP, "stripe.com") + } }) } } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 1199c170d6f7..633f473eb90e 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -797,7 +797,8 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { session.XCSRF = csrfToken httplib.SetNoCacheHeaders(w.Header()) - httplib.SetIndexContentSecurityPolicy(w.Header(), r.URL.Path) + features := h.GetClusterFeatures() + httplib.SetIndexContentSecurityPolicy(w.Header(), features.GetIsStripeManaged(), r.URL.Path) if err := indexPage.Execute(w, session); err != nil { h.logger.ErrorContext(r.Context(), "Failed to execute index page template", "error", err) From 7f733b91fba64db632f318ef5ad03ff6eae0c564 Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Fri, 31 Jul 2026 10:07:21 -0700 Subject: [PATCH 086/347] Fix file watcher silently closing due to pre-mature context cancellation. (#69198) --- integrations/access/common/app.go | 14 ++++++++++++-- integrations/access/email/app.go | 6 +++++- integrations/access/jira/app.go | 5 ++++- integrations/access/msteams/app.go | 5 ++++- integrations/access/opsgenie/app.go | 5 ++++- integrations/access/pagerduty/app.go | 5 ++++- integrations/access/servicenow/app.go | 5 ++++- integrations/lib/config.go | 8 +++++++- 8 files changed, 44 insertions(+), 9 deletions(-) diff --git a/integrations/access/common/app.go b/integrations/access/common/app.go index 6c174e1422b7..9445e6dcdfd6 100644 --- a/integrations/access/common/app.go +++ b/integrations/access/common/app.go @@ -103,7 +103,13 @@ func (a *BaseApp) checkTeleportVersion(ctx context.Context) (proto.PingResponse, // initTeleport creates a Teleport client and validates Teleport connectivity. func (a *BaseApp) initTeleport(ctx context.Context, conf PluginConfiguration) (clusterName, webProxyAddr string, err error) { - clt, err := conf.GetTeleportClient(ctx) + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx + ctx, cancel := context.WithTimeout(ctx, initTimeout) + defer cancel() + + clt, err := conf.GetTeleportClient(clientCtx) if err != nil { return "", "", trace.Wrap(err) } @@ -173,15 +179,19 @@ func (a *BaseApp) run(ctx context.Context) error { } func (a *BaseApp) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() + log := logger.Get(ctx) if a.Clock == nil { a.Clock = clockwork.NewRealClock() } - clusterName, webProxyAddr, err := a.initTeleport(ctx, a.Conf) + clusterName, webProxyAddr, err := a.initTeleport(clientCtx, a.Conf) if err != nil { return trace.Wrap(err) } diff --git a/integrations/access/email/app.go b/integrations/access/email/app.go index b59319a14e54..4088feedebcf 100644 --- a/integrations/access/email/app.go +++ b/integrations/access/email/app.go @@ -151,11 +151,15 @@ func (a *App) run(ctx context.Context) error { // init inits plugin func (a *App) init(ctx context.Context) error { log := logger.Get(ctx) + + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() var err error - if a.apiClient, err = a.conf.GetTeleportClient(ctx); err != nil { + if a.apiClient, err = a.conf.GetTeleportClient(clientCtx); err != nil { return trace.Wrap(err) } diff --git a/integrations/access/jira/app.go b/integrations/access/jira/app.go index 1bb8d41ffb66..e61aa0e4a386 100644 --- a/integrations/access/jira/app.go +++ b/integrations/access/jira/app.go @@ -178,13 +178,16 @@ func (a *App) run(ctx context.Context) error { } func (a *App) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() log := logger.Get(ctx) var err error if a.teleport == nil { - if a.teleport, err = common.GetTeleportClient(ctx, a.conf.Teleport); err != nil { + if a.teleport, err = common.GetTeleportClient(clientCtx, a.conf.Teleport); err != nil { return trace.Wrap(err) } } diff --git a/integrations/access/msteams/app.go b/integrations/access/msteams/app.go index ac55be7be998..6060cb426ae9 100644 --- a/integrations/access/msteams/app.go +++ b/integrations/access/msteams/app.go @@ -108,6 +108,9 @@ func (a *App) WaitReady(ctx context.Context) (bool, error) { // init initializes the application func (a *App) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() @@ -115,7 +118,7 @@ func (a *App) init(ctx context.Context) error { a.apiClient = a.conf.Client } else { var err error - a.apiClient, err = common.GetTeleportClient(ctx, a.conf.Teleport) + a.apiClient, err = common.GetTeleportClient(clientCtx, a.conf.Teleport) if err != nil { return trace.Wrap(err) } diff --git a/integrations/access/opsgenie/app.go b/integrations/access/opsgenie/app.go index 82b698926c95..2809c9317641 100644 --- a/integrations/access/opsgenie/app.go +++ b/integrations/access/opsgenie/app.go @@ -159,11 +159,14 @@ func (a *App) run(ctx context.Context) error { } func (a *App) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() var err error - a.teleport, err = a.conf.GetTeleportClient(ctx) + a.teleport, err = a.conf.GetTeleportClient(clientCtx) if err != nil { return trace.Wrap(err, "getting teleport client") } diff --git a/integrations/access/pagerduty/app.go b/integrations/access/pagerduty/app.go index 60e55b5afc72..2fa4450d5744 100644 --- a/integrations/access/pagerduty/app.go +++ b/integrations/access/pagerduty/app.go @@ -157,6 +157,9 @@ func (a *App) run(ctx context.Context) error { } func (a *App) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() log := logger.Get(ctx) @@ -167,7 +170,7 @@ func (a *App) init(ctx context.Context) error { ) if a.teleport == nil { - if a.teleport, err = common.GetTeleportClient(ctx, a.conf.Teleport); err != nil { + if a.teleport, err = common.GetTeleportClient(clientCtx, a.conf.Teleport); err != nil { return trace.Wrap(err) } } diff --git a/integrations/access/servicenow/app.go b/integrations/access/servicenow/app.go index 2d8ec64f666e..66cdbec1c608 100644 --- a/integrations/access/servicenow/app.go +++ b/integrations/access/servicenow/app.go @@ -165,12 +165,15 @@ func (a *App) run(ctx context.Context) error { } func (a *App) init(ctx context.Context) error { + // Preserve the parent context without deadline for the client and its + // goroutines (IdentityFileWatcher) which will outlive init. + clientCtx := ctx ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() log := logger.Get(ctx) var err error - a.teleport, err = a.conf.GetTeleportClient(ctx) + a.teleport, err = a.conf.GetTeleportClient(clientCtx) if err != nil { return trace.Wrap(err, "getting teleport client") } diff --git a/integrations/lib/config.go b/integrations/lib/config.go index 5a78e7a1f12f..3f86a7d85f1f 100644 --- a/integrations/lib/config.go +++ b/integrations/lib/config.go @@ -132,12 +132,15 @@ func NewIdentityFileWatcher(ctx context.Context, path string, interval time.Dura for { select { case <-ctx.Done(): + slog.DebugContext(ctx, "Stopping identity file watcher", "error", ctx.Err()) return case <-timer.C: } if err := dynamicCred.Reload(); err != nil { slog.ErrorContext(ctx, "Failed to reload identity file from disk", "error", err) + } else if expiry, ok := dynamicCred.Expiry(); ok { + slog.DebugContext(ctx, "Reloaded identity file from disk", "path", path, "expires", expiry) } timer.Reset(interval) } @@ -182,9 +185,12 @@ func (cfg TeleportConfig) NewClient(ctx context.Context) (*client.Client, error) slog.InfoContext(ctx, "At least one non-expired credential has been found, continuing startup") } + dialCtx, cancel := context.WithTimeout(ctx, initTimeout) + defer cancel() + bk := grpcbackoff.DefaultConfig bk.MaxDelay = grpcBackoffMaxDelay - clt, err := client.New(ctx, client.Config{ + clt, err := client.New(dialCtx, client.Config{ Addrs: []string{addr}, Credentials: creds, DialOpts: []grpc.DialOption{ From 58cb6c5e3b5ff8a7ee53f48ce24eeb47fb7c9f6a Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:00:42 -0400 Subject: [PATCH 087/347] add data-testid to sidenav (#69233) --- web/packages/teleport/src/Main/Main.test.tsx | 8 ++++++-- web/packages/teleport/src/Navigation/Section.tsx | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/web/packages/teleport/src/Main/Main.test.tsx b/web/packages/teleport/src/Main/Main.test.tsx index 59be24ebbbf2..76fd8b99808e 100644 --- a/web/packages/teleport/src/Main/Main.test.tsx +++ b/web/packages/teleport/src/Main/Main.test.tsx @@ -184,7 +184,9 @@ test('toggle rendering of info guide panel', async () => { // render the component that has the guide info button fireEvent.click(screen.queryAllByText('Zero Trust Access')[0]); - fireEvent.click(screen.getByTestId(testFeature.route.path)); + fireEvent.click( + screen.getByRole('link', { name: testFeature.navigationItem.title }) + ); expect(screen.getByText(/info guide title/i)).toBeInTheDocument(); // test opening of panel @@ -223,7 +225,9 @@ test('notification render and auto dismissal', async () => { // render the component that has the add note button fireEvent.click(screen.queryAllByText('Zero Trust Access')[0]); - fireEvent.click(screen.getByTestId(testFeature.route.path)); + fireEvent.click( + screen.getByRole('link', { name: testFeature.navigationItem.title }) + ); expect(screen.queryAllByText(/some note/i)).toHaveLength(0); diff --git a/web/packages/teleport/src/Navigation/Section.tsx b/web/packages/teleport/src/Navigation/Section.tsx index e94ed581d8a2..d8c2a884784b 100644 --- a/web/packages/teleport/src/Navigation/Section.tsx +++ b/web/packages/teleport/src/Navigation/Section.tsx @@ -86,6 +86,7 @@ export function DefaultSection({ return ( <> + {title} @@ -388,7 +395,7 @@ export function SubsectionItem({ end={exact} tabIndex={0} onClick={onClick} - data-testid={to} + data-testid="side-nav-item" > {children} From 6d3d215e0d3229bee1dfdf7b44e3bf314be066bd Mon Sep 17 00:00:00 2001 From: Lion Chen Date: Fri, 31 Jul 2026 13:10:38 -0700 Subject: [PATCH 088/347] Show user display name in the access request review box (#69212) --- .../RequestView/RequestReview/RequestReview.tsx | 14 +++++++++++++- .../RequestView/RequestView.test.tsx | 16 ++++++++++++---- .../ReviewRequests/RequestView/RequestView.tsx | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx index 94a8a1be8e2f..56f26b0d4101 100644 --- a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx +++ b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestReview/RequestReview.tsx @@ -26,6 +26,7 @@ import { HoverTooltip } from 'design/Tooltip'; import { FieldSelect } from 'shared/components/FieldSelect'; import { FieldTextArea } from 'shared/components/FieldTextArea'; import { Option } from 'shared/components/Select'; +import { UserDisplayName } from 'shared/components/UserDisplayName'; import Validation, { Validator } from 'shared/components/Validation'; import { requiredField } from 'shared/components/Validation/rules'; import { Attempt } from 'shared/hooks/useAsync'; @@ -33,6 +34,7 @@ import { AccessRequest, RequestKind, RequestState, + UserDisplay, } from 'shared/services/accessRequests'; import { AccessDurationReview } from '../../../AccessDuration'; @@ -50,6 +52,7 @@ export interface RequestReviewProps { fetchSuggestedAccessListsAttempt: Attempt; shortTermDuration: string; user: string; + userDisplay?: UserDisplay; submitReviewAttempt: Attempt; request: AccessRequest; } @@ -58,6 +61,7 @@ export default function RequestReview({ submitReviewAttempt, submitReview, user, + userDisplay, fetchSuggestedAccessListsAttempt, shortTermDuration, request, @@ -125,7 +129,15 @@ export default function RequestReview({ style={{ position: 'relative' }} > -

    {user} - add a review

    +

    + + {' - add a review'} +

    {submitReviewAttempt.status === 'error' && ( diff --git a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx index 9089adab211a..f2ee29c08922 100644 --- a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx +++ b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.test.tsx @@ -47,6 +47,10 @@ const sampleFlags: RequestFlags = { const props: RequestViewProps = { user: 'loggedInUsername', + userDisplay: { + primary: 'Logged In User', + secondary: 'logged-in-user@example.com', + }, fetchRequestAttempt: makeSuccessAttempt(requestRolePending), submitReviewAttempt: makeEmptyAttempt(), getFlags: () => sampleFlags, @@ -61,11 +65,15 @@ const props: RequestViewProps = { deleteRequest: () => null, }; -const reviewBoxText = `${props.user} - add a review`; - test('renders review box if user can review', async () => { render(); - expect(screen.getByText(reviewBoxText)).toBeInTheDocument(); + const reviewHeader = screen.getByText(/add a review/i); + + expect(within(reviewHeader).getByText('Logged In User')).toBeVisible(); + expect(within(reviewHeader).getByText('loggedInUsername')).toBeVisible(); + expect( + within(reviewHeader).queryByText('logged-in-user@example.com') + ).not.toBeInTheDocument(); }); test('does not render review box if user cannot review', async () => { @@ -78,7 +86,7 @@ test('does not render review box if user cannot review', async () => { })} /> ); - expect(screen.queryByText(reviewBoxText)).not.toBeInTheDocument(); + expect(screen.queryByText(/add a review/i)).not.toBeInTheDocument(); }); test('renders requester and reviewer display names with usernames', () => { diff --git a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.tsx b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.tsx index fba7cae899d8..f85be6cd2db9 100644 --- a/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.tsx +++ b/web/packages/shared/components/AccessRequests/ReviewRequests/RequestView/RequestView.tsx @@ -78,6 +78,7 @@ import { SuggestedAccessList } from './types'; export interface RequestViewProps { user: string; + userDisplay?: UserDisplay; getFlags(accessRequest: AccessRequest): RequestFlags; fetchRequestAttempt: Attempt; fetchSuggestedAccessListsAttempt: Attempt; @@ -94,6 +95,7 @@ export interface RequestViewProps { export function RequestView({ user, + userDisplay, fetchRequestAttempt, getFlags, confirmDelete, @@ -312,6 +314,7 @@ export function RequestView({ Date: Fri, 31 Jul 2026 16:34:44 -0400 Subject: [PATCH 089/347] Stop the rust toolchain warning from printing erroneously when invoked from another directory. (#69237) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1c05c9c23ba..ea3e4015479d 100644 --- a/Makefile +++ b/Makefile @@ -2100,7 +2100,7 @@ export rust_shadowed_warning # on PATH is not rustup-managed (e.g. the Homebrew 'rust' formula), which # silently bypasses the toolchain file even though the checks below pass. .PHONY: rustup-toolchain-warning -rustup-toolchain-warning: EXPECTED = $(shell $(MAKE) print-rust-toolchain-version) +rustup-toolchain-warning: EXPECTED = $(shell $(MAKE) --no-print-directory print-rust-toolchain-version) rustup-toolchain-warning: @if [ "$(shell rustup show active-toolchain | cut -d'-' -f1)" != "$(EXPECTED)" ]; then \ echo -en "\033[31m";\ From c58eb87e241efc8ad6a79ce68388bb50c0af82b1 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Fri, 31 Jul 2026 22:44:19 +0200 Subject: [PATCH 090/347] Linux desktop improvements (#69186) * Add desktop name to active session * Fix ListLinuxDesktops * Add xauth entry before starting Xvfb * Fix labels handling * fix-imports --- api/types/role.go | 4 +++ e2e/runner/bootstrap.go | 4 +-- .../linuxdesktop/linuxdesktopv1/service.go | 2 +- .../linuxdesktopv1/service_test.go | 34 +++++++++++++++---- lib/services/role.go | 1 + lib/services/role_test.go | 8 +++++ lib/srv/desktop/linux_server.go | 1 + lib/srv/desktop/x11/xvfb.go | 20 +++++++---- 8 files changed, 58 insertions(+), 16 deletions(-) diff --git a/api/types/role.go b/api/types/role.go index 0cf5a217f75e..a501f5c0fd8c 100644 --- a/api/types/role.go +++ b/api/types/role.go @@ -2387,6 +2387,10 @@ func (r *RoleV6) SetLabelMatchers(rct RoleConditionType, kind string, labelMatch cond.DatabaseServiceLabels = labelMatchers.Labels cond.DatabaseServiceLabelsExpression = labelMatchers.Expression return nil + case KindLinuxDesktop: + cond.LinuxDesktopLabels = labelMatchers.Labels + cond.LinuxDesktopLabelsExpression = labelMatchers.Expression + return nil case KindWindowsDesktop: cond.WindowsDesktopLabels = labelMatchers.Labels cond.WindowsDesktopLabelsExpression = labelMatchers.Expression diff --git a/e2e/runner/bootstrap.go b/e2e/runner/bootstrap.go index e83e1cecc4e4..cdb309f7c5e7 100644 --- a/e2e/runner/bootstrap.go +++ b/e2e/runner/bootstrap.go @@ -105,8 +105,8 @@ func readRoleFile(e2eDir, filename string) (*customRole, error) { // should appear as the session's principal and the freshly-generated session // ID assigned to this copy of the recording. type recordingOwner struct { - user string - sessionID string + user string + sessionID string } type recordingOwners map[string][]recordingOwner diff --git a/lib/auth/linuxdesktop/linuxdesktopv1/service.go b/lib/auth/linuxdesktop/linuxdesktopv1/service.go index 4954f2e0e302..36fc507c4fe9 100644 --- a/lib/auth/linuxdesktop/linuxdesktopv1/service.go +++ b/lib/auth/linuxdesktop/linuxdesktopv1/service.go @@ -127,7 +127,7 @@ func (s *Service) ListLinuxDesktops(ctx context.Context, req *linuxdesktopv1.Lis return nil, trace.Wrap(err) } - resources := clientutils.Resources(ctx, s.reader.ListLinuxDesktops) + resources := clientutils.RangeResources(ctx, req.GetPageToken(), "", s.reader.ListLinuxDesktops, nil) rsp, nextToken, err := generic.CollectPageAndCursor( iterstream.FilterMap(resources, func(desktop *linuxdesktopv1.LinuxDesktop) (*linuxdesktopv1.LinuxDesktop, bool) { if err := checkAccess(authCtx, desktop); err == nil { diff --git a/lib/auth/linuxdesktop/linuxdesktopv1/service_test.go b/lib/auth/linuxdesktop/linuxdesktopv1/service_test.go index c8e188e9e9c4..921a057d607a 100644 --- a/lib/auth/linuxdesktop/linuxdesktopv1/service_test.go +++ b/lib/auth/linuxdesktop/linuxdesktopv1/service_test.go @@ -20,6 +20,7 @@ package linuxdesktopv1 import ( "context" + "fmt" "testing" "github.com/gravitational/trace" @@ -198,10 +199,13 @@ func TestServiceListLinuxDesktops(t *testing.T) { t.Parallel() ctx := t.Context() - desktop := newTestDesktop(t, "desktop-1") + var desktops []*linuxdesktopv1pb.LinuxDesktop + for i := 0; i < 10; i++ { + desktops = append(desktops, newTestDesktop(t, fmt.Sprintf("desktop-%d", i))) + } reader := &fakeReader{ - listResp: []*linuxdesktopv1pb.LinuxDesktop{desktop}, + listResp: desktops, listNext: "", } backend := &fakeBackend{} @@ -213,16 +217,32 @@ func TestServiceListLinuxDesktops(t *testing.T) { } service := newTestService(t, checker, authz.AdminActionAuthNotRequired, backend, reader) - resp, err := service.ListLinuxDesktops(ctx, linuxdesktopv1pb.ListLinuxDesktopsRequest_builder{ - PageSize: 10, - PageToken: "next-token", + resp, err := service.ListLinuxDesktops(ctx, linuxdesktopv1pb.ListLinuxDesktopsRequest_builder{PageSize: 5}.Build()) + require.NoError(t, err) + require.Equal(t, desktops[:5], resp.GetLinuxDesktops()) + require.Equal(t, "desktop-5", resp.GetNextPageToken()) + require.True(t, reader.listCalled) + require.Equal(t, defaults.DefaultChunkSize, reader.listPageSize) + require.Empty(t, reader.listToken) + require.Equal(t, []check{ + {kind: types.KindLinuxDesktop, verb: types.VerbList}, + {kind: types.KindLinuxDesktop, verb: types.VerbRead}, + }, checker.checks) + reader.listToken = "" + reader.listPageSize = 0 + reader.listCalled = false + reader.listResp = desktops[5:] + checker.checks = checker.checks[:0] + resp, err = service.ListLinuxDesktops(ctx, linuxdesktopv1pb.ListLinuxDesktopsRequest_builder{ + PageSize: 5, + PageToken: "desktop-5", }.Build()) require.NoError(t, err) - require.Equal(t, []*linuxdesktopv1pb.LinuxDesktop{desktop}, resp.GetLinuxDesktops()) + require.Equal(t, desktops[5:], resp.GetLinuxDesktops()) require.Empty(t, resp.GetNextPageToken()) require.True(t, reader.listCalled) require.Equal(t, defaults.DefaultChunkSize, reader.listPageSize) - require.Empty(t, reader.listToken) + require.Equal(t, "desktop-5", reader.listToken) require.Equal(t, []check{ {kind: types.KindLinuxDesktop, verb: types.VerbList}, {kind: types.KindLinuxDesktop, verb: types.VerbRead}, diff --git a/lib/services/role.go b/lib/services/role.go index bf4331dc35f2..140e3d2f0746 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -770,6 +770,7 @@ func ApplyTraitsWithContext(r types.Role, ctx RoleTemplateContext) (types.Role, types.KindDatabase, types.KindDatabaseService, types.KindWindowsDesktop, + types.KindLinuxDesktop, types.KindUserGroup, types.KindSAMLIdPServiceProvider, types.KindWorkloadIdentity, diff --git a/lib/services/role_test.go b/lib/services/role_test.go index ddedc6c956c5..811bed876544 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -43,6 +43,7 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" beamsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/beams/v1" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + linuxdesktopv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/linuxdesktop/v1" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" @@ -10216,6 +10217,13 @@ func TestCheckAccessWithLabelExpressions(t *testing.T) { Labels: map[string]string{}, }.Build(), }.Build()), + types.Resource153ToResourceWithLabels(linuxdesktopv1.LinuxDesktop_builder{ + Kind: types.KindLinuxDesktop, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Labels: map[string]string{}, + }.Build(), + }.Build()), } for _, r := range resources { r.SetStaticLabels(map[string]string{"env": "prod"}) diff --git a/lib/srv/desktop/linux_server.go b/lib/srv/desktop/linux_server.go index c77a5d881799..76a038e89448 100644 --- a/lib/srv/desktop/linux_server.go +++ b/lib/srv/desktop/linux_server.go @@ -941,6 +941,7 @@ func (s *LinuxService) trackSession(ctx context.Context, id *tlsca.Identity, lin Kind: string(types.LinuxDesktopSessionKind), State: types.SessionState_SessionStateRunning, Hostname: s.cfg.Hostname, + DesktopName: s.cfg.Hostname, Address: s.cfg.Heartbeat.HostUUID, ClusterName: s.clusterName, Login: linuxUser, diff --git a/lib/srv/desktop/x11/xvfb.go b/lib/srv/desktop/x11/xvfb.go index d49b7fb307eb..c0aab37102cb 100644 --- a/lib/srv/desktop/x11/xvfb.go +++ b/lib/srv/desktop/x11/xvfb.go @@ -236,6 +236,11 @@ func NewBackend(ctx context.Context, config Config) (*Backend, error) { } }() + cookie := make([]byte, 16) + if _, err := rand.Read(cookie); err != nil { + return nil, trace.Wrap(err) + } + authorityFile, err := os.CreateTemp("", "teleport-x11-") if err != nil { return nil, trace.Wrap(err) @@ -247,6 +252,14 @@ func NewBackend(ctx context.Context, config Config) (*Backend, error) { } }() + entry, err := generateXauthorityEntry(":phony", cookie) + if err != nil { + return nil, trace.Wrap(err) + } + if _, err := authorityFile.Write(entry); err != nil { + return nil, trace.Wrap(err) + } + cmd, err := getBackendCommand(ctx, authorityFile.Name()) if err != nil { return nil, trace.Wrap(err) @@ -295,12 +308,7 @@ func NewBackend(ctx context.Context, config Config) (*Backend, error) { display = ":" + strings.TrimPrefix(display, ":") - cookie := make([]byte, 16) - if _, err := rand.Read(cookie); err != nil { - return nil, trace.Wrap(err) - } - - entry, err := generateXauthorityEntry(display, cookie) + entry, err = generateXauthorityEntry(display, cookie) if err != nil { return nil, trace.Wrap(err) } From af07466c05ba61d2ee6de5afd1663766d175b1dc Mon Sep 17 00:00:00 2001 From: Kevin <76608931+kshi36@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:52:48 -0700 Subject: [PATCH 091/347] docs: Add Slack native review guide (#68221) * Add slack native review guide * Update docker steps; minor touchup * Add page_type; fix systemd step * Clarify bot/app tokens --- docs/cspell.json | 1 + .../access-requests/plugins/how-to-build.mdx | 3 +- .../access-requests/plugins/slack-review.mdx | 552 ++++++++++++++++++ .../access-requests/plugins/slack.mdx | 9 +- .../pages/includes/plugins/create-request.mdx | 6 +- .../plugins/rbac-impersonate-machine-id.mdx | 6 +- .../includes/plugins/rbac-impersonate.mdx | 34 +- .../includes/plugins/slack-review-config.mdx | 143 +++++ 8 files changed, 731 insertions(+), 23 deletions(-) create mode 100644 docs/pages/identity-governance/access-requests/plugins/slack-review.mdx create mode 100644 docs/pages/includes/plugins/slack-review-config.mdx diff --git a/docs/cspell.json b/docs/cspell.json index 8a3fb3479670..6c506a382f4e 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -1169,6 +1169,7 @@ "workloadcluster", "wtmp", "xample", + "xapp", "xauth", "xclip", "xeyes", diff --git a/docs/pages/identity-governance/access-requests/plugins/how-to-build.mdx b/docs/pages/identity-governance/access-requests/plugins/how-to-build.mdx index 85a060dc37ba..f10b1cf86a92 100644 --- a/docs/pages/identity-governance/access-requests/plugins/how-to-build.mdx +++ b/docs/pages/identity-governance/access-requests/plugins/how-to-build.mdx @@ -1,6 +1,6 @@ --- title: How to Build an Access Request Plugin -sidebar_position: 11 +sidebar_position: 12 description: Manage Access Requests using custom workflows with the Teleport API tags: - access-requests @@ -500,4 +500,3 @@ the plugin. For production usage, we recommend provisioning short-lived credentials via Machine & Workload Identity, which reduces the risk of these credentials becoming stolen. View our [Machine & Workload Identity documentation](../../../machine-workload-identity/machine-workload-identity.mdx) to learn more. - diff --git a/docs/pages/identity-governance/access-requests/plugins/slack-review.mdx b/docs/pages/identity-governance/access-requests/plugins/slack-review.mdx new file mode 100644 index 000000000000..5a58d65208df --- /dev/null +++ b/docs/pages/identity-governance/access-requests/plugins/slack-review.mdx @@ -0,0 +1,552 @@ +--- +title: Run the Slack Access Request Plugin with Native Reviews +description: How to set up Teleport's Slack plugin to review Access Requests directly within Slack. +sidebar_label: Slack with Native Reviews +tags: + - access-requests + - how-to + - identity-governance + - privileged-access +enterprise: Identity Governance +page_type: how-to +--- + +In this guide, you will configure the Teleport Slack plugin to be able to review +Access Requests directly within Slack. + +Currently, only self-hosted plugins support this feature. +Support for Teleport-hosted plugins will be added in the future. + +Security-critical Teleport deployments should follow the [regular Slack plugin guide](slack.mdx), +which requires reviewers to authenticate to Teleport before approving or denying requests. + + +Enabling this feature reduces the security of the Teleport cluster. +Access Request reviews submitted through Slack are bound to a Slack identity rather than +the reviewer's Teleport identity, and bypass Teleport authentication +(and MFA for admin actions if enabled on cluster). +As a result, a compromised Slack account can approve requests from Slack and escalate privileges in Teleport. + + +## How it works + +Teleport's Slack integration notifies individuals and channels of +Access Requests. By enabling the plugin with native Access Request reviews, users can then +approve or deny Access Requests directly within Slack without being redirected to the Teleport Web UI. + +## Prerequisites + +(!docs/pages/includes/edition-prereqs-tabs.mdx edition="Teleport Enterprise"!) + +(!docs/pages/includes/machine-id/plugin-prerequisites.mdx!) + +- Slack admin privileges to create an app and install it to your workspace. Your + Slack profile must have the "Workspace Owner" or "Workspace Admin" banner + below your profile picture. + +- Slack users that are linked to persistent Teleport users in order to review Access Requests from Slack. + When using an external Identity Provider, users should be imported using + the User Sync feature from the + [Teleport Okta integration](../../integrations/okta/user-sync.mdx) or the + [Teleport Entra ID integration](../../integrations/entra-id/entra-id.mdx). + SSO-only users are represented by temporary users in Teleport and they + will be able to submit reviews until their temporary Teleport user expires, + after which they must re-login to Teleport again. + +- Either a Linux host or Kubernetes cluster where you will run the Teleport Slack plugin. + +- A Slack application, Bot OAuth token, and App-level token to use for the plugin. + +
    + Creating a Slack app + + 1. Visit [https://api.slack.com/apps](https://api.slack.com/apps) to create + a new Slack app. Click "Create an App", then "From scratch". Fill in the + form as shown below: + + ![Create Slack App](../../../../img/enterprise/plugins/slack/Create-a-Slack-App.png) + + The "App Name" should be "Teleport". Click the "Development Slack + Workspace" dropdown and choose the workspace where you would like to see + Access Request messages. + + 1. Configure your application to authenticate to the Slack API. We will do + this by generating an OAuth token that the plugin will present to the + Slack API. + + We will restrict the plugin to the narrowest possible permissions by + using OAuth scopes. The Slack plugin needs to post messages to your + workspace. It also needs to read usernames and email addresses in order + to direct Access Request notifications from the Auth Service to the + appropriate Teleport users in Slack. + + After creating your app, the Slack website will open a console where you + can specify configuration options. + + 1. On the sidebar menu under "Features", click "OAuth & Permissions". + + 1. Scroll to the "Scopes" section and click "Add an OAuth Scope" for each of + the following scopes: + + - `chat:write` + - `incoming-webhook` + - `users:read` + - `users:read.email` + + The result should look like this: + + ![API Scopes](../../../../img/enterprise/plugins/slack/api-scopes.png) + + 1. After you have configured scopes for your plugin, scroll back to the top + of the OAuth & Permissions page, find the "OAuth Tokens for Your + Workspace" section, and click "Install to Workspace". You will see a + summary of the permission you configured for the Slack plugin earlier. + + 1. In "Where should Teleport post?", choose "Slackbot" as the default + channel the plugin will post to. The plugin will post here when sending + direct messages. Later in this guide, we will configure the plugin to + post in other channels as well. + + After submitting this form, you will see an OAuth token in the "OAuth & + Permissions" tab under "Tokens for Your Workspace": + + ![OAuth Tokens](../../../../img/enterprise/plugins/slack/OAuth.png) + + You will use this token later when configuring the Slack plugin. + +
    + +(!docs/pages/includes/tctl.mdx!) + +## Step 1/7. Configure your Slack app + +In this step, you will configure your Slack app to support the native Access Request reviews feature. + +### Generate an app-level token + +Enter the following URL and select your Slack app to reach your app's settings: +[`https://api.slack.com/apps`](https://api.slack.com/apps) + +In your Slack app's settings, head to the **Basic Information** tab. + +Then, under **App-Level Tokens**, click **Generate Token and Scopes**. Set a name +and add the `connections:write` scope. This will provide the plugin with the necessary permissions +to receive user interactions from Slack. + +Copy the generated app-level token for use in a later step. + + + +The Slack app-level token is different from the Slack bot token and must be set up separately. + +The bot token `xoxb-...` provides the plugin permissions to read Slack user info and write to channels. +The app-level token `xapp-...` provides the plugin permissions to receive user interactions from Slack, +which is necessary for the native Access Request reviews feature. + + + +### Enable Socket Mode setting + +Head to the **Socket Mode** tab and enable Socket Mode. This will allow the Teleport Slack plugin +to build a WebSocket connection with the Slack server in order to process Access Request reviews from Slack. + +![Enable Slack Socket Mode](https://goteleport.com/_uploads/slack_socketmode_4badb28ce0.png) + +## Step 2/7. Define requester and reviewer users + +For the purpose of this guide, we will define an `access-requester` role that +can request the built-in `access` role, and an `access-reviewer` role that can +review requests for the `access` role. + +Create a file called `access-request-rbac.yaml` with the following content: + +```yaml +kind: role +version: v8 +metadata: + name: access-reviewer +spec: + allow: + review_requests: + roles: ['access'] +--- +kind: role +version: v8 +metadata: + name: access-requester +spec: + allow: + request: + roles: ['access'] + thresholds: + - approve: 1 + deny: 1 +``` + +Create the roles you defined: + +```code +$ tctl create -f access-request-rbac.yaml +role 'access-reviewer' has been created +role 'access-requester' has been created +``` + +(!docs/pages/includes/create-role-using-web.mdx!) + +### Set up the requester user + +Create a user called `myuser` who has the `access-requester` role: + +```code +$ tctl users add myuser --roles=access-requester +``` + +Visit the invitation URL that is printed and login as `myuser` for the first time, +registering credentials as configured for your Teleport cluster. + +Later in this guide, you will have `myuser` request the `access` role so you can +review the request using the Teleport Slack plugin. + +### Set up the reviewer user + +Next, we will set up a user that can review Access Requests directly within Slack. + + +All users performing Access Request reviews within Slack must be linked to a persistent Teleport user. +When using an external Identity Provider, users should be imported using +the User Sync feature from the +[Teleport Okta integration](../../integrations/okta/user-sync.mdx) or the +[Teleport Entra ID integration](../../integrations/entra-id/entra-id.mdx). +SSO-only users are represented by temporary users in Teleport and they +will be able to submit reviews until their temporary Teleport user expires, +after which they must re-login to Teleport again. + + +There are two identity binding models for Slack reviewers: +- binding based on Teleport username and Slack email +- binding based on Teleport trait and Slack user ID + +The binding based on Teleport trait is stronger, but requires a Teleport user to be given a trait with +their Slack user ID, often coming from the Identity Provider/SSO connector. + +You can set up both types of identity bindings if some users are imported +from an Identity Provider/SSO connector while others are local Teleport users. + + + + +Create a Teleport user with a username set as +and assigned the role `access-reviewer`. + +We will assign this reviewer user with a Teleport trait named +that holds the value of the Slack user's ID: . +The trait name can also be configured in the plugin configuration step. + + + + +Create a local user: + +```code +$ tctl users add --roles=access-reviewer +``` + +Visit the invitation URL that is printed and login as for the first time, +registering credentials as configured for your Teleport cluster. + +Create a file called `user-traits.yaml` with the following content: + +```yaml +kind: user +version: v2 +metadata: + name: +spec: + roles: ['access-reviewer'] + traits: + : [''] +``` + +Update the newly created user: + +```code +$ tctl create -f user-traits.yaml +``` + + + + +When using a single sign-on user, the Teleport trait should be configured in the Identity Provider. + +To get started integrating your Identity Provider with Teleport, read [Integrate +your Identity Provider](../../../zero-trust-access/sso/integrate-idp/integrate-idp.mdx). + + + + + + + +Prepare a Slack user with an email address. +Create a Teleport user with a username set to the Slack user's email +and assigned the role `access-reviewer`. + +```code +$ tctl users add --roles=access-reviewer +``` + +Visit the invitation URL that is printed and login as for the first time, +registering credentials as configured for your Teleport cluster. + + + + +The Slack user will now be able to review Access Requests for the `access` role within Slack +once we set up the Teleport Slack plugin. + +## Step 3/7. Install the Teleport Slack plugin + +(!docs/pages/includes/plugins/install-access-request.mdx name="slack"!) + +## Step 4/7. Set up the Teleport Slack plugin + +In this section, you will set up the Teleport Slack plugin +and generate credentials that the plugin will use for authentication. + +### Enable issuing of credentials for the plugin user + +The required permissions for the plugin are configured in the preset `access-plugin-with-review` +role. To generate credentials for the plugin, define either a Machine ID bot user +or a regular Teleport user. + + + +(!docs/pages/includes/plugins/rbac-impersonate-machine-id.mdx role="access-plugin-with-review"!) + + +(!docs/pages/includes/plugins/rbac-impersonate.mdx role="access-plugin-with-review"!) + + + +### Export an identity file for the plugin user + +Give the plugin access to a Teleport identity file. We recommend using Machine +ID for this in order to produce short-lived identity files that are less +dangerous if exfiltrated, though in demo deployments, you can generate +longer-lived identity files with `tctl`: + + + +(!docs/pages/includes/plugins/tbot-identity.mdx secret="teleport-plugin-slack-identity"!) + + +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin-with-review" plugin="slack" secret="teleport-plugin-slack-identity"!) + + + +## Step 5/7. Run the Teleport Slack plugin + +At this point, the Teleport Slack plugin has the credentials it needs to +communicate with your Teleport cluster and the Slack API. In this step, you will +configure the Slack plugin to use these credentials. You will also configure the +plugin to notify the right Slack channels when it receives an Access Request +update. + +Lastly, you will configure the plugin to enable reviews of Access Requests +directly within Slack. + +### Configure the plugin + +(!docs/pages/includes/plugins/slack-review-config.mdx!) + +### Invite the plugin to your Slack app + +Once you have configured the channels that the Slack plugin will notify when it +receives an Access Request, you will need to ensure that the plugin can post in +those channels. + +You have already configured the plugin to send direct messages as Slackbot. For +any other channel you mention in your `role_to_recipients` map, you will need +to invite the plugin to that channel. Navigate to each channel and enter `/invite +@teleport` in the message box. + +
    +Suggested reviewers + +Users can suggest reviewers when they create an Access Request, e.g.: + +```code +$ tsh request create --roles=dbadmin --reviewers=alice@example.com,ivan@example.com +``` + +If an Access Request includes suggested reviewers, the Slack plugin will add +these to the list of channels to notify. If a suggested reviewer is an email +address, the plugin will look up the direct message channel for that +address and post a message in that channel. + +
    + +### Start the plugin + +Start the plugin by following the instructions below. + + + +```code +$ teleport-slack start --config teleport-slack.toml +``` + + + +```code +$ docker run \ + -v $(pwd)/teleport-slack.toml:/etc/teleport-slack.toml \ + -v : \ + public.ecr.aws/gravitational/teleport-plugin-slack:(=teleport.version=) start +``` + + + +```code +$ helm upgrade --install teleport-plugin-slack teleport/teleport-plugin-slack --values teleport-plugin-slack-values.yaml +``` + +To inspect the plugin's logs, use the following command: + +```code +$ kubectl logs deploy/teleport-plugin-slack +``` + +Debug logs can be enabled by setting `log.severity` to `DEBUG` in +`teleport-plugin-slack-values.yaml` and executing the `helm upgrade ...` command +above again. Then you can restart the plugin with the following command: + +```code +$ kubectl rollout restart deployment teleport-plugin-slack +``` + + + +## Step 6/7. Test your Slack app + +Once you've created the Slack app and the Teleport Slack plugin is +running, you can now test the workflow. + +### Create an Access Request + +(!docs/pages/includes/plugins/create-request.mdx role="access"!) + +The user you configured earlier to review the request should receive a direct +message from "Teleport" in Slack with Access Request details. +This message will contain “Approve” and “Deny” buttons for the user to either +approve or deny the request. + +![Slack Access Request message with approve and deny buttons](https://goteleport.com/_uploads/slack_review_request_20fcbbb8fe.png) + +### Resolve the request + +As the Slack user you set as an Access Request reviewer, click the "Approve" button, +and the plugin will send a reply to the original message with the review response. + +Once the request is resolved, the Slack bot will add an emoji reaction of ✅ or +❌ to the Slack message for the Access Request, depending on whether the request +was approved or denied. + +The Web UI will also reflect the resolved Access Request. + +![Access Request reviewed on Web UI](https://goteleport.com/_uploads/slack_approve_request_00532ec851.png) + +The Teleport audit log will report the Access Request as submitted by the Teleport Slack plugin identity. +The `submitted_by` field represents the plugin identity that submitted on behalf of the `reviewer` user. + +
    +Audit log + +```json +{ + "RequestedResourceAccessIDs": null, + "cluster_name": "kevin18.cloud.gravitational.io", + "code": "T5002I", + "ei": 0, + "event": "access_request.review", + "expires": "2026-06-18T11:26:07Z", + "id": "019ed7ee-e581-7644-81be-dffd1229028c", + "max_duration": "2026-06-18T11:26:07Z", + "proposed_state": "APPROVED", + "reviewer": "kevin.shi@goteleport.com", + "state": "APPROVED", + "submitted_by": "access-plugin-with-review", + "time": "2026-06-17T23:34:06.274Z", + "uid": "8912fd42-d1f4-4847-9621-fb7313e0ef08" +} +``` + +
    + + + +When the Slack plugin posts an Access Request notification to a channel, anyone +with access to the channel can view the notification and follow the link. While +users must be authorized via their Teleport roles to review Access Requests, you +should still check the Teleport audit log to ensure that the right users are +reviewing the right requests. + +When auditing Access Request reviews, check for events with the type `Access +Request Reviewed` in the Teleport Web UI. + + + +## Step 7/7. Set up systemd + +This section is only relevant if you are running the Teleport Slack plugin on a +Linux host. + +Copy the `teleport-slack.toml` file to `/etc` on your Linux server: + +```code +$ sudo mv teleport-slack.toml /etc +``` + +In production, we recommend starting the Teleport plugin daemon via an init +system like systemd. Here's the recommended Teleport plugin service unit file +for systemd: + +```ini +(!examples/systemd/plugins/teleport-slack.service!) +``` + +Save this as `teleport-slack.service` in either `/usr/lib/systemd/system/` or +another [unit file load +path](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Unit%20File%20Load%20Path) +supported by systemd. + +Enable and start the plugin: + +```code +$ sudo systemctl enable teleport-slack +$ sudo systemctl start teleport-slack +``` + +## Troubleshooting + +- Ensure Socket Mode is turned on in your Slack app’s settings. +- Ensure the Slack app-level token is provided for the plugin configuration field `review.app_token`. + This is different from the Slack bot OAuth token. +- If you receive an Access Request review reply with the following error: + `Insufficient permissions to review request`, + ensure the Slack reviewer is properly bound with a local Teleport user, + and that Teleport user has sufficient review permissions. + Also ensure that the Slack plugin is using the preset Teleport role + `access-plugin-with-review` which provides proper RBAC permissions for the Slack plugin. +- A Slack plugin using a long-lived identity file will not work with a Teleport cluster + with admin-action MFA enabled. If admin-action MFA is enabled, use a Slack plugin with a Machine ID bot. + +## Next steps + +- Read our guides to configuring [Resource Access + Requests](../resource-requests.mdx) and [Role Access + Requests](../role-requests.mdx) so you can get the most out + of your Access Request plugins. +- To see all of the options you can set in the values file for the + `teleport-plugin-slack` Helm chart, consult our [reference + guide](../../../reference/helm-reference/teleport-plugin-slack.mdx). diff --git a/docs/pages/identity-governance/access-requests/plugins/slack.mdx b/docs/pages/identity-governance/access-requests/plugins/slack.mdx index beaff65b3ec9..8faa885c2153 100644 --- a/docs/pages/identity-governance/access-requests/plugins/slack.mdx +++ b/docs/pages/identity-governance/access-requests/plugins/slack.mdx @@ -15,7 +15,12 @@ import SlackVideoMp4 from "@version/docs/img/enterprise/plugins/slack/slack.mp4" import SlackVideoWebm from "@version/docs/img/enterprise/plugins/slack/slack.webm" In this guide, you will set up Slack to receive Access Request messages from -Teleport. +Teleport. + +You can opt in to the [native Access Request reviews feature](slack-review.mdx) +in order to approve or deny Access Requests directly within Slack, avoiding a redirect +to the Teleport Web UI. Note that this feature reduces the security of the Teleport cluster +by relying on Slack authentication and bypassing Teleport authentication.
    This integration is hosted on Teleport Cloud @@ -448,6 +453,8 @@ $ sudo systemctl start teleport-slack ## Next steps +- Set up the [native Access Request reviews feature](slack-review.mdx) + that allows you to approve or deny Access Requests directly within Slack. - Read our guides to configuring [Resource Access Requests](../resource-requests.mdx) and [Role Access Requests](../role-requests.mdx) so you can get the most out diff --git a/docs/pages/includes/plugins/create-request.mdx b/docs/pages/includes/plugins/create-request.mdx index 77bf2afa9cf9..2a640efcf4e8 100644 --- a/docs/pages/includes/plugins/create-request.mdx +++ b/docs/pages/includes/plugins/create-request.mdx @@ -1,9 +1,11 @@ +{{ role="editor" }} + A Teleport admin can create an Access Request for another user with `tctl`: ```code - $ tctl request create myuser --roles=editor + $ tctl request create myuser --roles={{ role }} ``` @@ -11,7 +13,7 @@ Users can use `tsh` to create an Access Request and log in with approved roles: ```code - $ tsh request create --roles=editor + $ tsh request create --roles={{ role }} Seeking request approval... (id: 8f77d2d1-2bbf-4031-a300-58926237a807) ``` diff --git a/docs/pages/includes/plugins/rbac-impersonate-machine-id.mdx b/docs/pages/includes/plugins/rbac-impersonate-machine-id.mdx index 960257059df2..6926e0728647 100644 --- a/docs/pages/includes/plugins/rbac-impersonate-machine-id.mdx +++ b/docs/pages/includes/plugins/rbac-impersonate-machine-id.mdx @@ -1,10 +1,12 @@ +{{ role="access-plugin" }} + If you haven't set up a Machine ID bot yet, refer to the [deployment guide](../../machine-workload-identity/deployment/deployment.mdx) to run the `tbot` binary on your infrastructure. -Next, allow the Machine ID bot to generate credentials for the `access-plugin` +Next, allow the Machine ID bot to generate credentials for the `{{ role }}` role. You can do this using `tctl`, replacing `my-bot` with the name of your bot: ```code -$ tctl bots update my-bot --add-roles access-plugin +$ tctl bots update my-bot --add-roles {{ role }} ``` diff --git a/docs/pages/includes/plugins/rbac-impersonate.mdx b/docs/pages/includes/plugins/rbac-impersonate.mdx index b3284675f9f4..952b673bdde6 100644 --- a/docs/pages/includes/plugins/rbac-impersonate.mdx +++ b/docs/pages/includes/plugins/rbac-impersonate.mdx @@ -1,51 +1,53 @@ +{{ role="access-plugin" }} + As with all Teleport users, the Teleport Auth Service authenticates the -`access-plugin` user by issuing short-lived TLS credentials. In this case, we +`{{ role }}` user by issuing short-lived TLS credentials. In this case, we will need to request the credentials manually by *impersonating* the -`access-plugin` role and user. +`{{ role }}` role and user. If you are running a self-hosted Teleport Enterprise deployment and are using `tctl` from the Auth Service host, you will already have impersonation privileges. -To grant your user impersonation privileges for `access-plugin`, define a user -named `access-plugin` and a role named `access-plugin-impersonator` by adding -the following YAML document into a file called `access-plugin-impersonator.yaml`: +To grant your user impersonation privileges for `{{ role }}`, define a user +named `{{ role }}` and a role named `{{ role }}-impersonator` by adding +the following YAML document into a file called `{{ role }}-impersonator.yaml`: ```yaml kind: user metadata: - name: access-plugin + name: {{ role }} spec: - roles: ['access-plugin'] + roles: ['{{ role }}'] version: v2 --- kind: role version: v7 metadata: - name: access-plugin-impersonator + name: {{ role }}-impersonator spec: allow: impersonate: roles: - - access-plugin + - {{ role }} users: - - access-plugin + - {{ role }} ``` Create the user and role: ```code -$ tctl create -f access-plugin-impersonator.yaml -user "access-plugin" has been created -role "access-plugin-impersonator" has been created +$ tctl create -f {{ role }}-impersonator.yaml +user "{{ role }}" has been created +role "{{ role }}-impersonator" has been created ``` (!docs/pages/includes/create-role-using-web.mdx!) Assign this role to the user you plan to use to generate credentials for the -`access-plugin` role and user: +`{{ role }}` role and user: -(!docs/pages/includes/add-role-to-user.mdx role="access-plugin-impersonator"!) +(!docs/pages/includes/add-role-to-user.mdx role="{{ role }}-impersonator"!) -You will now be able to generate signed certificates for the `access-plugin` +You will now be able to generate signed certificates for the `{{ role }}` role and user. diff --git a/docs/pages/includes/plugins/slack-review-config.mdx b/docs/pages/includes/plugins/slack-review-config.mdx new file mode 100644 index 000000000000..eafe33e3e52b --- /dev/null +++ b/docs/pages/includes/plugins/slack-review-config.mdx @@ -0,0 +1,143 @@ + + + +On your local workstation, create a file called `teleport-slack.toml` +based on the following example: + +```toml +[teleport] +# Teleport Proxy/Auth Service address. +# +# Should be port 443 or 3080 for Proxy Service and port 3025 for Auth Service. +# For Teleport Cloud, should be in the form "your-account.teleport.sh:443". +addr = "" + +# Path to exported identity file. +# +# If providing credentials using `tbot` running on a Linux server, +# set this to `/opt/machine-id/identity`. +identity = "/identity" + +# Refresh identity file on a periodic basis. +refresh_identity = true + +[slack] +# Slack Bot OAuth token. +# +# Provides the plugin permissions to read Slack user info and write to channels. +# Set this up under "OAuth & Permissions" in your Slack app settings: +# https://api.slack.com/apps +# +# You can also use an absolute path to a token file, e.g., +# "/var/lib/teleport/plugins/slack/token" +token = "xoxb-your-bot-token" + +[review] +# Toggles native review feature. Default is false. +enabled = true + +# Slack App-level token. +# +# Provides the plugin permissions to receive user interactions from Slack. +# This is different from the Bot OAuth token, and must be set up separately. +# Set this up under "Basic Information" in your Slack app settings: +# https://api.slack.com/apps +# +# You can also use an absolute path to a token file, e.g., +# "/var/lib/teleport/plugins/slack/app-token" +app_token = "xapp-your-app-token" + +# Allows binding between Teleport username and Slack email. +# +# This should only be set to true if `review.slack_user_id_trait` is not configured in Teleport. +# We recommend configuring a trait for stronger identity binding. Default is false. +allow_email_username_match = + +# Name of the Teleport trait to bind Teleport user and Slack user. +# +# The trait should hold value of Slack user ID. +# If no trait is set up in Teleport, this should remain empty. +slack_user_id_trait = "" + +[role_to_recipients] +# Map roles to recipients. +# +# Provide slack user_email/channel recipients for access requests for specific roles. +# role.suggested_reviewers will automatically be treated as additional email recipients. +# "*" must be provided to match non-specified roles. +"*" = "" +"access" = [""] + +[log] +# Logger output. Could be "stdout", "stderr" or "/var/lib/teleport/slack.log" +output = "stdout" + +# Logger severity. Could be "INFO", "ERROR", "DEBUG" or "WARN". +severity = "INFO" +``` + + + + +On your local workstation, create a file called `teleport-plugin-slack-values.yaml` +based on the following example: + +```yaml +teleport: + # Teleport Proxy/Auth Service address. + # + # Should be port 443 or 3080 for Proxy Service and port 3025 for Auth Service. + # For Teleport Cloud, should be in the form "your-account.teleport.sh:443". + address: + # Name of the Kubernetes secret that contains the credentials for the connection + # to your Teleport cluster. + identitySecretName: teleport-plugin-slack-identity + identitySecretPath: identity + +slack: + # Slack Bot OAuth token. + # + # Provides the plugin permissions to read Slack user info and write to channels. + # Set this up under "OAuth & Permissions" in your Slack app settings: + # https://api.slack.com/apps + token: "xoxb-your-bot-token" + +review: + # Toggles native review feature. Default is false. + enabled: true + # Slack App-level token. + # + # Provides the plugin permissions to receive user interactions from Slack. + # This is different from the Bot OAuth token, and must be set up separately. + # Set this up under "Basic Information" in your Slack app settings: + # https://api.slack.com/apps + appToken: "xapp-your-app-token" + # Allows binding between Teleport username and Slack email. + # + # This should only be set to true if `review.slackUserIdTrait` is not configured in Teleport. + # We recommend configuring a trait for stronger identity binding. Default is false. + allowEmailUsernameMatch: + # Name of the Teleport trait to bind Teleport user and Slack user. + # + # The trait should hold value of Slack user ID. + # If no trait is set up in Teleport, this should remain empty. + slackUserIdTrait: + +# Map roles to recipients. +# +# Provide slack user_email/channel recipients for access requests for specific roles. +# role.suggested_reviewers will automatically be treated as additional email recipients. +# "*" must be provided to match non-specified roles. +roleToRecipients: + "*": "" + "access": [""] + +log: + # Logger output. Could be "stdout", "stderr" or "/var/lib/teleport/slack.log" + output: stdout + # Logger severity. Could be "INFO", "ERROR", "DEBUG" or "WARN". + severity: INFO +``` + + + From 34a1ebbeef4ad727640fc817ecc4fd9c9da62c78 Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 31 Jul 2026 17:08:00 -0400 Subject: [PATCH 092/347] [Access Requests] Tolerate unknown constraint kinds when decoding ResourceAccessIDs (#68785) * feat: tolerate unknown resource constraint kinds Certs carrying constraint kinds unknown to the current build previously failed the whole identity decode, and stored access requests carrying them failed reads outright. Unknown content now degrades to a non-nil Constraints with nil Details. Enforcement denies only the affected resource, re-encoding preserves the marker, and stored requests stay readable. Creation and storage writes remain strict, keeping the marker from overwriting newer constraint content in the backend. * refac: Address review feedback --- api/types/resource_constraints.go | 41 +++++- api/types/resource_ids.go | 12 +- api/types/resource_ids_test.go | 193 ++++++++++++++++++++++++++++ lib/services/access_checker.go | 27 +++- lib/services/access_checker_test.go | 102 +++++++++++++++ lib/services/access_request.go | 25 +++- lib/services/access_request_test.go | 59 +++++++++ lib/sshca/identity_test.go | 51 ++++++++ lib/tlsca/ca_test.go | 61 +++++++++ 9 files changed, 558 insertions(+), 13 deletions(-) diff --git a/api/types/resource_constraints.go b/api/types/resource_constraints.go index 62ebd8d28b19..3c495b8d4859 100644 --- a/api/types/resource_constraints.go +++ b/api/types/resource_constraints.go @@ -18,6 +18,7 @@ package types import ( "bytes" + "encoding/json" "github.com/gogo/protobuf/jsonpb" //nolint:depguard // needed for backwards compatibility "github.com/gravitational/trace" @@ -42,6 +43,8 @@ func (rc *ResourceConstraints) CheckAndSetDefaults() error { if err := d.Validate(); err != nil { return trace.Wrap(err) } + case nil: + return trace.BadParameter("constraints carry no supported content; they are either empty or from a newer Teleport version") default: return trace.BadParameter("unsupported Details type %T", d) } @@ -65,10 +68,44 @@ func (rc *ResourceConstraints) MarshalJSON() ([]byte, error) { } func (rc *ResourceConstraints) UnmarshalJSON(b []byte) error { - u := &jsonpb.Unmarshaler{ + strict := &jsonpb.Unmarshaler{ AllowUnknownFields: false, } - return trace.Wrap(u.Unmarshal(bytes.NewReader(b), rc)) + if err := strict.Unmarshal(bytes.NewReader(b), rc); err != nil { + // Content that doesn't strictly decode: an unknown kind, or an + // unknown field in a known one. + return trace.Wrap(rc.unmarshalVersionOnly(b, err)) + } + if rc.Version != "" && rc.Version != ResourceConstraintVersionV1 { + // Fields all decoded, but the version is newer than this build + // understands. Keep only the version; the entry becomes + // unenforceable. + *rc = ResourceConstraints{Version: rc.Version} + } + return nil +} + +// unmarshalVersionOnly resets rc to carry only the version found in b, +// leaving it unenforceable for enforcement to deny. If b yields no version +// at all, rc is zeroed and strictErr is returned so the caller doesn't use +// a half-parsed value. +func (rc *ResourceConstraints) unmarshalVersionOnly(b []byte, strictErr error) error { + var v struct { + Version string `json:"version"` + } + if err := json.Unmarshal(b, &v); err != nil { + *rc = ResourceConstraints{} + return strictErr + } + *rc = ResourceConstraints{Version: v.Version} + return nil +} + +// Unenforceable reports whether the constraints carry content this build +// cannot decode or validate. Such entries must deny their resource at +// enforcement rather than being treated as unconstrained. +func (rc *ResourceConstraints) Unenforceable() bool { + return rc != nil && rc.Details == nil } // Validate ensures RoleArns is non-nil and contains Role ARNs. diff --git a/api/types/resource_ids.go b/api/types/resource_ids.go index 0ec893bbdb9b..d397c6a8fc56 100644 --- a/api/types/resource_ids.go +++ b/api/types/resource_ids.go @@ -91,8 +91,16 @@ func (idl *ResourceAccessIDList) CheckAndSetDefaults() error { } if rc == nil { continue - } else if err := rc.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) + } + if rc.Unenforceable() { + // Keep the entry for enforcement to deny; erroring here would + // reject the whole identity. + continue + } + if err := rc.CheckAndSetDefaults(); err != nil { + // Treat invalid content like content we can't decode. + rc.Details = nil + continue } } return nil diff --git a/api/types/resource_ids_test.go b/api/types/resource_ids_test.go index 807ea06073fe..37ade56aed38 100644 --- a/api/types/resource_ids_test.go +++ b/api/types/resource_ids_test.go @@ -17,6 +17,7 @@ limitations under the License. package types import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -801,6 +802,113 @@ func TestLegacyKubeResourceIDs(t *testing.T) { } } +// TestResourceConstraintsDecodeDegradesUnknown verifies that constraint +// content this build can't strictly decode leaves a non-nil +// ResourceConstraints with nil Details. +func TestResourceConstraintsDecodeDegradesUnknown(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + wantVersion string + }{ + { + name: "unknown kind", + input: `{"version":"v1","some_future_kind":{"names":["a"]}}`, + wantVersion: "v1", + }, + { + name: "unknown field inside known kind", + input: `{"version":"v1","ssh":{"logins":["root"],"future_narrowing":["x"]}}`, + wantVersion: "v1", + }, + { + name: "unknown sibling field", + input: `{"version":"v1","ssh":{"logins":["root"]},"future_field":true}`, + wantVersion: "v1", + }, + { + name: "type mismatch inside known kind", + input: `{"version":"v1","ssh":{"logins":[123]}}`, + wantVersion: "v1", + }, + { + name: "unsupported version with known kind", + input: `{"version":"v2","ssh":{"logins":["root"]}}`, + wantVersion: "v2", + }, + { + name: "unsupported version alone", + input: `{"version":"v2"}`, + wantVersion: "v2", + }, + { + name: "unknown kind without version", + input: `{"some_future_kind":{"names":["a"]}}`, + wantVersion: "", + }, + { + name: "empty object", + input: `{}`, + wantVersion: "", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var rc ResourceConstraints + require.NoError(t, rc.UnmarshalJSON([]byte(tc.input))) + require.Equal(t, tc.wantVersion, rc.Version) + require.Nil(t, rc.Details, "unrecognized content must not decode to enforceable Details") + require.Error(t, rc.CheckAndSetDefaults()) + }) + } + + t.Run("valid current-version content decodes fully", func(t *testing.T) { + var rc ResourceConstraints + require.NoError(t, rc.UnmarshalJSON([]byte(`{"version":"v1","ssh":{"logins":["root"]}}`))) + require.NotNil(t, rc.Details) + require.NoError(t, rc.CheckAndSetDefaults()) + }) + + t.Run("malformed content errors at decode", func(t *testing.T) { + for _, input := range []string{`not json`, `{"version":42}`, `[]`} { + var rc ResourceConstraints + require.Error(t, rc.UnmarshalJSON([]byte(input)), "input: %s", input) + } + }) +} + +// TestResourceAccessIDsInvalidKnownKindDegrades verifies that decodable but +// invalid constraint content of a known kind is kept with nil Details. +func TestResourceAccessIDsInvalidKnownKindDegrades(t *testing.T) { + t.Parallel() + + ids := []ResourceAccessID{ + { + Id: ResourceID{ClusterName: "root", Kind: KindNode, Name: "n1"}, + Constraints: &ResourceConstraints{ + Version: "v1", + Details: &ResourceConstraints_Ssh{ + Ssh: &SSHResourceConstraints{Logins: []string{"root"}}, + }, + }, + }, + } + encoded, err := ResourceAccessIDsToString(ids) + require.NoError(t, err) + require.Contains(t, encoded, `"logins":["root"]`) + invalid := strings.Replace(encoded, `"logins":["root"]`, "", 1) + + decoded, err := ResourceAccessIDsFromString(invalid) + require.NoError(t, err) + require.Len(t, decoded, 1) + rc := decoded[0].GetConstraints() + require.NotNil(t, rc, "constraints must survive decoding") + require.Nil(t, rc.Details) + require.Equal(t, "v1", rc.Version) +} + func TestResourceIDsToResourceAccessIDs(t *testing.T) { t.Parallel() @@ -935,3 +1043,88 @@ func TestRiskyExtractResourceIDs(t *testing.T) { require.Empty(t, RiskyExtractResourceIDs(nil)) } + +// TestResourceAccessIDsNewerConstraintContent verifies that constraint +// content from a newer Auth (an unknown kind, or an unsupported version) +// decodes to an entry with nil Details instead of failing the whole +// list, and that the nil Details survive re-encoding. +func TestResourceAccessIDsNewerConstraintContent(t *testing.T) { + t.Parallel() + + ids := []ResourceAccessID{ + { + Id: ResourceID{ClusterName: "root", Kind: KindNode, Name: "n1"}, + }, + { + Id: ResourceID{ClusterName: "root", Kind: KindApp, Name: "aws-console"}, + Constraints: &ResourceConstraints{ + Version: "v1", + Details: &ResourceConstraints_AwsConsole{ + AwsConsole: &AWSConsoleResourceConstraints{RoleArns: []string{"arn:aws:iam::123:role/Allowed"}}, + }, + }, + }, + } + encoded, err := ResourceAccessIDsToString(ids) + require.NoError(t, err) + + tests := []struct { + name string + from, to string + wantVersion string + }{ + { + name: "unknown kind", + from: `"aws_console"`, + to: `"some_future_kind"`, + wantVersion: "v1", + }, + { + name: "unsupported version", + from: `"version":"v1"`, + to: `"version":"v2"`, + wantVersion: "v2", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Simulate the same cert created by a newer version. + require.Contains(t, encoded, tc.from) + fromNewer := strings.Replace(encoded, tc.from, tc.to, 1) + + decoded, err := ResourceAccessIDsFromString(fromNewer) + require.NoError(t, err) + require.Len(t, decoded, 2) + + // The plain entry is untouched. + require.Equal(t, ids[0].Id, decoded[0].Id) + require.Nil(t, decoded[0].GetConstraints()) + + rc := decoded[1].GetConstraints() + require.NotNil(t, rc, "constraints must survive decoding") + require.Nil(t, rc.Details) + require.Equal(t, tc.wantVersion, rc.Version) + + require.Error(t, rc.CheckAndSetDefaults()) + + // The entry must stay in the constrained set; re-encoding it + // into the legacy AllowedResourceIDs extension would read as + // unconstrained access on builds without constraint support. + plainIDs, constrainedIDs := UnwrapResourceAccessIDs(decoded) + require.Len(t, plainIDs, 1) + require.Equal(t, ids[0].Id, plainIDs[0]) + require.Len(t, constrainedIDs, 1) + require.NotNil(t, constrainedIDs[0].GetConstraints()) + + reencoded, err := ResourceAccessIDsToString(decoded) + require.NoError(t, err) + roundTripped, err := ResourceAccessIDsFromString(reencoded) + require.NoError(t, err) + require.Len(t, roundTripped, 2) + rt := roundTripped[1].GetConstraints() + require.NotNil(t, rt) + require.Nil(t, rt.Details) + require.Equal(t, tc.wantVersion, rt.Version) + }) + } +} diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index 15c2bc91ad2a..a7e153ab35df 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -502,17 +502,34 @@ func (a *accessChecker) checkAllowedResources(r AccessCheckable) (allowedResourc ctx := context.Background() isLoggingEnabled := rbacLogger.Enabled(ctx, logutils.TraceLevel) + var match *types.ResourceAccessID for _, resourceID := range a.info.AllowedResourceAccessIDs { - if id := resourceID.GetResourceID(); id.ClusterName == a.localCluster && matchesUCRResource(resourceID, r) { - // Allowed to access this resource by resource ID, move on to role checks. + id := resourceID.GetResourceID() + if id.ClusterName != a.localCluster || !matchesUCRResource(resourceID, r) { + continue + } + if resourceID.GetConstraints().Unenforceable() { if isLoggingEnabled { - rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Matched allowed resource ID", + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access denied, matched resource ID carries constraints this component cannot enforce", slog.String("resource_id", types.ResourceIDToString(id)), ) } - - return allowedResourceMatch{&resourceID}, nil + return allowedResourceMatch{}, trace.AccessDenied( + "access to %v %+q denied because it carries constraints this component cannot enforce; they may have been created by a newer Teleport version", + r.GetKind(), r.GetName()) + } + if match == nil { + match = &resourceID + } + } + if match != nil { + // Allowed to access this resource by resource ID, move on to role checks. + if isLoggingEnabled { + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Matched allowed resource ID", + slog.String("resource_id", types.ResourceIDToString(match.GetResourceID())), + ) } + return allowedResourceMatch{match}, nil } if isLoggingEnabled { diff --git a/lib/services/access_checker_test.go b/lib/services/access_checker_test.go index cbce90232378..43b4ff240afc 100644 --- a/lib/services/access_checker_test.go +++ b/lib/services/access_checker_test.go @@ -1555,6 +1555,108 @@ func TestAccessChecker_Constraints_AwsConsole(t *testing.T) { require.NoError(t, err) } +// TestAccessChecker_Constraints_UnknownKind verifies that nil-Details +// constraints deny their resource even when no role matchers are passed. +func TestAccessChecker_Constraints_UnknownKind(t *testing.T) { + const localCluster = "cluster" + + role := newRole(func(rv *types.RoleV6) { + rv.Spec.Allow.AppLabels = types.Labels{types.Wildcard: {types.Wildcard}} + rv.Spec.Allow.Namespaces = []string{types.Wildcard} + }) + + newApp := func(name string) *types.AppServerV3 { + return &types.AppServerV3{ + Kind: types.KindApp, + Metadata: types.Metadata{ + Name: name, + }, + } + } + appRID := func(name string, rc *types.ResourceConstraints) types.ResourceAccessID { + return types.ResourceAccessID{ + Id: types.ResourceID{ + ClusterName: localCluster, + Kind: types.KindApp, + Name: name, + }, + Constraints: rc, + } + } + unknownConstraints := func() *types.ResourceConstraints { + return &types.ResourceConstraints{Version: "v1"} + } + + info := &AccessInfo{AllowedResourceAccessIDs: []types.ResourceAccessID{ + appRID("constrained-app", unknownConstraints()), + appRID("plain-app", nil), + // Duplicate entries for one resource; must deny regardless of order. + appRID("shadowed-app", nil), + appRID("shadowed-app", unknownConstraints()), + }} + + ac := NewAccessCheckerWithRoleSet(info, localCluster, NewRoleSet(role)) + state := AccessState{MFARequired: MFARequiredNever} + + // No matchers, as when the app service authorizes a plain web app. + err := ac.CheckAccess(newApp("constrained-app"), state) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied, got %v", err) + + // Passing a matcher must not change the outcome. + err = ac.CheckAccess(newApp("constrained-app"), state, NewLoginMatcher("root")) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied, got %v", err) + + // A clean duplicate entry must not shadow the constrained one. + err = ac.CheckAccess(newApp("shadowed-app"), state) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied, got %v", err) + + // The rest of the identity keeps working. + require.NoError(t, ac.CheckAccess(newApp("plain-app"), state)) +} + +// TestAccessChecker_Constraints_UnknownKind_Kube verifies that a nil-Details +// pod entry denies cluster access even alongside a clean pod entry. +func TestAccessChecker_Constraints_UnknownKind_Kube(t *testing.T) { + const localCluster = "cluster" + + role := newRole(func(rv *types.RoleV6) { + rv.Spec.Allow.KubernetesLabels = types.Labels{types.Wildcard: {types.Wildcard}} + rv.Spec.Allow.Namespaces = []string{types.Wildcard} + }) + + kubeCluster, err := types.NewKubernetesClusterV3(types.Metadata{Name: "kc"}, types.KubernetesClusterSpecV3{}) + require.NoError(t, err) + + podRID := func(pod string, rc *types.ResourceConstraints) types.ResourceAccessID { + return types.ResourceAccessID{ + Id: types.ResourceID{ + ClusterName: localCluster, + Kind: types.KindKubePod, + Name: "kc", + SubResourceName: pod, + }, + Constraints: rc, + } + } + state := AccessState{MFARequired: MFARequiredNever} + + // Clean entry first, constrained entry second; still denied. + info := &AccessInfo{AllowedResourceAccessIDs: []types.ResourceAccessID{ + podRID("ns/pod-a", nil), + podRID("ns/pod-b", &types.ResourceConstraints{Version: "v1"}), + }} + ac := NewAccessCheckerWithRoleSet(info, localCluster, NewRoleSet(role)) + err = ac.CheckAccess(kubeCluster, state) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied, got %v", err) + + // Control: clean entries alone allow cluster access. + cleanInfo := &AccessInfo{AllowedResourceAccessIDs: []types.ResourceAccessID{ + podRID("ns/pod-a", nil), + }} + cleanAC := NewAccessCheckerWithRoleSet(cleanInfo, localCluster, NewRoleSet(role)) + require.NoError(t, cleanAC.CheckAccess(kubeCluster, state)) +} + // TestUserSessionRoleNotFoundError ensures that role not found errors during user session access checks include UserSessionRoleNotFoundErrorMsg when appropriate, func TestUserSessionRoleNotFoundError(t *testing.T) { // Create a mock RoleGetter that returns "role not found" error for a specific role diff --git a/lib/services/access_request.go b/lib/services/access_request.go index 763a2bd3c43d..465dfd7dddb8 100644 --- a/lib/services/access_request.go +++ b/lib/services/access_request.go @@ -77,6 +77,14 @@ const ( // ValidateAccessRequest validates the AccessRequest and sets default values func ValidateAccessRequest(ar types.AccessRequest) error { + return validateAccessRequest(ar, false) +} + +// validateAccessRequest implements [ValidateAccessRequest]. With +// allowUnenforceable set, unenforceable constraints (see +// [types.ResourceConstraints.Unenforceable]) pass validation, keeping +// requests written by newer Auths readable. +func validateAccessRequest(ar types.AccessRequest, allowUnenforceable bool) error { if err := CheckAndSetDefaults(ar); err != nil { return trace.Wrap(err) } @@ -96,14 +104,19 @@ func ValidateAccessRequest(ar types.AccessRequest) error { } for _, r := range ar.GetRequestedResourceAccessIDs() { - if r.GetConstraints() == nil { + rc := r.GetConstraints() + if rc == nil { + continue + } + if allowUnenforceable && rc.Unenforceable() { + // Skip all validation; these may carry a newer version. continue } - if err := r.GetConstraints().CheckAndSetDefaults(); err != nil { + if err := rc.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } kind := r.GetResourceID().Kind - switch c := r.GetConstraints().Details.(type) { + switch c := rc.Details.(type) { case *types.ResourceConstraints_AwsConsole: if kind != types.KindApp { return trace.BadParameter("aws_console constraints are not valid for resource kind %q", kind) @@ -2357,7 +2370,8 @@ func UnmarshalAccessRequest(data []byte, opts ...MarshalOption) (*types.AccessRe if err := utils.FastUnmarshal(data, &req); err != nil { return nil, trace.Wrap(err) } - if err := ValidateAccessRequest(&req); err != nil { + // Requests written by newer Auths must stay readable. + if err := validateAccessRequest(&req, true); err != nil { return nil, trace.Wrap(err) } if cfg.Revision != "" { @@ -2371,6 +2385,9 @@ func UnmarshalAccessRequest(data []byte, opts ...MarshalOption) (*types.AccessRe // MarshalAccessRequest marshals the AccessRequest resource to JSON. func MarshalAccessRequest(accessRequest types.AccessRequest, opts ...MarshalOption) ([]byte, error) { + // Writes stay strict; re-persisting a request whose constraints + // this build couldn't decode would overwrite the newer content in + // the backend. if err := ValidateAccessRequest(accessRequest); err != nil { return nil, trace.Wrap(err) } diff --git a/lib/services/access_request_test.go b/lib/services/access_request_test.go index d3492f5f4a40..78b25029a939 100644 --- a/lib/services/access_request_test.go +++ b/lib/services/access_request_test.go @@ -3506,6 +3506,65 @@ func TestValidate_RequestedMaxDuration(t *testing.T) { } } +// TestAccessRequestStorageToleratesUnenforceableConstraints verifies the +// storage codec against a request written by a newer Auth: reads keep +// working with the unknown constraint content zeroed to nil Details, +// while writes and creation stay strict. +func TestAccessRequestStorageToleratesUnenforceableConstraints(t *testing.T) { + t.Parallel() + + req, err := types.NewAccessRequestWithResources( + uuid.New().String(), "some-user", nil, /* roles */ + []types.ResourceAccessID{ + { + Id: types.ResourceID{ClusterName: "root", Kind: types.KindNode, Name: "n1"}, + }, + { + Id: types.ResourceID{ClusterName: "root", Kind: types.KindApp, Name: "aws-console"}, + Constraints: &types.ResourceConstraints{ + Version: "v1", + Details: &types.ResourceConstraints_AwsConsole{ + AwsConsole: &types.AWSConsoleResourceConstraints{RoleArns: []string{"arn:aws:iam::123:role/Allowed"}}, + }, + }, + }, + }) + require.NoError(t, err) + + stored, err := MarshalAccessRequest(req) + require.NoError(t, err) + + // Simulate the same request written by a newer Auth. + require.Contains(t, string(stored), `"aws_console"`) + fromNewer := []byte(strings.Replace(string(stored), `"aws_console"`, `"some_future_kind"`, 1)) + + decoded, err := UnmarshalAccessRequest(fromNewer) + require.NoError(t, err) + // The unconstrained node is unwrapped into RequestedResourceIDs at + // construction; only the constrained entry carries access IDs. + accessIDs := decoded.GetRequestedResourceAccessIDs() + require.Len(t, accessIDs, 1) + rc := accessIDs[0].GetConstraints() + require.NotNil(t, rc, "constraints must survive decoding") + require.Nil(t, rc.Details) + require.Equal(t, "v1", rc.Version) + + // Creation stays strict. + require.Error(t, ValidateAccessRequest(decoded)) + + // Writes stay strict; re-persisting would overwrite the newer content. + _, err = MarshalAccessRequest(decoded) + require.Error(t, err) + + // The stored request still reads. + roundTripped, err := UnmarshalAccessRequest(fromNewer) + require.NoError(t, err) + rt := roundTripped.GetRequestedResourceAccessIDs()[0].GetConstraints() + require.NotNil(t, rt) + require.Nil(t, rt.Details) + require.Equal(t, "v1", rt.Version) +} + func TestValidateAccessRequest_ExpandsUserNameAnnotations(t *testing.T) { ctx := t.Context() clock := clockwork.NewFakeClock() diff --git a/lib/sshca/identity_test.go b/lib/sshca/identity_test.go index 6b11fd7e2995..d30224f2cca9 100644 --- a/lib/sshca/identity_test.go +++ b/lib/sshca/identity_test.go @@ -20,6 +20,7 @@ package sshca import ( + "strings" "testing" "time" @@ -323,3 +324,53 @@ func TestAllowedResources_SSHEncodeDecode(t *testing.T) { }) } } + +// TestAllowedResources_SSHUnknownConstraintKindDecodes verifies that a cert +// from a newer Auth with a constraint kind this build doesn't understand +// still decodes, keeping the unknown content as an unenforceable entry. +func TestAllowedResources_SSHUnknownConstraintKindDecodes(t *testing.T) { + plainNode := types.ResourceID{ClusterName: "cluster", Kind: types.KindNode, Name: "prod-node"} + constrainedApp := types.ResourceAccessID{ + Id: types.ResourceID{ClusterName: "cluster", Kind: types.KindApp, Name: "aws-console"}, + Constraints: &types.ResourceConstraints{ + Version: types.V1, + Details: &types.ResourceConstraints_AwsConsole{ + AwsConsole: &types.AWSConsoleResourceConstraints{ + RoleArns: []string{"arn:aws:iam::123456789012:role/DevOps"}, + }, + }, + }, + } + + ident := &Identity{ + ValidBefore: uint64(time.Now().Add(time.Hour).Unix()), + CertType: ssh.UserCert, + Username: "test-user", + Roles: []string{"access"}, + AllowedResourceAccessIDs: append(types.ResourceIDsToResourceAccessIDs([]types.ResourceID{plainNode}), constrainedApp), + } + + cert, err := ident.Encode(constants.CertificateFormatStandard) + require.NoError(t, err) + + ext, ok := cert.Permissions.Extensions[teleport.CertExtensionAllowedResourceAccessIDs] + require.True(t, ok, "AllowedResourceAccessIDs extension not found") + require.Contains(t, ext, `"aws_console"`) + cert.Permissions.Extensions[teleport.CertExtensionAllowedResourceAccessIDs] = strings.Replace(ext, `"aws_console"`, `"some_future_kind"`, 1) + + decoded, err := DecodeIdentity(cert) + require.NoError(t, err) + + byName := map[string]types.ResourceAccessID{} + for _, r := range decoded.AllowedResourceAccessIDs { + byName[r.GetResourceID().Name] = r + } + require.Len(t, byName, 2) + require.Contains(t, byName, "prod-node") + require.Nil(t, byName["prod-node"].Constraints) + + rc := byName["aws-console"].Constraints + require.NotNil(t, rc, "constraints must survive decoding") + require.Nil(t, rc.Details) + require.Equal(t, types.V1, rc.Version) +} diff --git a/lib/tlsca/ca_test.go b/lib/tlsca/ca_test.go index 56bc60948eea..f378d087e36f 100644 --- a/lib/tlsca/ca_test.go +++ b/lib/tlsca/ca_test.go @@ -24,6 +24,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "strings" "testing" "time" @@ -605,6 +606,66 @@ func TestAllowedResources_EncodeDecodeCycle(t *testing.T) { } } +// TestAllowedResources_UnknownConstraintKindDecodes verifies that a cert +// minted by a newer Auth with a constraint kind this build does not know +// still decodes, keeping the unknown content as an unenforceable entry. +func TestAllowedResources_UnknownConstraintKindDecodes(t *testing.T) { + plainNode := types.ResourceID{ClusterName: "cluster", Kind: types.KindNode, Name: "prod-node"} + constrainedApp := types.ResourceAccessID{ + Id: types.ResourceID{ClusterName: "cluster", Kind: types.KindApp, Name: "aws-console"}, + Constraints: &types.ResourceConstraints{ + Version: types.V1, + Details: &types.ResourceConstraints_AwsConsole{ + AwsConsole: &types.AWSConsoleResourceConstraints{ + RoleArns: []string{"arn:aws:iam::123456789012:role/DevOps"}, + }, + }, + }, + } + + identity := &Identity{ + Username: "test-user", + Groups: []string{"access"}, + AllowedResourceAccessIDs: append(types.ResourceIDsToResourceAccessIDs([]types.ResourceID{plainNode}), constrainedApp), + } + + subj, err := identity.Subject() + require.NoError(t, err) + + // Simulate the same cert minted by a newer Auth. + tampered := false + for i, name := range subj.ExtraNames { + if !name.Type.Equal(AllowedResourceAccessIDsASN1ExtensionOID) { + continue + } + val, ok := name.Value.(string) + require.True(t, ok) + require.Contains(t, val, `"aws_console"`) + subj.ExtraNames[i].Value = strings.Replace(val, `"aws_console"`, `"some_future_kind"`, 1) + tampered = true + } + require.True(t, tampered, "AllowedResourceAccessIDs extension not found") + + subj.Names = append(subj.Names, subj.ExtraNames...) + subj.ExtraNames = nil + + decoded, err := FromSubject(subj, time.Time{}) + require.NoError(t, err) + + byName := map[string]types.ResourceAccessID{} + for _, r := range decoded.AllowedResourceAccessIDs { + byName[r.GetResourceID().Name] = r + } + require.Len(t, byName, 2) + require.Contains(t, byName, "prod-node") + require.Nil(t, byName["prod-node"].Constraints) + + rc := byName["aws-console"].Constraints + require.NotNil(t, rc, "constraints must survive decoding") + require.Nil(t, rc.Details) + require.Equal(t, types.V1, rc.Version) +} + // legacyExtensionIDs extracts ResourceIDs from the legacy AllowedResources // extension (OID 1.3.9999.2.10) in a pkix.Name subject. This is what an // old agent would parse from the cert. From bffd668ba93d2b8cc8e4da13b346066c3b949ae5 Mon Sep 17 00:00:00 2001 From: Aleksei Kozadaev Date: Fri, 31 Jul 2026 22:58:07 +0100 Subject: [PATCH 093/347] Do not follow URLs when getting/updating SAML connectors in tctl (#69092) --- tool/tctl/common/resource_command_test.go | 124 +++++++++++++++++++ tool/tctl/common/resources/saml_connector.go | 10 +- tool/tctl/common/saml_command.go | 4 +- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index c82d9a58b353..578905bda235 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -23,9 +23,13 @@ import ( "context" "encoding/json" "fmt" + "io" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" + "sync/atomic" "testing" "time" @@ -2856,6 +2860,126 @@ version: v1 require.Empty(t, cmp.Diff(databaseobject.ResourceToProto(&expected), databaseobject.ResourceToProto(&resources[0]), cmpOpts...)) } +func TestSAMLCommandsWithUnavailableMetadata(t *testing.T) { + const ( + connectorName = "unavailable-metadata" + metadata = ` + + + + +` + ) + + process, err := testenv.NewTeleportProcess(t.TempDir(), testenv.WithConfig(func(cfg *servicecfg.Config) { + cfg.Modules = &modulestest.Modules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ + entitlements.SAML: {Enabled: true}, + }, + }, + } + })) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, process.Close()) + require.NoError(t, process.Wait()) + }) + + clt, err := testenv.NewDefaultAuthClient(process) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, clt.Close()) }) + + var unavailable atomic.Bool + var metadataRequests atomic.Int64 + metadataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + metadataRequests.Add(1) + if unavailable.Load() { + http.Error(w, "metadata unavailable", http.StatusNotFound) + return + } + _, _ = io.WriteString(w, metadata) + })) + t.Cleanup(metadataServer.Close) + + connector, err := types.NewSAMLConnector(connectorName, types.SAMLConnectorSpecV2{ + Display: "Unavailable metadata", + AssertionConsumerService: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + Audience: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + ServiceProviderIssuer: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + EntityDescriptorURL: metadataServer.URL, + AttributesToRoles: []types.AttributeMapping{ + {Name: "groups", Value: "developers", Roles: []string{"access"}}, + }, + }) + require.NoError(t, err) + _, err = clt.CreateSAMLConnector(t.Context(), connector) + require.NoError(t, err) + + unavailable.Store(true) + + t.Run("list", func(t *testing.T) { + requestCount := metadataRequests.Load() + out, err := runResourceCommand(t, clt, []string{"get", types.KindSAMLConnector, "--format=json"}) + require.NoError(t, err) + require.Contains(t, out.String(), connectorName) + require.Equal(t, requestCount, metadataRequests.Load(), "tctl command followed the unavailable metadata URL") + }) + + t.Run("get", func(t *testing.T) { + requestCount := metadataRequests.Load() + out, err := runResourceCommand(t, clt, []string{"get", types.KindSAMLConnector + "/" + connectorName, "--format=json"}) + require.NoError(t, err) + require.Contains(t, out.String(), connectorName) + require.Equal(t, requestCount, metadataRequests.Load(), "tctl command followed the unavailable metadata URL") + }) + + t.Run("export", func(t *testing.T) { + requestCount := metadataRequests.Load() + var exportErr error + var closeErr error + stdoutReader, stdoutWriter, err := os.Pipe() + require.NoError(t, err) + previousStdout := os.Stdout + os.Stdout = stdoutWriter + func() { + defer func() { os.Stdout = previousStdout }() + exportErr = (&SAMLCommand{connectorName: connectorName}).export(t.Context(), clt) + closeErr = stdoutWriter.Close() + }() + require.NoError(t, exportErr) + require.NoError(t, closeErr) + exportedCert, err := io.ReadAll(stdoutReader) + require.NoError(t, err) + require.NoError(t, stdoutReader.Close()) + require.Contains(t, string(exportedCert), "BEGIN CERTIFICATE") + require.Equal(t, requestCount, metadataRequests.Load(), "tctl command followed the unavailable metadata URL") + }) + + t.Run("force replace", func(t *testing.T) { + requestCount := metadataRequests.Load() + replacement, err := types.NewSAMLConnector(connectorName, types.SAMLConnectorSpecV2{ + Display: "Replacement", + AssertionConsumerService: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + Audience: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + ServiceProviderIssuer: "https://proxy.example.com/v1/webapi/saml/acs/" + connectorName, + EntityDescriptor: metadata, + AttributesToRoles: []types.AttributeMapping{ + {Name: "groups", Value: "developers", Roles: []string{"access"}}, + }, + }) + require.NoError(t, err) + replacementYAML, err := services.MarshalSAMLConnector(replacement) + require.NoError(t, err) + replacementPath := filepath.Join(t.TempDir(), "replacement.yaml") + require.NoError(t, os.WriteFile(replacementPath, replacementYAML, 0o600)) + _, err = runResourceCommand(t, clt, []string{"create", "-f", replacementPath}) + require.NoError(t, err) + require.Equal(t, requestCount, metadataRequests.Load(), "tctl command followed the unavailable metadata URL") + }) +} + // TestCreateEnterpriseResources asserts that tctl create // behaves as expected for enterprise resources. The tests are // grouped to amortize the cost of creating and auth server since diff --git a/tool/tctl/common/resources/saml_connector.go b/tool/tctl/common/resources/saml_connector.go index 4b60280da4fa..62847b22d40b 100644 --- a/tool/tctl/common/resources/saml_connector.go +++ b/tool/tctl/common/resources/saml_connector.go @@ -70,11 +70,11 @@ func getSAMLConnector(ctx context.Context, client *authclient.Client, ref servic // TODO(okraport): DELETE IN v21.0.0, remove GetSAMLConnectors connectors, err := clientutils.CollectWithFallback(ctx, func(ctx context.Context, limit int, start string) ([]types.SAMLConnector, string, error) { - return client.ListSAMLConnectorsWithOptions(ctx, limit, start, opts.WithSecrets) + return client.ListSAMLConnectorsWithOptions(ctx, limit, start, opts.WithSecrets, types.SAMLConnectorValidationFollowURLs(false)) }, func(ctx context.Context) ([]types.SAMLConnector, error) { //nolint:staticcheck // support older backends during migration - return client.GetSAMLConnectors(ctx, opts.WithSecrets) + return client.GetSAMLConnectorsWithValidationOptions(ctx, opts.WithSecrets, types.SAMLConnectorValidationFollowURLs(false)) }, ) @@ -83,7 +83,8 @@ func getSAMLConnector(ctx context.Context, client *authclient.Client, ref servic } return &samlConnectorCollection{connectors: connectors}, nil } - connector, err := client.GetSAMLConnector(ctx, ref.Name, opts.WithSecrets) + connector, err := client.GetSAMLConnectorWithValidationOptions(ctx, ref.Name, + opts.WithSecrets, types.SAMLConnectorValidationFollowURLs(false)) if err != nil { return nil, trace.Wrap(err) } @@ -101,7 +102,8 @@ func createSAMLConnector(ctx context.Context, client *authclient.Client, raw ser } connectorName := conn.GetName() - foundConn, err := client.GetSAMLConnector(ctx, connectorName, true) + foundConn, err := client.GetSAMLConnectorWithValidationOptions(ctx, connectorName, true, + types.SAMLConnectorValidationFollowURLs(false)) if err != nil && !trace.IsNotFound(err) { return trace.Wrap(err) } diff --git a/tool/tctl/common/saml_command.go b/tool/tctl/common/saml_command.go index 7500dcd21ad7..8ffebfa4cd0f 100644 --- a/tool/tctl/common/saml_command.go +++ b/tool/tctl/common/saml_command.go @@ -25,6 +25,7 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/service/servicecfg" commonclient "github.com/gravitational/teleport/tool/tctl/common/client" @@ -67,7 +68,8 @@ func (cmd *SAMLCommand) TryRun(ctx context.Context, selectedCommand string, clie // export executes 'tctl saml export ' func (cmd *SAMLCommand) export(ctx context.Context, c *authclient.Client) error { - sc, err := c.GetSAMLConnector(ctx, cmd.connectorName, false) + sc, err := c.GetSAMLConnectorWithValidationOptions(ctx, cmd.connectorName, false, + types.SAMLConnectorValidationFollowURLs(false)) if err != nil { return trace.Wrap(err) } From 53c69c3536f555fba44c552298bc3afd853048d6 Mon Sep 17 00:00:00 2001 From: Gavin Frazar Date: Fri, 31 Jul 2026 17:58:38 -0700 Subject: [PATCH 094/347] Add ECS database agent Terraform module (#68960) --- .../terraform-modules/gen/update-version.sh | 9 +- .../teleport/container-service/aws/README.md | 24 +- .../aws/aws_cloudwatch_log_group.tf | 12 +- .../container-service/aws/aws_ecs_cluster.tf | 9 +- .../aws/aws_ecs_task_definition.tf | 22 +- .../aws/aws_task_execution_role.tf | 10 +- .../container-service/aws/aws_task_role.tf | 72 ++++- .../teleport/container-service/aws/outputs.tf | 35 +++ .../teleport/container-service/aws/random.tf | 9 + .../container-service/aws/variables.tf | 51 +++- .../container-service/aws/versions.tf | 4 + .../teleport/db-agent/aws/LICENSE | 176 ++++++++++++ .../teleport/db-agent/aws/README.md | 112 ++++++++ .../teleport/db-agent/aws/data.tf | 68 +++++ .../db-agent/aws/examples/simple/README.md | 53 ++++ .../db-agent/aws/examples/simple/data.tf | 5 + .../aws/examples/simple/docs_description | 1 + .../db-agent/aws/examples/simple/docs_title | 1 + .../db-agent/aws/examples/simple/main.tf | 46 ++++ .../db-agent/aws/examples/simple/outputs.tf | 3 + .../db-agent/aws/examples/simple/providers.tf | 12 + .../db-agent/aws/examples/simple/variables.tf | 4 + .../db-agent/aws/examples/simple/versions.tf | 18 ++ .../teleport/db-agent/aws/main.tf | 82 ++++++ .../teleport/db-agent/aws/outputs.tf | 69 +++++ .../db-agent/aws/teleport_provision_token.tf | 31 +++ .../db-agent/aws/teleport_version_variable.tf | 9 + .../teleport/db-agent/aws/variables.tf | 258 ++++++++++++++++++ .../teleport/db-agent/aws/versions.tf | 18 ++ 29 files changed, 1192 insertions(+), 31 deletions(-) create mode 100644 integrations/terraform-modules/teleport/container-service/aws/random.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/LICENSE create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/README.md create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/data.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/README.md create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/data.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_description create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_title create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/main.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/outputs.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/providers.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/variables.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/examples/simple/versions.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/main.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/outputs.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/teleport_provision_token.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/teleport_version_variable.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/variables.tf create mode 100644 integrations/terraform-modules/teleport/db-agent/aws/versions.tf diff --git a/integrations/terraform-modules/gen/update-version.sh b/integrations/terraform-modules/gen/update-version.sh index 0da1da42c4d4..01442546a93a 100755 --- a/integrations/terraform-modules/gen/update-version.sh +++ b/integrations/terraform-modules/gen/update-version.sh @@ -28,7 +28,13 @@ if [[ -z "${VERSION}" ]]; then fi script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -cat > "${script_dir}"/../teleport/container-service/aws/teleport_version_variable.tf < "${script_dir}/../${module}/teleport_version_variable.tf" <= 1.5.7 | | aws | >= 6.0 | | http | >= 3.0 | +| random | >= 3.0 | ## Providers @@ -32,6 +33,7 @@ For bugs related to this code, please [open an issue](https://github.com/gravita | ---- | ------- | | aws | >= 6.0 | | http | >= 3.0 | +| random | >= 3.0 | ## Modules @@ -51,9 +53,11 @@ No modules. | [aws_iam_role_policy.ecs_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_security_group.teleport_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_vpc_security_group_egress_rule.allow_all_outbound_from_teleport_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [random_string.name_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | | [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.ecs_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.ecs_execution_trust](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ecs_task_inline_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.ecs_task_trust](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | | [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | @@ -69,18 +73,23 @@ No modules. | create | Toggle creation of all resources. | `bool` | `true` | no | | create\_security\_group | Whether to create a security group for the Teleport agent ECS tasks. | `bool` | `true` | no | | ecs\_cluster\_name | Name of the ECS cluster. | `string` | `"teleport"` | no | +| ecs\_cluster\_use\_name\_prefix | Determines whether `var.ecs_cluster_name` is used as a prefix of the ECS cluster name. | `bool` | `true` | no | | ecs\_service\_name | Name of the ECS service. | `string` | `"teleport-service"` | no | | ecs\_service\_subnets | Subnet IDs where the Teleport agent will be deployed. If var.assign\_public\_ip is true, then all of these subnets must be public subnets (route to an internet gateway). If var.assign\_public\_ip is false, then all of these subnets must be private subnets (route to a NAT gateway). | `list(string)` | n/a | yes | +| ecs\_task\_cloudwatch\_log\_group\_kms\_key\_id | KMS key ID or ARN used to encrypt the ECS task CloudWatch log group. When null, CloudWatch Logs uses its default encryption key. | `string` | `null` | no | | ecs\_task\_cloudwatch\_log\_group\_name | Name for the ECS task CloudWatch log group. | `string` | `"ecs-teleport"` | no | | ecs\_task\_cloudwatch\_log\_group\_region | AWS region for the ECS task CloudWatch log group. Defaults to the AWS provider region. | `string` | `null` | no | | ecs\_task\_cloudwatch\_log\_group\_retention\_days | Number of days to retain logs in the ECS task CloudWatch log group. | `number` | `30` | no | | ecs\_task\_cloudwatch\_log\_group\_skip\_destroy | Whether to preserve the ECS task CloudWatch log group when destroying module resources. Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state. | `bool` | `false` | no | -| ecs\_task\_cpu | Number of cpu units used by the ECS task. | `string` | `"2048"` | no | +| ecs\_task\_cloudwatch\_log\_group\_use\_name\_prefix | Determines whether `var.ecs_task_cloudwatch_log_group_name` is used as a prefix of the ECS task CloudWatch log group name. | `bool` | `true` | no | +| ecs\_task\_cpu | Number of cpu units used by the ECS task. | `number` | `2048` | no | +| ecs\_task\_definition\_name | Name of the ECS task. | `string` | `"teleport-agent"` | no | +| ecs\_task\_definition\_use\_name\_prefix | Determines whether `var.ecs_task_definition_name` is used as a prefix of the ECS task definition name. | `bool` | `true` | no | | ecs\_task\_desired\_count | Desired number of Teleport ECS tasks to run. | `number` | `2` | no | | ecs\_task\_force\_new\_deployment | Set to true to force the ECS service to redeploy tasks without configuration changes. | `bool` | `false` | no | -| ecs\_task\_memory | Amount (in MiB) of memory used by the ECS task. | `string` | `"4096"` | no | -| ecs\_task\_name | Name of the ECS task. | `string` | `"teleport-agent"` | no | -| ecs\_task\_role\_inline\_policy | Optional JSON policy document to attach inline to the ECS task IAM role. | `string` | `null` | no | +| ecs\_task\_memory | Amount (in MiB) of memory used by the ECS task. | `number` | `4096` | no | +| ecs\_task\_role\_inline\_policy | Optional JSON policy document to merge into the inline policy attached to the ECS task IAM role. | `string` | `null` | no | +| ecs\_task\_role\_self\_assumption\_allowed | Whether the ECS task IAM role can assume itself. | `bool` | `true` | no | | environment\_vars | Environment variables to set on the Teleport ECS container. | `map(string)` | `{}` | no | | managed\_updates\_enabled | Whether to resolve the Teleport container version from the configured Managed Updates endpoint when applying this module. | `bool` | `false` | no | | managed\_updates\_group | Update group to query through the v2 Managed Updates endpoint. | `string` | `"default"` | no | @@ -94,8 +103,15 @@ No modules. | Name | Description | | ---- | ----------- | +| ecs\_cluster\_arn | ARN of the ECS cluster for the Teleport ECS service. | +| ecs\_cluster\_name | Name of the ECS cluster for the Teleport ECS service. | | ecs\_execution\_role\_arn | The ARN of the execution IAM role for the Teleport ECS task. | | ecs\_execution\_role\_name | The name of the execution IAM role for the Teleport ECS task. | +| ecs\_service\_arn | ARN of the Teleport ECS service. | +| ecs\_service\_name | Name of the Teleport ECS service. | +| ecs\_task\_cloudwatch\_log\_group\_arn | ARN of the CloudWatch log group for the Teleport ECS task. | +| ecs\_task\_cloudwatch\_log\_group\_name | Name of the CloudWatch log group for the Teleport ECS task. | +| ecs\_task\_definition\_arn | ARN of the Teleport ECS task definition. | | ecs\_task\_role\_arn | The ARN of the task IAM role for the Teleport agent ECS task. | | ecs\_task\_role\_name | The name of the task IAM role for the Teleport agent ECS task. | | security\_group\_id | Security group ID created for the Teleport agent ECS service. | diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_cloudwatch_log_group.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_cloudwatch_log_group.tf index 2054907dc119..5627d0d7bb5e 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_cloudwatch_log_group.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_cloudwatch_log_group.tf @@ -1,11 +1,19 @@ resource "aws_cloudwatch_log_group" "this" { count = var.create ? 1 : 0 - name = var.ecs_task_cloudwatch_log_group_name + name = ( + var.ecs_task_cloudwatch_log_group_use_name_prefix + ? format("%v-%v", + var.ecs_task_cloudwatch_log_group_name, + one(random_string.name_suffix[*].result), + ) + : var.ecs_task_cloudwatch_log_group_name + ) region = coalesce( var.ecs_task_cloudwatch_log_group_region, - one(data.aws_region.this[*].name), + one(data.aws_region.this[*].region), ) + kms_key_id = var.ecs_task_cloudwatch_log_group_kms_key_id retention_in_days = var.ecs_task_cloudwatch_log_group_retention_days skip_destroy = var.ecs_task_cloudwatch_log_group_skip_destroy tags = var.apply_aws_tags diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_cluster.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_cluster.tf index 196d05ccd675..61517c9714de 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_cluster.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_cluster.tf @@ -5,6 +5,13 @@ resource "aws_ecs_cluster" "teleport_agent" { count = var.create ? 1 : 0 - name = var.ecs_cluster_name + name = ( + var.ecs_cluster_use_name_prefix + ? format("%v-%v", + var.ecs_cluster_name, + one(random_string.name_suffix[*].result), + ) + : var.ecs_cluster_name + ) tags = var.apply_aws_tags } diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf index a7d8b340bd41..71e98521f0f1 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_ecs_task_definition.tf @@ -51,17 +51,27 @@ resource "aws_ecs_task_definition" "teleport_agent" { logConfiguration = { logDriver = "awslogs" options = { - "awslogs-group" = one(aws_cloudwatch_log_group.this[*].name) - "awslogs-region" = one(aws_cloudwatch_log_group.this[*].region) - "awslogs-stream-prefix" = "${var.ecs_cluster_name}-${var.ecs_service_name}" + "awslogs-group" = one(aws_cloudwatch_log_group.this[*].name) + "awslogs-region" = one(aws_cloudwatch_log_group.this[*].region) + "awslogs-stream-prefix" = format("%v-%v", + one(aws_ecs_cluster.teleport_agent[*].name), + var.ecs_service_name, + ) } } name = "teleport" } ]) - cpu = var.ecs_task_cpu - execution_role_arn = one(aws_iam_role.ecs_execution[*].arn) - family = var.ecs_task_name + cpu = var.ecs_task_cpu + execution_role_arn = one(aws_iam_role.ecs_execution[*].arn) + family = ( + var.ecs_task_definition_use_name_prefix + ? format("%v-%v", + var.ecs_task_definition_name, + one(random_string.name_suffix[*].result), + ) + : var.ecs_task_definition_name + ) memory = var.ecs_task_memory network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_task_execution_role.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_task_execution_role.tf index b8d0e9fe457b..aca16237e702 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_task_execution_role.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_task_execution_role.tf @@ -7,8 +7,12 @@ resource "aws_iam_role" "ecs_execution" { assume_role_policy = one(data.aws_iam_policy_document.ecs_execution_trust[*].json) description = "Execution role used by the Teleport ECS agent task." - name_prefix = "${var.ecs_cluster_name}-exec" - tags = var.apply_aws_tags + name = format("%v-%v-exec-%v", + one(aws_ecs_cluster.teleport_agent[*].name), + one(data.aws_region.this[*].region), + one(random_string.name_suffix[*].result), + ) + tags = var.apply_aws_tags } data "aws_iam_policy_document" "ecs_execution_trust" { @@ -31,7 +35,7 @@ data "aws_iam_policy_document" "ecs_execution_trust" { format( "arn:%s:ecs:%s:%s:*", one(data.aws_partition.this[*].partition), - one(data.aws_region.this[*].name), + one(data.aws_region.this[*].region), one(data.aws_caller_identity.this[*].account_id), ), ] diff --git a/integrations/terraform-modules/teleport/container-service/aws/aws_task_role.tf b/integrations/terraform-modules/teleport/container-service/aws/aws_task_role.tf index 924c43c5cfc3..e33028016751 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/aws_task_role.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/aws_task_role.tf @@ -2,27 +2,66 @@ # Task role ################################################################################ +locals { + enable_ecs_task_inline_policy = var.create && ( + var.ecs_task_role_self_assumption_allowed || + var.ecs_task_role_inline_policy != null + ) + ecs_task_role_name = format("%v-%v-task-%v", + one(aws_ecs_cluster.teleport_agent[*].name), + one(data.aws_region.this[*].region), + one(random_string.name_suffix[*].result), + ) + ecs_task_role_arn = format( + "arn:%s:iam::%s:role/%s", + one(data.aws_partition.this[*].partition), + one(data.aws_caller_identity.this[*].account_id), + local.ecs_task_role_name, + ) +} + resource "aws_iam_role" "ecs_task" { count = var.create ? 1 : 0 assume_role_policy = one(data.aws_iam_policy_document.ecs_task_trust[*].json) description = "Task role used by the Teleport ECS agent task." - name_prefix = "${var.ecs_cluster_name}-task" + name = local.ecs_task_role_name tags = var.apply_aws_tags } resource "aws_iam_role_policy" "ecs_task" { - count = var.create && var.ecs_task_role_inline_policy != null ? 1 : 0 + count = local.enable_ecs_task_inline_policy ? 1 : 0 name = "ecs-task" - policy = var.ecs_task_role_inline_policy + policy = one(data.aws_iam_policy_document.ecs_task_inline_policy[*].json) role = one(aws_iam_role.ecs_task[*].id) } +data "aws_iam_policy_document" "ecs_task_inline_policy" { + count = local.enable_ecs_task_inline_policy ? 1 : 0 + + source_policy_documents = ( + var.ecs_task_role_inline_policy == null + ? [] + : [var.ecs_task_role_inline_policy] + ) + + dynamic "statement" { + for_each = var.ecs_task_role_self_assumption_allowed ? [true] : [] + + content { + actions = ["sts:AssumeRole"] + effect = "Allow" + resources = [local.ecs_task_role_arn] + } + } +} + data "aws_iam_policy_document" "ecs_task_trust" { count = var.create ? 1 : 0 statement { + sid = "TrustECS" actions = ["sts:AssumeRole"] effect = "Allow" @@ -39,7 +78,7 @@ data "aws_iam_policy_document" "ecs_task_trust" { format( "arn:%s:ecs:%s:%s:*", one(data.aws_partition.this[*].partition), - one(data.aws_region.this[*].name), + one(data.aws_region.this[*].region), one(data.aws_caller_identity.this[*].account_id), ), ] @@ -51,4 +90,29 @@ data "aws_iam_policy_document" "ecs_task_trust" { type = "Service" } } + + dynamic "statement" { + for_each = var.ecs_task_role_self_assumption_allowed ? [true] : [] + + content { + sid = "TrustSelf" + actions = ["sts:AssumeRole"] + effect = "Allow" + + condition { + test = "ArnEquals" + values = [local.ecs_task_role_arn] + variable = "aws:PrincipalArn" + } + + principals { + identifiers = [format( + "arn:%s:iam::%s:root", + one(data.aws_partition.this[*].partition), + one(data.aws_caller_identity.this[*].account_id), + )] + type = "AWS" + } + } + } } diff --git a/integrations/terraform-modules/teleport/container-service/aws/outputs.tf b/integrations/terraform-modules/teleport/container-service/aws/outputs.tf index 925e8886ded8..5a5a25cde7b2 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/outputs.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/outputs.tf @@ -1,3 +1,38 @@ +output "ecs_cluster_name" { + description = "Name of the ECS cluster for the Teleport ECS service." + value = one(aws_ecs_cluster.teleport_agent[*].name) +} + +output "ecs_cluster_arn" { + description = "ARN of the ECS cluster for the Teleport ECS service." + value = one(aws_ecs_cluster.teleport_agent[*].arn) +} + +output "ecs_service_name" { + description = "Name of the Teleport ECS service." + value = one(aws_ecs_service.teleport_agent[*].name) +} + +output "ecs_service_arn" { + description = "ARN of the Teleport ECS service." + value = one(aws_ecs_service.teleport_agent[*].id) +} + +output "ecs_task_definition_arn" { + description = "ARN of the Teleport ECS task definition." + value = one(aws_ecs_task_definition.teleport_agent[*].arn) +} + +output "ecs_task_cloudwatch_log_group_name" { + description = "Name of the CloudWatch log group for the Teleport ECS task." + value = one(aws_cloudwatch_log_group.this[*].name) +} + +output "ecs_task_cloudwatch_log_group_arn" { + description = "ARN of the CloudWatch log group for the Teleport ECS task." + value = one(aws_cloudwatch_log_group.this[*].arn) +} + output "security_group_id" { description = "Security group ID created for the Teleport agent ECS service." value = one(aws_security_group.teleport_agent[*].id) diff --git a/integrations/terraform-modules/teleport/container-service/aws/random.tf b/integrations/terraform-modules/teleport/container-service/aws/random.tf new file mode 100644 index 000000000000..9c415ffb8045 --- /dev/null +++ b/integrations/terraform-modules/teleport/container-service/aws/random.tf @@ -0,0 +1,9 @@ +resource "random_string" "name_suffix" { + count = var.create ? 1 : 0 + + length = 4 + lower = true + numeric = true + special = false + upper = false +} diff --git a/integrations/terraform-modules/teleport/container-service/aws/variables.tf b/integrations/terraform-modules/teleport/container-service/aws/variables.tf index ebb33f9105a8..0aafe2bd064e 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/variables.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/variables.tf @@ -71,6 +71,12 @@ variable "ecs_cluster_name" { type = string } +variable "ecs_cluster_use_name_prefix" { + default = true + description = "Determines whether `var.ecs_cluster_name` is used as a prefix of the ECS cluster name." + type = bool +} + variable "ecs_service_name" { default = "teleport-service" description = "Name of the ECS service." @@ -83,6 +89,12 @@ variable "ecs_task_cloudwatch_log_group_name" { type = string } +variable "ecs_task_cloudwatch_log_group_use_name_prefix" { + default = true + description = "Determines whether `var.ecs_task_cloudwatch_log_group_name` is used as a prefix of the ECS task CloudWatch log group name." + type = bool +} + variable "ecs_task_cloudwatch_log_group_region" { default = null description = "AWS region for the ECS task CloudWatch log group. Defaults to the AWS provider region." @@ -90,6 +102,13 @@ variable "ecs_task_cloudwatch_log_group_region" { type = string } +variable "ecs_task_cloudwatch_log_group_kms_key_id" { + default = null + description = "KMS key ID or ARN used to encrypt the ECS task CloudWatch log group. When null, CloudWatch Logs uses its default encryption key." + nullable = true + type = string +} + variable "ecs_task_cloudwatch_log_group_retention_days" { default = 30 description = "Number of days to retain logs in the ECS task CloudWatch log group." @@ -106,11 +125,23 @@ EOF } variable "ecs_task_cpu" { - default = "2048" + default = 2048 description = "Number of cpu units used by the ECS task." + type = number +} + +variable "ecs_task_definition_name" { + default = "teleport-agent" + description = "Name of the ECS task." type = string } +variable "ecs_task_definition_use_name_prefix" { + default = true + description = "Determines whether `var.ecs_task_definition_name` is used as a prefix of the ECS task definition name." + type = bool +} + variable "ecs_task_desired_count" { default = 2 description = "Desired number of Teleport ECS tasks to run." @@ -124,24 +155,24 @@ variable "ecs_task_force_new_deployment" { } variable "ecs_task_memory" { - default = "4096" + default = 4096 description = "Amount (in MiB) of memory used by the ECS task." - type = string -} - -variable "ecs_task_name" { - default = "teleport-agent" - description = "Name of the ECS task." - type = string + type = number } variable "ecs_task_role_inline_policy" { default = null - description = "Optional JSON policy document to attach inline to the ECS task IAM role." + description = "Optional JSON policy document to merge into the inline policy attached to the ECS task IAM role." nullable = true type = string } +variable "ecs_task_role_self_assumption_allowed" { + default = true + description = "Whether the ECS task IAM role can assume itself." + type = bool +} + variable "environment_vars" { default = {} description = "Environment variables to set on the Teleport ECS container." diff --git a/integrations/terraform-modules/teleport/container-service/aws/versions.tf b/integrations/terraform-modules/teleport/container-service/aws/versions.tf index 844b90ea3170..36f51034c3e6 100644 --- a/integrations/terraform-modules/teleport/container-service/aws/versions.tf +++ b/integrations/terraform-modules/teleport/container-service/aws/versions.tf @@ -6,6 +6,10 @@ terraform { source = "hashicorp/aws" version = ">= 6.0" } + random = { + source = "hashicorp/random" + version = ">= 3.0" + } http = { source = "hashicorp/http" version = ">= 3.0" diff --git a/integrations/terraform-modules/teleport/db-agent/aws/LICENSE b/integrations/terraform-modules/teleport/db-agent/aws/LICENSE new file mode 100644 index 000000000000..d9a10c0d8e86 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/integrations/terraform-modules/teleport/db-agent/aws/README.md b/integrations/terraform-modules/teleport/db-agent/aws/README.md new file mode 100644 index 000000000000..5efa37fc564e --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/README.md @@ -0,0 +1,112 @@ +# AWS Database Agent Terraform module + +This Terraform module deploys a Teleport Database Service agent to Amazon ECS. It creates the ECS resources, IAM task role, and optional security group required for the agent to join a Teleport cluster using AWS IAM. + +## Prerequisites + +- [Configure Teleport Terraform Provider](https://goteleport.com/docs/configuration/terraform-provider/) +- [Configure AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) + +## Examples + +Refer to the [examples](./examples) for example usage of this module. + +## How to get help + +If you're having trouble, check out our [GitHub Discussions](https://github.com/gravitational/teleport/discussions). + +For bugs related to this code, please [open an issue](https://github.com/gravitational/teleport/issues/new/choose). + + +## Requirements + +| Name | Version | +| ---- | ------- | +| terraform | >= 1.5.7 | +| aws | >= 6.0 | +| random | >= 3.0 | +| teleport | >= 18.8.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| aws | >= 6.0 | +| random | >= 3.0 | +| teleport | >= 18.8.0 | + +## Modules + +| Name | Source | Version | +| ---- | ------ | ------- | +| teleport\_db\_service | ../../container-service/aws | n/a | + +## Resources + +| Name | Type | +| ---- | ---- | +| [random_id.teleport_provision_token_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| teleport_provision_token.agent_aws_iam | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.ecs_task_inline_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| apply\_aws\_tags | Additional AWS tags to apply to all created AWS resources. | `map(string)` | `{}` | no | +| assign\_public\_ip | Whether to assign public IP addresses to Teleport db agent ECS tasks. If this is set to true, then var.ecs\_service\_subnets must be public subnets (route to an internet gateway). Otherwise, var.ecs\_service\_subnets must be private subnets (route to a NAT gateway). | `bool` | `false` | no | +| create | Toggle creation of all resources. | `bool` | `true` | no | +| create\_security\_group | Whether to create a security group for the Teleport db agent ECS tasks. | `bool` | `true` | no | +| database\_service\_resources | Override the db\_service resource matchers. When null, a default matcher is used that matches databases in the same account, region, and VPC. | ```list(object({ labels = map(list(string)) aws = optional(object({ assume_role_arn = optional(string, "") external_id = optional(string, "") })) }))``` | `null` | no | +| database\_types\_for\_default\_iam\_policy | Database types for which default IAM policy statements will be added to the ECS task role's inline policy. Currently, only `rds` is supported. Statements in the default IAM policy can be overridden by a statement with a matching SID in var.ecs\_task\_role\_inline\_policy. | `list(string)` | `[]` | no | +| ecs\_cluster\_name | Name of the ECS cluster. | `string` | `"teleport-db-services"` | no | +| ecs\_cluster\_use\_name\_prefix | Determines whether `var.ecs_cluster_name` is used as a prefix of the ECS cluster name. | `bool` | `true` | no | +| ecs\_service\_name | Name of the ECS service. | `string` | `"teleport-db-service"` | no | +| ecs\_service\_subnets | Subnet IDs where the Teleport db agent will be deployed. If var.assign\_public\_ip is true, then all of these subnets must be public subnets (route to an internet gateway). If var.assign\_public\_ip is false, then all of these subnets must be private subnets (route to a NAT gateway). | `list(string)` | n/a | yes | +| ecs\_task\_cloudwatch\_log\_group\_kms\_key\_id | KMS key ID or ARN used to encrypt the ECS task CloudWatch log group. When null, CloudWatch Logs uses its default encryption key. | `string` | `null` | no | +| ecs\_task\_cloudwatch\_log\_group\_name | Name for the ECS task CloudWatch log group. | `string` | `"ecs-teleport"` | no | +| ecs\_task\_cloudwatch\_log\_group\_region | AWS region for the ECS task CloudWatch log group. Defaults to the AWS provider region. | `string` | `null` | no | +| ecs\_task\_cloudwatch\_log\_group\_retention\_days | Number of days to retain logs in the ECS task CloudWatch log group. | `number` | `30` | no | +| ecs\_task\_cloudwatch\_log\_group\_skip\_destroy | Whether to preserve the ECS task CloudWatch log group when destroying module resources. Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state. | `bool` | `false` | no | +| ecs\_task\_cloudwatch\_log\_group\_use\_name\_prefix | Determines whether `var.ecs_task_cloudwatch_log_group_name` is used as a prefix of the ECS task CloudWatch log group name. | `bool` | `true` | no | +| ecs\_task\_cpu | Number of CPU units used by the ECS task. | `number` | `2048` | no | +| ecs\_task\_definition\_name | Name of the ECS task. | `string` | `"teleport-db-agent"` | no | +| ecs\_task\_definition\_use\_name\_prefix | Determines whether `var.ecs_task_definition_name` is used as a prefix of the ECS task definition name. | `bool` | `true` | no | +| ecs\_task\_desired\_count | Desired number of Teleport db agent ECS tasks to run. | `number` | `2` | no | +| ecs\_task\_force\_new\_deployment | Set to true to force the ECS service to redeploy tasks without configuration changes. | `bool` | `false` | no | +| ecs\_task\_memory | Amount (in MiB) of memory used by the ECS task. | `number` | `4096` | no | +| ecs\_task\_role\_inline\_policy | Optional JSON policy document to merge into the inline policy attached to the ECS task IAM role. | `string` | `null` | no | +| environment\_vars | Environment variables to set on the Teleport db agent ECS container. | `map(string)` | `{}` | no | +| join\_params | Override the Teleport join parameters. When null, the module creates an IAM join token automatically. Set this to use a pre-existing token or a different join method. | ```object({ token_name = string method = string })``` | `null` | no | +| log\_level | Teleport agent log level. | `string` | `"INFO"` | no | +| managed\_updates\_enabled | Whether to resolve the Teleport container version from the configured Managed Updates endpoint when applying this module. | `bool` | `false` | no | +| managed\_updates\_group | Update group to query through the v2 Managed Updates endpoint. | `string` | `"default"` | no | +| security\_group\_ids | Additional security group IDs to attach to the Teleport db agent ECS tasks. | `list(string)` | `[]` | no | +| teleport\_container\_image | Container image used for the Teleport db agent ECS tasks. | `string` | `"public.ecr.aws/gravitational/teleport-ent-distroless"` | no | +| teleport\_provision\_token\_name | Name for the Teleport provision token resource. | `string` | `"db-agent"` | no | +| teleport\_provision\_token\_use\_name\_prefix | Determines whether the name of the Teleport provision token is used as a prefix. | `bool` | `true` | no | +| teleport\_proxy\_public\_addr | Teleport cluster proxy public address `host:port`. | `string` | n/a | yes | +| teleport\_version | The version of Teleport to deploy. Generally, the version of Teleport should be controlled by using the appropriate version of this module. This variable is intended for development usage. | `string` | `"19.0.0-prealpha.2"` | no | +| vpc\_id | VPC ID where the Teleport db agent will be deployed. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| ecs\_cluster\_arn | ARN of the ECS cluster for the Teleport db agent ECS service. | +| ecs\_cluster\_name | Name of the ECS cluster for the Teleport db agent ECS service. | +| ecs\_execution\_role\_arn | The ARN of the execution IAM role for the Teleport db agent ECS task. | +| ecs\_execution\_role\_name | The name of the execution IAM role for the Teleport db agent ECS task. | +| ecs\_service\_arn | ARN of the Teleport db agent ECS service. | +| ecs\_service\_name | Name of the Teleport db agent ECS service. | +| ecs\_task\_cloudwatch\_log\_group\_arn | ARN of the CloudWatch log group for the Teleport db agent ECS task. | +| ecs\_task\_cloudwatch\_log\_group\_name | Name of the CloudWatch log group for the Teleport db agent ECS task. | +| ecs\_task\_definition\_arn | ARN of the Teleport db agent ECS task definition. | +| ecs\_task\_role\_arn | The ARN of the task IAM role for the Teleport db agent ECS task. | +| ecs\_task\_role\_name | The name of the task IAM role for the Teleport db agent ECS task. | +| security\_group\_id | Security group ID created for the Teleport db agent ECS service. | +| teleport\_provision\_token\_allow\_aws\_arn | A value that can be used with a Teleport IAM join token to allow the ECS cluster to join the Teleport cluster using its IAM credentials. | +| teleport\_provision\_token\_name | Name of the Teleport provision token that allows the db agent to join the cluster using AWS IAM credentials. | + diff --git a/integrations/terraform-modules/teleport/db-agent/aws/data.tf b/integrations/terraform-modules/teleport/db-agent/aws/data.tf new file mode 100644 index 000000000000..48391ebaa02b --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/data.tf @@ -0,0 +1,68 @@ +data "aws_caller_identity" "this" { + count = var.create ? 1 : 0 + + lifecycle { + precondition { + condition = var.teleport_proxy_public_addr != "" + error_message = "The Teleport proxy public address must not be empty when create is true." + } + } +} + +data "aws_region" "this" { + count = var.create ? 1 : 0 +} + +data "aws_iam_policy_document" "ecs_task_inline_policy" { + count = var.create && ( + var.ecs_task_role_inline_policy != null || + contains(var.database_types_for_default_iam_policy, "rds") + ) ? 1 : 0 + + override_policy_documents = ( + var.ecs_task_role_inline_policy == null + ? [] + : [var.ecs_task_role_inline_policy] + ) + + dynamic "statement" { + for_each = contains(var.database_types_for_default_iam_policy, "rds") ? [true] : [] + + content { + sid = "RDSAutoEnableIAMAuth" + + actions = [ + "rds:ModifyDBCluster", + "rds:ModifyDBInstance", + ] + effect = "Allow" + resources = ["*"] + } + } + + dynamic "statement" { + for_each = contains(var.database_types_for_default_iam_policy, "rds") ? [true] : [] + + content { + sid = "RDSConnect" + actions = ["rds-db:connect"] + effect = "Allow" + resources = ["*"] + } + } + + dynamic "statement" { + for_each = contains(var.database_types_for_default_iam_policy, "rds") ? [true] : [] + + content { + sid = "RDSFetchMetadata" + + actions = [ + "rds:DescribeDBClusters", + "rds:DescribeDBInstances", + ] + effect = "Allow" + resources = ["*"] + } + } +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/README.md b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/README.md new file mode 100644 index 000000000000..903497145dcb --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/README.md @@ -0,0 +1,53 @@ +# Simple AWS Database Agent + +This example deploys a Teleport Database Service agent to Amazon ECS in a new VPC. It uses public subnets and requires the address of an existing Teleport proxy. + +```shell +terraform init +terraform apply -var='teleport_proxy_addr=teleport.example.com:443' +``` + + +## Requirements + +| Name | Version | +| ---- | ------- | +| terraform | >= 1.5.7 | +| aws | ~> 6.0 | +| random | ~> 3.0 | +| teleport | ~> 18.8 | + +## Providers + +| Name | Version | +| ---- | ------- | +| aws | ~> 6.0 | + +## Modules + +| Name | Source | Version | +| ---- | ------ | ------- | +| teleport\_db\_agent | ../.. | n/a | +| vpc | terraform-aws-modules/vpc/aws | 6.6.0 | + +## Resources + +| Name | Type | +| ---- | ---- | +| [aws_availability_zones.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.example_statement_override](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| teleport\_proxy\_addr | The address of the Teleport proxy service in host:port form. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| teleport\_db\_agent | n/a | + \ No newline at end of file diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/data.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/data.tf new file mode 100644 index 000000000000..539d05484219 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/data.tf @@ -0,0 +1,5 @@ +data "aws_availability_zones" "this" {} + +data "aws_caller_identity" "this" {} + +data "aws_region" "this" {} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_description b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_description new file mode 100644 index 000000000000..71cb19ffbd94 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_description @@ -0,0 +1 @@ +Deploy a Teleport Database Service agent to Amazon ECS in a new VPC. diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_title b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_title new file mode 100644 index 000000000000..22102c1778c6 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/docs_title @@ -0,0 +1 @@ +Simple AWS Database Agent diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/main.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/main.tf new file mode 100644 index 000000000000..cb9a446b612d --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/main.tf @@ -0,0 +1,46 @@ +locals { + namespace = "example" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "6.6.0" + + azs = slice(data.aws_availability_zones.this.names, 0, 3) + cidr = "10.0.0.0/16" + name = "${local.namespace}-vpc" + + public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] +} + +module "teleport_db_agent" { + source = "../.." + + assign_public_ip = true # Required when using public subnets. + ecs_service_subnets = module.vpc.public_subnets + managed_updates_enabled = true + teleport_proxy_public_addr = var.teleport_proxy_addr + vpc_id = module.vpc.vpc_id + + ecs_task_role_inline_policy = data.aws_iam_policy_document.example_statement_override.json + database_types_for_default_iam_policy = ["rds"] +} + +data "aws_iam_policy_document" "example_statement_override" { + statement { + # matches the SID of a statement in default IAM policy for "rds" database + # and overrides it + sid = "RDSAutoEnableIAMAuth" + effect = "Allow" + + actions = [ + "rds:ModifyDBCluster", + "rds:ModifyDBInstance", + ] + resources = [ + # restrict the default modify IAM permissions to specific databases instead of "*" + "arn:aws:rds:${data.aws_region.this.region}:${data.aws_caller_identity.this.account_id}:db:example" + ] + } +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/outputs.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/outputs.tf new file mode 100644 index 000000000000..d9fcd43fcf13 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/outputs.tf @@ -0,0 +1,3 @@ +output "teleport_db_agent" { + value = module.teleport_db_agent +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/providers.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/providers.tf new file mode 100644 index 000000000000..3cd5d1418133 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/providers.tf @@ -0,0 +1,12 @@ +provider "aws" { + default_tags { + tags = { + env = "example" + } + } +} + +provider "teleport" { + addr = var.teleport_proxy_addr + profile_name = replace(var.teleport_proxy_addr, "/:[0-9]+.*/", "") +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/variables.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/variables.tf new file mode 100644 index 000000000000..aaae7f1e3049 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/variables.tf @@ -0,0 +1,4 @@ +variable "teleport_proxy_addr" { + description = "The address of the Teleport proxy service in host:port form." + type = string +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/versions.tf b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/versions.tf new file mode 100644 index 000000000000..d287396031cd --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/examples/simple/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + teleport = { + source = "terraform.releases.teleport.dev/gravitational/teleport" + version = "~> 18.8" + } + } +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/main.tf b/integrations/terraform-modules/teleport/db-agent/aws/main.tf new file mode 100644 index 000000000000..e7b38904b0e4 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/main.tf @@ -0,0 +1,82 @@ +module "teleport_db_service" { + # TODO(gavin): change this to the published module version + # source = "terraform.releases.teleport.dev/teleport/container-service/aws" + source = "../../container-service/aws" + + apply_aws_tags = var.apply_aws_tags + assign_public_ip = var.assign_public_ip + create = var.create + create_security_group = var.create_security_group + ecs_cluster_name = var.ecs_cluster_name + ecs_cluster_use_name_prefix = var.ecs_cluster_use_name_prefix + ecs_service_name = var.ecs_service_name + ecs_service_subnets = var.ecs_service_subnets + ecs_task_cloudwatch_log_group_name = var.ecs_task_cloudwatch_log_group_name + ecs_task_cloudwatch_log_group_use_name_prefix = var.ecs_task_cloudwatch_log_group_use_name_prefix + ecs_task_cloudwatch_log_group_region = var.ecs_task_cloudwatch_log_group_region + ecs_task_cloudwatch_log_group_kms_key_id = var.ecs_task_cloudwatch_log_group_kms_key_id + ecs_task_cloudwatch_log_group_retention_days = var.ecs_task_cloudwatch_log_group_retention_days + ecs_task_cloudwatch_log_group_skip_destroy = var.ecs_task_cloudwatch_log_group_skip_destroy + ecs_task_cpu = var.ecs_task_cpu + ecs_task_definition_name = var.ecs_task_definition_name + ecs_task_definition_use_name_prefix = var.ecs_task_definition_use_name_prefix + ecs_task_desired_count = var.ecs_task_desired_count + ecs_task_force_new_deployment = var.ecs_task_force_new_deployment + ecs_task_memory = var.ecs_task_memory + ecs_task_role_inline_policy = one(data.aws_iam_policy_document.ecs_task_inline_policy[*].json) + environment_vars = var.environment_vars + managed_updates_enabled = var.managed_updates_enabled + managed_updates_group = var.managed_updates_group + security_group_ids = var.security_group_ids + teleport_container_image = var.teleport_container_image + teleport_version = var.teleport_version + vpc_id = var.vpc_id + + teleport_config = { + version = "v3" + teleport = { + nodename = "" # setting nodename to an empty string ensures that it's picked up from the host's hostname + proxy_server = var.teleport_proxy_public_addr + join_params = ( + var.join_params != null + ? var.join_params + : var.create + ? { + token_name = local.teleport_provision_token_name + method = "iam" + } + : null + ) + log = { + output = "stderr" + severity = var.log_level + } + } + auth_service = { + enabled = false + } + proxy_service = { + enabled = false + } + ssh_service = { + enabled = false + } + db_service = { + enabled = true + resources = ( + var.database_service_resources != null + ? var.database_service_resources + : [{ + aws = { + assume_role_arn = module.teleport_db_service.ecs_task_role_arn + } + labels = { + "account-id" = data.aws_caller_identity.this[*].account_id + "region" = data.aws_region.this[*].region + "vpc-id" = [var.vpc_id] + } + }] + ) + } + } +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/outputs.tf b/integrations/terraform-modules/teleport/db-agent/aws/outputs.tf new file mode 100644 index 000000000000..8ad3653db731 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/outputs.tf @@ -0,0 +1,69 @@ +output "ecs_cluster_name" { + description = "Name of the ECS cluster for the Teleport db agent ECS service." + value = module.teleport_db_service.ecs_cluster_name +} + +output "ecs_cluster_arn" { + description = "ARN of the ECS cluster for the Teleport db agent ECS service." + value = module.teleport_db_service.ecs_cluster_arn +} + +output "ecs_service_name" { + description = "Name of the Teleport db agent ECS service." + value = module.teleport_db_service.ecs_service_name +} + +output "ecs_service_arn" { + description = "ARN of the Teleport db agent ECS service." + value = module.teleport_db_service.ecs_service_arn +} + +output "ecs_task_definition_arn" { + description = "ARN of the Teleport db agent ECS task definition." + value = module.teleport_db_service.ecs_task_definition_arn +} + +output "ecs_task_cloudwatch_log_group_name" { + description = "Name of the CloudWatch log group for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_task_cloudwatch_log_group_name +} + +output "ecs_task_cloudwatch_log_group_arn" { + description = "ARN of the CloudWatch log group for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_task_cloudwatch_log_group_arn +} + +output "security_group_id" { + description = "Security group ID created for the Teleport db agent ECS service." + value = module.teleport_db_service.security_group_id +} + +output "ecs_execution_role_arn" { + description = "The ARN of the execution IAM role for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_execution_role_arn +} + +output "ecs_execution_role_name" { + description = "The name of the execution IAM role for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_execution_role_name +} + +output "ecs_task_role_arn" { + description = "The ARN of the task IAM role for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_task_role_arn +} + +output "ecs_task_role_name" { + description = "The name of the task IAM role for the Teleport db agent ECS task." + value = module.teleport_db_service.ecs_task_role_name +} + +output "teleport_provision_token_allow_aws_arn" { + description = "A value that can be used with a Teleport IAM join token to allow the ECS cluster to join the Teleport cluster using its IAM credentials." + value = module.teleport_db_service.teleport_provision_token_allow_aws_arn +} + +output "teleport_provision_token_name" { + description = "Name of the Teleport provision token that allows the db agent to join the cluster using AWS IAM credentials." + value = nonsensitive(try(teleport_provision_token.agent_aws_iam[0].metadata.name, null)) +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/teleport_provision_token.tf b/integrations/terraform-modules/teleport/db-agent/aws/teleport_provision_token.tf new file mode 100644 index 000000000000..ea3c22dd3375 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/teleport_provision_token.tf @@ -0,0 +1,31 @@ +locals { + teleport_provision_token_name = ( + var.teleport_provision_token_use_name_prefix + ? "${var.teleport_provision_token_name}-${one(random_id.teleport_provision_token_suffix[*].hex)}" + : var.teleport_provision_token_name + ) +} + +resource "random_id" "teleport_provision_token_suffix" { + count = var.create && var.join_params == null && var.teleport_provision_token_use_name_prefix ? 1 : 0 + + byte_length = 4 +} + +resource "teleport_provision_token" "agent_aws_iam" { + count = var.create && var.join_params == null ? 1 : 0 + + metadata = { + name = local.teleport_provision_token_name + description = "Allow Teleport db agent to join the cluster using AWS IAM credentials." + } + spec = { + allow = [{ + aws_account = try(data.aws_caller_identity.this[0].account_id, "") + aws_arn = module.teleport_db_service.teleport_provision_token_allow_aws_arn + }] + join_method = "iam" + roles = ["Db"] + } + version = "v2" +} diff --git a/integrations/terraform-modules/teleport/db-agent/aws/teleport_version_variable.tf b/integrations/terraform-modules/teleport/db-agent/aws/teleport_version_variable.tf new file mode 100644 index 000000000000..2dff5cccd7d5 --- /dev/null +++ b/integrations/terraform-modules/teleport/db-agent/aws/teleport_version_variable.tf @@ -0,0 +1,9 @@ +variable "teleport_version" { + default = "19.0.0-prealpha.2" + description = < Date: Fri, 31 Jul 2026 21:19:09 -0600 Subject: [PATCH 095/347] Update Apple provisioning profiles (#69239) --- .../Contents/embedded.provisionprofile | Bin 12285 -> 12321 bytes build.assets/macos/tctl/tctl.provisionprofile | Bin 12285 -> 12321 bytes .../Contents/embedded.provisionprofile | Bin 12170 -> 12305 bytes build.assets/macos/tsh/tsh.provisionprofile | Bin 12170 -> 12305 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build.assets/macos/tctl/tctl.app/Contents/embedded.provisionprofile b/build.assets/macos/tctl/tctl.app/Contents/embedded.provisionprofile index 9f4646e38bd91fe2a2d643b59284c7f4ab9901f9..21fb4cac597009e0f82b8af2762546f6593ce01e 100644 GIT binary patch delta 6306 zcmZ`-d)(txncf*^z`+3r7-qPR7jS2^d^Anc+W@nBa!=Z%P1+=FL@-U-v`O0Zo~HBD zx+5Tn=rA8Q6-H3p6|dkd;$>wyqx$=e`$T4u}sFf>LB;JIqIbwC}m^Z9E zdG+5RJ2u^ZaN3F+c5M3GsvVndx^-J`#k$l2l3u_PlM1V$ zhCUfvtQPUKPxCYrTI{4-MH$4L>Df`CMQ~P|LCuQNv{`>#Ycm5laa4_PC&w^+fo2Fv z<#9~|T~QH3T3*^`o&sH^L^TPytdRvqlSO3^vdjPFC_?!Eo;f*q4%PAu_*z-yd4hLe zd$2SEK!F5u2F(8_ogBQT0iLf+#c|$aY+hmbya;V#I<3KCH-dBv-(=ai#!yo4hDG58)cOtD7z_M$3iuX;_)7>c>^Uq zPVq*R;EG7DmXt-U*lU+2BR?-AraWmloj6*a)dHaxpL*@49R(%X>qvq{yQ(&=0Uxk`}E00mT%!e?#Vm_WIzNsPbw}+0*_yOX2a>T zGG1^dUAj_p(&ki2r3@qHHna(&hn!19gyZxH2bPCy!7^0ZA}fb^r^-Vl5QMC9BFM3P zyNTEwNadrfYg7cpijqx>BL+q(>DEwWY{2p~b)_S=k{(1$C?0zMD!{VB;;!R2&kjs; z!DL;swR=q^bnzfGSSS%CSoaa<4($v-yKAhaFqLOcM zO0(9Me4fp=c}@x7O^C^ABc43~wN{N)9SfV!Q<_#=hKp%YtpazeCq15(AnKIv68btK z45sSX70J(&uLd(a5KUM^A$Bns#PW^4)ls@e+jpxKr90n+#R<(SroiZz7laE94Lu1D z9uKAPU~+&%>p66u9Aa8%WyUsUWBK+{z<$0B11c>iZ0RIskSq+HNx&l-TWueymog!< zoB|)nT%3SRLmrYj5Q-r;=K=*O5JZ(`@;xCL+FD?{-KJcsH>_?kO$m5*WOYS=&&kTB zLYjz&5aS`wITUCRPO!|gFqJb{W=(>iMQRF5r0h+WnG!BTflzlr`OwL5uCyE!56Stp zyaEY(iC1XAh?E{`Or)fujNL3MCX1GdVv&?{oEbBxc6=b=TC+HoM0{2$P1I~>P{|g; zq-3}HUNTFN!oW$|i4ii1BuonHego$iSBaxWR3QSSkZqMQHLx0>8oJeCL1_0GnVv{> zuZ#6ZEv%BP4A5rJokW8{*g}m#M~U@Dg&yXVYw)NYt*HB@Xp(HSK{!(jL~;;CBWqxp z3NY1dqNzqD5-r5>;W#6Hl$osMJj zfTnV78)+6Tz!Vgnf+_T!U=K@4IY+{5s&jGYxRWurez1a%AR zDUF`r#E42_!iEAWMLery47mi%l-F;hG1obF@y~0v&BRz(6>@>W!;*pdu*iVt#WZZP zRKb~Z`GhKiJmL6G({l(zhCR!fV>p*Sw80V~>%g%A#5vicSQ7N)5T=|ix0#_KhqTQ~ zNR0xp3~U|J%hZGo4L*kui5U<{A3{Rc)EYAB+HI8t&2~?OMMN^h0K%nR!-Sy-R1qNA zvQB|A1(Jf5XhL36{PLU_blW!oO~4{DC_<5~b{2L?3BoKnmG#MB-dar^;(uPbQ(#kI zLRoEXxr@%cc{^1rRGCLb%9_H8#l@)@NlXxOPRMJl)~u46LQiZGpmCGwRZ^4rr7!_h{b!10IvqOlM zkwFrosV9A?ZoHcAk9c|}L2`!?QXowc+98Tepyqw(0Kr$pkaysNdI_qHkA{spn@2Fk z(Kxf-)B{@$5D&>uIsqn(ibkf;>RYg($Rb5A?H+-rw@j}XNet%7u|^X8GEtyrki21o z8&#K%O?~D2)#Jm5u<0GlrxW%L({SWca~v6%(6k}lE^|Aq6*38Fc;Z`ZHnQP0=pe9a|8>ml;|Fsuh$h1>4;2w=+|{AsKVpR>x^4PwW$>)I! z7AJ<36N-liiT5C+`Qp6&dLo1_Cj=05K;}u3#MUrz-i53=(iZ7sKXV4uwel3kibM7e zB|m_;5RLOB-U#3g|K? zA+q1#m;gymFhbT*sB5~8+Ott5T|sahNe?U~uf?-U3>Djg9o5^kj%32}gfT%eQPn}@ zXXVVGEZZE7RZ!DU)+@CsW2H+YyB>iwn)g*yOBr3NtB@}1$}Ry!0$J`^j;s@l(($Lw zKyj|^IYlHuBSRBq$QiIN{*B&75H83;<@Cx1Wyoey06l~cT*!d24lyB&I_o0{D|LqH zxH6y|f-R-p#=xs86FHtuc6wr98vW^jrxL<6;bjbDG-LaXUQ5(~OAL)5kfWh2CFr>1 zs$y)&cinDT8sR8rGHKi6GR?9uZu8ZSoFpf9ElRKf0~48VLybk&Ot=Y$ zCV}Hjsd^e8Ba;9!(&52RiOyX3FRpp>#qU}&GZSeFBGKjN9|++kGXuZjSQ!=&+^t9v z=Hfd~`s8AB!|CVCteH~u8iqE6ELV_%REbU0B#kn>(x9!HLMIiKh!{TBs#JE`GDibL zi87<~bmW1WOwfpu(Oc#)5W=od?fXf`7?-h$8Q>fy@*J}Go^>adxVbM=5EoWb$OJ$g zrKx(LIn$QV^IF|becZ?T{cJxkJHEUBTxvlmJoSb83C zfd(}}VbY7~ratO)AmS51GHSVGDZx@~3#&Opc+wf!&)`DeHPf6sYfv05V7-nTbh16n zB1M*-@hnzHQ(!3WizKTflxJ;{}`s+TEF zbA!3>2Ml_bOF@_eFiA@`ovCPuQsNod1~H>+p>yl&kBkV~nRXVowTaI& zQrAF}-CEn4&{Lo++xI{uz2)Td^y2-;Zk#b*NQYH!u63p^0tpbR?B#1n0v07Zfc8Ll z+_K_jh_X=2>*+$z%J*+IXSeYNp*Csf2?N0RgS~t7w=zv23hy&sRAEp0`0Vv0UC2Bfmff(FtuJN z&!c+9m_hYLOJy+UV%cL7AxtGS zbkfwQh@-<;n<%?pofT}rw7Yy`6i-WPD~n7@8LcrxC{S41wVYX|bJ*4h9E8*XMQghT z2+MyHQ;sA+OtGLvgH|gPVnR}&%W@}$!8YbJ+-g~fX9Cr9lM^{NY}$?;+IdDLd6{Zz z2AYX=vN-DDokBP*IANIS7RI`j5x^MLvc)2tQZWb&44 zdv4Ui{DBjhYkZFuOFYe!z)jR5x{t7efQWr)y6p;D7!#G`usTx6Ha)9D3n>XbD~gE> zf1sEsnHH!i!-%xF>?YwtOXeZ8p!!$_)$(t+1oRM4xGysTEJR9%%EMvcn8gyML1FG9 zU;jTHEU}1pkVZ&?u7(O!7mb>qjK{lXIu@lerLN+L<%B!PF-@N-2cxX-23!N}#sjVk zNNDs)TW<2DBIyQRA?eT)KTM~zVi~8}g@zc|g+f2sMLS5Xx#x;gKD=TbK7ZqgrVhMu z+!H%3w&IMmnW-m}jl_bP!jJ_#Q!^Jbb}GAIC6ZVYtz|G2NA^rN1#8y5=`J zudhCD+e6P^@PoxaZNBW}6p}y_i3|cK8yk}E|Tt%GTp|ve)ve{hu3B<(fX^pXWqDb&(9lQyJ+8&C&iBr|MKWZ_8J?d;~)I` z<_n&=XUESj3p#BxQ`nZbZa=$sNA}!YX9(T8= zpK!MSWX;+qh&xW0f^BbD$v&`W{kzt#T>rw(5A1t8aUHk)@x7pW#9Jz#K6>4ei};zz z+1m5IYF?JvbMgA4R(}3JPrEDkk;-J(hUfn9;)g!=y(b=S|K)qiQkrJfBgHcHy?A`$6g4g@!Njkey;Y-Oa5!m z2e#jH@5A<`Ph4^H?EHm~UVH8@ueD#e<4c=wzVmAPvxs)ZLzfjcgO8wh&Q>m5dP{ND z?mbuidRTq_``>=?iz}&jT(I$b^$lwy_M&v}sv}zy;&gTAaYXRXP3zceUliBtCtTk< zq4;rT+q1v=hj{-Y`?zoJ7~THvj{8mdW%HB!xBtsk_g?z__im$hJ^kN*eDV?X!r>3D znB4wGV&{z~ZrSuU^09Y*@P^*DQ*K1pUHO)M$;w^dP6SrQ%e1qJQPY>U^ZS%&f z&wKw1zq7s=-TzhmW1n1q;;}!y&A4*&r=HsVi=DN_b1ttZKl-LG3+vu zx6u!M>zOBMbHDlAr)vv0FC@>!ZeIB6cio5oV>Elto9(|X+Hlg>Dl2|_X7B@4tp22ef{jMZp&`izW(nI-M)U?*U#-NTTO1l5}SyGLf~hU_}N5c_MK&4Ec6(f zrZTPrAWs2@=M9pr2W6&iN*H7$J(F!2e0^x;fW!)v1YlKR`y$=r0IsqC!iLOggi#s| zElwNrtk2GFJmJ(?^B8NIu3I_LV_|N>6QbfwL`B3(3vK$mEwaCDQu1i3Ibkaz<#4nU z6f>szqX6aPGzy4fUX^`2(0XQs@-3Ij$TUS+X{uK# zo|!pt(4eYVUi>XP{~WSqA3*A_z)Nhv=N3}v8Tl2T`Egs zK^TvrV=5LZf*I1QiZkNda(mh-z%rE70UZ(Att1n zQ7u%j2UOQ(du;=&mGMLYOO#bJZ*-a*hmlUR#$#TIDoyPS>2g%YBWfXaoD6B9R(qm0oGw)^^^&AchAO0)baNaL zGlL>i;WKXCPTBdO(+l#jS}htq-OVOK@sdz+fCG4d$Fh6{!;_&@qtNTcyG42s5yLfl zfEi@ERV@fzCDticibG=9rNz zmd!JXVaw_Z1;I<<5|n_>XQ7Vbq^1!H*&GALA_F*j4tq&WQ4>JbU3qr@v1d-_81x$= zqobi&dydU?5wPf86?vXw7a|Nfy4c~0ycy96DFqao3M0%zxMUWGhmNU>j+rZ>WNPcB zsAm+Zq3Kdsh4Uvom-DTr+%^K=Ls0g4E)(Us)Sx;oQcy2vuxgR2Tag@YmNe8k<>`b5 zZPY<0RL3NA$L8ujpO#xjiFEWb1x*eORLbE`?i!+FQDq>B4H#Xr*Yt`63p7sEttu2KzH{z6)ptDG#;HcQv07GfKE` zHsy)n0fKT6DjbyNl0^eq?^Mzsn#+Hn&P3v}9Zkdrx z+fFxzBBjr>)XqpO6^4>1g4SC>gCZkR*uwXWBb6vhu0>(Ad=p=~g zWD+&oLfh(hNW7Nq^c8O4QyHV}cjC2CM6}h0Fp+z_8ewpIKzUWUu5;Z{Oo1lM^zv~r z+71VHJ1b_~9vWio2U*+<$50+7bRn+u!vdFtMuM-&@+jxx-C8Z~#nWz7^_m_WE5m8E zI%G2WXxR>nsh&OT2}4bYC>)co6dHO2=4%6aGFF?BVMgv+9z_zI6l&{rtL5oFRi*+V zQXh1cF*%BXLDCPk23|drvNet)IR|B#StJ)5>qK^Y(|W>MfgLrnWIc@#2RIzjnZTSq;wb| z9$_n5ghbg+Y}8brp5&D?zGnNdJ$}QKZQDvX6F~ zL5mtJ=DIeX@Az$sjZy~Y<#HWX%9;8UbsU-v8SPRnGm3=L#u?`r9;FRC7V#G3C0JI| znIWRNo}tQZDa)KUY}IGEtA32-Tz4kxiTH_X^3Fl4DYDBCp`j%abA^}MCh0@Y^ z@$Udn7Qj-4(4sm~uUASWI?|kHlyeiIWFypT5S>z<0i%N#>2@6SNg(;9rMUsKf;YYG z`+31CC)}KnDn?aEJ4k<#xt(|z%1Uvz@u!fjTn>U*t zed&}UL`2!t7>T<`Ceb-_RB=F20JwnQ0rEa=VS)pzqx2i&%-E4d1ZY}ISS3$srmYlZ z8qA;+P{nq{NcU@s=P85Hi0zldcsj0+bw84c>9VOzr~<3CKv=8mOty?O<57;nEXB^- zow6UbhoUy=XQOzWXNLMfQ;c#uXykpQ8;@#ln^GxY(UZ#~wdroQ2MXIeSn*V&=PA)X~LVA}wS9;Q*0SX;Gq?n3ZNzK3OR% zl1@}3!z2gufHj0cE;N?w=@b)=1l88K-VTO&=oHjup`?tOMY7Tlb@&k)gTn*2&SY^A zQI!NC<)ldrbA!Au5^>M8Tjf3>)y63?2d?R$X=T=@F6P$D%iX#IfkN5dEOPhBLK31uM8 zJt(F$y+6JX5kH3F3XE<$WejUZqKR1~;k9{F>)~oHkh5b$ZUwb&hEK=cq~;VN`AjD^ z^3%dNKFr4?st;I2PLC>?4&gNEhVKr_0Tm<*wN%G*Y8L#EQ$x%T7eb&chCqeLz*6A} zs4conk>wara%SG@lg^~LCEf`|ONt>%g}FJh_xo27a9nm)UU4RJYIEDCjZGqn=|rk2 zPC#T~L^;8tkPbw8>|{HjRd$q}{2zjH{OgL`k-v$>-A=*9Qk2z3hk?yMCouIQ2=pnv3DJ_8)lPMqcWXq~ zhCI|2(Mc4W;gD{PyKa*=7@Vl$Tmy6Xq1}x-d^9Kb(QAxyo`k?D zSLSU9j0m9C{X|3e?N)%M0VLqW2 zoiq^Q#6T3{`8Lx;j>_q0b2_M?z?gos*D<8pgzQJz7Rgh}{0P(@64<4PSxOSb=lpcn*l2ib7FuLotr5I#Q5>QJg8w^c80y80V0?L8b+D zag0U3&x0c6Aq8|N=s95V<{n+8#B>iaX4U7AD0KNaV3QVrLYJvjxNz$5u|(oCB)e( zXz}Ux?C5n{rYN)yN`<#|G zblMO@Ofat(=Myjfk1$4~3yXP~w_q_A?$01>T9W@9q`FPq(O$uR!icYYCWTgU0xK%EQe*y zLXpM&JYBWNe7I81clvG#lv2ZXwNP(uJtu5e`jvB!Sh?^yV&&aGIB~LUHL-A4NFKN= zygPqVXxZsF79}fKG`b0kRjQlFWDMJstdjO7!p0C0n}i*SB<{X`eQV|FGY5ROmyjP; zzmkpEPd@O;*-2-8W*vzUcnl|T0#6#p9Z4jx2p+?5JQ9u09z1I}{l=k}Jh<-8!TIjh zAHU*gW6kz^urF-(PPugVN!xB)_4@7YM=$^5`!ub%$YY00bBee**4 z@mu$NddK%}UM8M=!S$05UGa;%X7S-o-i}?*{hK{q*=Mgk|LpTFvw>+VgxeA0)H|I`osJ%8TPCp}KD{pyY=ShicFMMr%;`LX$ z`)*zL%sn6ZyKnvR(S2<7`1^y0ZrgkK@=wFN6C2*U{Cy|y`z5#GkjpRpDfX(q9(&^@ zYB0O( zwoL}zrWC;hLEf41dg@4ojd+(TcAZeIKJ|w!`_75K_S@wJD(Pg{HIb-DZ}_RY@tX7jp_9wYaI9bcRM!{&dS62JJ% zt9#brZ=Ck}6<_+&7V7~1swA*>FvM_{qZ?$8wQr<%#beO{}=>fem{e|61yv zcb~Is-<==7zUN=_r?a2gQ@irS?yq+L_gNoK{Lg*Q+`hYQfSu9H&ENRD-?f)}^@%@j z-FDw8JJ0#UI)Odpl$!L1<`Kd5=`UXR-e0c#oX~#hyL<1y^^DB)JpN*GOa4PR{Xi&t f&adw{ZuO-l_ec66)U&UB=O?f1yyR2MZ`l36_Rx$s diff --git a/build.assets/macos/tctl/tctl.provisionprofile b/build.assets/macos/tctl/tctl.provisionprofile index 9f4646e38bd91fe2a2d643b59284c7f4ab9901f9..21fb4cac597009e0f82b8af2762546f6593ce01e 100644 GIT binary patch delta 6306 zcmZ`-d)(txncf*^z`+3r7-qPR7jS2^d^Anc+W@nBa!=Z%P1+=FL@-U-v`O0Zo~HBD zx+5Tn=rA8Q6-H3p6|dkd;$>wyqx$=e`$T4u}sFf>LB;JIqIbwC}m^Z9E zdG+5RJ2u^ZaN3F+c5M3GsvVndx^-J`#k$l2l3u_PlM1V$ zhCUfvtQPUKPxCYrTI{4-MH$4L>Df`CMQ~P|LCuQNv{`>#Ycm5laa4_PC&w^+fo2Fv z<#9~|T~QH3T3*^`o&sH^L^TPytdRvqlSO3^vdjPFC_?!Eo;f*q4%PAu_*z-yd4hLe zd$2SEK!F5u2F(8_ogBQT0iLf+#c|$aY+hmbya;V#I<3KCH-dBv-(=ai#!yo4hDG58)cOtD7z_M$3iuX;_)7>c>^Uq zPVq*R;EG7DmXt-U*lU+2BR?-AraWmloj6*a)dHaxpL*@49R(%X>qvq{yQ(&=0Uxk`}E00mT%!e?#Vm_WIzNsPbw}+0*_yOX2a>T zGG1^dUAj_p(&ki2r3@qHHna(&hn!19gyZxH2bPCy!7^0ZA}fb^r^-Vl5QMC9BFM3P zyNTEwNadrfYg7cpijqx>BL+q(>DEwWY{2p~b)_S=k{(1$C?0zMD!{VB;;!R2&kjs; z!DL;swR=q^bnzfGSSS%CSoaa<4($v-yKAhaFqLOcM zO0(9Me4fp=c}@x7O^C^ABc43~wN{N)9SfV!Q<_#=hKp%YtpazeCq15(AnKIv68btK z45sSX70J(&uLd(a5KUM^A$Bns#PW^4)ls@e+jpxKr90n+#R<(SroiZz7laE94Lu1D z9uKAPU~+&%>p66u9Aa8%WyUsUWBK+{z<$0B11c>iZ0RIskSq+HNx&l-TWueymog!< zoB|)nT%3SRLmrYj5Q-r;=K=*O5JZ(`@;xCL+FD?{-KJcsH>_?kO$m5*WOYS=&&kTB zLYjz&5aS`wITUCRPO!|gFqJb{W=(>iMQRF5r0h+WnG!BTflzlr`OwL5uCyE!56Stp zyaEY(iC1XAh?E{`Or)fujNL3MCX1GdVv&?{oEbBxc6=b=TC+HoM0{2$P1I~>P{|g; zq-3}HUNTFN!oW$|i4ii1BuonHego$iSBaxWR3QSSkZqMQHLx0>8oJeCL1_0GnVv{> zuZ#6ZEv%BP4A5rJokW8{*g}m#M~U@Dg&yXVYw)NYt*HB@Xp(HSK{!(jL~;;CBWqxp z3NY1dqNzqD5-r5>;W#6Hl$osMJj zfTnV78)+6Tz!Vgnf+_T!U=K@4IY+{5s&jGYxRWurez1a%AR zDUF`r#E42_!iEAWMLery47mi%l-F;hG1obF@y~0v&BRz(6>@>W!;*pdu*iVt#WZZP zRKb~Z`GhKiJmL6G({l(zhCR!fV>p*Sw80V~>%g%A#5vicSQ7N)5T=|ix0#_KhqTQ~ zNR0xp3~U|J%hZGo4L*kui5U<{A3{Rc)EYAB+HI8t&2~?OMMN^h0K%nR!-Sy-R1qNA zvQB|A1(Jf5XhL36{PLU_blW!oO~4{DC_<5~b{2L?3BoKnmG#MB-dar^;(uPbQ(#kI zLRoEXxr@%cc{^1rRGCLb%9_H8#l@)@NlXxOPRMJl)~u46LQiZGpmCGwRZ^4rr7!_h{b!10IvqOlM zkwFrosV9A?ZoHcAk9c|}L2`!?QXowc+98Tepyqw(0Kr$pkaysNdI_qHkA{spn@2Fk z(Kxf-)B{@$5D&>uIsqn(ibkf;>RYg($Rb5A?H+-rw@j}XNet%7u|^X8GEtyrki21o z8&#K%O?~D2)#Jm5u<0GlrxW%L({SWca~v6%(6k}lE^|Aq6*38Fc;Z`ZHnQP0=pe9a|8>ml;|Fsuh$h1>4;2w=+|{AsKVpR>x^4PwW$>)I! z7AJ<36N-liiT5C+`Qp6&dLo1_Cj=05K;}u3#MUrz-i53=(iZ7sKXV4uwel3kibM7e zB|m_;5RLOB-U#3g|K? zA+q1#m;gymFhbT*sB5~8+Ott5T|sahNe?U~uf?-U3>Djg9o5^kj%32}gfT%eQPn}@ zXXVVGEZZE7RZ!DU)+@CsW2H+YyB>iwn)g*yOBr3NtB@}1$}Ry!0$J`^j;s@l(($Lw zKyj|^IYlHuBSRBq$QiIN{*B&75H83;<@Cx1Wyoey06l~cT*!d24lyB&I_o0{D|LqH zxH6y|f-R-p#=xs86FHtuc6wr98vW^jrxL<6;bjbDG-LaXUQ5(~OAL)5kfWh2CFr>1 zs$y)&cinDT8sR8rGHKi6GR?9uZu8ZSoFpf9ElRKf0~48VLybk&Ot=Y$ zCV}Hjsd^e8Ba;9!(&52RiOyX3FRpp>#qU}&GZSeFBGKjN9|++kGXuZjSQ!=&+^t9v z=Hfd~`s8AB!|CVCteH~u8iqE6ELV_%REbU0B#kn>(x9!HLMIiKh!{TBs#JE`GDibL zi87<~bmW1WOwfpu(Oc#)5W=od?fXf`7?-h$8Q>fy@*J}Go^>adxVbM=5EoWb$OJ$g zrKx(LIn$QV^IF|becZ?T{cJxkJHEUBTxvlmJoSb83C zfd(}}VbY7~ratO)AmS51GHSVGDZx@~3#&Opc+wf!&)`DeHPf6sYfv05V7-nTbh16n zB1M*-@hnzHQ(!3WizKTflxJ;{}`s+TEF zbA!3>2Ml_bOF@_eFiA@`ovCPuQsNod1~H>+p>yl&kBkV~nRXVowTaI& zQrAF}-CEn4&{Lo++xI{uz2)Td^y2-;Zk#b*NQYH!u63p^0tpbR?B#1n0v07Zfc8Ll z+_K_jh_X=2>*+$z%J*+IXSeYNp*Csf2?N0RgS~t7w=zv23hy&sRAEp0`0Vv0UC2Bfmff(FtuJN z&!c+9m_hYLOJy+UV%cL7AxtGS zbkfwQh@-<;n<%?pofT}rw7Yy`6i-WPD~n7@8LcrxC{S41wVYX|bJ*4h9E8*XMQghT z2+MyHQ;sA+OtGLvgH|gPVnR}&%W@}$!8YbJ+-g~fX9Cr9lM^{NY}$?;+IdDLd6{Zz z2AYX=vN-DDokBP*IANIS7RI`j5x^MLvc)2tQZWb&44 zdv4Ui{DBjhYkZFuOFYe!z)jR5x{t7efQWr)y6p;D7!#G`usTx6Ha)9D3n>XbD~gE> zf1sEsnHH!i!-%xF>?YwtOXeZ8p!!$_)$(t+1oRM4xGysTEJR9%%EMvcn8gyML1FG9 zU;jTHEU}1pkVZ&?u7(O!7mb>qjK{lXIu@lerLN+L<%B!PF-@N-2cxX-23!N}#sjVk zNNDs)TW<2DBIyQRA?eT)KTM~zVi~8}g@zc|g+f2sMLS5Xx#x;gKD=TbK7ZqgrVhMu z+!H%3w&IMmnW-m}jl_bP!jJ_#Q!^Jbb}GAIC6ZVYtz|G2NA^rN1#8y5=`J zudhCD+e6P^@PoxaZNBW}6p}y_i3|cK8yk}E|Tt%GTp|ve)ve{hu3B<(fX^pXWqDb&(9lQyJ+8&C&iBr|MKWZ_8J?d;~)I` z<_n&=XUESj3p#BxQ`nZbZa=$sNA}!YX9(T8= zpK!MSWX;+qh&xW0f^BbD$v&`W{kzt#T>rw(5A1t8aUHk)@x7pW#9Jz#K6>4ei};zz z+1m5IYF?JvbMgA4R(}3JPrEDkk;-J(hUfn9;)g!=y(b=S|K)qiQkrJfBgHcHy?A`$6g4g@!Njkey;Y-Oa5!m z2e#jH@5A<`Ph4^H?EHm~UVH8@ueD#e<4c=wzVmAPvxs)ZLzfjcgO8wh&Q>m5dP{ND z?mbuidRTq_``>=?iz}&jT(I$b^$lwy_M&v}sv}zy;&gTAaYXRXP3zceUliBtCtTk< zq4;rT+q1v=hj{-Y`?zoJ7~THvj{8mdW%HB!xBtsk_g?z__im$hJ^kN*eDV?X!r>3D znB4wGV&{z~ZrSuU^09Y*@P^*DQ*K1pUHO)M$;w^dP6SrQ%e1qJQPY>U^ZS%&f z&wKw1zq7s=-TzhmW1n1q;;}!y&A4*&r=HsVi=DN_b1ttZKl-LG3+vu zx6u!M>zOBMbHDlAr)vv0FC@>!ZeIB6cio5oV>Elto9(|X+Hlg>Dl2|_X7B@4tp22ef{jMZp&`izW(nI-M)U?*U#-NTTO1l5}SyGLf~hU_}N5c_MK&4Ec6(f zrZTPrAWs2@=M9pr2W6&iN*H7$J(F!2e0^x;fW!)v1YlKR`y$=r0IsqC!iLOggi#s| zElwNrtk2GFJmJ(?^B8NIu3I_LV_|N>6QbfwL`B3(3vK$mEwaCDQu1i3Ibkaz<#4nU z6f>szqX6aPGzy4fUX^`2(0XQs@-3Ij$TUS+X{uK# zo|!pt(4eYVUi>XP{~WSqA3*A_z)Nhv=N3}v8Tl2T`Egs zK^TvrV=5LZf*I1QiZkNda(mh-z%rE70UZ(Att1n zQ7u%j2UOQ(du;=&mGMLYOO#bJZ*-a*hmlUR#$#TIDoyPS>2g%YBWfXaoD6B9R(qm0oGw)^^^&AchAO0)baNaL zGlL>i;WKXCPTBdO(+l#jS}htq-OVOK@sdz+fCG4d$Fh6{!;_&@qtNTcyG42s5yLfl zfEi@ERV@fzCDticibG=9rNz zmd!JXVaw_Z1;I<<5|n_>XQ7Vbq^1!H*&GALA_F*j4tq&WQ4>JbU3qr@v1d-_81x$= zqobi&dydU?5wPf86?vXw7a|Nfy4c~0ycy96DFqao3M0%zxMUWGhmNU>j+rZ>WNPcB zsAm+Zq3Kdsh4Uvom-DTr+%^K=Ls0g4E)(Us)Sx;oQcy2vuxgR2Tag@YmNe8k<>`b5 zZPY<0RL3NA$L8ujpO#xjiFEWb1x*eORLbE`?i!+FQDq>B4H#Xr*Yt`63p7sEttu2KzH{z6)ptDG#;HcQv07GfKE` zHsy)n0fKT6DjbyNl0^eq?^Mzsn#+Hn&P3v}9Zkdrx z+fFxzBBjr>)XqpO6^4>1g4SC>gCZkR*uwXWBb6vhu0>(Ad=p=~g zWD+&oLfh(hNW7Nq^c8O4QyHV}cjC2CM6}h0Fp+z_8ewpIKzUWUu5;Z{Oo1lM^zv~r z+71VHJ1b_~9vWio2U*+<$50+7bRn+u!vdFtMuM-&@+jxx-C8Z~#nWz7^_m_WE5m8E zI%G2WXxR>nsh&OT2}4bYC>)co6dHO2=4%6aGFF?BVMgv+9z_zI6l&{rtL5oFRi*+V zQXh1cF*%BXLDCPk23|drvNet)IR|B#StJ)5>qK^Y(|W>MfgLrnWIc@#2RIzjnZTSq;wb| z9$_n5ghbg+Y}8brp5&D?zGnNdJ$}QKZQDvX6F~ zL5mtJ=DIeX@Az$sjZy~Y<#HWX%9;8UbsU-v8SPRnGm3=L#u?`r9;FRC7V#G3C0JI| znIWRNo}tQZDa)KUY}IGEtA32-Tz4kxiTH_X^3Fl4DYDBCp`j%abA^}MCh0@Y^ z@$Udn7Qj-4(4sm~uUASWI?|kHlyeiIWFypT5S>z<0i%N#>2@6SNg(;9rMUsKf;YYG z`+31CC)}KnDn?aEJ4k<#xt(|z%1Uvz@u!fjTn>U*t zed&}UL`2!t7>T<`Ceb-_RB=F20JwnQ0rEa=VS)pzqx2i&%-E4d1ZY}ISS3$srmYlZ z8qA;+P{nq{NcU@s=P85Hi0zldcsj0+bw84c>9VOzr~<3CKv=8mOty?O<57;nEXB^- zow6UbhoUy=XQOzWXNLMfQ;c#uXykpQ8;@#ln^GxY(UZ#~wdroQ2MXIeSn*V&=PA)X~LVA}wS9;Q*0SX;Gq?n3ZNzK3OR% zl1@}3!z2gufHj0cE;N?w=@b)=1l88K-VTO&=oHjup`?tOMY7Tlb@&k)gTn*2&SY^A zQI!NC<)ldrbA!Au5^>M8Tjf3>)y63?2d?R$X=T=@F6P$D%iX#IfkN5dEOPhBLK31uM8 zJt(F$y+6JX5kH3F3XE<$WejUZqKR1~;k9{F>)~oHkh5b$ZUwb&hEK=cq~;VN`AjD^ z^3%dNKFr4?st;I2PLC>?4&gNEhVKr_0Tm<*wN%G*Y8L#EQ$x%T7eb&chCqeLz*6A} zs4conk>wara%SG@lg^~LCEf`|ONt>%g}FJh_xo27a9nm)UU4RJYIEDCjZGqn=|rk2 zPC#T~L^;8tkPbw8>|{HjRd$q}{2zjH{OgL`k-v$>-A=*9Qk2z3hk?yMCouIQ2=pnv3DJ_8)lPMqcWXq~ zhCI|2(Mc4W;gD{PyKa*=7@Vl$Tmy6Xq1}x-d^9Kb(QAxyo`k?D zSLSU9j0m9C{X|3e?N)%M0VLqW2 zoiq^Q#6T3{`8Lx;j>_q0b2_M?z?gos*D<8pgzQJz7Rgh}{0P(@64<4PSxOSb=lpcn*l2ib7FuLotr5I#Q5>QJg8w^c80y80V0?L8b+D zag0U3&x0c6Aq8|N=s95V<{n+8#B>iaX4U7AD0KNaV3QVrLYJvjxNz$5u|(oCB)e( zXz}Ux?C5n{rYN)yN`<#|G zblMO@Ofat(=Myjfk1$4~3yXP~w_q_A?$01>T9W@9q`FPq(O$uR!icYYCWTgU0xK%EQe*y zLXpM&JYBWNe7I81clvG#lv2ZXwNP(uJtu5e`jvB!Sh?^yV&&aGIB~LUHL-A4NFKN= zygPqVXxZsF79}fKG`b0kRjQlFWDMJstdjO7!p0C0n}i*SB<{X`eQV|FGY5ROmyjP; zzmkpEPd@O;*-2-8W*vzUcnl|T0#6#p9Z4jx2p+?5JQ9u09z1I}{l=k}Jh<-8!TIjh zAHU*gW6kz^urF-(PPugVN!xB)_4@7YM=$^5`!ub%$YY00bBee**4 z@mu$NddK%}UM8M=!S$05UGa;%X7S-o-i}?*{hK{q*=Mgk|LpTFvw>+VgxeA0)H|I`osJ%8TPCp}KD{pyY=ShicFMMr%;`LX$ z`)*zL%sn6ZyKnvR(S2<7`1^y0ZrgkK@=wFN6C2*U{Cy|y`z5#GkjpRpDfX(q9(&^@ zYB0O( zwoL}zrWC;hLEf41dg@4ojd+(TcAZeIKJ|w!`_75K_S@wJD(Pg{HIb-DZ}_RY@tX7jp_9wYaI9bcRM!{&dS62JJ% zt9#brZ=Ck}6<_+&7V7~1swA*>FvM_{qZ?$8wQr<%#beO{}=>fem{e|61yv zcb~Is-<==7zUN=_r?a2gQ@irS?yq+L_gNoK{Lg*Q+`hYQfSu9H&ENRD-?f)}^@%@j z-FDw8JJ0#UI)Odpl$!L1<`Kd5=`UXR-e0c#oX~#hyL<1y^^DB)JpN*GOa4PR{Xi&t f&adw{ZuO-l_ec66)U&UB=O?f1yyR2MZ`l36_Rx$s diff --git a/build.assets/macos/tsh/tsh.app/Contents/embedded.provisionprofile b/build.assets/macos/tsh/tsh.app/Contents/embedded.provisionprofile index bd396c3542b0768db859b8e3077709572756749b..f3e60851997b89312d1520bf5cc25535b42a3f79 100644 GIT binary patch delta 7604 zcmZ`;3Eb1<{r7!&K{@w=2s#yq=s+uL+9Z8jL|m>WX`8f3o2HEuOw%?!()&morp0%N zBI?khbBcJ(P3Js8=QYtWC%SphV;r;NVrG&aa?R=NE$m1FosiQc6NGQs53H z!Rk>-=vy=94-*y^z;!;3V+{@~YTf=XX2QKLXJHUWCc9jW0^b4EZ$jNLxp$jiGvMEVJi!f4DTQJ86>A{}=ta;L4)@h(dl z6ifL{jqX&FZa<{TzE(ItfcKYAJ=CWM{eCt~`Eg!ik$4=daw2C^!q|L5BvuupWDXZ% zaU$%G3!K<6>G9u!{ZpnM-v8~4Sv{7;*~0#7L-{c*Okjc+S{;0ud?h zX37E|ZTh8}yBBuny|p6hD5s4PCsCHlkqSblH6xSnQY{80;e1t++W}|1nW70J=PPRU zc%s-Xy5VG_fuxEppV-7=1rIC3ret=V&KeajHpG#(hgG^RCISUhsKCV|wNkXxGC~5R z3Z1N8cDiG!N}%TKS;bN=-K|80Vwn$UR6&YciBP-L(5sn(V+4*fbO5TF?s$WbbDYRB zyvKoOc`9TOWnzSJ1q+pM0SnZzB35Zwij$6dDtcZHMB)*}6RlKzE?9`kloe*;xZakH znxiFAHJ31AP!XEVBRx#02`q^T7+yhGLM#vjR%A^Q10fWl7$z`I9Aiz4#Ikk@GEt7A zNle!97!uYyRVtCvM^sDncofA^$i_Q<*<=cM7uWSVu7kV~G>~j!iK7KGq$gRB>NqY) z42K12wv>a?JqGiHy1s&v2tZmF&1nqYP@-rd<%Ze{$U;>X%Um8U0z8=9J};o@fc=8; z=i5n`H$$bdEHy$UzMYEt#@aZNYwmeZ0qfZL#OPy_({XU#^T)u9MatB5>KlN%`64cW zwyf9GHI62T28{`lA2(S7K*~+Bv?Z0|4ORBHl5R;)xe+~0=c-y4ADf6OD`QYmqIfurScYzgZYb689M7^eeJOl>T8%r?LB=(9eW^yKD^MnY6aXd-+L$v^ErsI`#p_&t- znXFcgjF17u*V3vShS;o?CQS-=nt&usmeI$OwKC4Nb|`f!hvO7SS|EpF#m+cmf?i0> z?8yZzhU@#>%#aPTKobpju2$%D8x1q>RvKl|QEP>3upUc+wbtVi z*@$*KXtsozQW!@Y-EK>3XsU>*lFyZsx>3R%b|TGQ8*O>jL?@%CEhDeHYB2{^FF7zG z<_;ufA4)JmH~=@*VhoSFt+qpGP-L)_hH?=)-O2{>Fw=~Wh&tKH!SQw;b~FTcv+nF< z6};gnBSjc7V{q3WQ|c{OC8NPLF32qu|k_zDNo0Cy<>3A_ye``so;qFyjg8%-rHwqvy6Rs@!y z)d~F{C>Qff|aV~ zwjs*nDY|0|JwRF9vU82Yfr3+fSCPl%Rit}!Jdi4JEe=OG(h!ZL>`$* zOf>=CW%Ed{D1s9yLB<3yG_A`QC4Mh^GfS9_f902$R4;ms0 zL>5e13`oT|83!JK#0(tJdO)PW;ADwo{35V8aWe%6AeI8;CA$Ez2|8Hm79c+|MH@yh zRR$KtG*dzXH$lTLfG1d@R{a#`JEOmQYUSI>{Jjab%;%`6bav1CCF@xSysfaUIytB|--R}w}+rTx`Z z-es$qp}|0I;Xpf`z%4+{9J#MOYz1osz%T@$8X#>c?y7)ebpUByZ+5a>7HY&pMhpw~ zM`m8Vz&5iM)(Mjo%8`m~aIJ7`-?sjVGtTcZNdVa-W7CCIfB=M({vbk87{QWB9ivL& zVml(?-D=n!W6MFD>R4$ikmz(&KCGop$=@u7Q58H>P8TySUpUN%M;I#V$~vWNvk=H9 zv~o4)Z$>!EE6O_Qll?kZ%}S9F1uq39o!AJH;+xPD=S&|7-7I#`$c>F#uj%B?Rj&b&c#`ST0f`Fbl zQpGS6&{79uBUMuqIF&&5AH1npREop6x zz;?$Di|xvGo2}J0=>Uzk54nHnVVC#iImax=ORZwODhJU_l1@QBuUre`J^)f5 z+2@Am_Hgf*rvOm{)UIOwP|JjC{v?Ow)hb(4;JU}>@i^6PH(KYYnK!o&m>nz|Jjj#tcBfvRzYZm+FfKIh`DG$rLh?NjmrDz=RUT1}NSeUEBJ@t7*7TdItf z_bQl!KrC&g1O*V@zh@@MLj^bs8t>NVD2Mm}Pl8n2b#77&2C*0(z=A2H9D#rf=KN7t z%c_+LPpKVDB}GVf#70shRy18jwG3J-D=o&1Gmb!nZ|<#;s$0Q2Cx9&YV0ZG=r9n^5IER925?sm zm$7hkug(Y%;U-BpC6zWTptt+zI%~HvVw}7tfJ25Ft*YlvYDJbA*dJu|b5I5D$I)i??;?N|bo=nh5Mq{F#IF<){ zFL62Dj#QoLYB*puIIF^OkzlOuDuaqA=mh|`n26Gk#RNwZSifZF(_smFv z3h<<@9YtW|lYu0wu$U~7V21*)2$enPutk$2zF86bIh>?`XT$tKFI6^?3{+=IaR<}I z@`gLr2*={ta4?c~V?79Jw4o8D8ueIsbWbvY9XmqscElJ?a@ekpz*3U}2evYh@s7u@ zft6t~P=SKY1e;dIcKbkxSR4e3!F8!l>KuVv|M@#WKcJ%CURD-fj~_TD zqj}Mk3-K<_W)XBTlF>#Mr$$iEV%6*RW_&Kqr9yDOFpoyBgGPrWXmr9!S4|ik7#y5^ z;u))^{Od1+SMPY|rJud7mTw?Fs&FXn#w z(UtR-rDtX@dw2QP-yrwhaKilZbMrRLKkJ0%?`I6FH!RfxX9Qk*xb@r<+cqA=tSsDp z+-;9!zkK-IIZtgKS~_~qD&rqtPpQ9s<5wqEn5~=E&EzgVee=ro&cP%2f3sVkuiO#d z+Ii^3S2jQP$P3w-Cn|4Frh1LFk!SdSW?03DROP;*q zwNsB;fHeT$^pMFL41;4z0g}(at4Rgx% zsnl<8uCBSawP5W{-A{I0`^=X=`M!I~iN{=Y%J;6`_>^C`vw`0|}q@^|KL zI@xjd!uj`pIyhnAfPr6}`7U(kJJ7626Qm;#9vJv~=pbkk_!=BIpfAinnj$A1eQ{s^6QuJ54NZf!v`?4Q454`2wSz$9pR(v-1%cP%|> zaA0T<#QU3_csu+PMh6BCP##3Dd11$tP2x2Nj2>-1R(G!d{ODU|AN%MHQ}EL++IC;- z$hSj(I^^{;ia#7)-rIQ!)tvsGb^19MKK_cf4qyB9p|4_F5}PI+bT;$1AKtRy*?+!$ z!fCI+eQocEH#h#|xeY5qDc`yL%?F=J0qc!hV$x=Ws(W!$-L>IKg)d#5sR!{Z!v+Dofz+0^TYWuV8-{hEr# zcb0daV-kO0w9h{kw$zt*{}F%UclWNo+pBbLp2>*!PDj^mc;(UNWx0>p(>C1r!S!1X zS+oAhMAGJ#X?4-}Ux4KX&wlw{;@(Ftz2cM{gTiybi#p zF%F&Kseh0{4?Osn=3NtiwCt&mRy@7my8RbCESNWM?sT`uMvlu1p)fjzBgpOYja?%B z)}nTA%MrIOJ?@Cdj<_N7$0^60#XrP*tJ!;>dEWcXrQUPm^`YtsS8v+#M)tR7J@EI# zoYT#nFPwM3GM7L2$-5S=c;}(f9jnd8qKD@WoRq%fTyFNyH(d!Y`rgVDYg>JPl3%{z zyT+EpQTI*0efz;%ttH1?Rr!j3c|mK*splY1{?c*n*{`3;eL&7$*^?#teBzeig>OxJ zXU&VjcmI9=iC?eUw)o-PEo&!zw(-yU=Sw}?zkXv${dd8(^ZpAiA^e-3{^&W!Mdc*7 zHnsEv$9t#G3ZK#I9zHzz`J3OEc(A2K-@nY|7`<(>bj84}kt^UAmYnb*^2aIb@mp4$ ze$fMOx2r2hUe#CrZSu5N@vVng*s=*d`mu4yj1GFC(Ln^7wg)tW&;ig<_SJ%+L!mjV zzCFdOyRieXl!j8!;@#KK;F|fnu%u<`&Bk6}GR<^dM}Lsc)FIcjJ@*f+IkrFN`1uQU z6hLxxU<_K{8yX#$4K9YkF>TksQCfQ90CeV8{Tq(oKHanX>A`_H$M>&1c22+VI3xnu zgFb5LIA|es>{Sb{n!o0#-2=Xk9yl7`$gF(%ek?iZsKhnXhY##u z=RkYYezaL#=iRbq_LRXJ9zXIA!SfQGi{^az{%2?Z^pRJeE$w_n{Kq-ok6$|IqX!eB z{)?yPJ@n=8qqnU%cJ{nW|GkZSw($Eeab)>}FTJ-OzRzwPB#&J&_mRw;XF{Ba#d~F;a9tdHyrfu3{{?eL&l>y> zwD!+myu{^!MqmI#7=}Fxv|!Iai<5_3fIY9CyJOmq|319ngS(%* z*q!`C*B$=m#QPpRzPIBLW67wPx%b{Vw>zhtw=DDFhBq%mYuB7h|EptcS?+%4(mz7a z1UFURI_CKGqrpC5esc0tPvj1}`qZ1BnRn-mb?FoR!|GRVWD@btuU49eec;oAyD6G%YGbY zxUuiF>af1kiuWwc7{Ohd7OHon_tIoQ^UX&9Ps5eXVqNKDCvyoFrYnQmmjj5L;Y zQvt$_G7-uejUo+JLlIPAq9{Vsfe4qCbJ2=uL9P@BN*=V5h%u^6X0vrsD=3`B ztID}7#K*a!2-{QgTKKq%!&60$1!Sh|#nAc9G>@%|d&k{ntFa1O%q{gunk9A{#X~W*$j>IU~z=pMY1P^5a4r!rYMvc zz=%#_p%NLQL<@alRzxNQa<9n8Y?{yxp>;8hLJ2b4qDUqy z<+-Ae%W44mS&H=9DAHX^hESc4dm-?yN*2Z}#-d=XTjSxVE`xp^jwm9`D>A8ODGyf= zVpfHZqq<0f=fu2VLujizP9WV)0Yf1n?yX8qR26fq(&k|o=*U8en5zaF>p~heOK_y} zlf`(zLcs&Oi|GCkOx^_fnmd@c-A(9H&aHtq?cjuv5Hn}RJlFDT2#hKKR|+5nR>BaD zFC{!2B;kd2-EWaCa`VWeKl0&GRgQB7(c&%2o=lxa`2lQr*#T_OG*b96t~!m=C^XQI zD{Kr!xs*+}m6W1UC#j%GD_fkt2%m1`vk9k$1W=_7V#zvDB;^DNPGhKb0BF8J{**NV z;ZcfZIUI2U01-AiOf&$Pz%=0HF6e~LA2AjmTol&j+<(EtT~0v}QY{MAC`3R3JkgdF zi#;5R5k4B1TUjM%Dr-tT7LOEbbS)E4W4hvmTP>Sd^cSUS$!bpdO4Tq~%c5SPm@PR{ z4GS$tGX54RzraX{Z$@S!0n`rGmz1(Kwp(Thv-P+_X0ANX?Zs7HXR6b<0tb zv^xBbOsK)tHIZeRdQ*-#^9hA*mJwte+0~rdrDvop$sTW!&qoJ-7xiRy zfz?TlXlFxQgo)5thQ=apT>@StoO4$}rvw&N5#nfF%Cia!yQ*RgP#A{NiI^=*5e#q) z(VC1SWgM*@?jY)7=J1Vp!6Kr$M0_AvM;Qd9g0~Sa8jWNDhjSTPi>3ibX6Ytm6G*n< z4|8coqpN|KTV*hFj@KBK$?>g$F}a8y(DmryMv6Ux5~D2|qX^1xvV<}TXVzRnvu-SD zlg&=t9tP{KQgRtewNtgEO-b15R-H4_ln2L=h8u8ZQ6?K%K=d`)!vNKw0CY)RN$7f* z)dBaqs{R}}EimTbdy$6+L*kDezI%%HMUU`eKrFhym79EA0CTw81M%QvdQJZExVw`U z0BUFoX6j<-5I1K80B5nw6>+LNUKK;;qCkR04hTWC37~(3Kcg{)cIT?uWQ-~Bv24vB z8&Iv5)HLS-Hq4U8OY69z(?=LL%vu6TO9i{Sn&}z>kGBB08DS|5!z^wSrG*-iT6B>3 zf#%Xy7vOHZbIO=Cb5Ls>4-*iLvn{XzD;>`0Ott{pXxmY3o%{)F+OmeQs3Mirj1~y8 z6z~?2vcj?mr7&p(3Gw!j)5Usx8HYfpr2-N5Fc6zfdE5oa*07*#3}zkKq7o^|Fz1Lv zRx9MFNSr@va&bnO&Uxi#GDpN9ryg#&6G+g^$B<^$#X2itR`w8eP1B7SHqYYF@0@a5|CSP@!xC*YIWXPeVDjA+@uqKx+ zU!n^Mq1>uaxY^q>XPg35Bg?5$nvNnGS``WfwpnEvGht%12yLuVMXeZSYF5~ia68VE zGajcJ_SSP0pJ#1YLl&E6vVaE^9x*1dyi9goz0R2NaAUiqgW94r?816#QBz za(w@c7BFoZ)qMeC@g@MJbqS^kC~Z)GaWv7vCSTXqV__tGWE6kcIE|t@gCQV!5_T>o zTHut|46=&KXN5 zss*oJ^z!AbO%dv0OCwRRdMq?G+XK;v^KifFra{aGsQF0G+7ZT1X>-b9mJoQ;fQDFs zwfxb;40zKb@TUKXH60`YkV|!$WK^L=>&)PsDHOyU7_UkJD=9vT&mA;euDTy)n&MGj zchD|In}0b}tGBEr93Cx&dkC+k zl-CM*SIHt_G)c6mAW<}#+BpwhvKE^p>yqvULZE z$qirF70Eb*EnfwA07RDn@O;%nbb`2&n7bKHxESOpI>BfU)$nQv*xX>@>gmc8AY7_* z!Q?*m$2u7YY@>ChFQO0D$&rIn2%MrunPVCnphE@Shk%o0SscgQ@hHTFC^y;^Ekbd? zI^?o7p$VMvLAj*dOi_SsMKLFJ9-44g=aF$U+SY*4L30q|=1N-0OO~=YTF@()HK0Ta z>71EO1*%!Lpq6-lAwn_;TCdh}s5K%NbV2r26D2DY+^aDy)}l(iO4<3V4BAha>7O)IobPKTf7o05K4qFmr`OoIa{ri74GRnRF_A zE~3bBCJTH;Hc2uS@VVH%od8u%PP9;pkdM}Q3%G=6!!}TJWxJ}^1O}}F>a03N97IGn zlrU!o1G{w@Fe$D&xY`&nC7=`nWswV*HKqj&TkS|L59ev!RkhpXl(hjHbHQ>-Mub{X zw>N>?1l7rp#Yy{z>IBz+xZUYiCwll_=AFV&An)8rnn0XZmnmR&G~LdO50yBkC}Ip$ zaEMmkkINpu3}?xfg-^?Biohr=X102&zF@IXA*-l15b>aTOg<8t~3*RsuP@W-7G-k!MLJI8Mqz>s`FE)?ZZ%exMMhZe)}i(6a$6a z!I%OA?qzU7rqRkk9mP`ug~TE_*exjPF%|2QD7fQlX%M*2qICbEQ|aD$We7S1tQV1v zBVK<{36}lAg49Z<0%nsMO_?}j$&ts~=?cW?4!>0rYf_DNQ%6jWx7==;p!FXn8n{dY z?13MgcFZe^_0bivCzQnqUF%-;(g~$ znPZqK!!}V)BrWq?vTUAb746_tB4M4E5-kaP$^yFJWaskJ$VqdczG2g$zM;K04(&1Y z^c-h+bI0SZ+>K*9=g!clnk^6vIbgHZX>|xEjfM~Z3N78x3C@_<`C#hk-nr16V}?eC zjT|+jbIsHloy(`XJIBtR)_&F*-uztj_NQmMJnygmc;Cejv}di`VGXrjSS&3`=<-FI zj-7G8>$~8*3DJ0Q-p?P;KkB`7q3P2%e{u3nv1hg{n)JQ)&4h8(gySaPdHd^g;Ey&) zDmP)l>XrK*&P-bW_Ru>GKl|ly<%Q!fUGLC78gc(!%JZyz!;=^MX-6!t*p}4o8&|ZK z=ABo(YMj62PTewbgxdV33|30=k2-b z(p~R8e*Kf`rn~$9sr_C+hcVv)LX&+^sY4|h{1-oELL$dKFade8^mwd=-vcFbP#&Io<)L)M!O z&$j=%yZ4>5A9!%zc5(gZ!zHBe`7vit@vq4M&UrImeKvtve0n0lXS#_xyC zLI8?qA4G8Ng7d#=_5b;9X!M~AdBaF>L;r8_Iu$zYq|s(OWQLs(WVTu@N5Q>w?|;?X zZ%;TUwsLoGc*ewSuJYEm&Y$U+dhUv=-y1pd$t|D175~+c-z@v4aO<+tlG28)75}IH z(_h_u%oSIUd-n2M{_?=A5g$A-HhlWDFBY7+QGB4kxei*h_}k^3=f9)Z-Lz~c`(o_! z54O>!bAq$x?)~NCSIs+b*Y&C0Usj&~fc?{JY1h~TFX$iqc24`L6-`*$^r!bi+~hqk ztljf!`g_~ZA@bcrKP|*geftJ%$^DB)$a8OgV);Yon_r#s^~KcI$6oK>Y50ES)Lk1c zShxY-1D)7<;@vk--oC|~e(m@PW0sClT+Ocx*MD^Lc=Q2Z5gK&}_C3%LXlnP{jA4_Y ziA(-4;?G8D8Sf61$Si>0b8hJ3xl04i1Ej<7&ee8#2^mK+BA3VW!=&NDkY_(b~ zc6J(Pb*Q=4_M-mv>!%Datj$c^d8K#$m{psQ^M;FCj~oB%QBRCpyaTa502}+l=D(ag=bJg|OCvWiTiQz>eeSZE z$NqWym2nlLsU?ScK&6}*zvJf{=WE~*N&a|$d{APlb%paH;Pb@{XX@FXRdu_R%6q; z)32{DykU&ON{=`GzW>XeSHF2D`t{y@i+&-Ar|!D&uZi1U8uiSG(t-Vbh0ECKGwD6c z&Ut0}n!u^wZh~&z`Q*7L)U7wnzv29y#m^RO`s|ibcPEN>ty3tRU&hL8v_<8Lg?_V@GsGo9UAF9>X??3-hVTMS4`@$^u zx|dh)dG6MA-;TYUUbbYPx&1fp5BSQB`SpvQ>%TjknL7PGZ}sE8-nrx7T}!PSxp(GS dtyS(-FWx?>|HG%=f8>j+Z&)+$jh;_G`ad!6643wv diff --git a/build.assets/macos/tsh/tsh.provisionprofile b/build.assets/macos/tsh/tsh.provisionprofile index bd396c3542b0768db859b8e3077709572756749b..f3e60851997b89312d1520bf5cc25535b42a3f79 100644 GIT binary patch delta 7604 zcmZ`;3Eb1<{r7!&K{@w=2s#yq=s+uL+9Z8jL|m>WX`8f3o2HEuOw%?!()&morp0%N zBI?khbBcJ(P3Js8=QYtWC%SphV;r;NVrG&aa?R=NE$m1FosiQc6NGQs53H z!Rk>-=vy=94-*y^z;!;3V+{@~YTf=XX2QKLXJHUWCc9jW0^b4EZ$jNLxp$jiGvMEVJi!f4DTQJ86>A{}=ta;L4)@h(dl z6ifL{jqX&FZa<{TzE(ItfcKYAJ=CWM{eCt~`Eg!ik$4=daw2C^!q|L5BvuupWDXZ% zaU$%G3!K<6>G9u!{ZpnM-v8~4Sv{7;*~0#7L-{c*Okjc+S{;0ud?h zX37E|ZTh8}yBBuny|p6hD5s4PCsCHlkqSblH6xSnQY{80;e1t++W}|1nW70J=PPRU zc%s-Xy5VG_fuxEppV-7=1rIC3ret=V&KeajHpG#(hgG^RCISUhsKCV|wNkXxGC~5R z3Z1N8cDiG!N}%TKS;bN=-K|80Vwn$UR6&YciBP-L(5sn(V+4*fbO5TF?s$WbbDYRB zyvKoOc`9TOWnzSJ1q+pM0SnZzB35Zwij$6dDtcZHMB)*}6RlKzE?9`kloe*;xZakH znxiFAHJ31AP!XEVBRx#02`q^T7+yhGLM#vjR%A^Q10fWl7$z`I9Aiz4#Ikk@GEt7A zNle!97!uYyRVtCvM^sDncofA^$i_Q<*<=cM7uWSVu7kV~G>~j!iK7KGq$gRB>NqY) z42K12wv>a?JqGiHy1s&v2tZmF&1nqYP@-rd<%Ze{$U;>X%Um8U0z8=9J};o@fc=8; z=i5n`H$$bdEHy$UzMYEt#@aZNYwmeZ0qfZL#OPy_({XU#^T)u9MatB5>KlN%`64cW zwyf9GHI62T28{`lA2(S7K*~+Bv?Z0|4ORBHl5R;)xe+~0=c-y4ADf6OD`QYmqIfurScYzgZYb689M7^eeJOl>T8%r?LB=(9eW^yKD^MnY6aXd-+L$v^ErsI`#p_&t- znXFcgjF17u*V3vShS;o?CQS-=nt&usmeI$OwKC4Nb|`f!hvO7SS|EpF#m+cmf?i0> z?8yZzhU@#>%#aPTKobpju2$%D8x1q>RvKl|QEP>3upUc+wbtVi z*@$*KXtsozQW!@Y-EK>3XsU>*lFyZsx>3R%b|TGQ8*O>jL?@%CEhDeHYB2{^FF7zG z<_;ufA4)JmH~=@*VhoSFt+qpGP-L)_hH?=)-O2{>Fw=~Wh&tKH!SQw;b~FTcv+nF< z6};gnBSjc7V{q3WQ|c{OC8NPLF32qu|k_zDNo0Cy<>3A_ye``so;qFyjg8%-rHwqvy6Rs@!y z)d~F{C>Qff|aV~ zwjs*nDY|0|JwRF9vU82Yfr3+fSCPl%Rit}!Jdi4JEe=OG(h!ZL>`$* zOf>=CW%Ed{D1s9yLB<3yG_A`QC4Mh^GfS9_f902$R4;ms0 zL>5e13`oT|83!JK#0(tJdO)PW;ADwo{35V8aWe%6AeI8;CA$Ez2|8Hm79c+|MH@yh zRR$KtG*dzXH$lTLfG1d@R{a#`JEOmQYUSI>{Jjab%;%`6bav1CCF@xSysfaUIytB|--R}w}+rTx`Z z-es$qp}|0I;Xpf`z%4+{9J#MOYz1osz%T@$8X#>c?y7)ebpUByZ+5a>7HY&pMhpw~ zM`m8Vz&5iM)(Mjo%8`m~aIJ7`-?sjVGtTcZNdVa-W7CCIfB=M({vbk87{QWB9ivL& zVml(?-D=n!W6MFD>R4$ikmz(&KCGop$=@u7Q58H>P8TySUpUN%M;I#V$~vWNvk=H9 zv~o4)Z$>!EE6O_Qll?kZ%}S9F1uq39o!AJH;+xPD=S&|7-7I#`$c>F#uj%B?Rj&b&c#`ST0f`Fbl zQpGS6&{79uBUMuqIF&&5AH1npREop6x zz;?$Di|xvGo2}J0=>Uzk54nHnVVC#iImax=ORZwODhJU_l1@QBuUre`J^)f5 z+2@Am_Hgf*rvOm{)UIOwP|JjC{v?Ow)hb(4;JU}>@i^6PH(KYYnK!o&m>nz|Jjj#tcBfvRzYZm+FfKIh`DG$rLh?NjmrDz=RUT1}NSeUEBJ@t7*7TdItf z_bQl!KrC&g1O*V@zh@@MLj^bs8t>NVD2Mm}Pl8n2b#77&2C*0(z=A2H9D#rf=KN7t z%c_+LPpKVDB}GVf#70shRy18jwG3J-D=o&1Gmb!nZ|<#;s$0Q2Cx9&YV0ZG=r9n^5IER925?sm zm$7hkug(Y%;U-BpC6zWTptt+zI%~HvVw}7tfJ25Ft*YlvYDJbA*dJu|b5I5D$I)i??;?N|bo=nh5Mq{F#IF<){ zFL62Dj#QoLYB*puIIF^OkzlOuDuaqA=mh|`n26Gk#RNwZSifZF(_smFv z3h<<@9YtW|lYu0wu$U~7V21*)2$enPutk$2zF86bIh>?`XT$tKFI6^?3{+=IaR<}I z@`gLr2*={ta4?c~V?79Jw4o8D8ueIsbWbvY9XmqscElJ?a@ekpz*3U}2evYh@s7u@ zft6t~P=SKY1e;dIcKbkxSR4e3!F8!l>KuVv|M@#WKcJ%CURD-fj~_TD zqj}Mk3-K<_W)XBTlF>#Mr$$iEV%6*RW_&Kqr9yDOFpoyBgGPrWXmr9!S4|ik7#y5^ z;u))^{Od1+SMPY|rJud7mTw?Fs&FXn#w z(UtR-rDtX@dw2QP-yrwhaKilZbMrRLKkJ0%?`I6FH!RfxX9Qk*xb@r<+cqA=tSsDp z+-;9!zkK-IIZtgKS~_~qD&rqtPpQ9s<5wqEn5~=E&EzgVee=ro&cP%2f3sVkuiO#d z+Ii^3S2jQP$P3w-Cn|4Frh1LFk!SdSW?03DROP;*q zwNsB;fHeT$^pMFL41;4z0g}(at4Rgx% zsnl<8uCBSawP5W{-A{I0`^=X=`M!I~iN{=Y%J;6`_>^C`vw`0|}q@^|KL zI@xjd!uj`pIyhnAfPr6}`7U(kJJ7626Qm;#9vJv~=pbkk_!=BIpfAinnj$A1eQ{s^6QuJ54NZf!v`?4Q454`2wSz$9pR(v-1%cP%|> zaA0T<#QU3_csu+PMh6BCP##3Dd11$tP2x2Nj2>-1R(G!d{ODU|AN%MHQ}EL++IC;- z$hSj(I^^{;ia#7)-rIQ!)tvsGb^19MKK_cf4qyB9p|4_F5}PI+bT;$1AKtRy*?+!$ z!fCI+eQocEH#h#|xeY5qDc`yL%?F=J0qc!hV$x=Ws(W!$-L>IKg)d#5sR!{Z!v+Dofz+0^TYWuV8-{hEr# zcb0daV-kO0w9h{kw$zt*{}F%UclWNo+pBbLp2>*!PDj^mc;(UNWx0>p(>C1r!S!1X zS+oAhMAGJ#X?4-}Ux4KX&wlw{;@(Ftz2cM{gTiybi#p zF%F&Kseh0{4?Osn=3NtiwCt&mRy@7my8RbCESNWM?sT`uMvlu1p)fjzBgpOYja?%B z)}nTA%MrIOJ?@Cdj<_N7$0^60#XrP*tJ!;>dEWcXrQUPm^`YtsS8v+#M)tR7J@EI# zoYT#nFPwM3GM7L2$-5S=c;}(f9jnd8qKD@WoRq%fTyFNyH(d!Y`rgVDYg>JPl3%{z zyT+EpQTI*0efz;%ttH1?Rr!j3c|mK*splY1{?c*n*{`3;eL&7$*^?#teBzeig>OxJ zXU&VjcmI9=iC?eUw)o-PEo&!zw(-yU=Sw}?zkXv${dd8(^ZpAiA^e-3{^&W!Mdc*7 zHnsEv$9t#G3ZK#I9zHzz`J3OEc(A2K-@nY|7`<(>bj84}kt^UAmYnb*^2aIb@mp4$ ze$fMOx2r2hUe#CrZSu5N@vVng*s=*d`mu4yj1GFC(Ln^7wg)tW&;ig<_SJ%+L!mjV zzCFdOyRieXl!j8!;@#KK;F|fnu%u<`&Bk6}GR<^dM}Lsc)FIcjJ@*f+IkrFN`1uQU z6hLxxU<_K{8yX#$4K9YkF>TksQCfQ90CeV8{Tq(oKHanX>A`_H$M>&1c22+VI3xnu zgFb5LIA|es>{Sb{n!o0#-2=Xk9yl7`$gF(%ek?iZsKhnXhY##u z=RkYYezaL#=iRbq_LRXJ9zXIA!SfQGi{^az{%2?Z^pRJeE$w_n{Kq-ok6$|IqX!eB z{)?yPJ@n=8qqnU%cJ{nW|GkZSw($Eeab)>}FTJ-OzRzwPB#&J&_mRw;XF{Ba#d~F;a9tdHyrfu3{{?eL&l>y> zwD!+myu{^!MqmI#7=}Fxv|!Iai<5_3fIY9CyJOmq|319ngS(%* z*q!`C*B$=m#QPpRzPIBLW67wPx%b{Vw>zhtw=DDFhBq%mYuB7h|EptcS?+%4(mz7a z1UFURI_CKGqrpC5esc0tPvj1}`qZ1BnRn-mb?FoR!|GRVWD@btuU49eec;oAyD6G%YGbY zxUuiF>af1kiuWwc7{Ohd7OHon_tIoQ^UX&9Ps5eXVqNKDCvyoFrYnQmmjj5L;Y zQvt$_G7-uejUo+JLlIPAq9{Vsfe4qCbJ2=uL9P@BN*=V5h%u^6X0vrsD=3`B ztID}7#K*a!2-{QgTKKq%!&60$1!Sh|#nAc9G>@%|d&k{ntFa1O%q{gunk9A{#X~W*$j>IU~z=pMY1P^5a4r!rYMvc zz=%#_p%NLQL<@alRzxNQa<9n8Y?{yxp>;8hLJ2b4qDUqy z<+-Ae%W44mS&H=9DAHX^hESc4dm-?yN*2Z}#-d=XTjSxVE`xp^jwm9`D>A8ODGyf= zVpfHZqq<0f=fu2VLujizP9WV)0Yf1n?yX8qR26fq(&k|o=*U8en5zaF>p~heOK_y} zlf`(zLcs&Oi|GCkOx^_fnmd@c-A(9H&aHtq?cjuv5Hn}RJlFDT2#hKKR|+5nR>BaD zFC{!2B;kd2-EWaCa`VWeKl0&GRgQB7(c&%2o=lxa`2lQr*#T_OG*b96t~!m=C^XQI zD{Kr!xs*+}m6W1UC#j%GD_fkt2%m1`vk9k$1W=_7V#zvDB;^DNPGhKb0BF8J{**NV z;ZcfZIUI2U01-AiOf&$Pz%=0HF6e~LA2AjmTol&j+<(EtT~0v}QY{MAC`3R3JkgdF zi#;5R5k4B1TUjM%Dr-tT7LOEbbS)E4W4hvmTP>Sd^cSUS$!bpdO4Tq~%c5SPm@PR{ z4GS$tGX54RzraX{Z$@S!0n`rGmz1(Kwp(Thv-P+_X0ANX?Zs7HXR6b<0tb zv^xBbOsK)tHIZeRdQ*-#^9hA*mJwte+0~rdrDvop$sTW!&qoJ-7xiRy zfz?TlXlFxQgo)5thQ=apT>@StoO4$}rvw&N5#nfF%Cia!yQ*RgP#A{NiI^=*5e#q) z(VC1SWgM*@?jY)7=J1Vp!6Kr$M0_AvM;Qd9g0~Sa8jWNDhjSTPi>3ibX6Ytm6G*n< z4|8coqpN|KTV*hFj@KBK$?>g$F}a8y(DmryMv6Ux5~D2|qX^1xvV<}TXVzRnvu-SD zlg&=t9tP{KQgRtewNtgEO-b15R-H4_ln2L=h8u8ZQ6?K%K=d`)!vNKw0CY)RN$7f* z)dBaqs{R}}EimTbdy$6+L*kDezI%%HMUU`eKrFhym79EA0CTw81M%QvdQJZExVw`U z0BUFoX6j<-5I1K80B5nw6>+LNUKK;;qCkR04hTWC37~(3Kcg{)cIT?uWQ-~Bv24vB z8&Iv5)HLS-Hq4U8OY69z(?=LL%vu6TO9i{Sn&}z>kGBB08DS|5!z^wSrG*-iT6B>3 zf#%Xy7vOHZbIO=Cb5Ls>4-*iLvn{XzD;>`0Ott{pXxmY3o%{)F+OmeQs3Mirj1~y8 z6z~?2vcj?mr7&p(3Gw!j)5Usx8HYfpr2-N5Fc6zfdE5oa*07*#3}zkKq7o^|Fz1Lv zRx9MFNSr@va&bnO&Uxi#GDpN9ryg#&6G+g^$B<^$#X2itR`w8eP1B7SHqYYF@0@a5|CSP@!xC*YIWXPeVDjA+@uqKx+ zU!n^Mq1>uaxY^q>XPg35Bg?5$nvNnGS``WfwpnEvGht%12yLuVMXeZSYF5~ia68VE zGajcJ_SSP0pJ#1YLl&E6vVaE^9x*1dyi9goz0R2NaAUiqgW94r?816#QBz za(w@c7BFoZ)qMeC@g@MJbqS^kC~Z)GaWv7vCSTXqV__tGWE6kcIE|t@gCQV!5_T>o zTHut|46=&KXN5 zss*oJ^z!AbO%dv0OCwRRdMq?G+XK;v^KifFra{aGsQF0G+7ZT1X>-b9mJoQ;fQDFs zwfxb;40zKb@TUKXH60`YkV|!$WK^L=>&)PsDHOyU7_UkJD=9vT&mA;euDTy)n&MGj zchD|In}0b}tGBEr93Cx&dkC+k zl-CM*SIHt_G)c6mAW<}#+BpwhvKE^p>yqvULZE z$qirF70Eb*EnfwA07RDn@O;%nbb`2&n7bKHxESOpI>BfU)$nQv*xX>@>gmc8AY7_* z!Q?*m$2u7YY@>ChFQO0D$&rIn2%MrunPVCnphE@Shk%o0SscgQ@hHTFC^y;^Ekbd? zI^?o7p$VMvLAj*dOi_SsMKLFJ9-44g=aF$U+SY*4L30q|=1N-0OO~=YTF@()HK0Ta z>71EO1*%!Lpq6-lAwn_;TCdh}s5K%NbV2r26D2DY+^aDy)}l(iO4<3V4BAha>7O)IobPKTf7o05K4qFmr`OoIa{ri74GRnRF_A zE~3bBCJTH;Hc2uS@VVH%od8u%PP9;pkdM}Q3%G=6!!}TJWxJ}^1O}}F>a03N97IGn zlrU!o1G{w@Fe$D&xY`&nC7=`nWswV*HKqj&TkS|L59ev!RkhpXl(hjHbHQ>-Mub{X zw>N>?1l7rp#Yy{z>IBz+xZUYiCwll_=AFV&An)8rnn0XZmnmR&G~LdO50yBkC}Ip$ zaEMmkkINpu3}?xfg-^?Biohr=X102&zF@IXA*-l15b>aTOg<8t~3*RsuP@W-7G-k!MLJI8Mqz>s`FE)?ZZ%exMMhZe)}i(6a$6a z!I%OA?qzU7rqRkk9mP`ug~TE_*exjPF%|2QD7fQlX%M*2qICbEQ|aD$We7S1tQV1v zBVK<{36}lAg49Z<0%nsMO_?}j$&ts~=?cW?4!>0rYf_DNQ%6jWx7==;p!FXn8n{dY z?13MgcFZe^_0bivCzQnqUF%-;(g~$ znPZqK!!}V)BrWq?vTUAb746_tB4M4E5-kaP$^yFJWaskJ$VqdczG2g$zM;K04(&1Y z^c-h+bI0SZ+>K*9=g!clnk^6vIbgHZX>|xEjfM~Z3N78x3C@_<`C#hk-nr16V}?eC zjT|+jbIsHloy(`XJIBtR)_&F*-uztj_NQmMJnygmc;Cejv}di`VGXrjSS&3`=<-FI zj-7G8>$~8*3DJ0Q-p?P;KkB`7q3P2%e{u3nv1hg{n)JQ)&4h8(gySaPdHd^g;Ey&) zDmP)l>XrK*&P-bW_Ru>GKl|ly<%Q!fUGLC78gc(!%JZyz!;=^MX-6!t*p}4o8&|ZK z=ABo(YMj62PTewbgxdV33|30=k2-b z(p~R8e*Kf`rn~$9sr_C+hcVv)LX&+^sY4|h{1-oELL$dKFade8^mwd=-vcFbP#&Io<)L)M!O z&$j=%yZ4>5A9!%zc5(gZ!zHBe`7vit@vq4M&UrImeKvtve0n0lXS#_xyC zLI8?qA4G8Ng7d#=_5b;9X!M~AdBaF>L;r8_Iu$zYq|s(OWQLs(WVTu@N5Q>w?|;?X zZ%;TUwsLoGc*ewSuJYEm&Y$U+dhUv=-y1pd$t|D175~+c-z@v4aO<+tlG28)75}IH z(_h_u%oSIUd-n2M{_?=A5g$A-HhlWDFBY7+QGB4kxei*h_}k^3=f9)Z-Lz~c`(o_! z54O>!bAq$x?)~NCSIs+b*Y&C0Usj&~fc?{JY1h~TFX$iqc24`L6-`*$^r!bi+~hqk ztljf!`g_~ZA@bcrKP|*geftJ%$^DB)$a8OgV);Yon_r#s^~KcI$6oK>Y50ES)Lk1c zShxY-1D)7<;@vk--oC|~e(m@PW0sClT+Ocx*MD^Lc=Q2Z5gK&}_C3%LXlnP{jA4_Y ziA(-4;?G8D8Sf61$Si>0b8hJ3xl04i1Ej<7&ee8#2^mK+BA3VW!=&NDkY_(b~ zc6J(Pb*Q=4_M-mv>!%Datj$c^d8K#$m{psQ^M;FCj~oB%QBRCpyaTa502}+l=D(ag=bJg|OCvWiTiQz>eeSZE z$NqWym2nlLsU?ScK&6}*zvJf{=WE~*N&a|$d{APlb%paH;Pb@{XX@FXRdu_R%6q; z)32{DykU&ON{=`GzW>XeSHF2D`t{y@i+&-Ar|!D&uZi1U8uiSG(t-Vbh0ECKGwD6c z&Ut0}n!u^wZh~&z`Q*7L)U7wnzv29y#m^RO`s|ibcPEN>ty3tRU&hL8v_<8Lg?_V@GsGo9UAF9>X??3-hVTMS4`@$^u zx|dh)dG6MA-;TYUUbbYPx&1fp5BSQB`SpvQ>%TjknL7PGZ}sE8-nrx7T}!PSxp(GS dtyS(-FWx?>|HG%=f8>j+Z&)+$jh;_G`ad!6643wv From a7af0c79403e4c64a9560b0b2700224e05b260ce Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:20:20 -0400 Subject: [PATCH 096/347] update eref (#69244) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 8303b4cdbf09..a8918e492dd2 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 8303b4cdbf09304a23e37d814cb2868987f1431c +Subproject commit a8918e492dd2c57a5b91536071648f492e4a7794 From 5bd35c22949a6b862b17c45127f53c8d28c81bfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:48:12 +0100 Subject: [PATCH 097/347] Bump github.com/sigstore/sigstore-go from 1.2.0 to 1.2.1 (#69249) * Bump github.com/sigstore/sigstore-go from 1.2.0 to 1.2.1 Bumps [github.com/sigstore/sigstore-go](https://github.com/sigstore/sigstore-go) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/sigstore/sigstore-go/releases) - [Commits](https://github.com/sigstore/sigstore-go/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: github.com/sigstore/sigstore-go dependency-version: 1.2.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * go mod tidy all --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tiago Silva --- build.assets/tooling/go.mod | 2 +- build.assets/tooling/go.sum | 4 ++-- e2e/runner/go.mod | 2 +- e2e/runner/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integrations/event-handler/go.mod | 2 +- integrations/event-handler/go.sum | 4 ++-- integrations/terraform-mwi/go.mod | 2 +- integrations/terraform-mwi/go.sum | 4 ++-- integrations/terraform/go.mod | 2 +- integrations/terraform/go.sum | 4 ++-- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index 5f6399484461..ae2af5e6d09b 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -235,7 +235,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.0 // indirect + github.com/sigstore/sigstore-go v1.2.1 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index 9514fb25d232..d187dff9e12b 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -732,8 +732,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= diff --git a/e2e/runner/go.mod b/e2e/runner/go.mod index b9bae1b985b7..fd1e1a6c68b5 100644 --- a/e2e/runner/go.mod +++ b/e2e/runner/go.mod @@ -230,7 +230,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.0 // indirect + github.com/sigstore/sigstore-go v1.2.1 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect diff --git a/e2e/runner/go.sum b/e2e/runner/go.sum index 8d616c16f7e5..f2e4a8cd0929 100644 --- a/e2e/runner/go.sum +++ b/e2e/runner/go.sum @@ -748,8 +748,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= diff --git a/go.mod b/go.mod index e63f6f3b6112..0a84c089f1f0 100644 --- a/go.mod +++ b/go.mod @@ -218,7 +218,7 @@ require ( github.com/sigstore/cosign/v3 v3.0.6 github.com/sigstore/protobuf-specs v0.5.1 github.com/sigstore/sigstore v1.10.8 - github.com/sigstore/sigstore-go v1.2.0 + github.com/sigstore/sigstore-go v1.2.1 github.com/sijms/go-ora/v2 v2.9.0 github.com/snowflakedb/gosnowflake v1.17.0 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 4ea183561bcf..c13062b3970b 100644 --- a/go.sum +++ b/go.sum @@ -1260,8 +1260,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index bffa34989d23..91566739aa3e 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -298,7 +298,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.0 // indirect + github.com/sigstore/sigstore-go v1.2.1 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index edd167d9becf..61e9a9cd1207 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -838,8 +838,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= diff --git a/integrations/terraform-mwi/go.mod b/integrations/terraform-mwi/go.mod index e911b906e29f..161251993cec 100644 --- a/integrations/terraform-mwi/go.mod +++ b/integrations/terraform-mwi/go.mod @@ -403,7 +403,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.0 // indirect + github.com/sigstore/sigstore-go v1.2.1 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect diff --git a/integrations/terraform-mwi/go.sum b/integrations/terraform-mwi/go.sum index 6643691e022e..0e831500141c 100644 --- a/integrations/terraform-mwi/go.sum +++ b/integrations/terraform-mwi/go.sum @@ -1189,8 +1189,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index 1c6d0dba983e..eee6a6767ead 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -405,7 +405,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.0 // indirect + github.com/sigstore/sigstore-go v1.2.1 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index 523c99cf4b6b..d79736400988 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -1350,8 +1350,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= -github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= From 85cd70ee13199f7a0cccc7c82b8f17dadb108a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Smoli=C5=84ski?= Date: Sat, 1 Aug 2026 18:23:04 +0200 Subject: [PATCH 098/347] Fix AWS ProvisionedThroughputExceededException error conv logic (#63757) --- lib/events/dynamoevents/dynamoevents.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index ab99dbfa2361..54cb262a7a04 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -1549,8 +1549,8 @@ func convertError(err error) error { } var collectionLimitExceededError *dynamodbtypes.ItemCollectionSizeLimitExceededException - if errors.As(err, ¬FoundError) { - return trace.BadParameter("%s", collectionLimitExceededError.ErrorMessage()) + if errors.As(err, &collectionLimitExceededError) { + return trace.LimitExceeded("%s", collectionLimitExceededError.ErrorMessage()) } var internalError *dynamodbtypes.InternalServerError From 2fa7acb6b3be0708e1e16d72c11d5b1254c7cfba Mon Sep 17 00:00:00 2001 From: Dan Share <10600907+danielashare@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:31:17 +0100 Subject: [PATCH 099/347] Add ARM Network dependency (#69093) * Add ARM Network dependency * Deduplicate scale set names * make go-mod-tidy-all * Add ListAll and address feedback * Limit resource group for scale sets * Add RG check * case-insensitive sub id check --- build.assets/tooling/go.sum | 2 + e2e/runner/go.sum | 2 + go.mod | 1 + go.sum | 2 + integrations/event-handler/go.mod | 1 + integrations/event-handler/go.sum | 2 + integrations/terraform-mwi/go.mod | 1 + integrations/terraform-mwi/go.sum | 2 + integrations/terraform/go.mod | 1 + integrations/terraform/go.sum | 2 + lib/cloud/azure/mocks.go | 56 +++ lib/cloud/azure/network.go | 270 ++++++++++++++ lib/cloud/azure/network_integration_test.go | 158 ++++++++ lib/cloud/azure/network_property_test.go | 253 +++++++++++++ lib/cloud/azure/network_test.go | 379 ++++++++++++++++++++ 15 files changed, 1132 insertions(+) create mode 100644 lib/cloud/azure/network.go create mode 100644 lib/cloud/azure/network_integration_test.go create mode 100644 lib/cloud/azure/network_property_test.go create mode 100644 lib/cloud/azure/network_test.go diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index d187dff9e12b..89c3ffcf9ca9 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -72,6 +72,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/e2e/runner/go.sum b/e2e/runner/go.sum index f2e4a8cd0929..e036b7c94088 100644 --- a/e2e/runner/go.sum +++ b/e2e/runner/go.sum @@ -58,6 +58,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/go.mod b/go.mod index 0a84c089f1f0..cb2f57cf9c8f 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 diff --git a/go.sum b/go.sum index c13062b3970b..e0fe73fc57e4 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 91566739aa3e..5ab528aa3ab4 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -44,6 +44,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index 61e9a9cd1207..9fe0dc6bc6b0 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -65,6 +65,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/integrations/terraform-mwi/go.mod b/integrations/terraform-mwi/go.mod index 161251993cec..6a816665e146 100644 --- a/integrations/terraform-mwi/go.mod +++ b/integrations/terraform-mwi/go.mod @@ -47,6 +47,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect diff --git a/integrations/terraform-mwi/go.sum b/integrations/terraform-mwi/go.sum index 0e831500141c..5544031e4aa1 100644 --- a/integrations/terraform-mwi/go.sum +++ b/integrations/terraform-mwi/go.sum @@ -91,6 +91,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index eee6a6767ead..bb9bdb9b42f5 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -56,6 +56,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index d79736400988..454cb38ec348 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -122,6 +122,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= diff --git a/lib/cloud/azure/mocks.go b/lib/cloud/azure/mocks.go index c4619c981d40..9036f40cf80c 100644 --- a/lib/cloud/azure/mocks.go +++ b/lib/cloud/azure/mocks.go @@ -31,6 +31,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3" @@ -763,3 +764,58 @@ func NewUserAssignedIdentity(subscription, resourceGroupName, resourceName, clie }, } } + +// ARMNetworkMock mocks armnetwork.InterfacesClient. +type ARMNetworkMock struct { + // NetworkInterfaces maps resource group name to the standalone NICs in it. + NetworkInterfaces map[string][]*armnetwork.Interface + // VMSSNetworkInterfaces maps "/" to the NICs + // of that uniform scale set. + VMSSNetworkInterfaces map[string][]*armnetwork.Interface + // VMSSListErrs maps scale set names to an error returned when listing + // that scale set's NICs. + VMSSListErrs map[string]error + // NoAuth indicates that the mock should return an access denied error + // for all requests. + NoAuth bool +} + +// Fail if ARMNetworkMock does not implement networkInterfacesLister. +var _ networkInterfacesLister = (*ARMNetworkMock)(nil) + +func (m *ARMNetworkMock) NewListPager(resourceGroup string, _ *armnetwork.InterfacesClientListOptions) *runtime.Pager[armnetwork.InterfacesClientListResponse] { + return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListResponse, error) { + return armnetwork.InterfacesClientListResponse{ + InterfaceListResult: armnetwork.InterfaceListResult{ + Value: m.NetworkInterfaces[resourceGroup], + }, + }, nil + }) +} + +func (m *ARMNetworkMock) NewListAllPager(_ *armnetwork.InterfacesClientListAllOptions) *runtime.Pager[armnetwork.InterfacesClientListAllResponse] { + return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListAllResponse, error) { + var allNics []*armnetwork.Interface + for _, nics := range m.NetworkInterfaces { + allNics = append(allNics, nics...) + } + return armnetwork.InterfacesClientListAllResponse{ + InterfaceListResult: armnetwork.InterfaceListResult{ + Value: allNics, + }, + }, nil + }) +} + +func (m *ARMNetworkMock) NewListVirtualMachineScaleSetNetworkInterfacesPager(resourceGroup, scaleSetName string, _ *armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesOptions) *runtime.Pager[armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse] { + return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse, error) { + if err := m.VMSSListErrs[scaleSetName]; err != nil { + return armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse{}, err + } + return armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse{ + InterfaceListResult: armnetwork.InterfaceListResult{ + Value: m.VMSSNetworkInterfaces[resourceGroup+"/"+scaleSetName], + }, + }, nil + }) +} diff --git a/lib/cloud/azure/network.go b/lib/cloud/azure/network.go new file mode 100644 index 000000000000..d6a8502e3bcc --- /dev/null +++ b/lib/cloud/azure/network.go @@ -0,0 +1,270 @@ +// Teleport +// Copyright (C) 2026 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 azure + +import ( + "context" + "log/slog" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/utils/slices" +) + +// IPConfiguration represents an Azure network interface IP configuration. +type IPConfiguration struct { + // Primary indicates whether this IP configuration is the primary one for the network interface. + Primary bool + // PrivateIP is the private IP address assigned to this IP configuration. This + // field may be in CIDR notation, if this isn't the Primary IP configuration. + PrivateIP string +} + +// NetworkInterface represents an Azure network interface. +type NetworkInterface struct { + // ID is the resource ID of the network interface. + ID string + // Name is the resource name of the network interface. + Name string + // Primary indicates whether this network interface is the primary one for the + // virtual machine or virtual machine scale set instance. + Primary bool + // AttachedVMID is the resource ID of the virtual machine the network + // interface is attached to. Empty for network interfaces not attached to any + // virtual machine. Resource IDs are not casing-stable across Azure APIs, so + // compare case-insensitively when matching against VM resource IDs. + AttachedVMID string + // IPConfigurations is a list of IP configurations associated with the network + // interface. + IPConfigurations []IPConfiguration +} + +// networkInterfacesLister is satisfied by *armnetwork.InterfacesClient. +type networkInterfacesLister interface { + // NewListPager lists Azure network interfaces in the given resource group. + NewListPager(resourceGroup string, opts *armnetwork.InterfacesClientListOptions) *runtime.Pager[armnetwork.InterfacesClientListResponse] + // NewListAllPager lists all Azure network interfaces in the subscription. + NewListAllPager(opts *armnetwork.InterfacesClientListAllOptions) *runtime.Pager[armnetwork.InterfacesClientListAllResponse] + // NewListVirtualMachineScaleSetNetworkInterfacesPager lists Azure network interfaces in the given resource group and scale set. + NewListVirtualMachineScaleSetNetworkInterfacesPager(resourceGroup, scaleSetName string, opts *armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesOptions) *runtime.Pager[armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse] +} + +// NetworkInterfacesClient is an interface for listing Azure network interfaces. +type NetworkInterfacesClient interface { + // ListNetworkInterfaces lists all network interfaces in the given resource group. + ListNetworkInterfaces(ctx context.Context, resourceGroup string, scaleSetIDs ...string) ([]*NetworkInterface, error) +} + +type networkInterfacesClient struct { + networkInterfacesLister networkInterfacesLister + logger *slog.Logger + // subscriptionID is the Azure subscription ID to list network interfaces from. + // This is used to validate that any scale set IDs passed are in the same + // subscription. + subscriptionID string +} + +func NewNetworkInterfacesClient(subscriptionID string, cred azcore.TokenCredential, options *arm.ClientOptions) (NetworkInterfacesClient, error) { + networkInterfacesClient, err := armnetwork.NewInterfacesClient(subscriptionID, cred, options) + if err != nil { + return nil, trace.Wrap(err, "failed to create Azure network interfaces client") + } + + config := NetworkInterfacesClientConfig{ + NetworkInterfacesAPI: networkInterfacesClient, + SubscriptionID: subscriptionID, + } + return NewNetworkInterfacesClientByAPI(config), nil +} + +// NetworkInterfacesClientConfig is a configuration struct for creating a +// NetworkInterfacesClient. +type NetworkInterfacesClientConfig struct { + NetworkInterfacesAPI networkInterfacesLister + Logger *slog.Logger + // SubscriptionID is the Azure subscription ID to list network interfaces from. + // This is used to validate that any scale set IDs passed are in the same + // subscription. + SubscriptionID string +} + +func NewNetworkInterfacesClientByAPI(config NetworkInterfacesClientConfig) NetworkInterfacesClient { + if config.Logger == nil { + config.Logger = slog.Default().With(teleport.ComponentKey, "azure_networkinterfaces_client") + } + + return &networkInterfacesClient{ + networkInterfacesLister: config.NetworkInterfacesAPI, + logger: config.Logger, + subscriptionID: config.SubscriptionID, + } +} + +// ListNetworkInterfaces lists all network interfaces in the given resource group. +// +// scaleSetIDs is a list of resource IDs for the virtual machine scale sets. +// e.g. "/subscriptions//resourceGroups//providers/Microsoft.Compute/virtualMachineScaleSets/" +// If a scale set ID is not in the same subscription or resource group, it is +// skipped with a warning. +func (c *networkInterfacesClient) ListNetworkInterfaces(ctx context.Context, resourceGroup string, scaleSetIDs ...string) ([]*NetworkInterface, error) { + standardAndFlexibleNICs, err := c.listStandardAndFlexibleNICs(ctx, resourceGroup) + if err != nil { + return nil, trace.Wrap(err, "failed to list standard and flexible VMSS NICs") + } + + // Currently, we're more concerned with listing standard and flexible VMSS + // NICs, so if a uniform VMSS NIC listing fails, it is logged and the process + // continues. + uniformNICs := c.listUniformNICs(ctx, resourceGroup, scaleSetIDs) + + return append(standardAndFlexibleNICs, uniformNICs...), nil +} + +func (c *networkInterfacesClient) listStandardAndFlexibleNICs(ctx context.Context, resourceGroup string) ([]*NetworkInterface, error) { + var pager apiPager[armnetwork.Interface] + if resourceGroup == types.Wildcard { + pager = newAPIPager( + c.networkInterfacesLister.NewListAllPager(nil), + func(resp armnetwork.InterfacesClientListAllResponse) []*armnetwork.Interface { + return resp.InterfaceListResult.Value + }, + ) + } else { + pager = newAPIPager( + c.networkInterfacesLister.NewListPager(resourceGroup, nil), + func(resp armnetwork.InterfacesClientListResponse) []*armnetwork.Interface { + return resp.InterfaceListResult.Value + }, + ) + } + return c.collectNICs(ctx, pager) +} + +func (c *networkInterfacesClient) listUniformNICs(ctx context.Context, resourceGroup string, scaleSetIDs []string) []*NetworkInterface { + var allNICs []*NetworkInterface + for _, scaleSetID := range slices.DeduplicateKey(scaleSetIDs, strings.ToLower) { + id, err := arm.ParseResourceID(scaleSetID) + if err != nil { + c.logger.WarnContext(ctx, "Failed to parse scale set ID", "scale_set_id", scaleSetID, "error", err) + continue + } + + // Check the resource ID is for a uniform VMSS in the same subscription and resource group. + isComputeNamespace := strings.EqualFold(id.ResourceType.Namespace, "Microsoft.Compute") + isVMSSResourceType := strings.EqualFold(id.ResourceType.Type, "virtualMachineScaleSets") + if !isComputeNamespace || !isVMSSResourceType { + c.logger.WarnContext(ctx, "Skipping non-uniform scale set ID", "scale_set_id", scaleSetID) + continue + } + if !strings.EqualFold(id.SubscriptionID, c.subscriptionID) { + c.logger.WarnContext(ctx, "Skipping scale set ID in a different subscription", "scale_set_id", scaleSetID, "subscription_id", id.SubscriptionID) + continue + } + if resourceGroup != types.Wildcard && !strings.EqualFold(id.ResourceGroupName, resourceGroup) { + c.logger.WarnContext(ctx, "Skipping scale set ID in a different resource group", "scale_set_id", scaleSetID, "resource_group", resourceGroup) + continue + } + + pager := newAPIPager( + c.networkInterfacesLister.NewListVirtualMachineScaleSetNetworkInterfacesPager(id.ResourceGroupName, id.Name, nil), + func(resp armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse) []*armnetwork.Interface { + return resp.InterfaceListResult.Value + }, + ) + nics, err := c.collectNICs(ctx, pager) + if err != nil { + c.logger.WarnContext(ctx, "Failed to list NICs from Uniform VMSS", "scale_set_id", scaleSetID, "error", err) + continue + } + allNICs = append(allNICs, nics...) + } + return allNICs +} + +func (c *networkInterfacesClient) collectNICs(ctx context.Context, pager apiPager[armnetwork.Interface]) ([]*NetworkInterface, error) { + var nics []*NetworkInterface + for pager.more() { + res, err := pager.nextPage(ctx) + if err != nil { + return nil, trace.Wrap(ConvertResponseError(err)) + } + for _, rawNIC := range res { + if rawNIC == nil { + continue + } + nic, err := nicFromArmNetworkInterface(rawNIC) + if err != nil { + c.logger.DebugContext(ctx, "Skipping Azure Network Interface", + "resource_id", StringVal(rawNIC.ID), + "error", err, + ) + continue + } + nics = append(nics, nic) + } + } + return nics, nil +} + +func nicFromArmNetworkInterface(nic *armnetwork.Interface) (*NetworkInterface, error) { + if nic == nil { + return nil, trace.BadParameter("nil armnetwork.Interface") + } + + var primary bool + var attachedVMID string + ipConfigs := []IPConfiguration{} + if nic.Properties != nil { + primary = BoolVal(nic.Properties.Primary) + if nic.Properties.VirtualMachine != nil { + attachedVMID = StringVal(nic.Properties.VirtualMachine.ID) + } + if nic.Properties.IPConfigurations != nil { + for _, ipConfig := range nic.Properties.IPConfigurations { + if ipConfig != nil && ipConfig.Properties != nil { + ipConfigs = append(ipConfigs, IPConfiguration{ + Primary: BoolVal(ipConfig.Properties.Primary), + PrivateIP: StringVal(ipConfig.Properties.PrivateIPAddress), + }) + } + } + } + } + + return &NetworkInterface{ + ID: StringVal(nic.ID), + Name: StringVal(nic.Name), + Primary: primary, + AttachedVMID: attachedVMID, + IPConfigurations: ipConfigs, + }, nil +} + +// BoolVal converts a pointer of a bool to a bool value. +func BoolVal(b *bool) bool { + if b != nil { + return *b + } + return false +} diff --git a/lib/cloud/azure/network_integration_test.go b/lib/cloud/azure/network_integration_test.go new file mode 100644 index 000000000000..c13b4c164db4 --- /dev/null +++ b/lib/cloud/azure/network_integration_test.go @@ -0,0 +1,158 @@ +// Teleport +// Copyright (C) 2026 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 azure + +import ( + "context" + "os" + "strings" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/stretchr/testify/require" +) + +// TestNetworkInterfacesClientLiveList exercises the network interfaces client +// against a real Azure subscription, listing the standalone NICs (regular VMs +// and flexible scale set VMs) in a resource group. It is skipped unless +// TELEPORT_TEST_AZURE is set. +// +// Prerequisites: +// - Authenticate so DefaultAzureCredential can find a credential, e.g. run +// `az login`, or set AZURE_TENANT_ID / AZURE_CLIENT_ID / AZURE_CLIENT_SECRET. +// The identity needs Microsoft.Network/networkInterfaces/read in the +// resource group. +// - Set AZURE_SUBSCRIPTION_ID to the subscription to list from. +// - Set AZURE_RESOURCE_GROUP to a resource group with at least one NIC. +// +// Run with: +// +// TELEPORT_TEST_AZURE=1 \ +// AZURE_SUBSCRIPTION_ID= \ +// AZURE_RESOURCE_GROUP= \ +// go test ./lib/cloud/azure/ -run TestNetworkInterfacesClientLiveList -v +func TestNetworkInterfacesClientLiveList(t *testing.T) { + nicClient, resourceGroup := newLiveNetworkInterfacesClient(t) + + ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second) + defer cancel() + + nics, err := nicClient.ListNetworkInterfaces(ctx, resourceGroup) + require.NoError(t, err) + + logNICs(t, nics) +} + +// TestNetworkInterfacesClientLiveListUniformVMSS exercises the network +// interfaces client against a real Azure subscription, listing the NICs of +// uniform scale set VMs alongside the standalone NICs in the resource group. +// It is skipped unless TELEPORT_TEST_AZURE, TELEPORT_TEST_AZURE_SUBSCRIPTION_ID +// and TELEPORT_TEST_AZURE_SCALE_SET_IDS are set. +// Prerequisites are the same as TestNetworkInterfacesClientLiveList, plus: +// - Set TELEPORT_TEST_AZURE_SCALE_SET_IDS to a comma-separated list of +// uniform VM scale set IDs in the resource group, e.g. "/subscriptions//resourceGroups//providers/Microsoft.Compute/virtualMachineScaleSets/vmss1,/subscriptions//resourceGroups//providers/Microsoft.Compute/virtualMachineScaleSets/vmss2". +// +// Run with: +// +// TELEPORT_TEST_AZURE=1 \ +// AZURE_SUBSCRIPTION_ID= \ +// AZURE_RESOURCE_GROUP= \ +// TELEPORT_TEST_AZURE_SCALE_SET_IDS= \ +// go test ./lib/cloud/azure/ -run TestNetworkInterfacesClientLiveListUniformVMSS -v +func TestNetworkInterfacesClientLiveListUniformVMSS(t *testing.T) { + nicClient, resourceGroup := newLiveNetworkInterfacesClient(t) + + var scaleSetIDs []string + for id := range strings.SplitSeq(os.Getenv("TELEPORT_TEST_AZURE_SCALE_SET_IDS"), ",") { + if id = strings.TrimSpace(id); id != "" { + scaleSetIDs = append(scaleSetIDs, id) + } + } + if len(scaleSetIDs) == 0 { + t.Skip("Set TELEPORT_TEST_AZURE_SCALE_SET_IDS to a comma-separated list of uniform VM scale set IDs.") + } + + ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second) + defer cancel() + + nics, err := nicClient.ListNetworkInterfaces(ctx, resourceGroup, scaleSetIDs...) + require.NoError(t, err) + + // Uniform VMSS NICs are attached to a scale set VM instance, so they can + // be identified by their attached VM's resource ID. + var uniformNICs []*NetworkInterface + for _, nic := range nics { + if strings.Contains(strings.ToLower(nic.AttachedVMID), "/virtualmachinescalesets/") { + uniformNICs = append(uniformNICs, nic) + } + } + require.NotEmpty(t, uniformNICs, "expected the named scale sets to have at least one NIC") + + // A uniform VMSS NIC is a child resource of the scale set VM it is + // attached to, so its ID must extend its attached VM's ID. + for _, nic := range uniformNICs { + require.True(t, + strings.HasPrefix(strings.ToLower(nic.ID), strings.ToLower(nic.AttachedVMID)+"/"), + "expected NIC %q to be a child of its attached VM %q", nic.ID, nic.AttachedVMID) + } + + logNICs(t, uniformNICs) +} + +// newLiveNetworkInterfacesClient builds a NetworkInterfacesClient backed by a +// real Azure credential, skipping the test unless the live-test environment +// variables are set. +func newLiveNetworkInterfacesClient(t *testing.T) (NetworkInterfacesClient, string) { + t.Helper() + + if os.Getenv("TELEPORT_TEST_AZURE") == "" { + t.Skip("Set TELEPORT_TEST_AZURE to run this test against a real Azure subscription.") + } + subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID") + if subscriptionID == "" { + t.Skip("Set AZURE_SUBSCRIPTION_ID to the subscription to list network interfaces from.") + } + resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP") + if resourceGroup == "" { + t.Skip("Set AZURE_RESOURCE_GROUP to a resource group with at least one network interface.") + } + + cred, err := azidentity.NewDefaultAzureCredential(nil) + require.NoError(t, err, "failed to build a default Azure credential; have you run `az login`?") + + nicClient, err := NewNetworkInterfacesClient(subscriptionID, cred, nil) + require.NoError(t, err) + + return nicClient, resourceGroup +} + +// logNICs logs the listed NICs so live-test runs can be verified by eye, and +// sanity-checks the invariants every real Azure NIC satisfies. +func logNICs(t *testing.T, nics []*NetworkInterface) { + t.Helper() + + t.Logf("listed %d NIC(s)", len(nics)) + for _, nic := range nics { + require.NotEmpty(t, nic.ID) + require.NotEmpty(t, nic.Name) + t.Logf(" NIC %q: primary=%t attached VM=%q", nic.Name, nic.Primary, nic.AttachedVMID) + for _, ipConfig := range nic.IPConfigurations { + t.Logf(" ipconfig: primary=%t private IP=%q", ipConfig.Primary, ipConfig.PrivateIP) + } + } +} diff --git a/lib/cloud/azure/network_property_test.go b/lib/cloud/azure/network_property_test.go new file mode 100644 index 000000000000..e7bf19c1f06c --- /dev/null +++ b/lib/cloud/azure/network_property_test.go @@ -0,0 +1,253 @@ +// Teleport +// Copyright (C) 2026 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 azure + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "github.com/gravitational/teleport/lib/utils/log/logtest" +) + +// ipConfigGen returns a rapid.Generator that produces arbitrary Azure network +// interface IP configurations. +func ipConfigGen() *rapid.Generator[armnetwork.InterfaceIPConfiguration] { + return rapid.Custom(func(t *rapid.T) armnetwork.InterfaceIPConfiguration { + return armnetwork.InterfaceIPConfiguration{ + Name: rapid.Ptr(rapid.String(), true).Draw(t, "ipconfig_name"), + Properties: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.InterfaceIPConfigurationPropertiesFormat { + return armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: rapid.Ptr(rapid.String(), true).Draw(t, "private_ip"), + Primary: rapid.Ptr(rapid.Bool(), true).Draw(t, "ipconfig_primary"), + } + }), true).Draw(t, "ipconfig_props"), + } + }) +} + +// nicGen returns a rapid.Generator that produces arbitrary Azure network +// interfaces. +func nicGen() *rapid.Generator[*armnetwork.Interface] { + return rapid.Custom(func(t *rapid.T) *armnetwork.Interface { + return &armnetwork.Interface{ + ID: rapid.Ptr(rapid.String(), true).Draw(t, "id"), + Name: rapid.Ptr(rapid.String(), true).Draw(t, "name"), + Properties: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.InterfacePropertiesFormat { + return armnetwork.InterfacePropertiesFormat{ + Primary: rapid.Ptr(rapid.Bool(), true).Draw(t, "primary"), + VirtualMachine: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.SubResource { + return armnetwork.SubResource{ + ID: rapid.Ptr(rapid.String(), true).Draw(t, "vm_id"), + } + }), true).Draw(t, "virtual_machine"), + IPConfigurations: rapid.SliceOfN(rapid.Ptr(ipConfigGen(), true), 0, 5).Draw(t, "ipconfigs"), + } + }), true).Draw(t, "props"), + } + }) +} + +// TestProperty_nicFromArmNetworkInterface_NeverPanicsOnArbitraryInput ensures that +// nicFromArmNetworkInterface never panics, even when given arbitrary input. +func TestProperty_nicFromArmNetworkInterface_NeverPanicsOnArbitraryInput(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + nicGen := nicGen() + + nic, err := nicFromArmNetworkInterface(nicGen.Draw(t, "nic")) + require.NoError(t, err) + + if nicGen == nil { + require.Nil(t, nic) + } else { + require.NotNil(t, nic) + } + }) +} + +// TestProperty_nicFromArmNetworkInterface_FieldPassThrough ensures that all fields +// of an Azure network interface are passed through to the Teleport representation +// of the network interface. +func TestProperty_nicFromArmNetworkInterface_FieldPassThrough(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + rawNIC := nicGen().Draw(t, "nic") + + nic, err := nicFromArmNetworkInterface(rawNIC) + require.NoError(t, err) + require.NotNil(t, nic) + + if rawNIC.ID != nil { + require.Equal(t, *rawNIC.ID, nic.ID) + } else { + require.Empty(t, nic.ID) + } + if rawNIC.Name != nil { + require.Equal(t, *rawNIC.Name, nic.Name) + } else { + require.Empty(t, nic.Name) + } + + wantPrimary := rawNIC.Properties != nil && + rawNIC.Properties.Primary != nil && + *rawNIC.Properties.Primary + require.Equal(t, wantPrimary, nic.Primary) + + wantVMID := "" + if rawNIC.Properties != nil && + rawNIC.Properties.VirtualMachine != nil && + rawNIC.Properties.VirtualMachine.ID != nil { + wantVMID = *rawNIC.Properties.VirtualMachine.ID + } + require.Equal(t, wantVMID, nic.AttachedVMID) + + if rawNIC.Properties != nil { + outIdx := 0 + for _, ipConfig := range rawNIC.Properties.IPConfigurations { + if ipConfig == nil || ipConfig.Properties == nil { + continue + } + require.Less(t, outIdx, len(nic.IPConfigurations)) + got := nic.IPConfigurations[outIdx] + outIdx++ + + if ipConfig.Properties.Primary != nil { + require.Equal(t, *ipConfig.Properties.Primary, got.Primary) + } else { + require.False(t, got.Primary) + } + if ipConfig.Properties.PrivateIPAddress != nil { + require.Equal(t, *ipConfig.Properties.PrivateIPAddress, got.PrivateIP) + } else { + require.Empty(t, got.PrivateIP) + } + } + require.Len(t, nic.IPConfigurations, outIdx) + } + }) +} + +// TestProperty_ListNetworkInterfaces_Aggregation ensures that +// ListNetworkInterfaces correctly aggregates network interfaces from multiple +// sources, including standalone network interfaces and those associated with +// virtual machine scale sets. +func TestProperty_ListNetworkInterfaces_Aggregation(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + standalone := rapid.SliceOfN(nicGen(), 0, 5).Draw(t, "standalone") + scaleSets := rapid.MapOfN( + rapid.StringMatching(`vmss-[a-z]{1,5}`), + rapid.SliceOfN(nicGen(), 0, 3), + 0, 4, + ).Draw(t, "scale_sets") + + vmssNICs := make(map[string][]*armnetwork.Interface, len(scaleSets)) + vmssErrs := make(map[string]error) + var scaleSetIDs []string + wantCount := len(standalone) + for name, nics := range scaleSets { + scaleSetIDs = append(scaleSetIDs, createVMSSID(rgName, name)) + vmssNICs[rgName+"/"+name] = nics + if rapid.Bool().Draw(t, "fails_"+name) { + vmssErrs[name] = trace.AccessDenied("unauthorized") + } else { + wantCount += len(nics) + } + } + + client := NewNetworkInterfacesClientByAPI(NetworkInterfacesClientConfig{ + NetworkInterfacesAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{rgName: standalone}, + VMSSNetworkInterfaces: vmssNICs, + VMSSListErrs: vmssErrs, + }, + SubscriptionID: testSubID, + }) + + got, err := client.ListNetworkInterfaces(t.Context(), rgName, scaleSetIDs...) + require.NoError(t, err) + require.Len(t, got, wantCount) + }) +} + +// TestProperty_collectNICs_PageBoundaries ensures that collectNICs correctly +// aggregates network interfaces across multiple pages of results. +func TestProperty_collectNICs_PageBoundaries(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + nics := rapid.SliceOfN( + rapid.OneOf(rapid.Just[*armnetwork.Interface](nil), nicGen()), + 0, 20, + ).Draw(t, "nics") + + // Split the NICs into pages at arbitrary boundaries. + var pages [][]*armnetwork.Interface + for remaining := nics; len(remaining) > 0; { + n := rapid.IntRange(1, len(remaining)).Draw(t, "page_size") + pages = append(pages, remaining[:n]) + remaining = remaining[n:] + } + if len(pages) == 0 { + // A pager always serves at least one empty page. + pages = [][]*armnetwork.Interface{nil} + } + + pageIdx := 0 + pager := runtime.NewPager(runtime.PagingHandler[armnetwork.InterfacesClientListResponse]{ + More: func(page armnetwork.InterfacesClientListResponse) bool { + return page.NextLink != nil && *page.NextLink != "" + }, + Fetcher: func(_ context.Context, _ *armnetwork.InterfacesClientListResponse) (armnetwork.InterfacesClientListResponse, error) { + page := pages[pageIdx] + pageIdx++ + var nextLink *string + if pageIdx < len(pages) { + nextLink = to.Ptr("nextpage") + } + return armnetwork.InterfacesClientListResponse{ + InterfaceListResult: armnetwork.InterfaceListResult{ + Value: page, + NextLink: nextLink, + }, + }, nil + }, + }) + + c := &networkInterfacesClient{logger: logtest.NewLogger()} + got, err := c.collectNICs(t.Context(), newAPIPager(pager, func(resp armnetwork.InterfacesClientListResponse) []*armnetwork.Interface { + return resp.InterfaceListResult.Value + })) + require.NoError(t, err) + + // Check NICs appear converted and in order, no matter how the input was + // split into pages. nicFromArmNetworkInterface is used as the oracle to + // determine the expected output. + var want []*NetworkInterface + for _, rawNIC := range nics { + if rawNIC == nil { + continue + } + nic, err := nicFromArmNetworkInterface(rawNIC) + require.NoError(t, err) + want = append(want, nic) + } + require.Equal(t, want, got) + }) +} diff --git a/lib/cloud/azure/network_test.go b/lib/cloud/azure/network_test.go new file mode 100644 index 000000000000..cbf83d2fc345 --- /dev/null +++ b/lib/cloud/azure/network_test.go @@ -0,0 +1,379 @@ +// Teleport +// Copyright (C) 2026 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 azure + +import ( + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestListNetworkInterfaces(t *testing.T) { + t.Parallel() + + const ( + scaleSet1 = "vmss1" + scaleSet2 = "vmss2" + ) + + regularNIC := &armnetwork.Interface{ + ID: to.Ptr(createNICID(rgName, "nic1")), + Name: to.Ptr("nic1"), + Properties: &armnetwork.InterfacePropertiesFormat{ + Primary: to.Ptr(true), + VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMID(rgName, "vm1"))}, + IPConfigurations: []*armnetwork.InterfaceIPConfiguration{ + { + Name: to.Ptr("ipconfig1"), + Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: to.Ptr("10.0.0.1"), + Primary: to.Ptr(true), + }, + }, + }, + }, + } + wantRegularNIC := &NetworkInterface{ + ID: createNICID(rgName, "nic1"), + Name: "nic1", + Primary: true, + AttachedVMID: createVMID(rgName, "vm1"), + IPConfigurations: []IPConfiguration{{ + Primary: true, + PrivateIP: "10.0.0.1", + }}, + } + + multiIPConfigNIC := &armnetwork.Interface{ + ID: to.Ptr(createNICID(rgName, "nic2")), + Name: to.Ptr("nic2"), + Properties: &armnetwork.InterfacePropertiesFormat{ + Primary: to.Ptr(false), + IPConfigurations: []*armnetwork.InterfaceIPConfiguration{ + { + Name: to.Ptr("ipconfig1"), + Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: to.Ptr("10.0.0.2"), + Primary: to.Ptr(true), + }, + }, + { + Name: to.Ptr("ipconfig2"), + Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: to.Ptr("10.0.0.128/25"), + Primary: to.Ptr(false), + }, + }, + { + // IP configuration without properties is skipped. + Name: to.Ptr("ipconfig3"), + }, + }, + }, + } + wantMultiIPConfigNIC := &NetworkInterface{ + ID: createNICID(rgName, "nic2"), + Name: "nic2", + IPConfigurations: []IPConfiguration{ + { + Primary: true, + PrivateIP: "10.0.0.2", + }, + { + Primary: false, + PrivateIP: "10.0.0.128/25", + }, + }, + } + + // A NIC not attached to any VM has no Primary flag and may have no + // properties at all. + detachedNIC := &armnetwork.Interface{ + ID: to.Ptr(createNICID(rgName, "nic-detached")), + Name: to.Ptr("nic-detached"), + } + wantDetachedNIC := &NetworkInterface{ + ID: createNICID(rgName, "nic-detached"), + Name: "nic-detached", + IPConfigurations: []IPConfiguration{}, + } + + const otherRG = "rg2" + otherRGNIC := &armnetwork.Interface{ + ID: to.Ptr(createNICID(otherRG, "nic-rg2")), + Name: to.Ptr("nic-rg2"), + } + wantOtherRGNIC := &NetworkInterface{ + ID: createNICID(otherRG, "nic-rg2"), + Name: "nic-rg2", + IPConfigurations: []IPConfiguration{}, + } + + scaleSet1NIC := &armnetwork.Interface{ + ID: to.Ptr(createVMSSNICID(rgName, scaleSet1, "0", "vmss1-nic")), + Name: to.Ptr("vmss1-nic"), + Properties: &armnetwork.InterfacePropertiesFormat{ + Primary: to.Ptr(true), + VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMSSVMID(rgName, scaleSet1, "0"))}, + IPConfigurations: []*armnetwork.InterfaceIPConfiguration{ + { + Name: to.Ptr("ipconfig1"), + Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: to.Ptr("10.0.1.1"), + Primary: to.Ptr(true), + }, + }, + }, + }, + } + wantScaleSet1NIC := &NetworkInterface{ + ID: createVMSSNICID(rgName, scaleSet1, "0", "vmss1-nic"), + Name: "vmss1-nic", + Primary: true, + AttachedVMID: createVMSSVMID(rgName, scaleSet1, "0"), + IPConfigurations: []IPConfiguration{{ + Primary: true, + PrivateIP: "10.0.1.1", + }}, + } + + scaleSet2NIC := &armnetwork.Interface{ + ID: to.Ptr(createVMSSNICID(rgName, scaleSet2, "0", "vmss2-nic")), + Name: to.Ptr("vmss2-nic"), + Properties: &armnetwork.InterfacePropertiesFormat{ + Primary: to.Ptr(true), + VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMSSVMID(rgName, scaleSet2, "0"))}, + IPConfigurations: []*armnetwork.InterfaceIPConfiguration{ + { + Name: to.Ptr("ipconfig1"), + Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: to.Ptr("10.0.2.1"), + Primary: to.Ptr(true), + }, + }, + }, + }, + } + wantScaleSet2NIC := &NetworkInterface{ + ID: createVMSSNICID(rgName, scaleSet2, "0", "vmss2-nic"), + Name: "vmss2-nic", + Primary: true, + AttachedVMID: createVMSSVMID(rgName, scaleSet2, "0"), + IPConfigurations: []IPConfiguration{{ + Primary: true, + PrivateIP: "10.0.2.1", + }}, + } + + for _, tc := range []struct { + desc string + nicAPI *ARMNetworkMock + scaleSetIDs []string + resourceGroup string + want []*NetworkInterface + expectErr require.ErrorAssertionFunc + }{ + { + desc: "NICs from regular VMs, no scale set NICs", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + }, + }, + resourceGroup: rgName, + want: []*NetworkInterface{wantRegularNIC}, + expectErr: require.NoError, + }, + { + desc: "resource group without NICs returns nothing", + nicAPI: &ARMNetworkMock{}, + resourceGroup: rgName, + expectErr: require.NoError, + }, + { + desc: "converts secondary IP configurations and detached NICs", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {multiIPConfigNIC, detachedNIC}, + }, + }, + resourceGroup: rgName, + want: []*NetworkInterface{wantMultiIPConfigNIC, wantDetachedNIC}, + expectErr: require.NoError, + }, + { + desc: "combines standalone and uniform scale set NICs", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + }, + VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName + "/" + scaleSet1: {scaleSet1NIC}, + rgName + "/" + scaleSet2: {scaleSet2NIC}, + }, + }, + resourceGroup: rgName, + scaleSetIDs: []string{createVMSSID(rgName, scaleSet1), createVMSSID(rgName, scaleSet2)}, + want: []*NetworkInterface{wantRegularNIC, wantScaleSet1NIC, wantScaleSet2NIC}, + expectErr: require.NoError, + }, + { + desc: "scale set NICs are only listed for named scale sets", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + }, + VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName + "/" + scaleSet1: {scaleSet1NIC}, + }, + }, + resourceGroup: rgName, + want: []*NetworkInterface{wantRegularNIC}, + expectErr: require.NoError, + }, + { + desc: "failure listing one scale set does not fail the rest", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + }, + VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName + "/" + scaleSet1: {scaleSet1NIC}, + rgName + "/" + scaleSet2: {scaleSet2NIC}, + }, + VMSSListErrs: map[string]error{ + scaleSet1: trace.AccessDenied("unauthorized"), + }, + }, + resourceGroup: rgName, + scaleSetIDs: []string{createVMSSID(rgName, scaleSet1), createVMSSID(rgName, scaleSet2)}, + want: []*NetworkInterface{wantRegularNIC, wantScaleSet2NIC}, + expectErr: require.NoError, + }, + { + desc: "duplicate scale set IDs are only listed once", + nicAPI: &ARMNetworkMock{ + VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName + "/" + scaleSet1: {scaleSet1NIC}, + }, + }, + resourceGroup: rgName, + scaleSetIDs: []string{ + createVMSSID(rgName, scaleSet1), + createVMSSID(rgName, scaleSet1), + strings.ToUpper(createVMSSID(rgName, scaleSet1)), + }, + want: []*NetworkInterface{wantScaleSet1NIC}, + expectErr: require.NoError, + }, + { + desc: "invalid scale set IDs are skipped", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + }, + VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName + "/" + scaleSet1: {scaleSet1NIC}, + }, + }, + resourceGroup: rgName, + scaleSetIDs: []string{ + "not-a-resource-id", + createVMID(rgName, "vm1"), + createVMSSVMID(rgName, scaleSet1, "0"), + strings.ReplaceAll(createVMSSID(rgName, scaleSet1), testSubID, "22222222-2222-2222-2222-222222222222"), + createVMSSID(rgName, scaleSet1), + }, + want: []*NetworkInterface{wantRegularNIC, wantScaleSet1NIC}, + expectErr: require.NoError, + }, + { + desc: "nil NICs in the response are skipped", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {nil, regularNIC}, + }, + }, + resourceGroup: rgName, + want: []*NetworkInterface{wantRegularNIC}, + expectErr: require.NoError, + }, + { + desc: "auth error fails the whole listing", + nicAPI: &ARMNetworkMock{NoAuth: true}, + resourceGroup: rgName, + expectErr: func(t require.TestingT, err error, i ...any) { + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + }, + { + desc: "wildcard resource group lists NICs from all resource groups", + nicAPI: &ARMNetworkMock{ + NetworkInterfaces: map[string][]*armnetwork.Interface{ + rgName: {regularNIC}, + otherRG: {otherRGNIC}, + }, + }, + resourceGroup: types.Wildcard, + want: []*NetworkInterface{wantRegularNIC, wantOtherRGNIC}, + expectErr: require.NoError, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + client := NewNetworkInterfacesClientByAPI(NetworkInterfacesClientConfig{ + NetworkInterfacesAPI: tc.nicAPI, + SubscriptionID: testSubID, + }) + + nics, err := client.ListNetworkInterfaces( + t.Context(), + tc.resourceGroup, + tc.scaleSetIDs..., + ) + tc.expectErr(t, err) + require.ElementsMatch(t, tc.want, nics) + }) + } +} + +func createNICID(resourceGroup, nicName string) string { + return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Network/networkInterfaces/" + nicName +} + +func createVMID(resourceGroup, vmName string) string { + return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Compute/virtualMachines/" + vmName +} + +func createVMSSID(resourceGroup, scaleSetName string) string { + return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Compute/virtualMachineScaleSets/" + scaleSetName +} + +func createVMSSVMID(resourceGroup, scaleSetName, instanceID string) string { + return createVMSSID(resourceGroup, scaleSetName) + "/virtualMachines/" + instanceID +} + +func createVMSSNICID(resourceGroup, scaleSetName, instanceID, nicName string) string { + return createVMSSVMID(resourceGroup, scaleSetName, instanceID) + "/networkInterfaces/" + nicName +} From c929a85c76eae010694d06e1398d6cb7a3c8d56e Mon Sep 17 00:00:00 2001 From: Tiago Scolari <38940+tscolari@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:17:20 +0000 Subject: [PATCH 100/347] MWI Scopes: Github Join Method (#68939) * join: initial tests to support Github scoped join method * proto: add Github to ScopedTokenSpec proto * proto: generate joining.v1.Github * join: normalize GitHub method behind the provision.Token interface This captures that the change adds the getter across the interface layers (types.ProvisionToken, provision.Token, scoped adapter), the validation, and the round-trip/ equivalence tests * join: use interface in lib/join/githubactions instead of literal token type This allows `CheckGithubIDToken` to work with both scoped and non-scoped tokens. * join: add test for Bot with Github scoped token * join: refactor Bot test for bound_keypair to use new helper * join: explicitly disallow single_use tokens on scoped github join method Because delegated OIDC joins do not consume them, allowing unintended unlimited reuse. Proper enforcement requires a separate common Join service change. * lint: exclude lib/join/jointest from "testing/testify" linter rules This is a helper-test package: It can't be `_test` because it needs to be imported by other tests, and it needs the testing/testify package to fulfill its purpose. * tests: add negative bot join test case * Remove testing packages from jointest This removes any testing packages reference from the jointest package, which will instead return errors for the caller to verify. This ensures that no test symbol is leaked into this package. * Revert "Remove testing packages from jointest" This reverts commit ca29de6a6181d7076f87a3f89352a4512f6093d9. * Move new helpers from jointest down to the `join` pkg This moves the new helpers added to `joingtest` to the same package where they are being called (`join`). There's the potential for these helpers to be reused, but we can move them around when/if we get there. * Fix tests the diverged in master and remove jointest exceptions from the golangci.yaml --- .../scopes/joining/v1/token_protohybrid.pb.go | 658 +++++++-- .../scopes/joining/v1/token_protoopaque.pb.go | 593 ++++++-- .../teleport/scopes/joining/v1/token.proto | 103 ++ api/types/provisioning.go | 8 +- .../resources-teleport-dev-scopedtokensv1.mdx | 25 + .../data-sources/scoped_token.mdx | 27 + .../resources/scoped_token.mdx | 27 + ...resources.teleport.dev_scopedtokensv1.yaml | 59 + ...resources.teleport.dev_scopedtokensv1.yaml | 59 + .../scopes/joining/v1/token_terraform.go | 1303 +++++++++++++---- lib/join/githubactions/githubactions.go | 20 +- lib/join/join_bound_keypair_test.go | 71 +- lib/join/join_github_test.go | 181 ++- lib/join/jointest/scoped_token.go | 49 +- lib/join/provision/token.go | 2 + lib/join/scope_helpers_test.go | 189 +++ lib/scopes/joining/token.go | 68 + lib/scopes/joining/token_test.go | 172 +++ 18 files changed, 3043 insertions(+), 571 deletions(-) create mode 100644 lib/join/scope_helpers_test.go diff --git a/api/gen/proto/go/teleport/scopes/joining/v1/token_protohybrid.pb.go b/api/gen/proto/go/teleport/scopes/joining/v1/token_protohybrid.pb.go index 2a1a518dd7b2..eff374f138c2 100644 --- a/api/gen/proto/go/teleport/scopes/joining/v1/token_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/scopes/joining/v1/token_protohybrid.pb.go @@ -274,7 +274,9 @@ type ScopedTokenSpec struct { // Mutually exclusive with assigned_scope. Bot string `protobuf:"bytes,15,opt,name=bot,proto3" json:"bot,omitempty"` // Configuration specific to the "generic_oidc" join method. - GenericOidc *GenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3" json:"generic_oidc,omitempty"` + GenericOidc *GenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3" json:"generic_oidc,omitempty"` + // Configuration specific to the "github" join method. + Github *Github `protobuf:"bytes,17,opt,name=github,proto3" json:"github,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -402,6 +404,13 @@ func (x *ScopedTokenSpec) GetGenericOidc() *GenericOIDC { return nil } +func (x *ScopedTokenSpec) GetGithub() *Github { + if x != nil { + return x.Github + } + return nil +} + func (x *ScopedTokenSpec) SetAssignedScope(v string) { x.AssignedScope = v } @@ -458,6 +467,10 @@ func (x *ScopedTokenSpec) SetGenericOidc(v *GenericOIDC) { x.GenericOidc = v } +func (x *ScopedTokenSpec) SetGithub(v *Github) { + x.Github = v +} + func (x *ScopedTokenSpec) HasImmutableLabels() bool { if x == nil { return false @@ -521,6 +534,13 @@ func (x *ScopedTokenSpec) HasGenericOidc() bool { return x.GenericOidc != nil } +func (x *ScopedTokenSpec) HasGithub() bool { + if x == nil { + return false + } + return x.Github != nil +} + func (x *ScopedTokenSpec) ClearImmutableLabels() { x.ImmutableLabels = nil } @@ -557,6 +577,10 @@ func (x *ScopedTokenSpec) ClearGenericOidc() { x.GenericOidc = nil } +func (x *ScopedTokenSpec) ClearGithub() { + x.Github = nil +} + type ScopedTokenSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -601,6 +625,8 @@ type ScopedTokenSpec_builder struct { Bot string // Configuration specific to the "generic_oidc" join method. GenericOidc *GenericOIDC + // Configuration specific to the "github" join method. + Github *Github } func (b0 ScopedTokenSpec_builder) Build() *ScopedTokenSpec { @@ -621,6 +647,7 @@ func (b0 ScopedTokenSpec_builder) Build() *ScopedTokenSpec { x.BoundKeypair = b.BoundKeypair x.Bot = b.Bot x.GenericOidc = b.GenericOidc + x.Github = b.Github return m0 } @@ -2598,6 +2625,152 @@ func (b0 GenericOIDC_builder) Build() *GenericOIDC { return m0 } +// Configuration for Github token. +type Github struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // enterprise_server_host allows joining from runners associated with a + // GitHub Enterprise Server instance. When unconfigured, tokens will be + // validated against github.com, but when configured to the host of a GHES + // instance, then the tokens will be validated against host. + // + // This value should be the hostname of the GHES instance, and should not + // include the scheme or a path. The instance must be accessible over HTTPS + // at this hostname and the certificate must be trusted by the Auth Service. + EnterpriseServerHost string `protobuf:"bytes,1,opt,name=enterprise_server_host,json=enterpriseServerHost,proto3" json:"enterprise_server_host,omitempty"` + // enterprise_slug allows the slug of a GitHub Enterprise organisation to be + // included in the expected issuer of the OIDC tokens. This is for + // compatibility with the `include_enterprise_slug` option in GHE. + // + // This field should be set to the slug of your enterprise if this is enabled. If + // this is not enabled, then this field must be left empty. This field cannot + // be specified if `enterprise_server_host` is specified. + // + // See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + // for more information about customized issuer values. + EnterpriseSlug string `protobuf:"bytes,2,opt,name=enterprise_slug,json=enterpriseSlug,proto3" json:"enterprise_slug,omitempty"` + // static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC + // endpoints, and allows them to be directly specified. This allows joining + // from GitHub Actions in GHES instances that are not reachable by the + // Teleport Auth Service. + StaticJwks string `protobuf:"bytes,3,opt,name=static_jwks,json=staticJwks,proto3" json:"static_jwks,omitempty"` + // allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. + Allow []*Github_Rule `protobuf:"bytes,4,rep,name=allow,proto3" json:"allow,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Github) Reset() { + *x = Github{} + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Github) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Github) ProtoMessage() {} + +func (x *Github) ProtoReflect() protoreflect.Message { + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Github) GetEnterpriseServerHost() string { + if x != nil { + return x.EnterpriseServerHost + } + return "" +} + +func (x *Github) GetEnterpriseSlug() string { + if x != nil { + return x.EnterpriseSlug + } + return "" +} + +func (x *Github) GetStaticJwks() string { + if x != nil { + return x.StaticJwks + } + return "" +} + +func (x *Github) GetAllow() []*Github_Rule { + if x != nil { + return x.Allow + } + return nil +} + +func (x *Github) SetEnterpriseServerHost(v string) { + x.EnterpriseServerHost = v +} + +func (x *Github) SetEnterpriseSlug(v string) { + x.EnterpriseSlug = v +} + +func (x *Github) SetStaticJwks(v string) { + x.StaticJwks = v +} + +func (x *Github) SetAllow(v []*Github_Rule) { + x.Allow = v +} + +type Github_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // enterprise_server_host allows joining from runners associated with a + // GitHub Enterprise Server instance. When unconfigured, tokens will be + // validated against github.com, but when configured to the host of a GHES + // instance, then the tokens will be validated against host. + // + // This value should be the hostname of the GHES instance, and should not + // include the scheme or a path. The instance must be accessible over HTTPS + // at this hostname and the certificate must be trusted by the Auth Service. + EnterpriseServerHost string + // enterprise_slug allows the slug of a GitHub Enterprise organisation to be + // included in the expected issuer of the OIDC tokens. This is for + // compatibility with the `include_enterprise_slug` option in GHE. + // + // This field should be set to the slug of your enterprise if this is enabled. If + // this is not enabled, then this field must be left empty. This field cannot + // be specified if `enterprise_server_host` is specified. + // + // See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + // for more information about customized issuer values. + EnterpriseSlug string + // static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC + // endpoints, and allows them to be directly specified. This allows joining + // from GitHub Actions in GHES instances that are not reachable by the + // Teleport Auth Service. + StaticJwks string + // allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. + Allow []*Github_Rule +} + +func (b0 Github_builder) Build() *Github { + m0 := &Github{} + b, x := &b0, m0 + _, _ = b, x + x.EnterpriseServerHost = b.EnterpriseServerHost + x.EnterpriseSlug = b.EnterpriseSlug + x.StaticJwks = b.StaticJwks + x.Allow = b.Allow + return m0 +} + // A rule that a joining node must match in order to use the associated token // with AWS join methods. type AWS_Rule struct { @@ -2622,7 +2795,7 @@ type AWS_Rule struct { func (x *AWS_Rule) Reset() { *x = AWS_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[19] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2634,7 +2807,7 @@ func (x *AWS_Rule) String() string { func (*AWS_Rule) ProtoMessage() {} func (x *AWS_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[19] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2747,7 +2920,7 @@ type GCP_Rule struct { func (x *GCP_Rule) Reset() { *x = GCP_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2759,7 +2932,7 @@ func (x *GCP_Rule) String() string { func (*GCP_Rule) ProtoMessage() {} func (x *GCP_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2840,7 +3013,7 @@ type Azure_Rule struct { func (x *Azure_Rule) Reset() { *x = Azure_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2852,7 +3025,7 @@ func (x *Azure_Rule) String() string { func (*Azure_Rule) ProtoMessage() {} func (x *Azure_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2959,7 +3132,7 @@ type AzureDevops_Rule struct { func (x *AzureDevops_Rule) Reset() { *x = AzureDevops_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2971,7 +3144,7 @@ func (x *AzureDevops_Rule) String() string { func (*AzureDevops_Rule) ProtoMessage() {} func (x *AzureDevops_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3143,7 +3316,7 @@ type Oracle_Rule struct { func (x *Oracle_Rule) Reset() { *x = Oracle_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3155,7 +3328,7 @@ func (x *Oracle_Rule) String() string { func (*Oracle_Rule) ProtoMessage() {} func (x *Oracle_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3249,7 +3422,7 @@ type Kubernetes_StaticJWKSConfig struct { func (x *Kubernetes_StaticJWKSConfig) Reset() { *x = Kubernetes_StaticJWKSConfig{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3261,7 +3434,7 @@ func (x *Kubernetes_StaticJWKSConfig) String() string { func (*Kubernetes_StaticJWKSConfig) ProtoMessage() {} func (x *Kubernetes_StaticJWKSConfig) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3314,7 +3487,7 @@ type Kubernetes_OIDCConfig struct { func (x *Kubernetes_OIDCConfig) Reset() { *x = Kubernetes_OIDCConfig{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3326,7 +3499,7 @@ func (x *Kubernetes_OIDCConfig) String() string { func (*Kubernetes_OIDCConfig) ProtoMessage() {} func (x *Kubernetes_OIDCConfig) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3406,7 +3579,7 @@ type Kubernetes_Rule struct { func (x *Kubernetes_Rule) Reset() { *x = Kubernetes_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3418,7 +3591,7 @@ func (x *Kubernetes_Rule) String() string { func (*Kubernetes_Rule) ProtoMessage() {} func (x *Kubernetes_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3521,7 +3694,7 @@ type BoundKeypairSpec_OnboardingSpec struct { func (x *BoundKeypairSpec_OnboardingSpec) Reset() { *x = BoundKeypairSpec_OnboardingSpec{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3533,7 +3706,7 @@ func (x *BoundKeypairSpec_OnboardingSpec) String() string { func (*BoundKeypairSpec_OnboardingSpec) ProtoMessage() {} func (x *BoundKeypairSpec_OnboardingSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3656,7 +3829,7 @@ type BoundKeypairSpec_RecoverySpec struct { func (x *BoundKeypairSpec_RecoverySpec) Reset() { *x = BoundKeypairSpec_RecoverySpec{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3668,7 +3841,7 @@ func (x *BoundKeypairSpec_RecoverySpec) String() string { func (*BoundKeypairSpec_RecoverySpec) ProtoMessage() {} func (x *BoundKeypairSpec_RecoverySpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3749,7 +3922,7 @@ type GenericOIDC_ConditionEq struct { func (x *GenericOIDC_ConditionEq) Reset() { *x = GenericOIDC_ConditionEq{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3761,7 +3934,7 @@ func (x *GenericOIDC_ConditionEq) String() string { func (*GenericOIDC_ConditionEq) ProtoMessage() {} func (x *GenericOIDC_ConditionEq) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3809,7 +3982,7 @@ type GenericOIDC_ConditionNotEq struct { func (x *GenericOIDC_ConditionNotEq) Reset() { *x = GenericOIDC_ConditionNotEq{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3821,7 +3994,7 @@ func (x *GenericOIDC_ConditionNotEq) String() string { func (*GenericOIDC_ConditionNotEq) ProtoMessage() {} func (x *GenericOIDC_ConditionNotEq) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3869,7 +4042,7 @@ type GenericOIDC_ConditionIn struct { func (x *GenericOIDC_ConditionIn) Reset() { *x = GenericOIDC_ConditionIn{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3881,7 +4054,7 @@ func (x *GenericOIDC_ConditionIn) String() string { func (*GenericOIDC_ConditionIn) ProtoMessage() {} func (x *GenericOIDC_ConditionIn) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3929,7 +4102,7 @@ type GenericOIDC_ConditionNotIn struct { func (x *GenericOIDC_ConditionNotIn) Reset() { *x = GenericOIDC_ConditionNotIn{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3941,7 +4114,7 @@ func (x *GenericOIDC_ConditionNotIn) String() string { func (*GenericOIDC_ConditionNotIn) ProtoMessage() {} func (x *GenericOIDC_ConditionNotIn) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3998,7 +4171,7 @@ type GenericOIDC_Condition struct { func (x *GenericOIDC_Condition) Reset() { *x = GenericOIDC_Condition{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4010,7 +4183,7 @@ func (x *GenericOIDC_Condition) String() string { func (*GenericOIDC_Condition) ProtoMessage() {} func (x *GenericOIDC_Condition) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4166,7 +4339,7 @@ type GenericOIDC_Rule struct { func (x *GenericOIDC_Rule) Reset() { *x = GenericOIDC_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4178,7 +4351,7 @@ func (x *GenericOIDC_Rule) String() string { func (*GenericOIDC_Rule) ProtoMessage() {} func (x *GenericOIDC_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4233,6 +4406,274 @@ func (b0 GenericOIDC_Rule_builder) Build() *GenericOIDC_Rule { return m0 } +// A rule that a joining node must match in order to use the associated token +// with the "github" join method. All specified fields within a rule must +// match (AND). At least one rule in the allow list must match (OR). +type Github_Rule struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // sub also known as Subject is a string that roughly uniquely identifies + // the workload. The format of this varies depending on the type of + // github action run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"` + // repository from where the workflow is running. + // This includes the name of the owner e.g `gravitational/teleport` + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` + // repository_owner is the name of the organization in which the repository is stored. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + RepositoryOwner string `protobuf:"bytes,3,opt,name=repository_owner,json=repositoryOwner,proto3" json:"repository_owner,omitempty"` + // workflow name. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Workflow string `protobuf:"bytes,4,opt,name=workflow,proto3" json:"workflow,omitempty"` + // environment name used by the job. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Environment string `protobuf:"bytes,5,opt,name=environment,proto3" json:"environment,omitempty"` + // actor is the personal account that initiated the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Actor string `protobuf:"bytes,6,opt,name=actor,proto3" json:"actor,omitempty"` + // ref (git) that triggered the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Ref string `protobuf:"bytes,7,opt,name=ref,proto3" json:"ref,omitempty"` + // ref_type for example: "branch". + RefType string `protobuf:"bytes,8,opt,name=ref_type,json=refType,proto3" json:"ref_type,omitempty"` + // enterprise name in which the repository is stored. + Enterprise string `protobuf:"bytes,9,opt,name=enterprise,proto3" json:"enterprise,omitempty"` + // enterprise_id in which the repository is stored. + EnterpriseId string `protobuf:"bytes,10,opt,name=enterprise_id,json=enterpriseId,proto3" json:"enterprise_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Github_Rule) Reset() { + *x = Github_Rule{} + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Github_Rule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Github_Rule) ProtoMessage() {} + +func (x *Github_Rule) ProtoReflect() protoreflect.Message { + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Github_Rule) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *Github_Rule) GetRepository() string { + if x != nil { + return x.Repository + } + return "" +} + +func (x *Github_Rule) GetRepositoryOwner() string { + if x != nil { + return x.RepositoryOwner + } + return "" +} + +func (x *Github_Rule) GetWorkflow() string { + if x != nil { + return x.Workflow + } + return "" +} + +func (x *Github_Rule) GetEnvironment() string { + if x != nil { + return x.Environment + } + return "" +} + +func (x *Github_Rule) GetActor() string { + if x != nil { + return x.Actor + } + return "" +} + +func (x *Github_Rule) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *Github_Rule) GetRefType() string { + if x != nil { + return x.RefType + } + return "" +} + +func (x *Github_Rule) GetEnterprise() string { + if x != nil { + return x.Enterprise + } + return "" +} + +func (x *Github_Rule) GetEnterpriseId() string { + if x != nil { + return x.EnterpriseId + } + return "" +} + +func (x *Github_Rule) SetSub(v string) { + x.Sub = v +} + +func (x *Github_Rule) SetRepository(v string) { + x.Repository = v +} + +func (x *Github_Rule) SetRepositoryOwner(v string) { + x.RepositoryOwner = v +} + +func (x *Github_Rule) SetWorkflow(v string) { + x.Workflow = v +} + +func (x *Github_Rule) SetEnvironment(v string) { + x.Environment = v +} + +func (x *Github_Rule) SetActor(v string) { + x.Actor = v +} + +func (x *Github_Rule) SetRef(v string) { + x.Ref = v +} + +func (x *Github_Rule) SetRefType(v string) { + x.RefType = v +} + +func (x *Github_Rule) SetEnterprise(v string) { + x.Enterprise = v +} + +func (x *Github_Rule) SetEnterpriseId(v string) { + x.EnterpriseId = v +} + +type Github_Rule_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // sub also known as Subject is a string that roughly uniquely identifies + // the workload. The format of this varies depending on the type of + // github action run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Sub string + // repository from where the workflow is running. + // This includes the name of the owner e.g `gravitational/teleport` + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Repository string + // repository_owner is the name of the organization in which the repository is stored. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + RepositoryOwner string + // workflow name. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Workflow string + // environment name used by the job. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Environment string + // actor is the personal account that initiated the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Actor string + // ref (git) that triggered the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Ref string + // ref_type for example: "branch". + RefType string + // enterprise name in which the repository is stored. + Enterprise string + // enterprise_id in which the repository is stored. + EnterpriseId string +} + +func (b0 Github_Rule_builder) Build() *Github_Rule { + m0 := &Github_Rule{} + b, x := &b0, m0 + _, _ = b, x + x.Sub = b.Sub + x.Repository = b.Repository + x.RepositoryOwner = b.RepositoryOwner + x.Workflow = b.Workflow + x.Environment = b.Environment + x.Actor = b.Actor + x.Ref = b.Ref + x.RefType = b.RefType + x.Enterprise = b.Enterprise + x.EnterpriseId = b.EnterpriseId + return m0 +} + var File_teleport_scopes_joining_v1_token_proto protoreflect.FileDescriptor const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + @@ -4245,7 +4686,7 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12\x14\n" + "\x05scope\x18\x05 \x01(\tR\x05scope\x12?\n" + "\x04spec\x18\x06 \x01(\v2+.teleport.scopes.joining.v1.ScopedTokenSpecR\x04spec\x12E\n" + - "\x06status\x18\a \x01(\v2-.teleport.scopes.joining.v1.ScopedTokenStatusR\x06status\"\xa7\x06\n" + + "\x06status\x18\a \x01(\v2-.teleport.scopes.joining.v1.ScopedTokenStatusR\x06status\"\xe3\x06\n" + "\x0fScopedTokenSpec\x12%\n" + "\x0eassigned_scope\x18\x01 \x01(\tR\rassignedScope\x12\x14\n" + "\x05roles\x18\x02 \x03(\tR\x05roles\x12\x1f\n" + @@ -4265,7 +4706,8 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "kubernetes\x12Q\n" + "\rbound_keypair\x18\f \x01(\v2,.teleport.scopes.joining.v1.BoundKeypairSpecR\fboundKeypair\x12\x10\n" + "\x03bot\x18\x0f \x01(\tR\x03bot\x12J\n" + - "\fgeneric_oidc\x18\x10 \x01(\v2'.teleport.scopes.joining.v1.GenericOIDCR\vgenericOidcJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fR\bbot_nameR\tbot_scope\"\xb6\x01\n" + + "\fgeneric_oidc\x18\x10 \x01(\v2'.teleport.scopes.joining.v1.GenericOIDCR\vgenericOidc\x12:\n" + + "\x06github\x18\x11 \x01(\v2\".teleport.scopes.joining.v1.GithubR\x06githubJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fR\bbot_nameR\tbot_scope\"\xb6\x01\n" + "\x0eHostCertParams\x12\x17\n" + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x1b\n" + "\tnode_name\x18\x02 \x01(\tR\bnodeName\x12\x12\n" + @@ -4411,9 +4853,31 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "conditions\x12\x1e\n" + "\n" + "expression\x18\x02 \x01(\tR\n" + - "expressionBYZWgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1;joiningv1b\x06proto3" + "expression\"\xf3\x03\n" + + "\x06Github\x124\n" + + "\x16enterprise_server_host\x18\x01 \x01(\tR\x14enterpriseServerHost\x12'\n" + + "\x0fenterprise_slug\x18\x02 \x01(\tR\x0eenterpriseSlug\x12\x1f\n" + + "\vstatic_jwks\x18\x03 \x01(\tR\n" + + "staticJwks\x12=\n" + + "\x05allow\x18\x04 \x03(\v2'.teleport.scopes.joining.v1.Github.RuleR\x05allow\x1a\xa9\x02\n" + + "\x04Rule\x12\x10\n" + + "\x03sub\x18\x01 \x01(\tR\x03sub\x12\x1e\n" + + "\n" + + "repository\x18\x02 \x01(\tR\n" + + "repository\x12)\n" + + "\x10repository_owner\x18\x03 \x01(\tR\x0frepositoryOwner\x12\x1a\n" + + "\bworkflow\x18\x04 \x01(\tR\bworkflow\x12 \n" + + "\venvironment\x18\x05 \x01(\tR\venvironment\x12\x14\n" + + "\x05actor\x18\x06 \x01(\tR\x05actor\x12\x10\n" + + "\x03ref\x18\a \x01(\tR\x03ref\x12\x19\n" + + "\bref_type\x18\b \x01(\tR\arefType\x12\x1e\n" + + "\n" + + "enterprise\x18\t \x01(\tR\n" + + "enterprise\x12#\n" + + "\renterprise_id\x18\n" + + " \x01(\tR\fenterpriseIdBYZWgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1;joiningv1b\x06proto3" -var file_teleport_scopes_joining_v1_token_proto_msgTypes = make([]protoimpl.MessageInfo, 35) +var file_teleport_scopes_joining_v1_token_proto_msgTypes = make([]protoimpl.MessageInfo, 37) var file_teleport_scopes_joining_v1_token_proto_goTypes = []any{ (*ScopedToken)(nil), // 0: teleport.scopes.joining.v1.ScopedToken (*ScopedTokenSpec)(nil), // 1: teleport.scopes.joining.v1.ScopedTokenSpec @@ -4433,29 +4897,31 @@ var file_teleport_scopes_joining_v1_token_proto_goTypes = []any{ (*BoundKeypairSpec)(nil), // 15: teleport.scopes.joining.v1.BoundKeypairSpec (*BoundKeypairStatus)(nil), // 16: teleport.scopes.joining.v1.BoundKeypairStatus (*GenericOIDC)(nil), // 17: teleport.scopes.joining.v1.GenericOIDC - nil, // 18: teleport.scopes.joining.v1.ImmutableLabels.SshEntry - (*AWS_Rule)(nil), // 19: teleport.scopes.joining.v1.AWS.Rule - (*GCP_Rule)(nil), // 20: teleport.scopes.joining.v1.GCP.Rule - (*Azure_Rule)(nil), // 21: teleport.scopes.joining.v1.Azure.Rule - (*AzureDevops_Rule)(nil), // 22: teleport.scopes.joining.v1.AzureDevops.Rule - (*Oracle_Rule)(nil), // 23: teleport.scopes.joining.v1.Oracle.Rule - (*Kubernetes_StaticJWKSConfig)(nil), // 24: teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig - (*Kubernetes_OIDCConfig)(nil), // 25: teleport.scopes.joining.v1.Kubernetes.OIDCConfig - (*Kubernetes_Rule)(nil), // 26: teleport.scopes.joining.v1.Kubernetes.Rule - (*BoundKeypairSpec_OnboardingSpec)(nil), // 27: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec - (*BoundKeypairSpec_RecoverySpec)(nil), // 28: teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec - (*GenericOIDC_ConditionEq)(nil), // 29: teleport.scopes.joining.v1.GenericOIDC.ConditionEq - (*GenericOIDC_ConditionNotEq)(nil), // 30: teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq - (*GenericOIDC_ConditionIn)(nil), // 31: teleport.scopes.joining.v1.GenericOIDC.ConditionIn - (*GenericOIDC_ConditionNotIn)(nil), // 32: teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn - (*GenericOIDC_Condition)(nil), // 33: teleport.scopes.joining.v1.GenericOIDC.Condition - (*GenericOIDC_Rule)(nil), // 34: teleport.scopes.joining.v1.GenericOIDC.Rule - (*v1.Metadata)(nil), // 35: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 37: google.protobuf.Struct + (*Github)(nil), // 18: teleport.scopes.joining.v1.Github + nil, // 19: teleport.scopes.joining.v1.ImmutableLabels.SshEntry + (*AWS_Rule)(nil), // 20: teleport.scopes.joining.v1.AWS.Rule + (*GCP_Rule)(nil), // 21: teleport.scopes.joining.v1.GCP.Rule + (*Azure_Rule)(nil), // 22: teleport.scopes.joining.v1.Azure.Rule + (*AzureDevops_Rule)(nil), // 23: teleport.scopes.joining.v1.AzureDevops.Rule + (*Oracle_Rule)(nil), // 24: teleport.scopes.joining.v1.Oracle.Rule + (*Kubernetes_StaticJWKSConfig)(nil), // 25: teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig + (*Kubernetes_OIDCConfig)(nil), // 26: teleport.scopes.joining.v1.Kubernetes.OIDCConfig + (*Kubernetes_Rule)(nil), // 27: teleport.scopes.joining.v1.Kubernetes.Rule + (*BoundKeypairSpec_OnboardingSpec)(nil), // 28: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec + (*BoundKeypairSpec_RecoverySpec)(nil), // 29: teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec + (*GenericOIDC_ConditionEq)(nil), // 30: teleport.scopes.joining.v1.GenericOIDC.ConditionEq + (*GenericOIDC_ConditionNotEq)(nil), // 31: teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq + (*GenericOIDC_ConditionIn)(nil), // 32: teleport.scopes.joining.v1.GenericOIDC.ConditionIn + (*GenericOIDC_ConditionNotIn)(nil), // 33: teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn + (*GenericOIDC_Condition)(nil), // 34: teleport.scopes.joining.v1.GenericOIDC.Condition + (*GenericOIDC_Rule)(nil), // 35: teleport.scopes.joining.v1.GenericOIDC.Rule + (*Github_Rule)(nil), // 36: teleport.scopes.joining.v1.Github.Rule + (*v1.Metadata)(nil), // 37: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 38: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 39: google.protobuf.Struct } var file_teleport_scopes_joining_v1_token_proto_depIdxs = []int32{ - 35, // 0: teleport.scopes.joining.v1.ScopedToken.metadata:type_name -> teleport.header.v1.Metadata + 37, // 0: teleport.scopes.joining.v1.ScopedToken.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.scopes.joining.v1.ScopedToken.spec:type_name -> teleport.scopes.joining.v1.ScopedTokenSpec 5, // 2: teleport.scopes.joining.v1.ScopedToken.status:type_name -> teleport.scopes.joining.v1.ScopedTokenStatus 6, // 3: teleport.scopes.joining.v1.ScopedTokenSpec.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels @@ -4467,42 +4933,44 @@ var file_teleport_scopes_joining_v1_token_proto_depIdxs = []int32{ 14, // 9: teleport.scopes.joining.v1.ScopedTokenSpec.kubernetes:type_name -> teleport.scopes.joining.v1.Kubernetes 15, // 10: teleport.scopes.joining.v1.ScopedTokenSpec.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec 17, // 11: teleport.scopes.joining.v1.ScopedTokenSpec.generic_oidc:type_name -> teleport.scopes.joining.v1.GenericOIDC - 36, // 12: teleport.scopes.joining.v1.SingleUseStatus.used_at:type_name -> google.protobuf.Timestamp - 36, // 13: teleport.scopes.joining.v1.SingleUseStatus.reusable_until:type_name -> google.protobuf.Timestamp - 2, // 14: teleport.scopes.joining.v1.SingleUseStatus.host_cert_params:type_name -> teleport.scopes.joining.v1.HostCertParams - 3, // 15: teleport.scopes.joining.v1.UsageStatus.single_use:type_name -> teleport.scopes.joining.v1.SingleUseStatus - 16, // 16: teleport.scopes.joining.v1.UsageStatus.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairStatus - 4, // 17: teleport.scopes.joining.v1.ScopedTokenStatus.usage:type_name -> teleport.scopes.joining.v1.UsageStatus - 18, // 18: teleport.scopes.joining.v1.ImmutableLabels.ssh:type_name -> teleport.scopes.joining.v1.ImmutableLabels.SshEntry - 35, // 19: teleport.scopes.joining.v1.StaticScopedTokens.metadata:type_name -> teleport.header.v1.Metadata - 8, // 20: teleport.scopes.joining.v1.StaticScopedTokens.spec:type_name -> teleport.scopes.joining.v1.StaticScopedTokensSpec - 0, // 21: teleport.scopes.joining.v1.StaticScopedTokensSpec.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken - 19, // 22: teleport.scopes.joining.v1.AWS.allow:type_name -> teleport.scopes.joining.v1.AWS.Rule - 20, // 23: teleport.scopes.joining.v1.GCP.allow:type_name -> teleport.scopes.joining.v1.GCP.Rule - 21, // 24: teleport.scopes.joining.v1.Azure.allow:type_name -> teleport.scopes.joining.v1.Azure.Rule - 22, // 25: teleport.scopes.joining.v1.AzureDevops.allow:type_name -> teleport.scopes.joining.v1.AzureDevops.Rule - 23, // 26: teleport.scopes.joining.v1.Oracle.allow:type_name -> teleport.scopes.joining.v1.Oracle.Rule - 26, // 27: teleport.scopes.joining.v1.Kubernetes.allow:type_name -> teleport.scopes.joining.v1.Kubernetes.Rule - 24, // 28: teleport.scopes.joining.v1.Kubernetes.static_jwks:type_name -> teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig - 25, // 29: teleport.scopes.joining.v1.Kubernetes.oidc:type_name -> teleport.scopes.joining.v1.Kubernetes.OIDCConfig - 27, // 30: teleport.scopes.joining.v1.BoundKeypairSpec.onboarding:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec - 28, // 31: teleport.scopes.joining.v1.BoundKeypairSpec.recovery:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec - 36, // 32: teleport.scopes.joining.v1.BoundKeypairSpec.rotate_after:type_name -> google.protobuf.Timestamp - 36, // 33: teleport.scopes.joining.v1.BoundKeypairStatus.last_recovered_at:type_name -> google.protobuf.Timestamp - 36, // 34: teleport.scopes.joining.v1.BoundKeypairStatus.last_rotated_at:type_name -> google.protobuf.Timestamp - 37, // 35: teleport.scopes.joining.v1.GenericOIDC.must_match_fields:type_name -> google.protobuf.Struct - 34, // 36: teleport.scopes.joining.v1.GenericOIDC.allow_any:type_name -> teleport.scopes.joining.v1.GenericOIDC.Rule - 36, // 37: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec.must_register_before:type_name -> google.protobuf.Timestamp - 29, // 38: teleport.scopes.joining.v1.GenericOIDC.Condition.eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionEq - 30, // 39: teleport.scopes.joining.v1.GenericOIDC.Condition.not_eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq - 31, // 40: teleport.scopes.joining.v1.GenericOIDC.Condition.in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionIn - 32, // 41: teleport.scopes.joining.v1.GenericOIDC.Condition.not_in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn - 33, // 42: teleport.scopes.joining.v1.GenericOIDC.Rule.conditions:type_name -> teleport.scopes.joining.v1.GenericOIDC.Condition - 43, // [43:43] is the sub-list for method output_type - 43, // [43:43] is the sub-list for method input_type - 43, // [43:43] is the sub-list for extension type_name - 43, // [43:43] is the sub-list for extension extendee - 0, // [0:43] is the sub-list for field type_name + 18, // 12: teleport.scopes.joining.v1.ScopedTokenSpec.github:type_name -> teleport.scopes.joining.v1.Github + 38, // 13: teleport.scopes.joining.v1.SingleUseStatus.used_at:type_name -> google.protobuf.Timestamp + 38, // 14: teleport.scopes.joining.v1.SingleUseStatus.reusable_until:type_name -> google.protobuf.Timestamp + 2, // 15: teleport.scopes.joining.v1.SingleUseStatus.host_cert_params:type_name -> teleport.scopes.joining.v1.HostCertParams + 3, // 16: teleport.scopes.joining.v1.UsageStatus.single_use:type_name -> teleport.scopes.joining.v1.SingleUseStatus + 16, // 17: teleport.scopes.joining.v1.UsageStatus.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairStatus + 4, // 18: teleport.scopes.joining.v1.ScopedTokenStatus.usage:type_name -> teleport.scopes.joining.v1.UsageStatus + 19, // 19: teleport.scopes.joining.v1.ImmutableLabels.ssh:type_name -> teleport.scopes.joining.v1.ImmutableLabels.SshEntry + 37, // 20: teleport.scopes.joining.v1.StaticScopedTokens.metadata:type_name -> teleport.header.v1.Metadata + 8, // 21: teleport.scopes.joining.v1.StaticScopedTokens.spec:type_name -> teleport.scopes.joining.v1.StaticScopedTokensSpec + 0, // 22: teleport.scopes.joining.v1.StaticScopedTokensSpec.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken + 20, // 23: teleport.scopes.joining.v1.AWS.allow:type_name -> teleport.scopes.joining.v1.AWS.Rule + 21, // 24: teleport.scopes.joining.v1.GCP.allow:type_name -> teleport.scopes.joining.v1.GCP.Rule + 22, // 25: teleport.scopes.joining.v1.Azure.allow:type_name -> teleport.scopes.joining.v1.Azure.Rule + 23, // 26: teleport.scopes.joining.v1.AzureDevops.allow:type_name -> teleport.scopes.joining.v1.AzureDevops.Rule + 24, // 27: teleport.scopes.joining.v1.Oracle.allow:type_name -> teleport.scopes.joining.v1.Oracle.Rule + 27, // 28: teleport.scopes.joining.v1.Kubernetes.allow:type_name -> teleport.scopes.joining.v1.Kubernetes.Rule + 25, // 29: teleport.scopes.joining.v1.Kubernetes.static_jwks:type_name -> teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig + 26, // 30: teleport.scopes.joining.v1.Kubernetes.oidc:type_name -> teleport.scopes.joining.v1.Kubernetes.OIDCConfig + 28, // 31: teleport.scopes.joining.v1.BoundKeypairSpec.onboarding:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec + 29, // 32: teleport.scopes.joining.v1.BoundKeypairSpec.recovery:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec + 38, // 33: teleport.scopes.joining.v1.BoundKeypairSpec.rotate_after:type_name -> google.protobuf.Timestamp + 38, // 34: teleport.scopes.joining.v1.BoundKeypairStatus.last_recovered_at:type_name -> google.protobuf.Timestamp + 38, // 35: teleport.scopes.joining.v1.BoundKeypairStatus.last_rotated_at:type_name -> google.protobuf.Timestamp + 39, // 36: teleport.scopes.joining.v1.GenericOIDC.must_match_fields:type_name -> google.protobuf.Struct + 35, // 37: teleport.scopes.joining.v1.GenericOIDC.allow_any:type_name -> teleport.scopes.joining.v1.GenericOIDC.Rule + 36, // 38: teleport.scopes.joining.v1.Github.allow:type_name -> teleport.scopes.joining.v1.Github.Rule + 38, // 39: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec.must_register_before:type_name -> google.protobuf.Timestamp + 30, // 40: teleport.scopes.joining.v1.GenericOIDC.Condition.eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionEq + 31, // 41: teleport.scopes.joining.v1.GenericOIDC.Condition.not_eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq + 32, // 42: teleport.scopes.joining.v1.GenericOIDC.Condition.in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionIn + 33, // 43: teleport.scopes.joining.v1.GenericOIDC.Condition.not_in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn + 34, // 44: teleport.scopes.joining.v1.GenericOIDC.Rule.conditions:type_name -> teleport.scopes.joining.v1.GenericOIDC.Condition + 45, // [45:45] is the sub-list for method output_type + 45, // [45:45] is the sub-list for method input_type + 45, // [45:45] is the sub-list for extension type_name + 45, // [45:45] is the sub-list for extension extendee + 0, // [0:45] is the sub-list for field type_name } func init() { file_teleport_scopes_joining_v1_token_proto_init() } @@ -4520,7 +4988,7 @@ func file_teleport_scopes_joining_v1_token_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_scopes_joining_v1_token_proto_rawDesc), len(file_teleport_scopes_joining_v1_token_proto_rawDesc)), NumEnums: 0, - NumMessages: 35, + NumMessages: 37, NumExtensions: 0, NumServices: 0, }, diff --git a/api/gen/proto/go/teleport/scopes/joining/v1/token_protoopaque.pb.go b/api/gen/proto/go/teleport/scopes/joining/v1/token_protoopaque.pb.go index 9423ce1f3ab3..5e21e8765b98 100644 --- a/api/gen/proto/go/teleport/scopes/joining/v1/token_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/scopes/joining/v1/token_protoopaque.pb.go @@ -241,6 +241,7 @@ type ScopedTokenSpec struct { xxx_hidden_BoundKeypair *BoundKeypairSpec `protobuf:"bytes,12,opt,name=bound_keypair,json=boundKeypair,proto3"` xxx_hidden_Bot string `protobuf:"bytes,15,opt,name=bot,proto3"` xxx_hidden_GenericOidc *GenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3"` + xxx_hidden_Github *Github `protobuf:"bytes,17,opt,name=github,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -368,6 +369,13 @@ func (x *ScopedTokenSpec) GetGenericOidc() *GenericOIDC { return nil } +func (x *ScopedTokenSpec) GetGithub() *Github { + if x != nil { + return x.xxx_hidden_Github + } + return nil +} + func (x *ScopedTokenSpec) SetAssignedScope(v string) { x.xxx_hidden_AssignedScope = v } @@ -424,6 +432,10 @@ func (x *ScopedTokenSpec) SetGenericOidc(v *GenericOIDC) { x.xxx_hidden_GenericOidc = v } +func (x *ScopedTokenSpec) SetGithub(v *Github) { + x.xxx_hidden_Github = v +} + func (x *ScopedTokenSpec) HasImmutableLabels() bool { if x == nil { return false @@ -487,6 +499,13 @@ func (x *ScopedTokenSpec) HasGenericOidc() bool { return x.xxx_hidden_GenericOidc != nil } +func (x *ScopedTokenSpec) HasGithub() bool { + if x == nil { + return false + } + return x.xxx_hidden_Github != nil +} + func (x *ScopedTokenSpec) ClearImmutableLabels() { x.xxx_hidden_ImmutableLabels = nil } @@ -523,6 +542,10 @@ func (x *ScopedTokenSpec) ClearGenericOidc() { x.xxx_hidden_GenericOidc = nil } +func (x *ScopedTokenSpec) ClearGithub() { + x.xxx_hidden_Github = nil +} + type ScopedTokenSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -567,6 +590,8 @@ type ScopedTokenSpec_builder struct { Bot string // Configuration specific to the "generic_oidc" join method. GenericOidc *GenericOIDC + // Configuration specific to the "github" join method. + Github *Github } func (b0 ScopedTokenSpec_builder) Build() *ScopedTokenSpec { @@ -587,6 +612,7 @@ func (b0 ScopedTokenSpec_builder) Build() *ScopedTokenSpec { x.xxx_hidden_BoundKeypair = b.BoundKeypair x.xxx_hidden_Bot = b.Bot x.xxx_hidden_GenericOidc = b.GenericOidc + x.xxx_hidden_Github = b.Github return m0 } @@ -2422,6 +2448,131 @@ func (b0 GenericOIDC_builder) Build() *GenericOIDC { return m0 } +// Configuration for Github token. +type Github struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_EnterpriseServerHost string `protobuf:"bytes,1,opt,name=enterprise_server_host,json=enterpriseServerHost,proto3"` + xxx_hidden_EnterpriseSlug string `protobuf:"bytes,2,opt,name=enterprise_slug,json=enterpriseSlug,proto3"` + xxx_hidden_StaticJwks string `protobuf:"bytes,3,opt,name=static_jwks,json=staticJwks,proto3"` + xxx_hidden_Allow *[]*Github_Rule `protobuf:"bytes,4,rep,name=allow,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Github) Reset() { + *x = Github{} + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Github) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Github) ProtoMessage() {} + +func (x *Github) ProtoReflect() protoreflect.Message { + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Github) GetEnterpriseServerHost() string { + if x != nil { + return x.xxx_hidden_EnterpriseServerHost + } + return "" +} + +func (x *Github) GetEnterpriseSlug() string { + if x != nil { + return x.xxx_hidden_EnterpriseSlug + } + return "" +} + +func (x *Github) GetStaticJwks() string { + if x != nil { + return x.xxx_hidden_StaticJwks + } + return "" +} + +func (x *Github) GetAllow() []*Github_Rule { + if x != nil { + if x.xxx_hidden_Allow != nil { + return *x.xxx_hidden_Allow + } + } + return nil +} + +func (x *Github) SetEnterpriseServerHost(v string) { + x.xxx_hidden_EnterpriseServerHost = v +} + +func (x *Github) SetEnterpriseSlug(v string) { + x.xxx_hidden_EnterpriseSlug = v +} + +func (x *Github) SetStaticJwks(v string) { + x.xxx_hidden_StaticJwks = v +} + +func (x *Github) SetAllow(v []*Github_Rule) { + x.xxx_hidden_Allow = &v +} + +type Github_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // enterprise_server_host allows joining from runners associated with a + // GitHub Enterprise Server instance. When unconfigured, tokens will be + // validated against github.com, but when configured to the host of a GHES + // instance, then the tokens will be validated against host. + // + // This value should be the hostname of the GHES instance, and should not + // include the scheme or a path. The instance must be accessible over HTTPS + // at this hostname and the certificate must be trusted by the Auth Service. + EnterpriseServerHost string + // enterprise_slug allows the slug of a GitHub Enterprise organisation to be + // included in the expected issuer of the OIDC tokens. This is for + // compatibility with the `include_enterprise_slug` option in GHE. + // + // This field should be set to the slug of your enterprise if this is enabled. If + // this is not enabled, then this field must be left empty. This field cannot + // be specified if `enterprise_server_host` is specified. + // + // See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + // for more information about customized issuer values. + EnterpriseSlug string + // static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC + // endpoints, and allows them to be directly specified. This allows joining + // from GitHub Actions in GHES instances that are not reachable by the + // Teleport Auth Service. + StaticJwks string + // allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. + Allow []*Github_Rule +} + +func (b0 Github_builder) Build() *Github { + m0 := &Github{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_EnterpriseServerHost = b.EnterpriseServerHost + x.xxx_hidden_EnterpriseSlug = b.EnterpriseSlug + x.xxx_hidden_StaticJwks = b.StaticJwks + x.xxx_hidden_Allow = &b.Allow + return m0 +} + // A rule that a joining node must match in order to use the associated token // with AWS join methods. type AWS_Rule struct { @@ -2437,7 +2588,7 @@ type AWS_Rule struct { func (x *AWS_Rule) Reset() { *x = AWS_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[19] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2449,7 +2600,7 @@ func (x *AWS_Rule) String() string { func (*AWS_Rule) ProtoMessage() {} func (x *AWS_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[19] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2559,7 +2710,7 @@ type GCP_Rule struct { func (x *GCP_Rule) Reset() { *x = GCP_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2571,7 +2722,7 @@ func (x *GCP_Rule) String() string { func (*GCP_Rule) ProtoMessage() {} func (x *GCP_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[20] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2649,7 +2800,7 @@ type Azure_Rule struct { func (x *Azure_Rule) Reset() { *x = Azure_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2661,7 +2812,7 @@ func (x *Azure_Rule) String() string { func (*Azure_Rule) ProtoMessage() {} func (x *Azure_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[21] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2744,7 +2895,7 @@ type AzureDevops_Rule struct { func (x *AzureDevops_Rule) Reset() { *x = AzureDevops_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2756,7 +2907,7 @@ func (x *AzureDevops_Rule) String() string { func (*AzureDevops_Rule) ProtoMessage() {} func (x *AzureDevops_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[22] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2921,7 +3072,7 @@ type Oracle_Rule struct { func (x *Oracle_Rule) Reset() { *x = Oracle_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2933,7 +3084,7 @@ func (x *Oracle_Rule) String() string { func (*Oracle_Rule) ProtoMessage() {} func (x *Oracle_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[23] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3025,7 +3176,7 @@ type Kubernetes_StaticJWKSConfig struct { func (x *Kubernetes_StaticJWKSConfig) Reset() { *x = Kubernetes_StaticJWKSConfig{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3037,7 +3188,7 @@ func (x *Kubernetes_StaticJWKSConfig) String() string { func (*Kubernetes_StaticJWKSConfig) ProtoMessage() {} func (x *Kubernetes_StaticJWKSConfig) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[24] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3086,7 +3237,7 @@ type Kubernetes_OIDCConfig struct { func (x *Kubernetes_OIDCConfig) Reset() { *x = Kubernetes_OIDCConfig{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3098,7 +3249,7 @@ func (x *Kubernetes_OIDCConfig) String() string { func (*Kubernetes_OIDCConfig) ProtoMessage() {} func (x *Kubernetes_OIDCConfig) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[25] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3167,7 +3318,7 @@ type Kubernetes_Rule struct { func (x *Kubernetes_Rule) Reset() { *x = Kubernetes_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3179,7 +3330,7 @@ func (x *Kubernetes_Rule) String() string { func (*Kubernetes_Rule) ProtoMessage() {} func (x *Kubernetes_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[26] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3264,7 +3415,7 @@ type BoundKeypairSpec_OnboardingSpec struct { func (x *BoundKeypairSpec_OnboardingSpec) Reset() { *x = BoundKeypairSpec_OnboardingSpec{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3276,7 +3427,7 @@ func (x *BoundKeypairSpec_OnboardingSpec) String() string { func (*BoundKeypairSpec_OnboardingSpec) ProtoMessage() {} func (x *BoundKeypairSpec_OnboardingSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[27] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3378,7 +3529,7 @@ type BoundKeypairSpec_RecoverySpec struct { func (x *BoundKeypairSpec_RecoverySpec) Reset() { *x = BoundKeypairSpec_RecoverySpec{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3390,7 +3541,7 @@ func (x *BoundKeypairSpec_RecoverySpec) String() string { func (*BoundKeypairSpec_RecoverySpec) ProtoMessage() {} func (x *BoundKeypairSpec_RecoverySpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[28] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3470,7 +3621,7 @@ type GenericOIDC_ConditionEq struct { func (x *GenericOIDC_ConditionEq) Reset() { *x = GenericOIDC_ConditionEq{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3482,7 +3633,7 @@ func (x *GenericOIDC_ConditionEq) String() string { func (*GenericOIDC_ConditionEq) ProtoMessage() {} func (x *GenericOIDC_ConditionEq) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[29] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3529,7 +3680,7 @@ type GenericOIDC_ConditionNotEq struct { func (x *GenericOIDC_ConditionNotEq) Reset() { *x = GenericOIDC_ConditionNotEq{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3541,7 +3692,7 @@ func (x *GenericOIDC_ConditionNotEq) String() string { func (*GenericOIDC_ConditionNotEq) ProtoMessage() {} func (x *GenericOIDC_ConditionNotEq) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[30] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3588,7 +3739,7 @@ type GenericOIDC_ConditionIn struct { func (x *GenericOIDC_ConditionIn) Reset() { *x = GenericOIDC_ConditionIn{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3600,7 +3751,7 @@ func (x *GenericOIDC_ConditionIn) String() string { func (*GenericOIDC_ConditionIn) ProtoMessage() {} func (x *GenericOIDC_ConditionIn) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[31] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3647,7 +3798,7 @@ type GenericOIDC_ConditionNotIn struct { func (x *GenericOIDC_ConditionNotIn) Reset() { *x = GenericOIDC_ConditionNotIn{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3659,7 +3810,7 @@ func (x *GenericOIDC_ConditionNotIn) String() string { func (*GenericOIDC_ConditionNotIn) ProtoMessage() {} func (x *GenericOIDC_ConditionNotIn) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[32] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3710,7 +3861,7 @@ type GenericOIDC_Condition struct { func (x *GenericOIDC_Condition) Reset() { *x = GenericOIDC_Condition{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3722,7 +3873,7 @@ func (x *GenericOIDC_Condition) String() string { func (*GenericOIDC_Condition) ProtoMessage() {} func (x *GenericOIDC_Condition) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[33] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3872,7 +4023,7 @@ type GenericOIDC_Rule struct { func (x *GenericOIDC_Rule) Reset() { *x = GenericOIDC_Rule{} - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3884,7 +4035,7 @@ func (x *GenericOIDC_Rule) String() string { func (*GenericOIDC_Rule) ProtoMessage() {} func (x *GenericOIDC_Rule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[34] + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3941,6 +4092,233 @@ func (b0 GenericOIDC_Rule_builder) Build() *GenericOIDC_Rule { return m0 } +// A rule that a joining node must match in order to use the associated token +// with the "github" join method. All specified fields within a rule must +// match (AND). At least one rule in the allow list must match (OR). +type Github_Rule struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Sub string `protobuf:"bytes,1,opt,name=sub,proto3"` + xxx_hidden_Repository string `protobuf:"bytes,2,opt,name=repository,proto3"` + xxx_hidden_RepositoryOwner string `protobuf:"bytes,3,opt,name=repository_owner,json=repositoryOwner,proto3"` + xxx_hidden_Workflow string `protobuf:"bytes,4,opt,name=workflow,proto3"` + xxx_hidden_Environment string `protobuf:"bytes,5,opt,name=environment,proto3"` + xxx_hidden_Actor string `protobuf:"bytes,6,opt,name=actor,proto3"` + xxx_hidden_Ref string `protobuf:"bytes,7,opt,name=ref,proto3"` + xxx_hidden_RefType string `protobuf:"bytes,8,opt,name=ref_type,json=refType,proto3"` + xxx_hidden_Enterprise string `protobuf:"bytes,9,opt,name=enterprise,proto3"` + xxx_hidden_EnterpriseId string `protobuf:"bytes,10,opt,name=enterprise_id,json=enterpriseId,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Github_Rule) Reset() { + *x = Github_Rule{} + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Github_Rule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Github_Rule) ProtoMessage() {} + +func (x *Github_Rule) ProtoReflect() protoreflect.Message { + mi := &file_teleport_scopes_joining_v1_token_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Github_Rule) GetSub() string { + if x != nil { + return x.xxx_hidden_Sub + } + return "" +} + +func (x *Github_Rule) GetRepository() string { + if x != nil { + return x.xxx_hidden_Repository + } + return "" +} + +func (x *Github_Rule) GetRepositoryOwner() string { + if x != nil { + return x.xxx_hidden_RepositoryOwner + } + return "" +} + +func (x *Github_Rule) GetWorkflow() string { + if x != nil { + return x.xxx_hidden_Workflow + } + return "" +} + +func (x *Github_Rule) GetEnvironment() string { + if x != nil { + return x.xxx_hidden_Environment + } + return "" +} + +func (x *Github_Rule) GetActor() string { + if x != nil { + return x.xxx_hidden_Actor + } + return "" +} + +func (x *Github_Rule) GetRef() string { + if x != nil { + return x.xxx_hidden_Ref + } + return "" +} + +func (x *Github_Rule) GetRefType() string { + if x != nil { + return x.xxx_hidden_RefType + } + return "" +} + +func (x *Github_Rule) GetEnterprise() string { + if x != nil { + return x.xxx_hidden_Enterprise + } + return "" +} + +func (x *Github_Rule) GetEnterpriseId() string { + if x != nil { + return x.xxx_hidden_EnterpriseId + } + return "" +} + +func (x *Github_Rule) SetSub(v string) { + x.xxx_hidden_Sub = v +} + +func (x *Github_Rule) SetRepository(v string) { + x.xxx_hidden_Repository = v +} + +func (x *Github_Rule) SetRepositoryOwner(v string) { + x.xxx_hidden_RepositoryOwner = v +} + +func (x *Github_Rule) SetWorkflow(v string) { + x.xxx_hidden_Workflow = v +} + +func (x *Github_Rule) SetEnvironment(v string) { + x.xxx_hidden_Environment = v +} + +func (x *Github_Rule) SetActor(v string) { + x.xxx_hidden_Actor = v +} + +func (x *Github_Rule) SetRef(v string) { + x.xxx_hidden_Ref = v +} + +func (x *Github_Rule) SetRefType(v string) { + x.xxx_hidden_RefType = v +} + +func (x *Github_Rule) SetEnterprise(v string) { + x.xxx_hidden_Enterprise = v +} + +func (x *Github_Rule) SetEnterpriseId(v string) { + x.xxx_hidden_EnterpriseId = v +} + +type Github_Rule_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // sub also known as Subject is a string that roughly uniquely identifies + // the workload. The format of this varies depending on the type of + // github action run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Sub string + // repository from where the workflow is running. + // This includes the name of the owner e.g `gravitational/teleport` + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Repository string + // repository_owner is the name of the organization in which the repository is stored. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + RepositoryOwner string + // workflow name. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Workflow string + // environment name used by the job. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Environment string + // actor is the personal account that initiated the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Actor string + // ref (git) that triggered the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + Ref string + // ref_type for example: "branch". + RefType string + // enterprise name in which the repository is stored. + Enterprise string + // enterprise_id in which the repository is stored. + EnterpriseId string +} + +func (b0 Github_Rule_builder) Build() *Github_Rule { + m0 := &Github_Rule{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Sub = b.Sub + x.xxx_hidden_Repository = b.Repository + x.xxx_hidden_RepositoryOwner = b.RepositoryOwner + x.xxx_hidden_Workflow = b.Workflow + x.xxx_hidden_Environment = b.Environment + x.xxx_hidden_Actor = b.Actor + x.xxx_hidden_Ref = b.Ref + x.xxx_hidden_RefType = b.RefType + x.xxx_hidden_Enterprise = b.Enterprise + x.xxx_hidden_EnterpriseId = b.EnterpriseId + return m0 +} + var File_teleport_scopes_joining_v1_token_proto protoreflect.FileDescriptor const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + @@ -3953,7 +4331,7 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12\x14\n" + "\x05scope\x18\x05 \x01(\tR\x05scope\x12?\n" + "\x04spec\x18\x06 \x01(\v2+.teleport.scopes.joining.v1.ScopedTokenSpecR\x04spec\x12E\n" + - "\x06status\x18\a \x01(\v2-.teleport.scopes.joining.v1.ScopedTokenStatusR\x06status\"\xa7\x06\n" + + "\x06status\x18\a \x01(\v2-.teleport.scopes.joining.v1.ScopedTokenStatusR\x06status\"\xe3\x06\n" + "\x0fScopedTokenSpec\x12%\n" + "\x0eassigned_scope\x18\x01 \x01(\tR\rassignedScope\x12\x14\n" + "\x05roles\x18\x02 \x03(\tR\x05roles\x12\x1f\n" + @@ -3973,7 +4351,8 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "kubernetes\x12Q\n" + "\rbound_keypair\x18\f \x01(\v2,.teleport.scopes.joining.v1.BoundKeypairSpecR\fboundKeypair\x12\x10\n" + "\x03bot\x18\x0f \x01(\tR\x03bot\x12J\n" + - "\fgeneric_oidc\x18\x10 \x01(\v2'.teleport.scopes.joining.v1.GenericOIDCR\vgenericOidcJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fR\bbot_nameR\tbot_scope\"\xb6\x01\n" + + "\fgeneric_oidc\x18\x10 \x01(\v2'.teleport.scopes.joining.v1.GenericOIDCR\vgenericOidc\x12:\n" + + "\x06github\x18\x11 \x01(\v2\".teleport.scopes.joining.v1.GithubR\x06githubJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fR\bbot_nameR\tbot_scope\"\xb6\x01\n" + "\x0eHostCertParams\x12\x17\n" + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x1b\n" + "\tnode_name\x18\x02 \x01(\tR\bnodeName\x12\x12\n" + @@ -4119,9 +4498,31 @@ const file_teleport_scopes_joining_v1_token_proto_rawDesc = "" + "conditions\x12\x1e\n" + "\n" + "expression\x18\x02 \x01(\tR\n" + - "expressionBYZWgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1;joiningv1b\x06proto3" + "expression\"\xf3\x03\n" + + "\x06Github\x124\n" + + "\x16enterprise_server_host\x18\x01 \x01(\tR\x14enterpriseServerHost\x12'\n" + + "\x0fenterprise_slug\x18\x02 \x01(\tR\x0eenterpriseSlug\x12\x1f\n" + + "\vstatic_jwks\x18\x03 \x01(\tR\n" + + "staticJwks\x12=\n" + + "\x05allow\x18\x04 \x03(\v2'.teleport.scopes.joining.v1.Github.RuleR\x05allow\x1a\xa9\x02\n" + + "\x04Rule\x12\x10\n" + + "\x03sub\x18\x01 \x01(\tR\x03sub\x12\x1e\n" + + "\n" + + "repository\x18\x02 \x01(\tR\n" + + "repository\x12)\n" + + "\x10repository_owner\x18\x03 \x01(\tR\x0frepositoryOwner\x12\x1a\n" + + "\bworkflow\x18\x04 \x01(\tR\bworkflow\x12 \n" + + "\venvironment\x18\x05 \x01(\tR\venvironment\x12\x14\n" + + "\x05actor\x18\x06 \x01(\tR\x05actor\x12\x10\n" + + "\x03ref\x18\a \x01(\tR\x03ref\x12\x19\n" + + "\bref_type\x18\b \x01(\tR\arefType\x12\x1e\n" + + "\n" + + "enterprise\x18\t \x01(\tR\n" + + "enterprise\x12#\n" + + "\renterprise_id\x18\n" + + " \x01(\tR\fenterpriseIdBYZWgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1;joiningv1b\x06proto3" -var file_teleport_scopes_joining_v1_token_proto_msgTypes = make([]protoimpl.MessageInfo, 35) +var file_teleport_scopes_joining_v1_token_proto_msgTypes = make([]protoimpl.MessageInfo, 37) var file_teleport_scopes_joining_v1_token_proto_goTypes = []any{ (*ScopedToken)(nil), // 0: teleport.scopes.joining.v1.ScopedToken (*ScopedTokenSpec)(nil), // 1: teleport.scopes.joining.v1.ScopedTokenSpec @@ -4141,29 +4542,31 @@ var file_teleport_scopes_joining_v1_token_proto_goTypes = []any{ (*BoundKeypairSpec)(nil), // 15: teleport.scopes.joining.v1.BoundKeypairSpec (*BoundKeypairStatus)(nil), // 16: teleport.scopes.joining.v1.BoundKeypairStatus (*GenericOIDC)(nil), // 17: teleport.scopes.joining.v1.GenericOIDC - nil, // 18: teleport.scopes.joining.v1.ImmutableLabels.SshEntry - (*AWS_Rule)(nil), // 19: teleport.scopes.joining.v1.AWS.Rule - (*GCP_Rule)(nil), // 20: teleport.scopes.joining.v1.GCP.Rule - (*Azure_Rule)(nil), // 21: teleport.scopes.joining.v1.Azure.Rule - (*AzureDevops_Rule)(nil), // 22: teleport.scopes.joining.v1.AzureDevops.Rule - (*Oracle_Rule)(nil), // 23: teleport.scopes.joining.v1.Oracle.Rule - (*Kubernetes_StaticJWKSConfig)(nil), // 24: teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig - (*Kubernetes_OIDCConfig)(nil), // 25: teleport.scopes.joining.v1.Kubernetes.OIDCConfig - (*Kubernetes_Rule)(nil), // 26: teleport.scopes.joining.v1.Kubernetes.Rule - (*BoundKeypairSpec_OnboardingSpec)(nil), // 27: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec - (*BoundKeypairSpec_RecoverySpec)(nil), // 28: teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec - (*GenericOIDC_ConditionEq)(nil), // 29: teleport.scopes.joining.v1.GenericOIDC.ConditionEq - (*GenericOIDC_ConditionNotEq)(nil), // 30: teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq - (*GenericOIDC_ConditionIn)(nil), // 31: teleport.scopes.joining.v1.GenericOIDC.ConditionIn - (*GenericOIDC_ConditionNotIn)(nil), // 32: teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn - (*GenericOIDC_Condition)(nil), // 33: teleport.scopes.joining.v1.GenericOIDC.Condition - (*GenericOIDC_Rule)(nil), // 34: teleport.scopes.joining.v1.GenericOIDC.Rule - (*v1.Metadata)(nil), // 35: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 37: google.protobuf.Struct + (*Github)(nil), // 18: teleport.scopes.joining.v1.Github + nil, // 19: teleport.scopes.joining.v1.ImmutableLabels.SshEntry + (*AWS_Rule)(nil), // 20: teleport.scopes.joining.v1.AWS.Rule + (*GCP_Rule)(nil), // 21: teleport.scopes.joining.v1.GCP.Rule + (*Azure_Rule)(nil), // 22: teleport.scopes.joining.v1.Azure.Rule + (*AzureDevops_Rule)(nil), // 23: teleport.scopes.joining.v1.AzureDevops.Rule + (*Oracle_Rule)(nil), // 24: teleport.scopes.joining.v1.Oracle.Rule + (*Kubernetes_StaticJWKSConfig)(nil), // 25: teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig + (*Kubernetes_OIDCConfig)(nil), // 26: teleport.scopes.joining.v1.Kubernetes.OIDCConfig + (*Kubernetes_Rule)(nil), // 27: teleport.scopes.joining.v1.Kubernetes.Rule + (*BoundKeypairSpec_OnboardingSpec)(nil), // 28: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec + (*BoundKeypairSpec_RecoverySpec)(nil), // 29: teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec + (*GenericOIDC_ConditionEq)(nil), // 30: teleport.scopes.joining.v1.GenericOIDC.ConditionEq + (*GenericOIDC_ConditionNotEq)(nil), // 31: teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq + (*GenericOIDC_ConditionIn)(nil), // 32: teleport.scopes.joining.v1.GenericOIDC.ConditionIn + (*GenericOIDC_ConditionNotIn)(nil), // 33: teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn + (*GenericOIDC_Condition)(nil), // 34: teleport.scopes.joining.v1.GenericOIDC.Condition + (*GenericOIDC_Rule)(nil), // 35: teleport.scopes.joining.v1.GenericOIDC.Rule + (*Github_Rule)(nil), // 36: teleport.scopes.joining.v1.Github.Rule + (*v1.Metadata)(nil), // 37: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 38: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 39: google.protobuf.Struct } var file_teleport_scopes_joining_v1_token_proto_depIdxs = []int32{ - 35, // 0: teleport.scopes.joining.v1.ScopedToken.metadata:type_name -> teleport.header.v1.Metadata + 37, // 0: teleport.scopes.joining.v1.ScopedToken.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.scopes.joining.v1.ScopedToken.spec:type_name -> teleport.scopes.joining.v1.ScopedTokenSpec 5, // 2: teleport.scopes.joining.v1.ScopedToken.status:type_name -> teleport.scopes.joining.v1.ScopedTokenStatus 6, // 3: teleport.scopes.joining.v1.ScopedTokenSpec.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels @@ -4175,42 +4578,44 @@ var file_teleport_scopes_joining_v1_token_proto_depIdxs = []int32{ 14, // 9: teleport.scopes.joining.v1.ScopedTokenSpec.kubernetes:type_name -> teleport.scopes.joining.v1.Kubernetes 15, // 10: teleport.scopes.joining.v1.ScopedTokenSpec.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec 17, // 11: teleport.scopes.joining.v1.ScopedTokenSpec.generic_oidc:type_name -> teleport.scopes.joining.v1.GenericOIDC - 36, // 12: teleport.scopes.joining.v1.SingleUseStatus.used_at:type_name -> google.protobuf.Timestamp - 36, // 13: teleport.scopes.joining.v1.SingleUseStatus.reusable_until:type_name -> google.protobuf.Timestamp - 2, // 14: teleport.scopes.joining.v1.SingleUseStatus.host_cert_params:type_name -> teleport.scopes.joining.v1.HostCertParams - 3, // 15: teleport.scopes.joining.v1.UsageStatus.single_use:type_name -> teleport.scopes.joining.v1.SingleUseStatus - 16, // 16: teleport.scopes.joining.v1.UsageStatus.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairStatus - 4, // 17: teleport.scopes.joining.v1.ScopedTokenStatus.usage:type_name -> teleport.scopes.joining.v1.UsageStatus - 18, // 18: teleport.scopes.joining.v1.ImmutableLabels.ssh:type_name -> teleport.scopes.joining.v1.ImmutableLabels.SshEntry - 35, // 19: teleport.scopes.joining.v1.StaticScopedTokens.metadata:type_name -> teleport.header.v1.Metadata - 8, // 20: teleport.scopes.joining.v1.StaticScopedTokens.spec:type_name -> teleport.scopes.joining.v1.StaticScopedTokensSpec - 0, // 21: teleport.scopes.joining.v1.StaticScopedTokensSpec.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken - 19, // 22: teleport.scopes.joining.v1.AWS.allow:type_name -> teleport.scopes.joining.v1.AWS.Rule - 20, // 23: teleport.scopes.joining.v1.GCP.allow:type_name -> teleport.scopes.joining.v1.GCP.Rule - 21, // 24: teleport.scopes.joining.v1.Azure.allow:type_name -> teleport.scopes.joining.v1.Azure.Rule - 22, // 25: teleport.scopes.joining.v1.AzureDevops.allow:type_name -> teleport.scopes.joining.v1.AzureDevops.Rule - 23, // 26: teleport.scopes.joining.v1.Oracle.allow:type_name -> teleport.scopes.joining.v1.Oracle.Rule - 26, // 27: teleport.scopes.joining.v1.Kubernetes.allow:type_name -> teleport.scopes.joining.v1.Kubernetes.Rule - 24, // 28: teleport.scopes.joining.v1.Kubernetes.static_jwks:type_name -> teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig - 25, // 29: teleport.scopes.joining.v1.Kubernetes.oidc:type_name -> teleport.scopes.joining.v1.Kubernetes.OIDCConfig - 27, // 30: teleport.scopes.joining.v1.BoundKeypairSpec.onboarding:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec - 28, // 31: teleport.scopes.joining.v1.BoundKeypairSpec.recovery:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec - 36, // 32: teleport.scopes.joining.v1.BoundKeypairSpec.rotate_after:type_name -> google.protobuf.Timestamp - 36, // 33: teleport.scopes.joining.v1.BoundKeypairStatus.last_recovered_at:type_name -> google.protobuf.Timestamp - 36, // 34: teleport.scopes.joining.v1.BoundKeypairStatus.last_rotated_at:type_name -> google.protobuf.Timestamp - 37, // 35: teleport.scopes.joining.v1.GenericOIDC.must_match_fields:type_name -> google.protobuf.Struct - 34, // 36: teleport.scopes.joining.v1.GenericOIDC.allow_any:type_name -> teleport.scopes.joining.v1.GenericOIDC.Rule - 36, // 37: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec.must_register_before:type_name -> google.protobuf.Timestamp - 29, // 38: teleport.scopes.joining.v1.GenericOIDC.Condition.eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionEq - 30, // 39: teleport.scopes.joining.v1.GenericOIDC.Condition.not_eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq - 31, // 40: teleport.scopes.joining.v1.GenericOIDC.Condition.in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionIn - 32, // 41: teleport.scopes.joining.v1.GenericOIDC.Condition.not_in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn - 33, // 42: teleport.scopes.joining.v1.GenericOIDC.Rule.conditions:type_name -> teleport.scopes.joining.v1.GenericOIDC.Condition - 43, // [43:43] is the sub-list for method output_type - 43, // [43:43] is the sub-list for method input_type - 43, // [43:43] is the sub-list for extension type_name - 43, // [43:43] is the sub-list for extension extendee - 0, // [0:43] is the sub-list for field type_name + 18, // 12: teleport.scopes.joining.v1.ScopedTokenSpec.github:type_name -> teleport.scopes.joining.v1.Github + 38, // 13: teleport.scopes.joining.v1.SingleUseStatus.used_at:type_name -> google.protobuf.Timestamp + 38, // 14: teleport.scopes.joining.v1.SingleUseStatus.reusable_until:type_name -> google.protobuf.Timestamp + 2, // 15: teleport.scopes.joining.v1.SingleUseStatus.host_cert_params:type_name -> teleport.scopes.joining.v1.HostCertParams + 3, // 16: teleport.scopes.joining.v1.UsageStatus.single_use:type_name -> teleport.scopes.joining.v1.SingleUseStatus + 16, // 17: teleport.scopes.joining.v1.UsageStatus.bound_keypair:type_name -> teleport.scopes.joining.v1.BoundKeypairStatus + 4, // 18: teleport.scopes.joining.v1.ScopedTokenStatus.usage:type_name -> teleport.scopes.joining.v1.UsageStatus + 19, // 19: teleport.scopes.joining.v1.ImmutableLabels.ssh:type_name -> teleport.scopes.joining.v1.ImmutableLabels.SshEntry + 37, // 20: teleport.scopes.joining.v1.StaticScopedTokens.metadata:type_name -> teleport.header.v1.Metadata + 8, // 21: teleport.scopes.joining.v1.StaticScopedTokens.spec:type_name -> teleport.scopes.joining.v1.StaticScopedTokensSpec + 0, // 22: teleport.scopes.joining.v1.StaticScopedTokensSpec.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken + 20, // 23: teleport.scopes.joining.v1.AWS.allow:type_name -> teleport.scopes.joining.v1.AWS.Rule + 21, // 24: teleport.scopes.joining.v1.GCP.allow:type_name -> teleport.scopes.joining.v1.GCP.Rule + 22, // 25: teleport.scopes.joining.v1.Azure.allow:type_name -> teleport.scopes.joining.v1.Azure.Rule + 23, // 26: teleport.scopes.joining.v1.AzureDevops.allow:type_name -> teleport.scopes.joining.v1.AzureDevops.Rule + 24, // 27: teleport.scopes.joining.v1.Oracle.allow:type_name -> teleport.scopes.joining.v1.Oracle.Rule + 27, // 28: teleport.scopes.joining.v1.Kubernetes.allow:type_name -> teleport.scopes.joining.v1.Kubernetes.Rule + 25, // 29: teleport.scopes.joining.v1.Kubernetes.static_jwks:type_name -> teleport.scopes.joining.v1.Kubernetes.StaticJWKSConfig + 26, // 30: teleport.scopes.joining.v1.Kubernetes.oidc:type_name -> teleport.scopes.joining.v1.Kubernetes.OIDCConfig + 28, // 31: teleport.scopes.joining.v1.BoundKeypairSpec.onboarding:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec + 29, // 32: teleport.scopes.joining.v1.BoundKeypairSpec.recovery:type_name -> teleport.scopes.joining.v1.BoundKeypairSpec.RecoverySpec + 38, // 33: teleport.scopes.joining.v1.BoundKeypairSpec.rotate_after:type_name -> google.protobuf.Timestamp + 38, // 34: teleport.scopes.joining.v1.BoundKeypairStatus.last_recovered_at:type_name -> google.protobuf.Timestamp + 38, // 35: teleport.scopes.joining.v1.BoundKeypairStatus.last_rotated_at:type_name -> google.protobuf.Timestamp + 39, // 36: teleport.scopes.joining.v1.GenericOIDC.must_match_fields:type_name -> google.protobuf.Struct + 35, // 37: teleport.scopes.joining.v1.GenericOIDC.allow_any:type_name -> teleport.scopes.joining.v1.GenericOIDC.Rule + 36, // 38: teleport.scopes.joining.v1.Github.allow:type_name -> teleport.scopes.joining.v1.Github.Rule + 38, // 39: teleport.scopes.joining.v1.BoundKeypairSpec.OnboardingSpec.must_register_before:type_name -> google.protobuf.Timestamp + 30, // 40: teleport.scopes.joining.v1.GenericOIDC.Condition.eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionEq + 31, // 41: teleport.scopes.joining.v1.GenericOIDC.Condition.not_eq:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotEq + 32, // 42: teleport.scopes.joining.v1.GenericOIDC.Condition.in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionIn + 33, // 43: teleport.scopes.joining.v1.GenericOIDC.Condition.not_in:type_name -> teleport.scopes.joining.v1.GenericOIDC.ConditionNotIn + 34, // 44: teleport.scopes.joining.v1.GenericOIDC.Rule.conditions:type_name -> teleport.scopes.joining.v1.GenericOIDC.Condition + 45, // [45:45] is the sub-list for method output_type + 45, // [45:45] is the sub-list for method input_type + 45, // [45:45] is the sub-list for extension type_name + 45, // [45:45] is the sub-list for extension extendee + 0, // [0:45] is the sub-list for field type_name } func init() { file_teleport_scopes_joining_v1_token_proto_init() } @@ -4228,7 +4633,7 @@ func file_teleport_scopes_joining_v1_token_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_scopes_joining_v1_token_proto_rawDesc), len(file_teleport_scopes_joining_v1_token_proto_rawDesc)), NumEnums: 0, - NumMessages: 35, + NumMessages: 37, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/scopes/joining/v1/token.proto b/api/proto/teleport/scopes/joining/v1/token.proto index 703649ddde11..6ed5adc59cf0 100644 --- a/api/proto/teleport/scopes/joining/v1/token.proto +++ b/api/proto/teleport/scopes/joining/v1/token.proto @@ -108,6 +108,9 @@ message ScopedTokenSpec { // Configuration specific to the "generic_oidc" join method. GenericOIDC generic_oidc = 16; + + // Configuration specific to the "github" join method. + Github github = 17; } // The host certificate parameters that should be cached and leveraged for @@ -637,3 +640,103 @@ message GenericOIDC { // must be specified for any join attempts to succeed. repeated Rule allow_any = 7; } + +// Configuration for Github token. +message Github { + // enterprise_server_host allows joining from runners associated with a + // GitHub Enterprise Server instance. When unconfigured, tokens will be + // validated against github.com, but when configured to the host of a GHES + // instance, then the tokens will be validated against host. + // + // This value should be the hostname of the GHES instance, and should not + // include the scheme or a path. The instance must be accessible over HTTPS + // at this hostname and the certificate must be trusted by the Auth Service. + string enterprise_server_host = 1; + + // enterprise_slug allows the slug of a GitHub Enterprise organisation to be + // included in the expected issuer of the OIDC tokens. This is for + // compatibility with the `include_enterprise_slug` option in GHE. + // + // This field should be set to the slug of your enterprise if this is enabled. If + // this is not enabled, then this field must be left empty. This field cannot + // be specified if `enterprise_server_host` is specified. + // + // See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + // for more information about customized issuer values. + string enterprise_slug = 2; + + // static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC + // endpoints, and allows them to be directly specified. This allows joining + // from GitHub Actions in GHES instances that are not reachable by the + // Teleport Auth Service. + string static_jwks = 3; + + // allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. + repeated Rule allow = 4; + + // A rule that a joining node must match in order to use the associated token + // with the "github" join method. All specified fields within a rule must + // match (AND). At least one rule in the allow list must match (OR). + message Rule { + // sub also known as Subject is a string that roughly uniquely identifies + // the workload. The format of this varies depending on the type of + // github action run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string sub = 1; + + // repository from where the workflow is running. + // This includes the name of the owner e.g `gravitational/teleport` + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string repository = 2; + + // repository_owner is the name of the organization in which the repository is stored. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string repository_owner = 3; + + // workflow name. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string workflow = 4; + + // environment name used by the job. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string environment = 5; + + // actor is the personal account that initiated the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string actor = 6; + + // ref (git) that triggered the workflow run. + // + // This field supports "glob-style" matching: + // - Use '*' to match zero or more characters. + // - Use '?' to match any single character. + string ref = 7; + + // ref_type for example: "branch". + string ref_type = 8; + + // enterprise name in which the repository is stored. + string enterprise = 9; + + // enterprise_id in which the repository is stored. + string enterprise_id = 10; + } +} diff --git a/api/types/provisioning.go b/api/types/provisioning.go index 34855c74b9e1..0c20842ee3f6 100644 --- a/api/types/provisioning.go +++ b/api/types/provisioning.go @@ -178,6 +178,8 @@ type ProvisionToken interface { GetBoundKeypairStatus() *ProvisionTokenStatusV2BoundKeypair // GetGenericOIDC returns generic_oidc-specific configuration for this token. GetGenericOIDC() (*ProvisionTokenSpecV2GenericOIDC, error) + // GetGithub returns github-specific configuration for this token. + GetGithub() *ProvisionTokenSpecV2GitHub // GetAWSIIDTTL returns the TTL of EC2 IIDs GetAWSIIDTTL() Duration // GetJoinMethod returns joining method that must be used with this token. @@ -621,6 +623,11 @@ func (p *ProvisionTokenV2) GetGenericOIDC() (*ProvisionTokenSpecV2GenericOIDC, e return p.Spec.GenericOIDC, nil } +// GetGithub returns github-specific configuration for this token. +func (p *ProvisionTokenV2) GetGithub() *ProvisionTokenSpecV2GitHub { + return p.Spec.GitHub +} + // GetJoinMethod returns joining method that must be used with this token. func (p *ProvisionTokenV2) GetJoinMethod() JoinMethod { return p.Spec.JoinMethod @@ -1089,7 +1096,6 @@ func (a *ProvisionTokenSpecV2TPM) validate() error { "ekcert_allowed_cas[%d]: parsing certificate", i, ) - } } diff --git a/docs/pages/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedtokensv1.mdx b/docs/pages/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedtokensv1.mdx index b15c44777ec2..30846ad1de42 100644 --- a/docs/pages/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedtokensv1.mdx +++ b/docs/pages/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedtokensv1.mdx @@ -39,6 +39,7 @@ resource, which you can apply after installing the Teleport Kubernetes operator. |bound_keypair|[object](#specbound_keypair)|Configuration specific to the "bound_keypair" join method.| |gcp|[object](#specgcp)|The GCP-specific configuration used with the "gcp" join method.| |generic_oidc|[object](#specgeneric_oidc)|Configuration specific to the "generic_oidc" join method.| +|github|[object](#specgithub)|Configuration specific to the "github" join method.| |immutable_labels|[object](#specimmutable_labels)|Immutable labels that should be applied to any resulting resources provisioned using this token.| |join_method|string|The joining method required in order to use this token. Note that not all join methods support joining with scoped tokens.| |kubernetes|[object](#speckubernetes)|The Kubernetes-specific configuration used with the "kubernetes" join method.| @@ -188,6 +189,30 @@ resource, which you can apply after installing the Teleport Kubernetes operator. |---|---|---| |values|[]string|| +### spec.github + +|Field|Type|Description| +|---|---|---| +|allow|[][object](#specgithuballow-items)|allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token.| +|enterprise_server_host|string|enterprise_server_host allows joining from runners associated with a GitHub Enterprise Server instance. When unconfigured, tokens will be validated against github.com, but when configured to the host of a GHES instance, then the tokens will be validated against host. This value should be the hostname of the GHES instance, and should not include the scheme or a path. The instance must be accessible over HTTPS at this hostname and the certificate must be trusted by the Auth Service.| +|enterprise_slug|string|enterprise_slug allows the slug of a GitHub Enterprise organisation to be included in the expected issuer of the OIDC tokens. This is for compatibility with the `include_enterprise_slug` option in GHE. This field should be set to the slug of your enterprise if this is enabled. If this is not enabled, then this field must be left empty. This field cannot be specified if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise for more information about customized issuer values.| +|static_jwks|string|static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC endpoints, and allows them to be directly specified. This allows joining from GitHub Actions in GHES instances that are not reachable by the Teleport Auth Service.| + +### spec.github.allow items + +|Field|Type|Description| +|---|---|---| +|actor|string|| +|enterprise|string|| +|enterprise_id|string|| +|environment|string|| +|ref|string|| +|ref_type|string|| +|repository|string|| +|repository_owner|string|| +|sub|string|| +|workflow|string|| + ### spec.immutable_labels |Field|Type|Description| diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx index 0cdcab9e9d2e..8396310134c4 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_token.mdx @@ -64,6 +64,7 @@ Optional: - `bound_keypair` (Attributes) Configuration specific to the "bound_keypair" join method. (see [below for nested schema](#nested-schema-for-specbound_keypair)) - `gcp` (Attributes) The GCP-specific configuration used with the "gcp" join method. (see [below for nested schema](#nested-schema-for-specgcp)) - `generic_oidc` (Attributes) Configuration specific to the "generic_oidc" join method. (see [below for nested schema](#nested-schema-for-specgeneric_oidc)) +- `github` (Attributes) Configuration specific to the "github" join method. (see [below for nested schema](#nested-schema-for-specgithub)) - `immutable_labels` (Attributes) Immutable labels that should be applied to any resulting resources provisioned using this token. (see [below for nested schema](#nested-schema-for-specimmutable_labels)) - `kubernetes` (Attributes) The Kubernetes-specific configuration used with the "kubernetes" join method. (see [below for nested schema](#nested-schema-for-speckubernetes)) - `oracle` (Attributes) The Oracle-specific configuration used with the "oracle" join method. (see [below for nested schema](#nested-schema-for-specoracle)) @@ -227,6 +228,32 @@ Optional: +### Nested Schema for `spec.github` + +Optional: + +- `allow` (Attributes List) allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. (see [below for nested schema](#nested-schema-for-specgithuballow)) +- `enterprise_server_host` (String) enterprise_server_host allows joining from runners associated with a GitHub Enterprise Server instance. When unconfigured, tokens will be validated against github.com, but when configured to the host of a GHES instance, then the tokens will be validated against host. This value should be the hostname of the GHES instance, and should not include the scheme or a path. The instance must be accessible over HTTPS at this hostname and the certificate must be trusted by the Auth Service. +- `enterprise_slug` (String) enterprise_slug allows the slug of a GitHub Enterprise organisation to be included in the expected issuer of the OIDC tokens. This is for compatibility with the `include_enterprise_slug` option in GHE. This field should be set to the slug of your enterprise if this is enabled. If this is not enabled, then this field must be left empty. This field cannot be specified if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise for more information about customized issuer values. +- `static_jwks` (String) static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC endpoints, and allows them to be directly specified. This allows joining from GitHub Actions in GHES instances that are not reachable by the Teleport Auth Service. + +### Nested Schema for `spec.github.allow` + +Optional: + +- `actor` (String) actor is the personal account that initiated the workflow run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `enterprise` (String) enterprise name in which the repository is stored. +- `enterprise_id` (String) enterprise_id in which the repository is stored. +- `environment` (String) environment name used by the job. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `ref` (String) ref (git) that triggered the workflow run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `ref_type` (String) ref_type for example: "branch". +- `repository` (String) repository from where the workflow is running. This includes the name of the owner e.g `gravitational/teleport` This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `repository_owner` (String) repository_owner is the name of the organization in which the repository is stored. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `sub` (String) sub also known as Subject is a string that roughly uniquely identifies the workload. The format of this varies depending on the type of github action run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `workflow` (String) workflow name. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. + + + ### Nested Schema for `spec.immutable_labels` Optional: diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx index f12265d0e24d..ede3af3e3bfa 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.mdx @@ -108,6 +108,7 @@ Optional: - `bound_keypair` (Attributes) Configuration specific to the "bound_keypair" join method. (see [below for nested schema](#nested-schema-for-specbound_keypair)) - `gcp` (Attributes) The GCP-specific configuration used with the "gcp" join method. (see [below for nested schema](#nested-schema-for-specgcp)) - `generic_oidc` (Attributes) Configuration specific to the "generic_oidc" join method. (see [below for nested schema](#nested-schema-for-specgeneric_oidc)) +- `github` (Attributes) Configuration specific to the "github" join method. (see [below for nested schema](#nested-schema-for-specgithub)) - `immutable_labels` (Attributes) Immutable labels that should be applied to any resulting resources provisioned using this token. (see [below for nested schema](#nested-schema-for-specimmutable_labels)) - `kubernetes` (Attributes) The Kubernetes-specific configuration used with the "kubernetes" join method. (see [below for nested schema](#nested-schema-for-speckubernetes)) - `oracle` (Attributes) The Oracle-specific configuration used with the "oracle" join method. (see [below for nested schema](#nested-schema-for-specoracle)) @@ -271,6 +272,32 @@ Optional: +### Nested Schema for `spec.github` + +Optional: + +- `allow` (Attributes List) allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token. (see [below for nested schema](#nested-schema-for-specgithuballow)) +- `enterprise_server_host` (String) enterprise_server_host allows joining from runners associated with a GitHub Enterprise Server instance. When unconfigured, tokens will be validated against github.com, but when configured to the host of a GHES instance, then the tokens will be validated against host. This value should be the hostname of the GHES instance, and should not include the scheme or a path. The instance must be accessible over HTTPS at this hostname and the certificate must be trusted by the Auth Service. +- `enterprise_slug` (String) enterprise_slug allows the slug of a GitHub Enterprise organisation to be included in the expected issuer of the OIDC tokens. This is for compatibility with the `include_enterprise_slug` option in GHE. This field should be set to the slug of your enterprise if this is enabled. If this is not enabled, then this field must be left empty. This field cannot be specified if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise for more information about customized issuer values. +- `static_jwks` (String) static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC endpoints, and allows them to be directly specified. This allows joining from GitHub Actions in GHES instances that are not reachable by the Teleport Auth Service. + +### Nested Schema for `spec.github.allow` + +Optional: + +- `actor` (String) actor is the personal account that initiated the workflow run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `enterprise` (String) enterprise name in which the repository is stored. +- `enterprise_id` (String) enterprise_id in which the repository is stored. +- `environment` (String) environment name used by the job. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `ref` (String) ref (git) that triggered the workflow run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `ref_type` (String) ref_type for example: "branch". +- `repository` (String) repository from where the workflow is running. This includes the name of the owner e.g `gravitational/teleport` This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `repository_owner` (String) repository_owner is the name of the organization in which the repository is stored. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `sub` (String) sub also known as Subject is a string that roughly uniquely identifies the workload. The format of this varies depending on the type of github action run. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. +- `workflow` (String) workflow name. This field supports "glob-style" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character. + + + ### Nested Schema for `spec.immutable_labels` Optional: diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_scopedtokensv1.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_scopedtokensv1.yaml index a3fbaba816cb..88f9e37f683a 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_scopedtokensv1.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_scopedtokensv1.yaml @@ -354,6 +354,65 @@ spec: updated manually if the remote CA is updated. type: string type: object + github: + description: Configuration specific to the "github" join method. + nullable: true + properties: + allow: + description: allow is a set of claim-matching fields evaluated + against the GitHub Actions OIDC token. + items: + properties: + actor: + type: string + enterprise: + type: string + enterprise_id: + type: string + environment: + type: string + ref: + type: string + ref_type: + type: string + repository: + type: string + repository_owner: + type: string + sub: + type: string + workflow: + type: string + type: object + nullable: true + type: array + enterprise_server_host: + description: enterprise_server_host allows joining from runners + associated with a GitHub Enterprise Server instance. When unconfigured, + tokens will be validated against github.com, but when configured + to the host of a GHES instance, then the tokens will be validated + against host. This value should be the hostname of the GHES + instance, and should not include the scheme or a path. The instance + must be accessible over HTTPS at this hostname and the certificate + must be trusted by the Auth Service. + type: string + enterprise_slug: + description: enterprise_slug allows the slug of a GitHub Enterprise + organisation to be included in the expected issuer of the OIDC + tokens. This is for compatibility with the `include_enterprise_slug` + option in GHE. This field should be set to the slug of your + enterprise if this is enabled. If this is not enabled, then + this field must be left empty. This field cannot be specified + if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + for more information about customized issuer values. + type: string + static_jwks: + description: static_jwks disables fetching of the GHES signing + keys via the JWKS/OIDC endpoints, and allows them to be directly + specified. This allows joining from GitHub Actions in GHES instances + that are not reachable by the Teleport Auth Service. + type: string + type: object immutable_labels: description: Immutable labels that should be applied to any resulting resources provisioned using this token. diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_scopedtokensv1.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_scopedtokensv1.yaml index a3fbaba816cb..88f9e37f683a 100644 --- a/integrations/operator/config/crd/bases/resources.teleport.dev_scopedtokensv1.yaml +++ b/integrations/operator/config/crd/bases/resources.teleport.dev_scopedtokensv1.yaml @@ -354,6 +354,65 @@ spec: updated manually if the remote CA is updated. type: string type: object + github: + description: Configuration specific to the "github" join method. + nullable: true + properties: + allow: + description: allow is a set of claim-matching fields evaluated + against the GitHub Actions OIDC token. + items: + properties: + actor: + type: string + enterprise: + type: string + enterprise_id: + type: string + environment: + type: string + ref: + type: string + ref_type: + type: string + repository: + type: string + repository_owner: + type: string + sub: + type: string + workflow: + type: string + type: object + nullable: true + type: array + enterprise_server_host: + description: enterprise_server_host allows joining from runners + associated with a GitHub Enterprise Server instance. When unconfigured, + tokens will be validated against github.com, but when configured + to the host of a GHES instance, then the tokens will be validated + against host. This value should be the hostname of the GHES + instance, and should not include the scheme or a path. The instance + must be accessible over HTTPS at this hostname and the certificate + must be trusted by the Auth Service. + type: string + enterprise_slug: + description: enterprise_slug allows the slug of a GitHub Enterprise + organisation to be included in the expected issuer of the OIDC + tokens. This is for compatibility with the `include_enterprise_slug` + option in GHE. This field should be set to the slug of your + enterprise if this is enabled. If this is not enabled, then + this field must be left empty. This field cannot be specified + if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise + for more information about customized issuer values. + type: string + static_jwks: + description: static_jwks disables fetching of the GHES signing + keys via the JWKS/OIDC endpoints, and allows them to be directly + specified. This allows joining from GitHub Actions in GHES instances + that are not reachable by the Teleport Auth Service. + type: string + type: object immutable_labels: description: Immutable labels that should be applied to any resulting resources provisioned using this token. diff --git a/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go b/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go index b2c052119405..6d12fcdbc98c 100644 --- a/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go +++ b/integrations/terraform/tfschema/scopes/joining/v1/token_terraform.go @@ -403,6 +403,83 @@ func GenSchemaScopedToken(ctx context.Context) (github_com_hashicorp_terraform_p Description: "Configuration specific to the \"generic_oidc\" join method.", Optional: true, }, + "github": { + 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{ + "actor": { + Description: "actor is the personal account that initiated the workflow run. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "enterprise": { + Description: "enterprise name in which the repository is stored.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "enterprise_id": { + Description: "enterprise_id in which the repository is stored.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "environment": { + Description: "environment name used by the job. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "ref": { + Description: "ref (git) that triggered the workflow run. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "ref_type": { + Description: "ref_type for example: \"branch\".", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "repository": { + Description: "repository from where the workflow is running. This includes the name of the owner e.g `gravitational/teleport` This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "repository_owner": { + Description: "repository_owner is the name of the organization in which the repository is stored. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "sub": { + Description: "sub also known as Subject is a string that roughly uniquely identifies the workload. The format of this varies depending on the type of github action run. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "workflow": { + Description: "workflow name. This field supports \"glob-style\" matching: - Use '*' to match zero or more characters. - Use '?' to match any single character.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "allow is a set of claim-matching fields evaluated against the GitHub Actions OIDC token.", + Optional: true, + }, + "enterprise_server_host": { + Description: "enterprise_server_host allows joining from runners associated with a GitHub Enterprise Server instance. When unconfigured, tokens will be validated against github.com, but when configured to the host of a GHES instance, then the tokens will be validated against host. This value should be the hostname of the GHES instance, and should not include the scheme or a path. The instance must be accessible over HTTPS at this hostname and the certificate must be trusted by the Auth Service.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "enterprise_slug": { + Description: "enterprise_slug allows the slug of a GitHub Enterprise organisation to be included in the expected issuer of the OIDC tokens. This is for compatibility with the `include_enterprise_slug` option in GHE. This field should be set to the slug of your enterprise if this is enabled. If this is not enabled, then this field must be left empty. This field cannot be specified if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise for more information about customized issuer values.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "static_jwks": { + Description: "static_jwks disables fetching of the GHES signing keys via the JWKS/OIDC endpoints, and allows them to be directly specified. This allows joining from GitHub Actions in GHES instances that are not reachable by the Teleport Auth Service.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Configuration specific to the \"github\" join method.", + Optional: true, + }, "immutable_labels": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"ssh": { Description: "Labels that should be applied to SSH nodes.", @@ -2309,213 +2386,481 @@ func CopyScopedTokenFromTerraform(_ context.Context, tf github_com_hashicorp_ter } } } - } - } - } - } - return diags -} - -// CopyScopedTokenToTerraform copies contents of the source Terraform object into a target struct -func CopyScopedTokenToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.ScopedToken, 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{"ScopedToken.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{"ScopedToken.kind", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.sub_kind", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.version", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.metadata"}) - } else { - o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.name", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.namespace", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.description", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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"] + a, ok := tf.Attrs["github"] if !ok { - diags.Append(attrWriteMissingDiag{"ScopedToken.metadata.labels"}) + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github"}) } else { - o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.MapType"}) + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) } 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) + obj.Github = nil + if !v.Null && !v.Unknown { + tf := v + obj.Github = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.Github{} + obj := obj.Github + { + a, ok := tf.Attrs["enterprise_server_host"] 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{"ScopedToken.metadata.labels", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.enterprise_server_host"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.enterprise_server_host", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.EnterpriseServerHost = t + } + } + } + { + a, ok := tf.Attrs["enterprise_slug"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.enterprise_slug"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.enterprise_slug", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.EnterpriseSlug = t + } + } + } + { + a, ok := tf.Attrs["static_jwks"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.static_jwks"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.static_jwks", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.StaticJwks = t + } + } + } + { + a, ok := tf.Attrs["allow"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Allow = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.Github_Rule, 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{"ScopedToken.spec.github.allow", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.Github_Rule + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.Github_Rule{} + obj := t + { + a, ok := tf.Attrs["sub"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.sub"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.sub", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Sub = t + } + } + } + { + a, ok := tf.Attrs["repository"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.repository"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.repository", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Repository = t + } + } + } + { + a, ok := tf.Attrs["repository_owner"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.repository_owner"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.repository_owner", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.RepositoryOwner = t + } + } + } + { + a, ok := tf.Attrs["workflow"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.workflow"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.workflow", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Workflow = t + } + } + } + { + a, ok := tf.Attrs["environment"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.environment"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.environment", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Environment = t + } + } + } + { + a, ok := tf.Attrs["actor"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.actor"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.actor", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Actor = t + } + } + } + { + a, ok := tf.Attrs["ref"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.ref"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.ref", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Ref = t + } + } + } + { + a, ok := tf.Attrs["ref_type"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.ref_type"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.ref_type", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.RefType = t + } + } + } + { + a, ok := tf.Attrs["enterprise"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.enterprise"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.enterprise", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Enterprise = t + } + } + } + { + a, ok := tf.Attrs["enterprise_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedToken.spec.github.allow.enterprise_id"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedToken.spec.github.allow.enterprise_id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.EnterpriseId = t + } + } + } + } + obj.Allow[k] = t + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return diags +} + +// CopyScopedTokenToTerraform copies contents of the source Terraform object into a target struct +func CopyScopedTokenToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_joining_v1.ScopedToken, 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{"ScopedToken.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{"ScopedToken.kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.sub_kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.metadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.namespace", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.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{"ScopedToken.metadata.description", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.metadata.labels"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.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{"ScopedToken.metadata.labels", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.String"}) } v.Null = false } @@ -5052,129 +5397,505 @@ func CopyScopedTokenToTerraform(ctx context.Context, obj *github_com_gravitation 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.NotIn == nil { - v.Null = true - } else { - obj := obj.NotIn - tf := &v - { - a, ok := tf.AttrTypes["values"] - if !ok { - diags.Append(attrWriteMissingDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values"}) - } else { - o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) - } else { - c, ok := tf.Attrs["values"].(github_com_hashicorp_terraform_plugin_framework_types.List) - if !ok { - c = github_com_hashicorp_terraform_plugin_framework_types.List{ + 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.NotIn == nil { + v.Null = true + } else { + obj := obj.NotIn + tf := &v + { + a, ok := tf.AttrTypes["values"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["values"].(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.Values)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + } + if obj.Values != nil { + t := o.ElemType + if len(obj.Values) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + for k, a := range obj.Values { + v, ok := tf.Attrs["values"].(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{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(a) == "" + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Values) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["values"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["not_in"] = v + } + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Conditions) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["conditions"] = c + } + } + } + { + t, ok := tf.AttrTypes["expression"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.generic_oidc.allow_any.expression"}) + } else { + v, ok := tf.Attrs["expression"].(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{"ScopedToken.spec.generic_oidc.allow_any.expression", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.expression", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Expression) == "" + } + v.Value = string(obj.Expression) + v.Unknown = false + tf.Attrs["expression"] = v + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.AllowAny) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["allow_any"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["generic_oidc"] = v + } + } + } + { + a, ok := tf.AttrTypes["github"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["github"].(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.Github == nil { + v.Null = true + } else { + obj := obj.Github + tf := &v + { + t, ok := tf.AttrTypes["enterprise_server_host"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.enterprise_server_host"}) + } else { + v, ok := tf.Attrs["enterprise_server_host"].(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{"ScopedToken.spec.github.enterprise_server_host", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.enterprise_server_host", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.EnterpriseServerHost) == "" + } + v.Value = string(obj.EnterpriseServerHost) + v.Unknown = false + tf.Attrs["enterprise_server_host"] = v + } + } + { + t, ok := tf.AttrTypes["enterprise_slug"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.enterprise_slug"}) + } else { + v, ok := tf.Attrs["enterprise_slug"].(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{"ScopedToken.spec.github.enterprise_slug", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.enterprise_slug", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.EnterpriseSlug) == "" + } + v.Value = string(obj.EnterpriseSlug) + v.Unknown = false + tf.Attrs["enterprise_slug"] = v + } + } + { + t, ok := tf.AttrTypes["static_jwks"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.static_jwks"}) + } else { + v, ok := tf.Attrs["static_jwks"].(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{"ScopedToken.spec.github.static_jwks", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.static_jwks", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.StaticJwks) == "" + } + v.Value = string(obj.StaticJwks) + v.Unknown = false + tf.Attrs["static_jwks"] = v + } + } + { + a, ok := tf.AttrTypes["allow"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.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{ - ElemType: o.ElemType, - Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)), - Null: true, - } - } else { - if c.Elems == nil { - c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) - } - } - if obj.Values != nil { - t := o.ElemType - if len(obj.Values) != len(c.Elems) { - c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) - } - for k, a := range obj.Values { - v, ok := tf.Attrs["values"].(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{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", err}) - } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) - if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.String"}) - } - v.Null = string(a) == "" - } - v.Value = string(a) - v.Unknown = false - c.Elems[k] = v - } - if len(obj.Values) > 0 { - c.Null = false - } - } - c.Unknown = false - tf.Attrs["values"] = c - } - } - } - } - v.Unknown = false - tf.Attrs["not_in"] = v - } - } - } - } - v.Unknown = false - c.Elems[k] = v - } - if len(obj.Conditions) > 0 { - c.Null = false - } + 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["sub"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.sub"}) + } else { + v, ok := tf.Attrs["sub"].(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{"ScopedToken.spec.github.allow.sub", err}) } - c.Unknown = false - tf.Attrs["conditions"] = c + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.sub", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Sub) == "" } + v.Value = string(obj.Sub) + v.Unknown = false + tf.Attrs["sub"] = v } } { - t, ok := tf.AttrTypes["expression"] + t, ok := tf.AttrTypes["repository"] if !ok { - diags.Append(attrWriteMissingDiag{"ScopedToken.spec.generic_oidc.allow_any.expression"}) + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.repository"}) } else { - v, ok := tf.Attrs["expression"].(github_com_hashicorp_terraform_plugin_framework_types.String) + v, ok := tf.Attrs["repository"].(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{"ScopedToken.spec.generic_oidc.allow_any.expression", err}) + diags.Append(attrWriteGeneralError{"ScopedToken.spec.github.allow.repository", err}) } v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) if !ok { - diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.generic_oidc.allow_any.expression", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.repository", "github.com/hashicorp/terraform-plugin-framework/types.String"}) } - v.Null = string(obj.Expression) == "" + v.Null = string(obj.Repository) == "" } - v.Value = string(obj.Expression) + v.Value = string(obj.Repository) v.Unknown = false - tf.Attrs["expression"] = v + tf.Attrs["repository"] = v + } + } + { + t, ok := tf.AttrTypes["repository_owner"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.repository_owner"}) + } else { + v, ok := tf.Attrs["repository_owner"].(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{"ScopedToken.spec.github.allow.repository_owner", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.repository_owner", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.RepositoryOwner) == "" + } + v.Value = string(obj.RepositoryOwner) + v.Unknown = false + tf.Attrs["repository_owner"] = v + } + } + { + t, ok := tf.AttrTypes["workflow"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.workflow"}) + } else { + v, ok := tf.Attrs["workflow"].(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{"ScopedToken.spec.github.allow.workflow", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.workflow", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Workflow) == "" + } + v.Value = string(obj.Workflow) + v.Unknown = false + tf.Attrs["workflow"] = v + } + } + { + t, ok := tf.AttrTypes["environment"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.environment"}) + } else { + v, ok := tf.Attrs["environment"].(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{"ScopedToken.spec.github.allow.environment", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.environment", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Environment) == "" + } + v.Value = string(obj.Environment) + v.Unknown = false + tf.Attrs["environment"] = v + } + } + { + t, ok := tf.AttrTypes["actor"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.actor"}) + } else { + v, ok := tf.Attrs["actor"].(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{"ScopedToken.spec.github.allow.actor", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.actor", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Actor) == "" + } + v.Value = string(obj.Actor) + v.Unknown = false + tf.Attrs["actor"] = v + } + } + { + t, ok := tf.AttrTypes["ref"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.ref"}) + } else { + v, ok := tf.Attrs["ref"].(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{"ScopedToken.spec.github.allow.ref", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.ref", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Ref) == "" + } + v.Value = string(obj.Ref) + v.Unknown = false + tf.Attrs["ref"] = v + } + } + { + t, ok := tf.AttrTypes["ref_type"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.ref_type"}) + } else { + v, ok := tf.Attrs["ref_type"].(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{"ScopedToken.spec.github.allow.ref_type", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.ref_type", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.RefType) == "" + } + v.Value = string(obj.RefType) + v.Unknown = false + tf.Attrs["ref_type"] = v + } + } + { + t, ok := tf.AttrTypes["enterprise"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.enterprise"}) + } else { + v, ok := tf.Attrs["enterprise"].(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{"ScopedToken.spec.github.allow.enterprise", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.enterprise", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Enterprise) == "" + } + v.Value = string(obj.Enterprise) + v.Unknown = false + tf.Attrs["enterprise"] = v + } + } + { + t, ok := tf.AttrTypes["enterprise_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedToken.spec.github.allow.enterprise_id"}) + } else { + v, ok := tf.Attrs["enterprise_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{"ScopedToken.spec.github.allow.enterprise_id", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedToken.spec.github.allow.enterprise_id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.EnterpriseId) == "" + } + v.Value = string(obj.EnterpriseId) + v.Unknown = false + tf.Attrs["enterprise_id"] = v } } } v.Unknown = false c.Elems[k] = v } - if len(obj.AllowAny) > 0 { + if len(obj.Allow) > 0 { c.Null = false } } c.Unknown = false - tf.Attrs["allow_any"] = c + tf.Attrs["allow"] = c } } } } v.Unknown = false - tf.Attrs["generic_oidc"] = v + tf.Attrs["github"] = v } } } diff --git a/lib/join/githubactions/githubactions.go b/lib/join/githubactions/githubactions.go index c94a8f7c4970..f3125547c06c 100644 --- a/lib/join/githubactions/githubactions.go +++ b/lib/join/githubactions/githubactions.go @@ -195,15 +195,15 @@ func CheckGithubIDToken(ctx context.Context, m modules.Modules, params *CheckGit return nil, trace.AccessDenied("%s", err.Error()) } - token, ok := params.ProvisionToken.(*types.ProvisionTokenV2) - if !ok { - return nil, trace.BadParameter("github join method only supports ProvisionTokenV2, '%T' was provided", params.ProvisionToken) + githubCfg := params.ProvisionToken.GetGithub() + if githubCfg == nil { + return nil, trace.BadParameter("required github configuration is missing from the join token") } // enterpriseOverride is a hostname to use instead of github.com when // validating tokens. This allows GHES instances to be connected. - enterpriseOverride := token.Spec.GitHub.EnterpriseServerHost - enterpriseSlug := token.Spec.GitHub.EnterpriseSlug + enterpriseOverride := githubCfg.EnterpriseServerHost + enterpriseSlug := githubCfg.EnterpriseSlug if enterpriseOverride != "" || enterpriseSlug != "" { if m.BuildType() != modules.BuildEnterprise { return nil, trace.Wrap(services.ErrRequiresEnterprise, "github enterprise server joining") @@ -212,10 +212,10 @@ func CheckGithubIDToken(ctx context.Context, m modules.Modules, params *CheckGit var claims *IDTokenClaims var err error - if token.Spec.GitHub.StaticJWKS != "" { + if githubCfg.StaticJWKS != "" { claims, err = params.JWKSValidator( params.Clock.Now().UTC(), - []byte(token.Spec.GitHub.StaticJWKS), + []byte(githubCfg.StaticJWKS), string(params.IDToken), ) if err != nil { @@ -235,12 +235,12 @@ func CheckGithubIDToken(ctx context.Context, m modules.Modules, params *CheckGit "token", params.ProvisionToken.GetName(), ) - return claims, trace.Wrap(checkGithubAllowRules(token, claims)) + return claims, trace.Wrap(checkGithubAllowRules(githubCfg.Allow, claims)) } -func checkGithubAllowRules(token *types.ProvisionTokenV2, claims *IDTokenClaims) error { +func checkGithubAllowRules(rules []*types.ProvisionTokenSpecV2GitHub_Rule, claims *IDTokenClaims) error { // If a single rule passes, accept the IDToken - for i, rule := range token.Spec.GitHub.Allow { + for i, rule := range rules { // Please consider keeping these field validators in the same order they // are defined within the ProvisionTokenSpecV2Github proto spec. subMatches, err := joinutils.GlobMatchAllowEmptyPattern(rule.Sub, claims.Sub) diff --git a/lib/join/join_bound_keypair_test.go b/lib/join/join_bound_keypair_test.go index fda1f5d0df15..4faa5d35330c 100644 --- a/lib/join/join_bound_keypair_test.go +++ b/lib/join/join_bound_keypair_test.go @@ -34,7 +34,6 @@ import ( "github.com/go-jose/go-jose/v3/jwt" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" @@ -44,7 +43,6 @@ import ( "github.com/gravitational/teleport/api/client/proto" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" - scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" @@ -58,7 +56,6 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/join/joinclient" "github.com/gravitational/teleport/lib/scopes" - scopedaccess "github.com/gravitational/teleport/lib/scopes/access" "github.com/gravitational/teleport/lib/scopes/joining" "github.com/gravitational/teleport/lib/tlsca" ) @@ -1830,72 +1827,6 @@ func TestJoinBoundKeypair_JoinStateFailure_Instance(t *testing.T) { require.Contains(t, locks[0].Message(), "failed to verify its join state") } -// createScopedBot creates a scoped bot with necessary role assignments for testing. -func createScopedBot(t *testing.T, srv *authtest.TLSServer, adminClient *authclient.Client) { - t.Helper() - - // Create a scoped role for the bot. - scopedSvc := adminClient.ScopedAccessServiceClient() - _, err := scopedSvc.CreateScopedRole(t.Context(), scopedaccessv1.CreateScopedRoleRequest_builder{ - Role: scopedaccessv1.ScopedRole_builder{ - Kind: scopedaccess.KindScopedRole, - Version: types.V1, - Metadata: headerv1.Metadata_builder{ - Name: "scoped-example", - }.Build(), - Scope: "/test", - Spec: scopedaccessv1.ScopedRoleSpec_builder{ - AssignableScopes: []string{"/test"}, - }.Build(), - }.Build(), - }.Build()) - require.NoError(t, err) - - // Create the scoped bot. - _, err = adminClient.BotServiceClient().CreateBot(t.Context(), machineidv1pb.CreateBotRequest_builder{ - Bot: machineidv1pb.Bot_builder{ - Kind: types.KindBot, - Version: types.V1, - Scope: "/test", - Metadata: headerv1.Metadata_builder{ - Name: "test-scoped", - }.Build(), - Spec: &machineidv1pb.BotSpec{}, - }.Build(), - }.Build()) - require.NoError(t, err) - - // Create a scoped role assignment for the bot. - resp, err := srv.Auth().ScopedAccess().CreateScopedRoleAssignment(t.Context(), scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ - Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ - Kind: scopedaccess.KindScopedRoleAssignment, - Version: types.V1, - Metadata: headerv1.Metadata_builder{ - Name: uuid.NewString(), - }.Build(), - SubKind: scopedaccess.SubKindDynamic, - Scope: "/test", - Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ - Bot: scopes.QualifiedName{Scope: "/test", Name: "test-scoped"}.String(), - Assignments: []*scopedaccessv1.Assignment{ - scopedaccessv1.Assignment_builder{Role: "/test::scoped-example", Scope: "/test"}.Build(), - }, - }.Build(), - }.Build(), - }.Build()) - require.NoError(t, err) - - ctx := t.Context() - require.EventuallyWithT(t, func(t *assert.CollectT) { - _, err := srv.Auth().ScopedAccessCache.GetScopedRoleAssignment(ctx, scopedaccessv1.GetScopedRoleAssignmentRequest_builder{ - Name: resp.GetAssignment().GetMetadata().GetName(), - SubKind: resp.GetAssignment().GetSubKind(), - Scope: resp.GetAssignment().GetScope(), - }.Build()) - require.NoError(t, err) - }, time.Second*10, 100*time.Millisecond) -} - func TestJoinBoundKeypair_ScopedToken(t *testing.T) { t.Parallel() @@ -1931,7 +1862,7 @@ func TestJoinBoundKeypair_ScopedToken(t *testing.T) { }.Build()) require.NoError(t, err) - createScopedBot(t, srv, adminClient) + CreateScopedBot(t, srv.Auth(), "test-scoped") scopedToken := joiningv1.ScopedToken_builder{ Kind: types.KindScopedToken, diff --git a/lib/join/join_github_test.go b/lib/join/join_github_test.go index ecea7f92228a..dd8a589c0f16 100644 --- a/lib/join/join_github_test.go +++ b/lib/join/join_github_test.go @@ -28,14 +28,20 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authtest" "github.com/gravitational/teleport/lib/auth/state" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/join/githubactions" "github.com/gravitational/teleport/lib/join/joinclient" + "github.com/gravitational/teleport/lib/join/jointest" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/modules/modulestest" + "github.com/gravitational/teleport/lib/scopes" + "github.com/gravitational/teleport/lib/scopes/joining" + "github.com/gravitational/teleport/lib/tlsca" ) type mockIDTokenValidator struct { @@ -119,8 +125,9 @@ func TestJoinGHA(t *testing.T) { testModules := modulestest.OSSModules() authServer, err := authtest.NewTestServer(authtest.ServerConfig{ Auth: authtest.AuthServerConfig{ - Dir: t.TempDir(), - Modules: testModules, + Dir: t.TempDir(), + Modules: testModules, + ScopesFeatures: scopes.Features{Enabled: true}, }, }) require.NoError(t, err) @@ -698,6 +705,30 @@ func TestJoinGHA(t *testing.T) { } else { testModules.TestBuildType = modules.BuildOSS } + nopClient, err := authServer.NewClient(authtest.TestNop()) + require.NoError(t, err) + + t.Run("scoped joinclient", func(t *testing.T) { + scopedToken := CreateScopedToken(t, authServer.Auth(), tt.tokenSpec, "scoped_"+tt.name) + result, err := joinclient.Join(t.Context(), joinclient.JoinParams{ + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + JoinMethod: types.JoinMethodGitHub, + ID: state.IdentityID{ + Role: types.RoleInstance, + NodeName: "testnode", + }, + IDToken: tt.request.IDToken, + AuthClient: nopClient, + }) + tt.assertError(t, err) + if err != nil { + return + } + + checkMockGithubValidatorState(t, idTokenValidator, tt.tokenSpec) + RequireScopedHostResult(t, result, scopedToken) + }) + token, err := types.NewProvisionTokenFromSpec( tt.name, time.Now().Add(time.Minute), tt.tokenSpec, ) @@ -705,9 +736,6 @@ func TestJoinGHA(t *testing.T) { require.NoError(t, authServer.Auth().CreateToken(t.Context(), token)) tt.request.Token = tt.name - nopClient, err := authServer.NewClient(authtest.TestNop()) - require.NoError(t, err) - t.Run("legacy joinclient", func(t *testing.T) { _, err := joinclient.LegacyJoin(t.Context(), joinclient.JoinParams{ Token: tt.request.Token, @@ -746,7 +774,6 @@ func TestJoinGHA(t *testing.T) { checkMockGithubValidatorState(t, idTokenValidator, tt.tokenSpec) }) - t.Run("legacy", func(t *testing.T) { _, err = authServer.Auth().RegisterUsingToken(t.Context(), tt.request) tt.assertError(t, err) @@ -759,3 +786,145 @@ func TestJoinGHA(t *testing.T) { }) } } + +func TestJoinGHABot(t *testing.T) { + validIDToken := "test.fake.jwt" + idTokenValidator := &mockIDTokenValidator{ + tokens: map[string]githubactions.IDTokenClaims{ + validIDToken: { + Sub: "repo:octo-org/octo-repo:environment:prod", + Repository: "octo-org/octo-repo", + RepositoryOwner: "octo-org", + Workflow: "example-workflow", + Environment: "prod", + Actor: "octocat", + Ref: "refs/heads/main", + RefType: "branch", + Enterprise: "my-enterprise", + EnterpriseID: "123456", + }, + }, + } + + authServer, err := authtest.NewTestServer(authtest.ServerConfig{ + Auth: authtest.AuthServerConfig{ + Dir: t.TempDir(), + Modules: modulestest.OSSModules(), + ScopesFeatures: scopes.Features{Enabled: true}, + }, + }) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, authServer.Shutdown(t.Context())) }) + + authServer.Auth().SetGHAIDTokenValidator(idTokenValidator) + authServer.Auth().SetGHAIDTokenJWKSValidator(idTokenValidator.ValidateJWKS) + + // Create a scoped bot. + qualifiedBotName := CreateScopedBot(t, authServer.Auth(), "gha-bot") + + // Create the scoped token for bot joining. + tokenSpec := types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodGitHub, + Roles: []types.SystemRole{types.RoleBot}, + GitHub: &types.ProvisionTokenSpecV2GitHub{ + Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{ + { + Sub: "repo:octo-org/octo-repo:environment:prod", + Repository: "octo-org/octo-repo", + RepositoryOwner: "octo-org", + }, + }, + }, + } + + scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(tokenSpec, joiningv1.ScopedToken_builder{ + Scope: "/test", + Metadata: headerv1.Metadata_builder{ + Name: "github-bot-token", + }.Build(), + Spec: joiningv1.ScopedTokenSpec_builder{ + UsageMode: joining.TokenUsageModeBot, + Bot: qualifiedBotName, + }.Build(), + }.Build()) + require.NoError(t, err) + + _, err = authServer.Auth().CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{ + Token: scopedToken, + }.Build()) + require.NoError(t, err) + + nopClient, err := authServer.NewClient(authtest.TestNop()) + require.NoError(t, err) + + result, err := joinclient.Join(t.Context(), joinclient.JoinParams{ + Token: scopes.QualifiedName{Scope: scopedToken.GetScope(), Name: scopedToken.GetMetadata().GetName()}.String(), + JoinMethod: types.JoinMethodGitHub, + ID: state.IdentityID{ + Role: types.RoleBot, + }, + IDToken: validIDToken, + AuthClient: nopClient, + }) + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Certs) + + // Parse the TLS certificate and verify bot identity. + cert, err := tlsca.ParseCertificatePEM(result.Certs.TLS) + require.NoError(t, err) + identity, err := tlsca.FromSubject(cert.Subject, cert.NotAfter) + require.NoError(t, err) + + require.Equal(t, "gha-bot", identity.BotName) + require.NotEmpty(t, identity.BotInstanceID) + require.NotNil(t, identity.ScopePin) + require.Equal(t, "/test", identity.ScopePin.GetScope()) + require.Equal(t, "/test", identity.BotScope) + require.True(t, identity.BotInternal) + + // Bot results should not contain immutable labels (host-only). + require.Nil(t, result.ImmutableLabels) + + t.Run("must fail when claims do not match allow rules", func(t *testing.T) { + nonMatchingToken, err := jointest.ScopedTokenFromProvisionTokenSpec(types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodGitHub, + Roles: []types.SystemRole{types.RoleBot}, + GitHub: &types.ProvisionTokenSpecV2GitHub{ + Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{ + { + Repository: "other-org/other-repo", + RepositoryOwner: "other-org", + }, + }, + }, + }, joiningv1.ScopedToken_builder{ + Scope: "/test", + Metadata: headerv1.Metadata_builder{ + Name: "github-bot-token-no-match", + }.Build(), + Spec: joiningv1.ScopedTokenSpec_builder{ + UsageMode: joining.TokenUsageModeBot, + Bot: qualifiedBotName, + }.Build(), + }.Build()) + require.NoError(t, err) + + _, err = authServer.Auth().CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{ + Token: nonMatchingToken, + }.Build()) + require.NoError(t, err) + + _, err = joinclient.Join(t.Context(), joinclient.JoinParams{ + Token: scopes.QualifiedName{Scope: nonMatchingToken.GetScope(), Name: nonMatchingToken.GetMetadata().GetName()}.String(), + JoinMethod: types.JoinMethodGitHub, + ID: state.IdentityID{ + Role: types.RoleBot, + }, + IDToken: validIDToken, + AuthClient: nopClient, + }) + require.ErrorContains(t, err, "id token claims did not match any allow rules") + require.True(t, trace.IsAccessDenied(err)) + }) +} diff --git a/lib/join/jointest/scoped_token.go b/lib/join/jointest/scoped_token.go index 333f6df4799a..fa447e28958f 100644 --- a/lib/join/jointest/scoped_token.go +++ b/lib/join/jointest/scoped_token.go @@ -18,8 +18,11 @@ package jointest import ( "cmp" + "encoding/json" "github.com/gravitational/trace" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" "github.com/gravitational/teleport/api/types" @@ -49,10 +52,12 @@ func ScopedTokenFromProvisionTokenSpec(base types.ProvisionTokenSpecV2, override Scope: override.GetScope(), Metadata: override.GetMetadata(), Spec: joiningv1.ScopedTokenSpec_builder{ - AssignedScope: override.GetSpec().GetAssignedScope(), - JoinMethod: cmp.Or(override.GetSpec().GetJoinMethod(), string(base.JoinMethod)), - Roles: roles, - UsageMode: override.GetSpec().GetUsageMode(), + AssignedScope: override.GetSpec().GetAssignedScope(), + JoinMethod: cmp.Or(override.GetSpec().GetJoinMethod(), string(base.JoinMethod)), + Roles: roles, + UsageMode: override.GetSpec().GetUsageMode(), + Bot: override.GetSpec().GetBot(), + ImmutableLabels: override.GetSpec().GetImmutableLabels(), }.Build(), }.Build() @@ -176,9 +181,45 @@ func ScopedTokenFromProvisionTokenSpec(base types.ProvisionTokenSpecV2, override StaticJwks: staticJWKS, Oidc: oidc, }.Build()) + case types.JoinMethodGitHub: + if err := setProviderConfig(scopedToken.GetSpec(), "github", base.GitHub); err != nil { + return nil, trace.Wrap(err) + } default: return nil, trace.BadParameter("unsupported join method %q", base.JoinMethod) } return scopedToken, nil } + +// setProviderConfig populates a provider message by protobuf field name. This +// keeps forward-looking integration tests buildable before the generated scoped +// provider type exists, while still failing until the production schema defines +// and can decode the expected field. +func setProviderConfig(spec *joiningv1.ScopedTokenSpec, fieldName string, config any) error { + if config == nil { + return trace.BadParameter("missing %s configuration", fieldName) + } + + message := spec.ProtoReflect() + field := message.Descriptor().Fields().ByName(protoreflect.Name(fieldName)) + if field == nil { + return trace.NotImplemented("scoped token proto does not define %q configuration", fieldName) + } + if field.Kind() != protoreflect.MessageKind { + return trace.BadParameter("scoped token field %q must be a message", fieldName) + } + + encoded, err := json.Marshal(config) + if err != nil { + return trace.Wrap(err, "marshaling classic %s configuration", fieldName) + } + + value := message.NewField(field) + if err := (protojson.UnmarshalOptions{}).Unmarshal(encoded, value.Message().Interface()); err != nil { + return trace.Wrap(err, "converting classic %s configuration to scoped form", fieldName) + } + message.Set(field, value) + + return nil +} diff --git a/lib/join/provision/token.go b/lib/join/provision/token.go index 936e9e995a78..db7657ed2409 100644 --- a/lib/join/provision/token.go +++ b/lib/join/provision/token.go @@ -79,4 +79,6 @@ type Token interface { GetBoundKeypairStatus() *types.ProvisionTokenStatusV2BoundKeypair // GetGenericOIDC returns the generic_oidc-specific configuration for this token. GetGenericOIDC() (*types.ProvisionTokenSpecV2GenericOIDC, error) + // GetGithub returns the Github-specific configuration for this token. + GetGithub() *types.ProvisionTokenSpecV2GitHub } diff --git a/lib/join/scope_helpers_test.go b/lib/join/scope_helpers_test.go new file mode 100644 index 000000000000..8cca51cc8d09 --- /dev/null +++ b/lib/join/scope_helpers_test.go @@ -0,0 +1,189 @@ +// Teleport +// Copyright (C) 2026 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 join_test + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/auth/machineid/machineidv1" + "github.com/gravitational/teleport/lib/join/joinclient" + "github.com/gravitational/teleport/lib/join/jointest" + "github.com/gravitational/teleport/lib/scopes" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" + "github.com/gravitational/teleport/lib/scopes/joining" + "github.com/gravitational/teleport/lib/tlsca" +) + +const ( + testTokenScope = "/test" + testTokenAssignedScope = "/test/one" +) + +// ScopedTokenClient is the subset of the scoped token service used by join +// integration tests. +type ScopedTokenClient interface { + CreateScopedToken(context.Context, *joiningv1.CreateScopedTokenRequest) (*joiningv1.CreateScopedTokenResponse, error) + DeleteScopedToken(context.Context, *joiningv1.DeleteScopedTokenRequest) (*joiningv1.DeleteScopedTokenResponse, error) +} + +// CreateScopedToken creates a dynamic scoped token equivalent to base and +// registers cleanup. Provider configuration is intentionally derived from the +// classic token so the same high-level test matrix can exercise both forms. +func CreateScopedToken( + t testing.TB, + client ScopedTokenClient, + base types.ProvisionTokenSpecV2, + name string, +) *joiningv1.ScopedToken { + t.Helper() + + token, err := jointest.ScopedTokenFromProvisionTokenSpec(base, joiningv1.ScopedToken_builder{ + Scope: testTokenScope, + Metadata: headerv1.Metadata_builder{ + Name: name, + }.Build(), + Spec: joiningv1.ScopedTokenSpec_builder{ + AssignedScope: testTokenAssignedScope, + UsageMode: string(joining.TokenUsageModeUnlimited), + ImmutableLabels: joiningv1.ImmutableLabels_builder{ + Ssh: map[string]string{"scoped-join-test": "true"}, + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + + _, err = client.CreateScopedToken(t.Context(), joiningv1.CreateScopedTokenRequest_builder{ + Token: token, + }.Build()) + require.NoError(t, err) + + t.Cleanup(func() { + _, err := client.DeleteScopedToken(context.Background(), joiningv1.DeleteScopedTokenRequest_builder{ + Name: token.GetMetadata().GetName(), + Scope: token.GetScope(), + }.Build()) + require.NoError(t, err) + }) + + return token +} + +// RequireScopedHostResult verifies the scoped controls returned by a +// successful host join instead of treating a nil error as sufficient coverage. +func RequireScopedHostResult(t testing.TB, result *joinclient.JoinResult, token *joiningv1.ScopedToken) { + t.Helper() + + require.NotNil(t, result) + require.NotNil(t, result.Certs) + + cert, err := tlsca.ParseCertificatePEM(result.Certs.TLS) + require.NoError(t, err) + identity, err := tlsca.FromSubject(cert.Subject, cert.NotAfter) + require.NoError(t, err) + + require.Equal(t, token.GetSpec().GetAssignedScope(), identity.AgentScope) + require.Equal(t, joining.HashImmutableLabels(token.GetSpec().GetImmutableLabels()), identity.ImmutableLabelHash) + require.Equal(t, token.GetSpec().GetImmutableLabels().GetSsh(), result.ImmutableLabels.GetSsh()) +} + +// CreateScopedBot creates a scoped Bot in the test scope ("/test") with a +// scoped role and role assignment, then waits for cache propagation. Returns +// the qualified bot name (e.g., "/test::botName") for use in scoped token +// spec.bot. +func CreateScopedBot(t testing.TB, authServer *auth.Server, botName string) string { + t.Helper() + + ctx := t.Context() + scope := testTokenScope // "/test" + roleName := "jointest-role-" + botName + + // Create a scoped role for the bot. + _, err := authServer.ScopedAccess().CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1.ScopedRole_builder{ + Kind: scopedaccess.KindScopedRole, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: roleName, + }.Build(), + Scope: scope, + Spec: scopedaccessv1.ScopedRoleSpec_builder{ + AssignableScopes: []string{scope}, + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + + // Create the scoped bot. + _, err = machineidv1.UpsertBot(ctx, authServer, machineidv1pb.Bot_builder{ + Kind: types.KindBot, + Version: types.V1, + Scope: scope, + Metadata: headerv1.Metadata_builder{ + Name: botName, + }.Build(), + Spec: &machineidv1pb.BotSpec{}, + }.Build(), authServer.GetClock().Now(), "", scopes.Features{Enabled: true}) + require.NoError(t, err) + + // Create a scoped role assignment for the bot. + qualifiedBotName := scopes.QualifiedName{Scope: scope, Name: botName}.String() + resp, err := authServer.ScopedAccess().CreateScopedRoleAssignment(ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: uuid.NewString(), + }.Build(), + SubKind: scopedaccess.SubKindDynamic, + Scope: scope, + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + Bot: qualifiedBotName, + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{ + Role: scope + "::" + roleName, + Scope: scope, + }.Build(), + }, + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + + // Wait for the cache to propagate the assignment. + require.EventuallyWithT(t, func(ct *assert.CollectT) { + _, err := authServer.ScopedAccessCache.GetScopedRoleAssignment(ctx, scopedaccessv1.GetScopedRoleAssignmentRequest_builder{ + Name: resp.GetAssignment().GetMetadata().GetName(), + SubKind: resp.GetAssignment().GetSubKind(), + Scope: resp.GetAssignment().GetScope(), + }.Build()) + assert.NoError(ct, err) + }, time.Second*10, 100*time.Millisecond) + + return qualifiedBotName +} diff --git a/lib/scopes/joining/token.go b/lib/scopes/joining/token.go index 50fdf34b27b6..643be7f36ccc 100644 --- a/lib/scopes/joining/token.go +++ b/lib/scopes/joining/token.go @@ -348,6 +348,43 @@ func validateGenericOIDC(spec *joiningv1.GenericOIDC) error { return nil } +// validateGithub validates the GitHub-specific scoped token configuration. +// It checks that the token usage mode is compatible, that enterprise_server_host +// does not contain a scheme or path, that enterprise_server_host and enterprise_slug +// are mutually exclusive, and that at least one allow rule with a non-empty +// field is set. +func validateGithub(spec *joiningv1.Github, tokenUsageMode TokenUsageMode) error { + if spec == nil { + return trace.BadParameter("github configuration must be defined for a scoped token when using the github join method") + } + if tokenUsageMode == TokenUsageModeSingle { + return trace.BadParameter("usage mode %q is not supported for github join method", TokenUsageModeSingle) + } + if strings.Contains(spec.GetEnterpriseServerHost(), "/") { + return trace.BadParameter("'github.enterprise_server_host' should not contain the scheme or path") + } + if spec.GetEnterpriseServerHost() != "" && spec.GetEnterpriseSlug() != "" { + return trace.BadParameter("'github.enterprise_server_host' and 'github.enterprise_slug' cannot both be set") + } + + if len(spec.GetAllow()) == 0 { + return trace.BadParameter("the github join method requires at least one token allow rule") + } + + for _, rule := range spec.GetAllow() { + repoSet := rule.GetRepository() != "" + ownerSet := rule.GetRepositoryOwner() != "" + subSet := rule.GetSub() != "" + enterpriseSet := rule.GetEnterprise() != "" + enterpriseIDSet := rule.GetEnterpriseId() != "" + if !subSet && !ownerSet && !repoSet && !enterpriseSet && !enterpriseIDSet { + return trace.BadParameter(`allow rule for github must include at least one of "repository", "repository_owner", "sub", "enterprise" or "enterprise_id"`) + } + } + + return nil +} + // validates per join method token configurations func validateJoinMethod(token *joiningv1.ScopedToken) error { switch types.JoinMethod(token.GetSpec().GetJoinMethod()) { @@ -373,6 +410,10 @@ func validateJoinMethod(token *joiningv1.ScopedToken) error { // Bound keypair tokens are always valid case types.JoinMethodGenericOIDC: return trace.Wrap(validateGenericOIDC(token.GetSpec().GetGenericOidc()), "generic_oidc join method") + case types.JoinMethodGitHub: + if err := validateGithub(token.GetSpec().GetGithub(), TokenUsageMode(token.GetSpec().GetUsageMode())); err != nil { + return trace.Wrap(err, "github join method") + } default: return trace.BadParameter("join method %q does not support scoping", token.GetSpec().GetJoinMethod()) } @@ -1005,6 +1046,33 @@ func (t *Token) GetGenericOIDC() (*types.ProvisionTokenSpecV2GenericOIDC, error) }, nil } +func (t *Token) GetGithub() *types.ProvisionTokenSpecV2GitHub { + spec := t.scoped.GetSpec().GetGithub() + + allow := make([]*types.ProvisionTokenSpecV2GitHub_Rule, len(spec.GetAllow())) + for i, rule := range spec.GetAllow() { + allow[i] = &types.ProvisionTokenSpecV2GitHub_Rule{ + Sub: rule.GetSub(), + Repository: rule.GetRepository(), + RepositoryOwner: rule.GetRepositoryOwner(), + Workflow: rule.GetWorkflow(), + Environment: rule.GetEnvironment(), + Actor: rule.GetActor(), + Ref: rule.GetRef(), + RefType: rule.GetRefType(), + Enterprise: rule.GetEnterprise(), + EnterpriseID: rule.GetEnterpriseId(), + } + } + + return &types.ProvisionTokenSpecV2GitHub{ + EnterpriseServerHost: spec.GetEnterpriseServerHost(), + EnterpriseSlug: spec.GetEnterpriseSlug(), + StaticJWKS: spec.GetStaticJwks(), + Allow: allow, + } +} + // GetScoped returns the inner scoped token wrapped by this [provision.Token]. func (t *Token) GetScoped() *joiningv1.ScopedToken { return t.scoped diff --git a/lib/scopes/joining/token_test.go b/lib/scopes/joining/token_test.go index 1438b8f34a7a..36a9ad4b1dd1 100644 --- a/lib/scopes/joining/token_test.go +++ b/lib/scopes/joining/token_test.go @@ -1145,6 +1145,142 @@ func TestValidateScopedToken(t *testing.T) { }.Build()) }, }, + { + name: "valid github token with EnterpriseServerHost", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + EnterpriseServerHost: "example.com", + EnterpriseSlug: "", + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + }, + { + name: "valid github token with EnterpriseSlug", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + EnterpriseServerHost: "", + EnterpriseSlug: "exampleorg", + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + }, + { + name: "github token with single use mode", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + tok.GetSpec().SetUsageMode(string(joining.TokenUsageModeSingle)) + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + expectedStrongErr: `usage mode "single_use" is not supported for github join method`, + expectedWeakErr: `usage mode "single_use" is not supported for github join method`, + }, + { + name: "github token with EnterpriseSlug and EnterpriseServerHost set", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + EnterpriseServerHost: "example.com", + EnterpriseSlug: "exampleorg", + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + expectedStrongErr: "'github.enterprise_server_host' and 'github.enterprise_slug' cannot both be set", + expectedWeakErr: "'github.enterprise_server_host' and 'github.enterprise_slug' cannot both be set", + }, + { + name: "github token with EnterpriseServerHost with a path", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + EnterpriseServerHost: "example.com/v1", + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + expectedStrongErr: "github.enterprise_server_host' should not contain the scheme or path", + expectedWeakErr: "github.enterprise_server_host' should not contain the scheme or path", + }, + { + name: "github token with EnterpriseServerHost with a scheme", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + EnterpriseServerHost: "https://example.com", + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{ + Sub: "foo", + }.Build(), + }, + }.Build()) + }, + expectedStrongErr: "github.enterprise_server_host' should not contain the scheme or path", + expectedWeakErr: "github.enterprise_server_host' should not contain the scheme or path", + }, + { + name: "github token with no rules", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + Allow: []*joiningv1.Github_Rule{}, + }.Build()) + }, + expectedStrongErr: "the github join method requires at least one token allow rule", + expectedWeakErr: "the github join method requires at least one token allow rule", + }, + { + name: "github token with empty rules", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + + tok.GetSpec().SetGithub(joiningv1.Github_builder{ + Allow: []*joiningv1.Github_Rule{ + joiningv1.Github_Rule_builder{}.Build(), + joiningv1.Github_Rule_builder{}.Build(), + }, + }.Build()) + }, + expectedStrongErr: "allow rule for github must include at least one of", + expectedWeakErr: "allow rule for github must include at least one of", + }, + { + name: "github token with no github config", + modFn: func(tok *joiningv1.ScopedToken) { + tok.GetSpec().SetJoinMethod(string(types.JoinMethodGitHub)) + // deliberately not calling SetGithub() + }, + expectedStrongErr: "github configuration must be defined", + expectedWeakErr: "github configuration must be defined", + }, { name: "non-bot token with bot", modFn: func(tok *joiningv1.ScopedToken) { @@ -1307,6 +1443,42 @@ func TestScopedTokenAzureRoundTrip(t *testing.T) { }, token.GetAzure()) } +func TestScopedTokenGithubRoundTrip(t *testing.T) { + t.Parallel() + + githubConfig := types.ProvisionTokenSpecV2GitHub{ + EnterpriseServerHost: "example.com", + Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{ + { + Sub: "foo", + Repository: "bar", + RepositoryOwner: "myself", + }, + }, + } + + scopedToken, err := jointest.ScopedTokenFromProvisionTokenSpec(types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodGitHub, + Roles: []types.SystemRole{types.RoleNode}, + GitHub: &githubConfig, + }, joiningv1.ScopedToken_builder{ + Scope: "/test", + Metadata: headerv1.Metadata_builder{ + Name: "test-token", + }.Build(), + Spec: joiningv1.ScopedTokenSpec_builder{ + AssignedScope: "/test", + UsageMode: string(joining.TokenUsageModeUnlimited), + }.Build(), + }.Build()) + require.NoError(t, err) + + token, err := joining.NewToken(scopedToken) + require.NoError(t, err) + + require.Equal(t, &githubConfig, token.GetGithub()) +} + func TestNewTokenGetBot(t *testing.T) { t.Parallel() From b001c1f33601851db0b02e1029daabbc063383e2 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 3 Aug 2026 09:47:50 +0100 Subject: [PATCH 101/347] MWI: Scope Namespace Bot Instances (2/2) - `tctl` (#68391) * MWI: Scope Namespace Bot Instances (2/2) - tctl (#68391) * Fix up tctl rendering Squash of: - Fix up tctl rendering - tidier * fix up * handle `tctl get bot_instance/scope::bot' form with clear error * address timr feedback * address hugo feedback on consistency * regenerate tctl mdx --- docs/pages/reference/cli/tctl.mdx | 4 +- tool/tctl/common/bots_command.go | 91 +++++++-- tool/tctl/common/bots_command_test.go | 201 +++++++++++++++++++ tool/tctl/common/resource_command.go | 14 ++ tool/tctl/common/resource_command_test.go | 177 ++++++++++++++++ tool/tctl/common/resources/bot_instance.go | 100 ++++++++- tool/tctl/common/resources/scoped_handler.go | 1 + tool/tctl/common/scoped_ref.go | 18 ++ 8 files changed, 581 insertions(+), 25 deletions(-) diff --git a/docs/pages/reference/cli/tctl.mdx b/docs/pages/reference/cli/tctl.mdx index f9df1be4b209..1caf3b45e066 100644 --- a/docs/pages/reference/cli/tctl.mdx +++ b/docs/pages/reference/cli/tctl.mdx @@ -1236,7 +1236,7 @@ Arguments: |Argument|Default|Description| |---|---|---| -|name|*no default* (optional)|The name of the bot from which to list instances. If unset, lists instances from all bots.| +|name|*no default* (optional)|The name of the bot from which to list instances. For a scoped bot, provide a scope-qualified name of the form \[scope\]::\[name\]. If unset, lists instances from all bots.| ## tctl bots instances show @@ -1252,7 +1252,7 @@ Arguments: |Argument|Default|Description| |---|---|---| -|id|*no default* (required)|The full ID of the bot instance, in the form of \[bot name\]/\[uuid\]| +|id|*no default* (required)|The full ID of the bot instance, in the form of \[bot name\]/\[uuid\]. For an instance of a scoped bot, prefix the ID with the bot's scope: \[scope\]::\[bot name\]/\[uuid\].| ## tctl bots ls diff --git a/tool/tctl/common/bots_command.go b/tool/tctl/common/bots_command.go index c22fe65acdd0..d5e1e3d7027e 100644 --- a/tool/tctl/common/bots_command.go +++ b/tool/tctl/common/bots_command.go @@ -53,6 +53,7 @@ import ( "github.com/gravitational/teleport/lib/boundkeypair" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tbot/config/joinuri" @@ -185,10 +186,10 @@ func (c *BotsCommand) Initialize(app *kingpin.Application, _ *tctlcfg.GlobalCLIF c.botsInstances = bots.Command("instances", "Manage bot instances.").Alias("instance") c.botsInstancesShow = c.botsInstances.Command("show", "Shows information about a specific bot instance.").Alias("get").Alias("describe") - c.botsInstancesShow.Arg("id", "The full ID of the bot instance, in the form of [bot name]/[uuid]").Required().StringVar(&c.instanceID) + c.botsInstancesShow.Arg("id", "The full ID of the bot instance, in the form of [bot name]/[uuid]. For an instance of a scoped bot, prefix the ID with the bot's scope: [scope]::[bot name]/[uuid].").Required().StringVar(&c.instanceID) c.botsInstancesList = c.botsInstances.Command("list", "List bot instances.").Alias("ls") - c.botsInstancesList.Arg("name", "The name of the bot from which to list instances. If unset, lists instances from all bots.").StringVar(&c.botName) + c.botsInstancesList.Arg("name", "The name of the bot from which to list instances. For a scoped bot, provide a scope-qualified name of the form [scope]::[name]. If unset, lists instances from all bots.").StringVar(&c.botName) c.botsInstancesList.Flag("format", "Output format.").Default(teleport.Text).EnumVar(&c.format, teleport.Text, teleport.JSON, teleport.YAML) c.botsInstancesList.Flag("search", "Fuzzy search query used to filter bot instances").StringVar(&c.search) c.botsInstancesList.Flag("query", "An expression in the Teleport predicate language used to filter bot instances").StringVar(&c.query) @@ -740,6 +741,22 @@ func (c *BotsCommand) UpdateBot(ctx context.Context, client botsCommandClient) e // ListBotInstances lists bot instances, possibly filtering for a specific bot func (c *BotsCommand) ListBotInstances(ctx context.Context, client botsCommandClient) error { + // The bot may be given as a scope-qualified name (::) to + // list instances of a scoped bot; a bare name lists instances of an + // unscoped bot. + botName := c.botName + var botScope string + if scopes.MaybeSQN(botName) { + qn, err := scopes.ParseQualifiedName(botName) + if err != nil { + return trace.Wrap(err) + } + if err := qn.StrongValidate(); err != nil { + return trace.Wrap(err) + } + botScope, botName = qn.Scope, qn.Name + } + pageFunc := func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1pb.BotInstance, string, error) { resp, err := client.BotInstanceServiceClient().ListBotInstancesV2(ctx, machineidv1pb.ListBotInstancesV2Request_builder{ PageSize: int32(pageSize), @@ -747,7 +764,8 @@ func (c *BotsCommand) ListBotInstances(ctx context.Context, client botsCommandCl SortField: c.sortIndex, SortDesc: c.sortOrder == "descending", Filter: machineidv1pb.ListBotInstancesV2Request_Filters_builder{ - BotName: c.botName, + BotName: botName, + BotScope: botScope, SearchTerm: c.search, Query: c.query, }.Build(), @@ -759,11 +777,14 @@ func (c *BotsCommand) ListBotInstances(ctx context.Context, client botsCommandCl if c.query != "" { return nil, trace.NotImplemented("fallback not supported for requests with a query") } + if botScope != "" { + return nil, trace.NotImplemented("fallback not supported for requests with a bot scope") + } fallbackPageFunc := func(ctx context.Context, pageSize int, pageToken string) ([]*machineidv1pb.BotInstance, string, error) { // Needed for backwards compatibility //nolint:staticcheck // SA1019 resp, err := client.BotInstanceServiceClient().ListBotInstances(ctx, machineidv1pb.ListBotInstancesRequest_builder{ - FilterBotName: c.botName, + FilterBotName: botName, PageSize: int32(pageSize), PageToken: pageToken, FilterSearchTerm: c.search, @@ -872,8 +893,15 @@ func (c *BotsCommand) ListBotInstances(ctx context.Context, client botsCommandCl healthStatus = formatStatus(status, false) // Disable color, it messes with the table layout } + // Instances of scoped bots are identified by the bot's scope-qualified + // name; instances of unscoped bots by the bot's bare name. + id := scopes.QualifiedName{ + Scope: i.GetScope(), + Name: i.GetSpec().GetBotName() + "/" + i.GetSpec().GetInstanceId(), + }.String() + t.AddRow([]string{ - fmt.Sprintf("%s/%s", i.GetSpec().GetBotName(), i.GetSpec().GetInstanceId()), joinMethod, + id, joinMethod, version, hostname, healthStatus, lastSeen.Format(time.RFC3339), }) } @@ -882,7 +910,8 @@ func (c *BotsCommand) ListBotInstances(ctx context.Context, client botsCommandCl executableFileName := filepath.Base(os.Args[0]) fmt.Fprintf(c.stdout, "\nTo view more information on a particular instance, run:\n\n> %s bots instances show [id]\n", executableFileName) - if c.botName != "" { + // 'bots instances add' does not support scoped bots yet. + if c.botName != "" && botScope == "" { fmt.Fprintf(c.stdout, "\nTo onboard a new instance for this bot, run:\n\n> %s bots instances add %s\n", executableFileName, c.botName) } @@ -969,7 +998,8 @@ func (c *BotsCommand) AddBotInstance(ctx context.Context, client botsCommandClie var showMessageTemplate = template.Must(template.New("show").Funcs(template.FuncMap{ "bold": bold, }).Parse(`Bot: {{.instance.Spec.BotName}} -ID: {{.instance.Spec.InstanceId}} +{{if .scope}}Scope: {{.scope}} +{{end}}ID: {{.instance.Spec.InstanceId}} Status: {{.health_status}} Initial Authentication: {{.initial_authentication_table}} @@ -984,15 +1014,15 @@ Services: To view a full, machine-readable record including past heartbeats and authentication records, run: -> {{.executable}} get bot_instance/{{.instance.Spec.BotName}}/{{.instance.Spec.InstanceId}} - +> {{.executable}} get {{.get_ref}} +{{if .can_add_instance}} To onboard a new instance for this bot, run: > {{.executable}} bots instances add {{.instance.Spec.BotName}} -`)) +{{end}}`)) func (c *BotsCommand) ShowBotInstance(ctx context.Context, client botsCommandClient) error { - botName, instanceID, err := parseInstanceID(c.instanceID) + botScope, botName, instanceID, err := parseInstanceID(c.instanceID) if err != nil { return trace.Wrap(err) } @@ -1000,6 +1030,7 @@ func (c *BotsCommand) ShowBotInstance(ctx context.Context, client botsCommandCli instance, err := client.BotInstanceServiceClient().GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ BotName: botName, InstanceId: instanceID, + BotScope: botScope, }.Build()) if err != nil { return trace.Wrap(err) @@ -1033,9 +1064,22 @@ func (c *BotsCommand) ShowBotInstance(ctx context.Context, client botsCommandCli servicesTable = formatServices(instance.GetStatus().GetServiceHealth()) } + // Only the two-argument form of 'tctl get' can carry a scope, so use it for + // scoped and unscoped alike rather than changing shape between them. + instanceRef := instance.GetSpec().GetBotName() + "/" + instance.GetSpec().GetInstanceId() + if scope := instance.GetScope(); scope != "" { + instanceRef = scopes.QualifiedName{Scope: scope, Name: instanceRef}.String() + } + getRef := types.KindBotInstance + " " + instanceRef + templateData := map[string]any{ - "executable": os.Args[0], - "instance": instance, + "executable": os.Args[0], + "instance": instance, + "scope": instance.GetScope(), + "get_ref": getRef, + // A bare name in the 'instances add' hint would target the same-named + // unscoped bot, and that command can't onboard a scoped bot anyway. + "can_add_instance": instance.GetScope() == "", "initial_authentication_table": initialAuthenticationTable, "latest_authentication_table": latestAuthenticationTable, "heartbeat_table": heartbeatTable, @@ -1043,7 +1087,7 @@ func (c *BotsCommand) ShowBotInstance(ctx context.Context, client botsCommandCli "services_table": servicesTable, } - return trace.Wrap(showMessageTemplate.Execute(os.Stdout, templateData)) + return trace.Wrap(showMessageTemplate.Execute(c.stdout, templateData)) } // botJSONResponse is a structured response generated by the `tctl bots add` @@ -1310,14 +1354,25 @@ func formatStatus(status machineidv1pb.BotInstanceHealthStatus, useColor bool) s } // parseInstanceID converts an instance ID string in the form of -// '[bot name]/[uuid]' to separate bot name and UUID strings. -func parseInstanceID(s string) (name string, uuid string, err error) { +// [scope::][bot name]/[uuid] into its component parts. The scope prefix +// addresses an instance of a scoped bot; scope is empty when the prefix is +// absent. +func parseInstanceID(s string) (scope string, name string, uuid string, err error) { + if before, after, ok := strings.Cut(s, scopes.QualifiedNameSeparator); ok { + if err := scopes.StrongValidate(before); err != nil { + return "", "", "", trace.Wrap(err) + } + scope, s = before, after + } else if scopes.MaybeSQN(s) { + return "", "", "", trace.BadParameter("invalid bot instance syntax, must be: [scope::][bot name]/[uuid]") + } + name, uuid, ok := strings.Cut(s, "/") if !ok { - return "", "", trace.BadParameter("invalid bot instance syntax, must be: [bot name]/[uuid]") + return "", "", "", trace.BadParameter("invalid bot instance syntax, must be: [scope::][bot name]/[uuid]") } - return + return scope, name, uuid, nil } // indentString prefixes each line (ending with \n) with the provided prefix. diff --git a/tool/tctl/common/bots_command_test.go b/tool/tctl/common/bots_command_test.go index bb2fc8f1afee..f244c3ca64bc 100644 --- a/tool/tctl/common/bots_command_test.go +++ b/tool/tctl/common/bots_command_test.go @@ -737,6 +737,194 @@ func TestListBotInstances(t *testing.T) { }) } +// TestBotInstancesScoped exercises "tctl bots instances list/show" against +// instances of scoped bots, which are addressed by prefixing the bot's name +// with its scope: ::. +func TestBotInstancesScoped(t *testing.T) { + t.Parallel() + + dynAddr := helpers.NewDynamicServiceAddr(t) + fileConfig := &config.FileConfig{ + Global: config.Global{ + DataDir: t.TempDir(), + }, + Auth: config.Auth{ + Service: config.Service{ + EnabledFlag: "true", + ListenAddress: dynAddr.AuthAddr, + }, + }, + } + process := makeAndRunTestAuthServer(t, withFileConfig(fileConfig), withFileDescriptors(dynAddr.Descriptors), withEnableCache(true)) + ctx := t.Context() + client, err := testenv.NewDefaultAuthClient(process) + require.NoError(t, err) + + t.Cleanup(func() { _ = client.Close() }) + + // An unscoped and a scoped bot sharing a name, to prove reads and filters + // are scope-strict. + unscopedInstance := createBotInstance(t, ctx, process) + scopedInstance := createBotInstance(t, ctx, process, func(instance *machineidv1pb.BotInstance) { + instance.SetScope("/staging") + }) + + // Give the auth cache a chance to catch-up + require.EventuallyWithT(t, func(t *assert.CollectT) { + res, _, err := process.GetAuthServer().ListBotInstances(ctx, 0, "", nil) + require.NoError(t, err) + require.Len(t, res, 2) + }, time.Second*10, time.Millisecond*50) + + t.Run("list filter by scope-qualified bot name", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + format: teleport.JSON, + botName: "/staging::test-bot-1", + } + + require.NoError(t, cmd.ListBotInstances(t.Context(), client)) + + res, err := services.UnmarshalProtoResourceArray[*machineidv1pb.BotInstance]([]byte(buf.String())) + require.NoError(t, err) + + require.Len(t, res, 1) + assertContainsInstance(t, res, scopedInstance.GetSpec().GetInstanceId()) + }) + + t.Run("list filter by bare bot name excludes scoped bots", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + format: teleport.JSON, + botName: "test-bot-1", + } + + require.NoError(t, cmd.ListBotInstances(t.Context(), client)) + + res, err := services.UnmarshalProtoResourceArray[*machineidv1pb.BotInstance]([]byte(buf.String())) + require.NoError(t, err) + + require.Len(t, res, 1) + assertContainsInstance(t, res, unscopedInstance.GetSpec().GetInstanceId()) + }) + + t.Run("list text output shows scope-qualified id", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + format: teleport.Text, + } + + require.NoError(t, cmd.ListBotInstances(t.Context(), client)) + + out := buf.String() + require.Contains(t, out, "/staging::test-bot-1/"+scopedInstance.GetSpec().GetInstanceId()) + require.Contains(t, out, "test-bot-1/"+unscopedInstance.GetSpec().GetInstanceId()) + }) + + t.Run("list hides add hint for scoped bot", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + format: teleport.Text, + botName: "/staging::test-bot-1", + } + + require.NoError(t, cmd.ListBotInstances(t.Context(), client)) + + require.NotContains(t, buf.String(), "bots instances add") + }) + + t.Run("list shows add hint for unscoped bot", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + format: teleport.Text, + botName: "test-bot-1", + } + + require.NoError(t, cmd.ListBotInstances(t.Context(), client)) + + require.Contains(t, buf.String(), "bots instances add test-bot-1") + }) + + t.Run("show scoped instance", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + instanceID: "/staging::test-bot-1/" + scopedInstance.GetSpec().GetInstanceId(), + } + + require.NoError(t, cmd.ShowBotInstance(t.Context(), client)) + + out := buf.String() + require.Contains(t, out, "Scope: /staging") + require.Contains(t, out, "get bot_instance /staging::test-bot-1/"+scopedInstance.GetSpec().GetInstanceId()) + // The slash form can't carry a scope, so it must not be suggested here. + require.NotContains(t, out, "get bot_instance/test-bot-1") + require.NotContains(t, out, "bots instances add") + }) + + t.Run("show unscoped instance", func(t *testing.T) { + buf := strings.Builder{} + cmd := BotsCommand{ + stdout: &buf, + instanceID: "test-bot-1/" + unscopedInstance.GetSpec().GetInstanceId(), + } + + require.NoError(t, cmd.ShowBotInstance(t.Context(), client)) + + out := buf.String() + require.NotContains(t, out, "Scope:") + require.Contains(t, out, "bots instances add test-bot-1") + // Same two-argument shape as the scoped case. + require.Contains(t, out, "get bot_instance test-bot-1/"+unscopedInstance.GetSpec().GetInstanceId()) + }) + + t.Run("show scoped instance without scope is not found", func(t *testing.T) { + cmd := BotsCommand{ + stdout: &strings.Builder{}, + instanceID: "test-bot-1/" + scopedInstance.GetSpec().GetInstanceId(), + } + + err := cmd.ShowBotInstance(t.Context(), client) + require.True(t, trace.IsNotFound(err), "expected NotFound, got: %v", err) + }) + + t.Run("show scoped instance with wrong scope is not found", func(t *testing.T) { + cmd := BotsCommand{ + stdout: &strings.Builder{}, + instanceID: "/prod::test-bot-1/" + scopedInstance.GetSpec().GetInstanceId(), + } + + err := cmd.ShowBotInstance(t.Context(), client) + require.True(t, trace.IsNotFound(err), "expected NotFound, got: %v", err) + }) + + t.Run("list filter with scope prefix but no separator is rejected", func(t *testing.T) { + cmd := BotsCommand{ + stdout: &strings.Builder{}, + format: teleport.JSON, + botName: "/staging", + } + + err := cmd.ListBotInstances(t.Context(), client) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got: %v", err) + }) + + t.Run("show with scope prefix but no separator is rejected", func(t *testing.T) { + cmd := BotsCommand{ + stdout: &strings.Builder{}, + instanceID: "/staging/test-bot-1/" + scopedInstance.GetSpec().GetInstanceId(), + } + + err := cmd.ShowBotInstance(t.Context(), client) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got: %v", err) + }) +} + func assertContainsInstance(t *testing.T, res []*machineidv1pb.BotInstance, instanceId string) { assert.True(t, slices.ContainsFunc(res, func(in *machineidv1pb.BotInstance) bool { return in.GetSpec().GetInstanceId() == instanceId @@ -820,6 +1008,19 @@ func TestListBotInstancesFallback(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "fallback not supported for requests with a query") }) + + t.Run("fallback not allowed for bot scope", func(t *testing.T) { + cmd := BotsCommand{ + stdout: ptr(strings.Builder{}), + format: teleport.JSON, + // The bot scope filter is only available in ListBotInstancesV2. + botName: "/staging::test-bot-1", + } + + err := cmd.ListBotInstances(ctx, authClient) + require.Error(t, err) + require.ErrorContains(t, err, "fallback not supported for requests with a bot scope") + }) } // mockBotInstanceListerClient is a client which returns NotImplemented for diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 0cbd3d770594..671c4999a51a 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -52,6 +52,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/devicetrust" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" @@ -829,6 +830,19 @@ func (rc *ResourceCommand) getCollectionByRef(ctx context.Context, client *authc return nil, trace.BadParameter("specify resource to list, e.g. 'tctl get roles'") } + // ParseRef splits on '/', so a scope-qualified name given in the single-arg + // form arrives with its scope stranded in the name or the sub-kind. Kinds with + // only a scoped handler are already rejected below; kinds with both would + // otherwise treat it as an unscoped name and match nothing. + _, classicFound := resources.Handlers()[ref.Kind] + _, scopedFound := resources.ScopedHandlers()[ref.Kind] + if classicFound && scopedFound && (scopes.MaybeSQN(ref.Name) || scopes.MaybeSQN(ref.SubKind)) { + return nil, trace.BadParameter( + "resource type %q does not accept a scope-qualified name in the single-arg '/' form, try:\n tctl get %s ::", + ref.Kind, ref.Kind, + ) + } + if handler, found := resources.Handlers()[ref.Kind]; found { coll, err := handler.Get(ctx, client, ref, opts) if err != nil { diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index 578905bda235..2b6b544b6392 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -42,12 +42,14 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" "k8s.io/apimachinery/pkg/util/yaml" "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1" userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2" @@ -1158,6 +1160,181 @@ func TestScopedAndUnscopedWorkloadIdentityResource(t *testing.T) { }) } +// TestScopedAndUnscopedBotInstanceResource exercises tctl get on bot instances, +// which are double-registered as both a scoped and unscoped resource handler to +// support a mix of scope-qualified and classic interactions. An instance of a +// scoped bot is addressed as ::/; a bare +// :: lists the scoped bot's instances. +func TestScopedAndUnscopedBotInstanceResource(t *testing.T) { + t.Parallel() + + dynAddr := helpers.NewDynamicServiceAddr(t) + fileConfig := &config.FileConfig{ + Global: config.Global{DataDir: t.TempDir()}, + Auth: config.Auth{ + Service: config.Service{ + EnabledFlag: "true", + ListenAddress: dynAddr.AuthAddr, + }, + }, + } + auth := makeAndRunTestAuthServer(t, withFileConfig(fileConfig), withFileDescriptors(dynAddr.Descriptors), withEnableCache(true)) + clt, err := testenv.NewDefaultAuthClient(auth) + require.NoError(t, err) + t.Cleanup(func() { _ = clt.Close() }) + + // Bot instances are created server-side on join and have no create RPC, so + // seed them directly through the auth server's backend service. + makeInstance := func(scope, botName string) *machineidv1pb.BotInstance { + instance, err := auth.GetAuthServer().CreateBotInstance(t.Context(), machineidv1pb.BotInstance_builder{ + Scope: scope, + Spec: machineidv1pb.BotInstanceSpec_builder{ + BotName: botName, + InstanceId: uuid.NewString(), + }.Build(), + Status: machineidv1pb.BotInstanceStatus_builder{ + InitialHeartbeat: machineidv1pb.BotInstanceStatusHeartbeat_builder{ + RecordedAt: timestamppb.New(time.Now()), + IsStartup: true, + Hostname: "test-hostname", + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + return instance + } + + classicInstance := makeInstance("", "classic-bot") + stagingInstance0 := makeInstance("/staging", "staging-bot") + stagingInstance1 := makeInstance("/staging", "staging-bot") + // Same bot name in a different scope, to prove reads are scope-strict. + prodInstance := makeInstance("/prod", "staging-bot") + + // Poll until all instances appear via the list (cache propagation). + // runResourceCommand needs a *testing.T, so poll manually rather than via + // require.EventuallyWithT. + allInstances := []*machineidv1pb.BotInstance{classicInstance, stagingInstance0, stagingInstance1, prodInstance} + timeout := time.After(30 * time.Second) + for { + buf, err := runResourceCommand(t, clt, []string{"get", types.KindBotInstance, "--format=json"}) + if err == nil { + propagated := 0 + for _, instance := range allInstances { + if strings.Contains(buf.String(), instance.GetSpec().GetInstanceId()) { + propagated++ + } + } + if propagated == len(allInstances) { + break + } + } + select { + case <-timeout: + require.FailNow(t, "timed out waiting for bot instances to appear in list") + case <-time.After(100 * time.Millisecond): + } + } + + t.Run("list all shows scope-qualified bot name for instances of scoped bots", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{"get", types.KindBotInstance, "--format=text"}) + require.NoError(t, err) + out := buf.String() + require.Contains(t, out, "/staging::staging-bot") + require.Contains(t, out, "/prod::staging-bot") + require.Contains(t, out, "classic-bot") + }) + + t.Run("single-arg unscoped get", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{ + "get", + types.KindBotInstance + "/classic-bot/" + classicInstance.GetSpec().GetInstanceId(), + "--format=json", + }) + require.NoError(t, err) + require.Contains(t, buf.String(), classicInstance.GetSpec().GetInstanceId()) + }) + + t.Run("two-arg unscoped get is equivalent to the single-arg form", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{ + "get", types.KindBotInstance, + "classic-bot/" + classicInstance.GetSpec().GetInstanceId(), + "--format=json", + }) + require.NoError(t, err) + require.Contains(t, buf.String(), classicInstance.GetSpec().GetInstanceId()) + }) + + t.Run("two-arg unscoped get of a missing instance is not found", func(t *testing.T) { + // Not the silently-empty list it would give if Name became a bot filter. + _, err := runResourceCommand(t, clt, []string{ + "get", types.KindBotInstance, "classic-bot/does-not-exist", "--format=json", + }) + require.True(t, trace.IsNotFound(err), "expected NotFound, got: %v", err) + }) + + t.Run("scope-qualified get", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{ + "get", types.KindBotInstance, + "/staging::staging-bot/" + stagingInstance0.GetSpec().GetInstanceId(), + "--format=json", + }) + require.NoError(t, err) + require.Contains(t, buf.String(), stagingInstance0.GetSpec().GetInstanceId()) + }) + + t.Run("scope-qualified get with scope mismatch", func(t *testing.T) { + _, err := runResourceCommand(t, clt, []string{ + "get", types.KindBotInstance, + "/prod::staging-bot/" + stagingInstance0.GetSpec().GetInstanceId(), + "--format=json", + }) + require.True(t, trace.IsNotFound(err), "expected NotFound on scope mismatch, got: %v", err) + }) + + t.Run("scope-qualified list by bot", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{"get", types.KindBotInstance, "/staging::staging-bot", "--format=json"}) + require.NoError(t, err) + out := buf.String() + require.Contains(t, out, stagingInstance0.GetSpec().GetInstanceId()) + require.Contains(t, out, stagingInstance1.GetSpec().GetInstanceId()) + require.NotContains(t, out, prodInstance.GetSpec().GetInstanceId()) + require.NotContains(t, out, classicInstance.GetSpec().GetInstanceId()) + }) + + t.Run("unscoped list by bot excludes scoped bots of the same name", func(t *testing.T) { + buf, err := runResourceCommand(t, clt, []string{"get", types.KindBotInstance + "/staging-bot", "--format=json"}) + require.NoError(t, err) + out := buf.String() + require.NotContains(t, out, stagingInstance0.GetSpec().GetInstanceId()) + require.NotContains(t, out, stagingInstance1.GetSpec().GetInstanceId()) + require.NotContains(t, out, prodInstance.GetSpec().GetInstanceId()) + }) + + t.Run("sub-kind with SQN is rejected with a hint", func(t *testing.T) { + _, err := runResourceCommand(t, clt, []string{ + "get", types.KindBotInstance + "/staging-bot", + "/staging::" + stagingInstance0.GetSpec().GetInstanceId(), + "--format=json", + }) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got: %v", err) + require.ErrorContains(t, err, "::/") + }) + + t.Run("SQN in the single-arg form is rejected with a hint", func(t *testing.T) { + for _, ref := range []string{ + types.KindBotInstance + "//staging::staging-bot", + types.KindBotInstance + "//staging::staging-bot/" + stagingInstance0.GetSpec().GetInstanceId(), + } { + t.Run(ref, func(t *testing.T) { + _, err := runResourceCommand(t, clt, []string{"get", ref, "--format=json"}) + require.True(t, trace.IsBadParameter(err), "expected BadParameter, got: %v", err) + require.ErrorContains(t, err, "single-arg") + require.ErrorContains(t, err, "::") + }) + } + }) +} + // TestIntegrationResource tests tctl integration commands. func TestIntegrationResource(t *testing.T) { dynAddr := helpers.NewDynamicServiceAddr(t) diff --git a/tool/tctl/common/resources/bot_instance.go b/tool/tctl/common/resources/bot_instance.go index 9fa03d535090..2a4660498c06 100644 --- a/tool/tctl/common/resources/bot_instance.go +++ b/tool/tctl/common/resources/bot_instance.go @@ -19,6 +19,7 @@ package resources import ( "context" "io" + "strings" "github.com/gravitational/trace" @@ -28,6 +29,7 @@ import ( "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/itertools/stream" + "github.com/gravitational/teleport/lib/scopes" "github.com/gravitational/teleport/lib/services" ) @@ -50,7 +52,11 @@ func (c *botInstanceCollection) WriteText(w io.Writer, verbose bool) error { // last heartbeat, last auth, etc. var rows [][]string for _, item := range c.items { - rows = append(rows, []string{item.GetSpec().GetBotName(), item.GetSpec().GetInstanceId()}) + botName := scopes.QualifiedName{ + Scope: item.GetScope(), + Name: item.GetSpec().GetBotName(), + }.String() + rows = append(rows, []string{botName, item.GetSpec().GetInstanceId()}) } t := asciitable.MakeTable(headers, rows...) @@ -76,11 +82,20 @@ func getBotInstance( opts GetOpts, ) (Collection, error) { c := client.BotInstanceServiceClient() - if ref.Name != "" && ref.SubKind != "" { - // Gets a specific bot instance, e.g. bot_instance// + + // services.ParseRef splits the bot name into SubKind only for the one-argument + // form 'bot_instance//'; the two-argument form leaves it in Name. + botName, instanceID := ref.SubKind, ref.Name + if botName == "" { + if bot, id, ok := strings.Cut(ref.Name, "/"); ok { + botName, instanceID = bot, id + } + } + + if botName != "" && instanceID != "" { bi, err := c.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ - BotName: ref.SubKind, - InstanceId: ref.Name, + BotName: botName, + InstanceId: instanceID, }.Build()) if err != nil { return nil, trace.Wrap(err) @@ -108,3 +123,78 @@ func getBotInstance( return &botInstanceCollection{items: instances}, nil } + +// botInstanceScopedHandler returns a [ScopedHandler] for instances of scoped +// bots. Bot instances support both classic (unscoped) and scope-qualified +// access, so this is registered alongside the classic handler in +// ScopedHandlers(). +func botInstanceScopedHandler() ScopedHandler { + return ScopedHandler{ + getHandler: getBotInstanceScoped, + description: "A single instance of the tbot agent running in Teleport.", + } +} + +func getBotInstanceScoped( + ctx context.Context, + client *authclient.Client, + subKind string, + sqn *scopes.QualifiedName, + opts GetOpts, +) (Collection, error) { + if subKind != "" { + // The generic rejectSubKind hint would suggest '::', + // which is not how bot instances are addressed, so use a + // bot_instance-specific message. + return nil, trace.BadParameter( + "resource type %q does not support sub-kinds (got %q)\n"+ + "hint: address an instance of a scoped bot with a scope-qualified name:\n"+ + " tctl get %s ::/", + types.KindBotInstance, subKind, types.KindBotInstance, + ) + } + if sqn == nil { + // No SQN was provided, so this is a list-all. The classic handler + // normally serves list-all (bot_instance is registered in both maps), + // but fall back to it here for safety. + return getBotInstance(ctx, client, services.Ref{Kind: types.KindBotInstance}, opts) + } + + c := client.BotInstanceServiceClient() + + // The name component is either /, addressing a + // single instance, or a bare , listing the scoped bot's + // instances. The scope travels in the request: the server resolves it + // against scope-qualified storage, so a wrong-scope read is a not-found + // and no client-side scope comparison is needed. + if botName, instanceID, ok := strings.Cut(sqn.Name, "/"); ok { + bi, err := c.GetBotInstance(ctx, machineidv1pb.GetBotInstanceRequest_builder{ + BotName: botName, + InstanceId: instanceID, + BotScope: sqn.Scope, + }.Build()) + if err != nil { + return nil, trace.Wrap(err) + } + + return &botInstanceCollection{items: []*machineidv1pb.BotInstance{bi}}, nil + } + + instances, err := stream.Collect(clientutils.Resources(ctx, func(ctx context.Context, limit int, pageToken string) ([]*machineidv1pb.BotInstance, string, error) { + resp, err := c.ListBotInstancesV2(ctx, machineidv1pb.ListBotInstancesV2Request_builder{ + PageSize: int32(limit), + PageToken: pageToken, + Filter: machineidv1pb.ListBotInstancesV2Request_Filters_builder{ + BotName: sqn.Name, + BotScope: sqn.Scope, + }.Build(), + }.Build()) + + return resp.GetBotInstances(), resp.GetNextPageToken(), trace.Wrap(err) + })) + if err != nil { + return nil, trace.Wrap(err) + } + + return &botInstanceCollection{items: instances}, nil +} diff --git a/tool/tctl/common/resources/scoped_handler.go b/tool/tctl/common/resources/scoped_handler.go index 11dcbc8a7adf..a2ec4b5f7ffc 100644 --- a/tool/tctl/common/resources/scoped_handler.go +++ b/tool/tctl/common/resources/scoped_handler.go @@ -36,6 +36,7 @@ func ScopedHandlers() map[string]ScopedHandler { return map[string]ScopedHandler{ types.KindNode: serverScopedHandler(), types.KindWorkloadIdentity: workloadIdentityScopedHandler(), + types.KindBotInstance: botInstanceScopedHandler(), scopedaccess.KindScopedRole: scopedRoleScopedHandler(), types.KindScopedToken: scopedTokenScopedHandler(), scopedaccess.KindScopedRoleAssignment: scopedRoleAssignmentScopedHandler(), diff --git a/tool/tctl/common/scoped_ref.go b/tool/tctl/common/scoped_ref.go index 86b3f6eb1c9e..6f9ef22e5305 100644 --- a/tool/tctl/common/scoped_ref.go +++ b/tool/tctl/common/scoped_ref.go @@ -120,6 +120,24 @@ func ParseScopedRef(ref, id string) (ScopedRef, error) { qn.Name, _, _ = strings.Cut(qn.Name, ":") } + // A bot instance is identified by / under the bot's scope, + // so for bot_instance refs the name component of the SQN may itself be a + // two-part identifier containing a '/'. Validate the parts individually + // since whole-name validation would reject the separator. + if r.Kind == types.KindBotInstance { + if botName, instanceID, ok := strings.Cut(qn.Name, "/"); ok { + if err := scopes.StrongValidate(qn.Scope); err != nil { + return ScopedRef{}, trace.BadParameter("scope-qualified name %q has invalid scope: %v", qn, err) + } + for _, part := range []string{botName, instanceID} { + if err := scopes.StrongValidateSegment(part); err != nil { + return ScopedRef{}, trace.BadParameter("scope-qualified name %q has invalid name: %v", qn, err) + } + } + return ScopedRef{Kind: r.Kind, SubKind: subKind, Scope: qn.Scope, Name: qn.Name}, nil + } + } + if err := qn.StrongValidate(); err != nil { return ScopedRef{}, trace.Wrap(err) } From de4f8f298ac0ca58f1aaf4b011e2404229a70b71 Mon Sep 17 00:00:00 2001 From: Cam Hutchison Date: Mon, 3 Aug 2026 18:53:52 +1000 Subject: [PATCH 102/347] discovery: Add S3 bucket listing pagination (#69211) * Added pagination to S3 buckets * Added pagination to s3 buckets * discovery: Stop looking up S3 bucket region when we already have it Stop looking up the bucket region as a separate API call during S3 bucket discovery and use what ListS3Buckets returns instead. Since pagination was added with the MaxRequests input parameter, AWS returns the bucket region in the ListS3Buckets result. This means we do not have to look it up separately any more. We still do look up a bucket region if none is returned, but it is not expected that it will be used. This is a safety fallback in case the AWS API has cases where it does not return it, as the result is technically optional. While we're here, rework the region fetching to be a little more straight-forward - the returned closure for looking up region was not really necessary and just made things a more complicated than needed. Also change the lookup of existing buckets for failure handling to use a map as the pagination changes mean we can get a lot more buckets back and the linear slice scan ends up being n^2 complexity. Update and rework the tests for these changes - some parts of this code did not have coverage so the parts that were reworked now have coverage. --------- Co-authored-by: Kenneth Tran --- lib/cloud/mocks/aws_s3.go | 43 ++- lib/srv/discovery/fetchers/aws-sync/s3.go | 141 +++++--- .../discovery/fetchers/aws-sync/s3_test.go | 330 +++++++++++++++--- 3 files changed, 415 insertions(+), 99 deletions(-) diff --git a/lib/cloud/mocks/aws_s3.go b/lib/cloud/mocks/aws_s3.go index 1ddd902ac5bb..e17efa0ed53d 100644 --- a/lib/cloud/mocks/aws_s3.go +++ b/lib/cloud/mocks/aws_s3.go @@ -20,6 +20,7 @@ package mocks import ( "context" + "strconv" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" @@ -35,12 +36,46 @@ type S3Client struct { BucketACL map[string][]s3types.Grant BucketTags map[string][]s3types.Tag BucketLocations map[string]s3types.BucketLocationConstraint + // RequireMaxBuckets rejects unpaginated ListBuckets requests, as AWS does + // for accounts with a bucket quota above maxListBuckets. + RequireMaxBuckets bool } -func (m *S3Client) ListBuckets(_ context.Context, _ *s3.ListBucketsInput, _ ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { - return &s3.ListBucketsOutput{ - Buckets: m.Buckets, - }, nil +// maxListBuckets is the largest page size ListBuckets accepts for max-buckets. +const maxListBuckets = 10000 + +// ListBuckets pages through Buckets using max-buckets as the page size. The +// continuation token is the index of the first bucket of the next page. +func (m *S3Client) ListBuckets(_ context.Context, input *s3.ListBucketsInput, _ ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { + if input.MaxBuckets == nil { + if m.RequireMaxBuckets { + return nil, trace.BadParameter("unpaginated ListBuckets requests are rejected for accounts with a bucket quota greater than %d", maxListBuckets) + } + return &s3.ListBucketsOutput{ + Buckets: m.Buckets, + }, nil + } + pageSize := int(aws.ToInt32(input.MaxBuckets)) + if pageSize < 1 || pageSize > maxListBuckets { + return nil, trace.BadParameter("max-buckets must be between 1 and %d, got %d", maxListBuckets, pageSize) + } + + start := 0 + if token := aws.ToString(input.ContinuationToken); token != "" { + var err error + start, err = strconv.Atoi(token) + if err != nil { + return nil, trace.BadParameter("invalid continuation token %q", token) + } + } + end := min(start+pageSize, len(m.Buckets)) + out := &s3.ListBucketsOutput{ + Buckets: m.Buckets[start:end], + } + if end < len(m.Buckets) { + out.ContinuationToken = aws.String(strconv.Itoa(end)) + } + return out, nil } func (m *S3Client) GetBucketPolicy(_ context.Context, input *s3.GetBucketPolicyInput, _ ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) { diff --git a/lib/srv/discovery/fetchers/aws-sync/s3.go b/lib/srv/discovery/fetchers/aws-sync/s3.go index 486007688a24..53a565c2debe 100644 --- a/lib/srv/discovery/fetchers/aws-sync/s3.go +++ b/lib/srv/discovery/fetchers/aws-sync/s3.go @@ -19,6 +19,7 @@ package aws_sync import ( + "cmp" "context" "errors" "sync" @@ -32,7 +33,6 @@ import ( "google.golang.org/protobuf/proto" accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha" - awsregion "github.com/gravitational/teleport/lib/utils/aws/region" ) // s3Client defines a subset of the AWS S3 client API. @@ -82,42 +82,47 @@ func (a *Fetcher) fetchS3Buckets(ctx context.Context) ([]*accessgraphv1alpha.AWS } } - buckets, getBucketRegion, err := a.listS3Buckets(ctx) + // listClient is not tied to any bucket's region. It lists the buckets and + // resolves the region of any bucket that ListBuckets did not report one for. + listClient, err := a.getClient(ctx, a.s3ListRegion()) + if err != nil { + return existing.S3Buckets, trace.Wrap(err) + } + + // build a map of existing buckets to avoid quadratic lookup complexity from + // a linear scan of the slice (existing.S3Buckets). + existingByName := make(map[string]*accessgraphv1alpha.AWSS3BucketV1, len(existing.S3Buckets)) + for _, bucket := range existing.S3Buckets { + if bucket.GetAccountId() == a.AccountID { + existingByName[bucket.GetName()] = bucket + } + } + + buckets, err := listS3Buckets(ctx, listClient) if err != nil { return existing.S3Buckets, trace.Wrap(err) } - // Iterate over the buckets and fetch their inline and attached policies. for _, bucket := range buckets { eG.Go(func() error { - var failedReqs failedRequests - var errs []error - existingBucket := sliceFilterPickFirst(existing.S3Buckets, func(b *accessgraphv1alpha.AWSS3BucketV1) bool { - return b.GetName() == aws.ToString(bucket.Name) && b.GetAccountId() == a.AccountID - }, - ) - bucketRegion, err := getBucketRegion(bucket.Name) + bucketName := aws.ToString(bucket.Name) + existingBucket := existingByName[bucketName] + + region, err := getBucketRegion(ctx, listClient, bucket) if err != nil { - errs = append(errs, - trace.Wrap(err), - ) - failedReqs.policyFailed = true - failedReqs.failedPolicyStatus = true - failedReqs.failedAcls = true - failedReqs.failedTags = true - newBucket := awsS3Bucket(aws.ToString(bucket.Name), nil, nil, nil, nil, a.AccountID) - collect(mergeS3Protos(existingBucket, newBucket, failedReqs), trace.NewAggregate(errs...)) + // Without a region none of the detail requests can be made. + b := cmp.Or(existingBucket, awsS3Bucket(bucketName, nil, nil, nil, nil, a.AccountID)) + collect(b, err) return nil } - details, failedReqs, errsL := a.getS3BucketDetails(ctx, bucket, bucketRegion) - - newBucket := awsS3Bucket(aws.ToString(bucket.Name), details.policy, details.policyStatus, details.acls, details.tags, a.AccountID) - collect(mergeS3Protos(existingBucket, newBucket, failedReqs), trace.NewAggregate(append(errs, errsL...)...)) + details, failedReqs, errsL := a.getS3BucketDetails(ctx, bucket, region) + newBucket := awsS3Bucket(bucketName, details.policy, details.policyStatus, details.acls, details.tags, a.AccountID) + collect(mergeS3Protos(existingBucket, newBucket, failedReqs), trace.NewAggregate(errsL...)) return nil }) } - // always discard the error + // always discard the error as we never return an error in the errorgroup _ = eG.Wait() return s3s, trace.NewAggregate(errs...) @@ -176,7 +181,6 @@ type failedRequests struct { failedPolicyStatus bool failedAcls bool failedTags bool - headFailed bool } func mergeS3Protos(existing, new *accessgraphv1alpha.AWSS3BucketV1, failedReqs failedRequests) *accessgraphv1alpha.AWSS3BucketV1 { @@ -210,26 +214,42 @@ type s3Details struct { tags *s3.GetBucketTaggingOutput } +// getBucketRegion returns the region the bucket is in. A paginated ListBuckets +// reports it, but if the response omitted it, clt is used to look it up. clt +// may be for any region. +func getBucketRegion(ctx context.Context, clt s3Client, bucket s3types.Bucket) (string, error) { + if region := aws.ToString(bucket.BucketRegion); region != "" { + return region, nil + } + rsp, err := clt.GetBucketLocation(ctx, &s3.GetBucketLocationInput{Bucket: bucket.Name}) + if err != nil { + return "", trace.Wrap(err, "failed to fetch bucket %q region", aws.ToString(bucket.Name)) + } + // An absent location constraint means us-east-1, which has no enum value. + return string(cmp.Or(rsp.LocationConstraint, "us-east-1")), nil +} + +// getS3BucketDetails fetches the policy, policy status, ACLs and tags of a +// bucket. bucketRegion must be the region the bucket is in, as the requests +// are only served by that region's endpoint. func (a *Fetcher) getS3BucketDetails(ctx context.Context, bucket s3types.Bucket, bucketRegion string) (s3Details, failedRequests, []error) { var failedReqs failedRequests var errs []error var details s3Details - awsCfg, err := a.AWSConfigProvider.GetConfig(ctx, bucketRegion, a.getAWSOptions()...) + s3Client, err := a.getClient(ctx, bucketRegion) if err != nil { errs = append(errs, trace.Wrap(err, "failed to create s3 client for bucket %q", aws.ToString(bucket.Name)), ) return s3Details{}, failedRequests{ - headFailed: true, policyFailed: true, failedPolicyStatus: true, failedAcls: true, failedTags: true, }, errs } - s3Client := a.awsClients.getS3Client(awsCfg) details.policy, err = s3Client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: bucket.Name, @@ -290,36 +310,47 @@ func isS3BucketNoTagSet(err error) bool { return isAPIErrorCode(err, "NoSuchTagSet") } -func (a *Fetcher) listS3Buckets(ctx context.Context) ([]s3types.Bucket, func(*string) (string, error), error) { - region := awsregion.GetKnownRegions()[0] - if len(a.Regions) > 0 { - region = a.Regions[0] +func listS3Buckets(ctx context.Context, clt s3Client) ([]s3types.Bucket, error) { + // MaxBuckets must be set so the request is paginated. It also causes + // AWS to return BucketRegion in the response (providing any valid input + // parameter will do this). + pager := s3.NewListBucketsPaginator(clt, &s3.ListBucketsInput{ + MaxBuckets: aws.Int32(pageSize), + }, func(opts *s3.ListBucketsPaginatorOptions) { + opts.StopOnDuplicateToken = true + }) + + var buckets []s3types.Bucket + for pager.HasMorePages() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + buckets = append(buckets, page.Buckets...) } + return buckets, nil +} - // use any region to list buckets - awsCfg, err := a.AWSConfigProvider.GetConfig(ctx, region, a.getAWSOptions()...) - if err != nil { - return nil, nil, trace.Wrap(err) +// defaultListRegion is used when the fetcher has no configured regions. +// Discovery config requires at least one, so this is only a backstop. It is a +// fixed region rather than the first of awsregion.GetKnownRegions(), which is +// af-south-1 and changes whenever that generated list does. +const defaultListRegion = "us-west-2" + +// s3ListRegion is the region used for the AWS APIs that are not specific to a +// bucket's region: listing buckets and getting a bucket's region. +func (a *Fetcher) s3ListRegion() string { + if len(a.Regions) == 0 { + return defaultListRegion } - s3Client := a.awsClients.getS3Client(awsCfg) - rsp, err := s3Client.ListBuckets(ctx, &s3.ListBucketsInput{}) + return a.Regions[0] +} + +// getClient returns an s3Client for the given region. +func (a *Fetcher) getClient(ctx context.Context, region string) (s3Client, error) { + awsCfg, err := a.AWSConfigProvider.GetConfig(ctx, region, a.getAWSOptions()...) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, trace.Wrap(err) } - return rsp.Buckets, - func(bucket *string) (string, error) { - rsp, err := s3Client.GetBucketLocation( - ctx, - &s3.GetBucketLocationInput{ - Bucket: bucket, - }, - ) - if err != nil { - return "", trace.Wrap(err, "failed to fetch bucket %q region", aws.ToString(bucket)) - } - if rsp.LocationConstraint == "" { - return "us-east-1", nil - } - return string(rsp.LocationConstraint), nil - }, nil + return a.awsClients.getS3Client(awsCfg), nil } diff --git a/lib/srv/discovery/fetchers/aws-sync/s3_test.go b/lib/srv/discovery/fetchers/aws-sync/s3_test.go index 6b1ae9262085..41ab082420f3 100644 --- a/lib/srv/discovery/fetchers/aws-sync/s3_test.go +++ b/lib/srv/discovery/fetchers/aws-sync/s3_test.go @@ -21,11 +21,13 @@ package aws_sync import ( "context" "sort" + "strconv" "sync" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/google/go-cmp/cmp" "github.com/gravitational/trace" @@ -53,25 +55,33 @@ func TestPollAWSS3(t *testing.T) { require.NoError(t, err) const ( - accountID = "12345678" - bucketName = "bucket1" - otherBucketName = "bucket2" - missingBucket = "missing_perm_bucket" + accountID = "12345678" + bucketName = "bucket1" + otherBucketName = "bucket2" + missingBucket = "missing_perm_bucket" + regionlessBucket = "regionless_bucket" ) var ( regions = []string{"eu-west-1"} fakedClients = fakeAWSClients{ + // The absent entries in the various fields of mocks.S3Client mean + // the mocked calls to retrieve that data for the absent buckets will + // fail, triggering the fallback cases in the unit under test. s3Client: &mocks.S3Client{ - Buckets: s3Buckets(bucketName, otherBucketName, missingBucket), - BucketLocations: map[string]s3types.BucketLocationConstraint{ - bucketName: s3types.BucketLocationConstraintEuWest1, - otherBucketName: s3types.BucketLocationConstraintEuWest1, - missingBucket: s3types.BucketLocationConstraintEuWest1, - }, + // regionlessBucket reports no region and has no + // GetBucketLocation entry, so its region cannot be resolved and + // none of its details can be fetched. The rest report a region, + // so GetBucketLocation is never consulted for them. + Buckets: append( + s3Buckets("eu-west-1", bucketName, otherBucketName, missingBucket), + s3Buckets("", regionlessBucket)..., + ), + // No BucketPolicy for missingBucket or regionlessBucket BucketPolicy: map[string]string{ bucketName: "policy", otherBucketName: "otherPolicy", }, + // No BucketPolicyStatus for missingBucket or regionlessBucket BucketPolicyStatus: map[string]s3types.PolicyStatus{ bucketName: { IsPublic: aws.Bool(true), @@ -80,6 +90,7 @@ func TestPollAWSS3(t *testing.T) { IsPublic: aws.Bool(false), }, }, + // No BucketACL for missingBucket or regionlessBucket BucketACL: map[string][]s3types.Grant{ bucketName: { { @@ -98,6 +109,7 @@ func TestPollAWSS3(t *testing.T) { }, }, }, + // No BucketTags for otherBucketName, missingBucket or regionlessBucket BucketTags: map[string][]s3types.Tag{ bucketName: { { @@ -110,12 +122,61 @@ func TestPollAWSS3(t *testing.T) { } ) + // fetchedACLs and fetchedTags are what the mocked client returns for the + // buckets it has entries for. + fetchedACLs := func() []*accessgraphv1alpha.AWSS3BucketACL { + return []*accessgraphv1alpha.AWSS3BucketACL{ + accessgraphv1alpha.AWSS3BucketACL_builder{ + Grantee: accessgraphv1alpha.AWSS3BucketACLGrantee_builder{ + Id: "id", + }.Build(), + Permission: "READ", + }.Build(), + } + } + fetchedTags := func() []*accessgraphv1alpha.AWSTag { + return []*accessgraphv1alpha.AWSTag{ + accessgraphv1alpha.AWSTag_builder{ + Key: "tag", + Value: strPtrToWrapper(aws.String("val")), + }.Build(), + } + } + + // existing is what a previous sync recorded for a bucket. Every field a + // request fails to fetch must be carried over from here. + existing := func(name string) *accessgraphv1alpha.AWSS3BucketV1 { + return accessgraphv1alpha.AWSS3BucketV1_builder{ + Name: name, + AccountId: accountID, + PolicyDocument: []byte("existingPolicy"), + IsPublic: true, + Acls: []*accessgraphv1alpha.AWSS3BucketACL{ + accessgraphv1alpha.AWSS3BucketACL_builder{ + Grantee: accessgraphv1alpha.AWSS3BucketACLGrantee_builder{ + Id: "existingID", + }.Build(), + Permission: "WRITE", + }.Build(), + }, + Tags: []*accessgraphv1alpha.AWSTag{ + accessgraphv1alpha.AWSTag_builder{ + Key: "existingTag", + Value: strPtrToWrapper(aws.String("existingVal")), + }.Build(), + }, + }.Build() + } + tests := []struct { name string - want *Resources + // lastResult is what the previous sync recorded. + lastResult *Resources + want *Resources }{ { - name: "poll s3", + name: "poll s3", + lastResult: &Resources{}, want: &Resources{ S3Buckets: []*accessgraphv1alpha.AWSS3BucketV1{ accessgraphv1alpha.AWSS3BucketV1_builder{ @@ -123,39 +184,63 @@ func TestPollAWSS3(t *testing.T) { AccountId: accountID, PolicyDocument: []byte("policy"), IsPublic: true, - Acls: []*accessgraphv1alpha.AWSS3BucketACL{ - accessgraphv1alpha.AWSS3BucketACL_builder{ - Grantee: accessgraphv1alpha.AWSS3BucketACLGrantee_builder{ - Id: "id", - }.Build(), - Permission: "READ", - }.Build(), - }, - Tags: []*accessgraphv1alpha.AWSTag{ - accessgraphv1alpha.AWSTag_builder{ - Key: "tag", - Value: strPtrToWrapper(aws.String("val")), - }.Build(), - }, + Acls: fetchedACLs(), + Tags: fetchedTags(), }.Build(), accessgraphv1alpha.AWSS3BucketV1_builder{ Name: otherBucketName, AccountId: accountID, PolicyDocument: []byte("otherPolicy"), IsPublic: false, - Acls: []*accessgraphv1alpha.AWSS3BucketACL{ - accessgraphv1alpha.AWSS3BucketACL_builder{ - Grantee: accessgraphv1alpha.AWSS3BucketACLGrantee_builder{ - Id: "id", - }.Build(), - Permission: "READ", - }.Build(), - }, + Acls: fetchedACLs(), }.Build(), accessgraphv1alpha.AWSS3BucketV1_builder{ Name: missingBucket, AccountId: accountID, }.Build(), + accessgraphv1alpha.AWSS3BucketV1_builder{ + Name: regionlessBucket, + AccountId: accountID, + }.Build(), + }, + }, + }, + { + // A failed request must not look like the attribute was removed, so + // each one falls back to what the last sync recorded. + name: "reuse existing buckets on failure", + lastResult: &Resources{ + S3Buckets: []*accessgraphv1alpha.AWSS3BucketV1{ + existing(bucketName), + existing(otherBucketName), + existing(missingBucket), + existing(regionlessBucket), + }, + }, + want: &Resources{ + S3Buckets: []*accessgraphv1alpha.AWSS3BucketV1{ + // Every request succeeded, so nothing is carried over. + accessgraphv1alpha.AWSS3BucketV1_builder{ + Name: bucketName, + AccountId: accountID, + PolicyDocument: []byte("policy"), + IsPublic: true, + Acls: fetchedACLs(), + Tags: fetchedTags(), + }.Build(), + // Only the tag request failed. + accessgraphv1alpha.AWSS3BucketV1_builder{ + Name: otherBucketName, + AccountId: accountID, + PolicyDocument: []byte("otherPolicy"), + IsPublic: false, + Acls: fetchedACLs(), + Tags: existing(otherBucketName).GetTags(), + }.Build(), + // Every request failed, so every field is carried over. + existing(missingBucket), + // The region was never resolved, so no request was made. + existing(regionlessBucket), }, }, }, @@ -186,10 +271,10 @@ func TestPollAWSS3(t *testing.T) { Integration: awsOIDCIntegration.GetName(), awsClients: fakedClients, }, - lastResult: &Resources{}, + lastResult: tt.lastResult, } result := &Resources{} - execFunc := a.pollAWSS3Buckets(context.Background(), result, collectErr) + execFunc := a.pollAWSS3Buckets(t.Context(), result, collectErr) require.NoError(t, execFunc()) require.Error(t, trace.NewAggregate(errs...)) @@ -212,14 +297,179 @@ func TestPollAWSS3(t *testing.T) { } } -func s3Buckets(bucketNames ...string) []s3types.Bucket { +// failingListBucketsS3Client wraps an s3Client and returns err on the +// failOnPage'th ListBuckets call +type failingListBucketsS3Client struct { + s3Client + failOnPage int + err error + page int +} + +func (c *failingListBucketsS3Client) ListBuckets(ctx context.Context, input *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { + c.page++ + if c.page == c.failOnPage { + return nil, c.err + } + return c.s3Client.ListBuckets(ctx, input, optFns...) +} + +// The mock pages by the max-buckets the fetcher sends, so page boundaries are +// expressed relative to pageSize rather than chosen by each test case. +func TestListS3Buckets(t *testing.T) { + errListBuckets := trace.AccessDenied("access denied listing buckets") + + tests := []struct { + name string + totalBuckets int + failOnPage int + rejectUnpaginated bool + wantErr error + }{ + { + name: "no buckets", + totalBuckets: 0, + }, + { + name: "partial single page", + totalBuckets: int(pageSize) - 1, + }, + { + name: "exactly one full page", + totalBuckets: int(pageSize), + }, + { + name: "multi-page with a partial final page", + totalBuckets: int(pageSize)*4 + 1, + }, + { + name: "multi-page dividing evenly", + totalBuckets: int(pageSize) * 5, + }, + { + name: "error on the first page", + totalBuckets: int(pageSize) * 5, + failOnPage: 1, + wantErr: errListBuckets, + }, + { + name: "error on a later page", + totalBuckets: int(pageSize) * 5, + failOnPage: 3, + wantErr: errListBuckets, + }, + { + name: "reject unpaginated requests", + totalBuckets: int(pageSize) * 5, + rejectUnpaginated: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wantNames := make([]string, 0, tt.totalBuckets) + for i := range tt.totalBuckets { + wantNames = append(wantNames, "bucket-"+strconv.Itoa(i)) + } + + var client s3Client = &mocks.S3Client{ + Buckets: s3Buckets("eu-west-1", wantNames...), + RequireMaxBuckets: tt.rejectUnpaginated, + } + if tt.failOnPage > 0 { + client = &failingListBucketsS3Client{ + s3Client: client, + failOnPage: tt.failOnPage, + err: tt.wantErr, + } + } + + buckets, err := listS3Buckets(t.Context(), client) + + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + // A failed page discards the whole listing, so a partial list + // is never mistaken for the account's full set of buckets. + require.Nil(t, buckets) + return + } + + require.NoError(t, err) + gotNames := make([]string, 0, len(buckets)) + for _, b := range buckets { + gotNames = append(gotNames, aws.ToString(b.Name)) + } + require.Equal(t, wantNames, gotNames) + }) + } +} + +func TestBucketRegion(t *testing.T) { + const bucketName = "bucket" + + tests := []struct { + name string + // listedRegion is the BucketRegion reported by ListBuckets. Empty means + // the response omitted it. + listedRegion string + // locations are the GetBucketLocation responses, keyed by bucket name. + // A bucket absent from the map makes the lookup fail. + locations map[string]s3types.BucketLocationConstraint + wantRegion string + wantErr string + }{ + { + name: "BucketRegion is used when reported", + listedRegion: "ap-south-1", + // locations is empty, so any GetBucketLocation call fails the test. + wantRegion: "ap-south-1", + }, + { + name: "absent BucketRegion falls back to GetBucketLocation", + locations: map[string]s3types.BucketLocationConstraint{bucketName: "eu-west-1"}, + wantRegion: "eu-west-1", + }, + { + name: "an empty location constraint means us-east-1", + locations: map[string]s3types.BucketLocationConstraint{bucketName: ""}, + wantRegion: "us-east-1", + }, + { + name: "a failed lookup is an error", + wantErr: `failed to fetch bucket "bucket" region`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clt := &mocks.S3Client{BucketLocations: tt.locations} + bucket := s3Buckets(tt.listedRegion, bucketName)[0] + + region, err := getBucketRegion(t.Context(), clt, bucket) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.wantRegion, region) + }) + } +} + +// s3Buckets builds ListBuckets results. An empty region models a response that +// omitted BucketRegion, which is what AWS returns for unpaginated requests. +func s3Buckets(region string, bucketNames ...string) []s3types.Bucket { var output []s3types.Bucket for _, name := range bucketNames { - output = append(output, s3types.Bucket{ + bucket := s3types.Bucket{ Name: aws.String(name), CreationDate: aws.Time(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)), - }) - + } + if region != "" { + bucket.BucketRegion = aws.String(region) + } + output = append(output, bucket) } return output } From e46813bb0e4cee6c3e3edc8c4a4a2edddd38dd44 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Mon, 3 Aug 2026 11:56:55 +0200 Subject: [PATCH 103/347] Fix unified resource items shadows (#69224) --- .../CardsView/ResourceCard.tsx | 34 ++++++------------- .../ListView/ResourceListItem.tsx | 19 ++--------- 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx index b93631443f1d..8670e609c755 100644 --- a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx +++ b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx @@ -412,39 +412,25 @@ const CardOuterContainer = styled(Box)<{ shouldDisplayWarning: boolean; showHoverState: boolean; }>` - border-radius: ${props => props.theme.radii[3]}px; + border-radius: ${({ theme }) => theme.radii[3]}px; + transition: all 150ms; - ${props => - props.showAllLabels && + ${({ showAllLabels, shouldDisplayWarning }) => + showAllLabels && css` position: absolute; left: 0; // The padding is required to show the WarningRightEdgeBadgeIcon - right: ${props.shouldDisplayWarning ? '28px' : 0}; + right: ${shouldDisplayWarning ? '28px' : 0}; z-index: 1; `} - transition: all 150ms; - ${p => - (p.showHoverState || p.showAllLabels) && + ${({ showHoverState, showAllLabels, theme }) => + (showHoverState || showAllLabels) && css` - // Using double ampersand because of https://github.com/styled-components/styled-components/issues/3678. - ${CardContainer}:hover && { - background-color: ${props => props.theme.colors.levels.surface}; - - // We use a pseudo element for the shadow with position: absolute in order to prevent - // the shadow from increasing the size of the layout and causing scrollbar flicker. - &:after { - box-shadow: ${props => props.theme.boxShadow[3]}; - border-radius: ${props => props.theme.radii[3]}px; - content: ''; - position: absolute; - top: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; - } + ${CardContainer}:hover & { + background-color: ${theme.colors.levels.surface}; + box-shadow: ${theme.boxShadow[3]}; } `} `; diff --git a/web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.tsx b/web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.tsx index 3fc5f2891e29..357d58d72dfc 100644 --- a/web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.tsx +++ b/web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.tsx @@ -360,6 +360,8 @@ const RowContainer = styled(Box)<{ &:hover { background-color: ${props => props.showHoverState ? props.theme.colors.levels.surface : 'transparent'}; + box-shadow: ${({ showHoverState, theme }) => + showHoverState ? theme.boxShadow[3] : 'none'}; ${p => p.shouldDisplayWarning && @@ -372,23 +374,6 @@ const RowContainer = styled(Box)<{ viewType: 'list', })}; `} - - ${p => - p.showHoverState && - css` - // We use a pseudo element for the shadow with position: absolute in order to prevent - // the shadow from increasing the size of the layout and causing scrollbar flicker. - &:after { - box-shadow: ${props => props.theme.boxShadow[3]}; - content: ''; - position: absolute; - top: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; - } - `} } `; From 92f6587a1063bf6ecaf2122d71b9adcee38621ee Mon Sep 17 00:00:00 2001 From: Pawel Kopiczko Date: Mon, 3 Aug 2026 12:24:42 +0100 Subject: [PATCH 104/347] Okta: Remove OktaService.UpdateOktaAssignmentStatus (#66818) --- api/client/okta/okta.go | 13 - .../teleport/okta/v1/okta_service_grpc.pb.go | 40 --- .../okta/v1/okta_service_protohybrid.pb.go | 336 ++++++------------ .../okta/v1/okta_service_protoopaque.pb.go | 332 ++++++----------- api/proto/teleport/okta/v1/okta_service.proto | 13 - lib/auth/authclient/api.go | 11 - lib/services/local/okta.go | 25 -- lib/services/local/okta_test.go | 19 - lib/services/okta.go | 4 - 9 files changed, 220 insertions(+), 573 deletions(-) diff --git a/api/client/okta/okta.go b/api/client/okta/okta.go index a7b15188058d..7f0f68c8f2e9 100644 --- a/api/client/okta/okta.go +++ b/api/client/okta/okta.go @@ -16,10 +16,8 @@ package okta import ( "context" - "time" "github.com/gravitational/trace" - "google.golang.org/protobuf/types/known/durationpb" oktapb "github.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1" "github.com/gravitational/teleport/api/types" @@ -153,17 +151,6 @@ func (c *Client) UpdateOktaAssignment(ctx context.Context, assignment types.Okta return resp, trace.Wrap(err) } -// UpdateOktaAssignmentStatus will update the status for an Okta assignment if the given time has passed -// since the last transition. -func (c *Client) UpdateOktaAssignmentStatus(ctx context.Context, name, status string, timeHasPassed time.Duration) error { - _, err := c.grpcClient.UpdateOktaAssignmentStatus(ctx, &oktapb.UpdateOktaAssignmentStatusRequest{ - Name: name, - Status: types.OktaAssignmentStatusToProto(status), - TimeHasPassed: durationpb.New(timeHasPassed), - }) - return trace.Wrap(err) -} - // DeleteOktaAssignment removes the specified Okta assignment resource. func (c *Client) DeleteOktaAssignment(ctx context.Context, name string) error { _, err := c.grpcClient.DeleteOktaAssignment(ctx, &oktapb.DeleteOktaAssignmentRequest{ diff --git a/api/gen/proto/go/teleport/okta/v1/okta_service_grpc.pb.go b/api/gen/proto/go/teleport/okta/v1/okta_service_grpc.pb.go index fa83287484cb..5c393726a2b8 100644 --- a/api/gen/proto/go/teleport/okta/v1/okta_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/okta/v1/okta_service_grpc.pb.go @@ -45,7 +45,6 @@ const ( OktaService_GetOktaAssignment_FullMethodName = "/teleport.okta.v1.OktaService/GetOktaAssignment" OktaService_CreateOktaAssignment_FullMethodName = "/teleport.okta.v1.OktaService/CreateOktaAssignment" OktaService_UpdateOktaAssignment_FullMethodName = "/teleport.okta.v1.OktaService/UpdateOktaAssignment" - OktaService_UpdateOktaAssignmentStatus_FullMethodName = "/teleport.okta.v1.OktaService/UpdateOktaAssignmentStatus" OktaService_DeleteOktaAssignment_FullMethodName = "/teleport.okta.v1.OktaService/DeleteOktaAssignment" OktaService_DeleteAllOktaAssignments_FullMethodName = "/teleport.okta.v1.OktaService/DeleteAllOktaAssignments" OktaService_ConditionalUpdateOktaAssignment_FullMethodName = "/teleport.okta.v1.OktaService/ConditionalUpdateOktaAssignment" @@ -83,8 +82,6 @@ type OktaServiceClient interface { CreateOktaAssignment(ctx context.Context, in *CreateOktaAssignmentRequest, opts ...grpc.CallOption) (*types.OktaAssignmentV1, error) // UpdateOktaAssignment updates an existing Okta assignment resource. UpdateOktaAssignment(ctx context.Context, in *UpdateOktaAssignmentRequest, opts ...grpc.CallOption) (*types.OktaAssignmentV1, error) - // UpdateOktaAssignmentStatus will update the status for an Okta assignment. - UpdateOktaAssignmentStatus(ctx context.Context, in *UpdateOktaAssignmentStatusRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // DeleteOktaAssignment removes the specified Okta assignment resource. DeleteOktaAssignment(ctx context.Context, in *DeleteOktaAssignmentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // DeleteAllOktaAssignments removes all Okta assignments. @@ -213,16 +210,6 @@ func (c *oktaServiceClient) UpdateOktaAssignment(ctx context.Context, in *Update return out, nil } -func (c *oktaServiceClient) UpdateOktaAssignmentStatus(ctx context.Context, in *UpdateOktaAssignmentStatusRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, OktaService_UpdateOktaAssignmentStatus_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *oktaServiceClient) DeleteOktaAssignment(ctx context.Context, in *DeleteOktaAssignmentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) @@ -339,8 +326,6 @@ type OktaServiceServer interface { CreateOktaAssignment(context.Context, *CreateOktaAssignmentRequest) (*types.OktaAssignmentV1, error) // UpdateOktaAssignment updates an existing Okta assignment resource. UpdateOktaAssignment(context.Context, *UpdateOktaAssignmentRequest) (*types.OktaAssignmentV1, error) - // UpdateOktaAssignmentStatus will update the status for an Okta assignment. - UpdateOktaAssignmentStatus(context.Context, *UpdateOktaAssignmentStatusRequest) (*emptypb.Empty, error) // DeleteOktaAssignment removes the specified Okta assignment resource. DeleteOktaAssignment(context.Context, *DeleteOktaAssignmentRequest) (*emptypb.Empty, error) // DeleteAllOktaAssignments removes all Okta assignments. @@ -399,9 +384,6 @@ func (UnimplementedOktaServiceServer) CreateOktaAssignment(context.Context, *Cre func (UnimplementedOktaServiceServer) UpdateOktaAssignment(context.Context, *UpdateOktaAssignmentRequest) (*types.OktaAssignmentV1, error) { return nil, status.Error(codes.Unimplemented, "method UpdateOktaAssignment not implemented") } -func (UnimplementedOktaServiceServer) UpdateOktaAssignmentStatus(context.Context, *UpdateOktaAssignmentStatusRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpdateOktaAssignmentStatus not implemented") -} func (UnimplementedOktaServiceServer) DeleteOktaAssignment(context.Context, *DeleteOktaAssignmentRequest) (*emptypb.Empty, error) { return nil, status.Error(codes.Unimplemented, "method DeleteOktaAssignment not implemented") } @@ -630,24 +612,6 @@ func _OktaService_UpdateOktaAssignment_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } -func _OktaService_UpdateOktaAssignmentStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateOktaAssignmentStatusRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(OktaServiceServer).UpdateOktaAssignmentStatus(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: OktaService_UpdateOktaAssignmentStatus_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(OktaServiceServer).UpdateOktaAssignmentStatus(ctx, req.(*UpdateOktaAssignmentStatusRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _OktaService_DeleteOktaAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteOktaAssignmentRequest) if err := dec(in); err != nil { @@ -857,10 +821,6 @@ var OktaService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateOktaAssignment", Handler: _OktaService_UpdateOktaAssignment_Handler, }, - { - MethodName: "UpdateOktaAssignmentStatus", - Handler: _OktaService_UpdateOktaAssignmentStatus_Handler, - }, { MethodName: "DeleteOktaAssignment", Handler: _OktaService_DeleteOktaAssignment_Handler, diff --git a/api/gen/proto/go/teleport/okta/v1/okta_service_protohybrid.pb.go b/api/gen/proto/go/teleport/okta/v1/okta_service_protohybrid.pb.go index 0b81e850d291..d090502fb442 100644 --- a/api/gen/proto/go/teleport/okta/v1/okta_service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/okta/v1/okta_service_protohybrid.pb.go @@ -2703,111 +2703,6 @@ func (b0 UpdateOktaAssignmentRequest_builder) Build() *UpdateOktaAssignmentReque return m0 } -// UpdateOktaAssignmentStatusRequest is a request for updating an Okta assignment status. -type UpdateOktaAssignmentStatusRequest struct { - state protoimpl.MessageState `protogen:"hybrid.v1"` - // name is the name of the Okta assignment. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // status is the status to update the assignment to. - Status types.OktaAssignmentSpecV1_OktaAssignmentStatus `protobuf:"varint,2,opt,name=status,proto3,enum=types.OktaAssignmentSpecV1_OktaAssignmentStatus" json:"status,omitempty"` - // timeHasPassed is the amount of time that must have passed since the last transition - // in order to set the new transition. - TimeHasPassed *durationpb.Duration `protobuf:"bytes,3,opt,name=time_has_passed,json=timeHasPassed,proto3" json:"time_has_passed,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateOktaAssignmentStatusRequest) Reset() { - *x = UpdateOktaAssignmentStatusRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateOktaAssignmentStatusRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateOktaAssignmentStatusRequest) ProtoMessage() {} - -func (x *UpdateOktaAssignmentStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -func (x *UpdateOktaAssignmentStatusRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *UpdateOktaAssignmentStatusRequest) GetStatus() types.OktaAssignmentSpecV1_OktaAssignmentStatus { - if x != nil { - return x.Status - } - return types.OktaAssignmentSpecV1_OktaAssignmentStatus(0) -} - -func (x *UpdateOktaAssignmentStatusRequest) GetTimeHasPassed() *durationpb.Duration { - if x != nil { - return x.TimeHasPassed - } - return nil -} - -func (x *UpdateOktaAssignmentStatusRequest) SetName(v string) { - x.Name = v -} - -func (x *UpdateOktaAssignmentStatusRequest) SetStatus(v types.OktaAssignmentSpecV1_OktaAssignmentStatus) { - x.Status = v -} - -func (x *UpdateOktaAssignmentStatusRequest) SetTimeHasPassed(v *durationpb.Duration) { - x.TimeHasPassed = v -} - -func (x *UpdateOktaAssignmentStatusRequest) HasTimeHasPassed() bool { - if x == nil { - return false - } - return x.TimeHasPassed != nil -} - -func (x *UpdateOktaAssignmentStatusRequest) ClearTimeHasPassed() { - x.TimeHasPassed = nil -} - -type UpdateOktaAssignmentStatusRequest_builder struct { - _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. - - // name is the name of the Okta assignment. - Name string - // status is the status to update the assignment to. - Status types.OktaAssignmentSpecV1_OktaAssignmentStatus - // timeHasPassed is the amount of time that must have passed since the last transition - // in order to set the new transition. - TimeHasPassed *durationpb.Duration -} - -func (b0 UpdateOktaAssignmentStatusRequest_builder) Build() *UpdateOktaAssignmentStatusRequest { - m0 := &UpdateOktaAssignmentStatusRequest{} - b, x := &b0, m0 - _, _ = b, x - x.Name = b.Name - x.Status = b.Status - x.TimeHasPassed = b.TimeHasPassed - return m0 -} - // DeleteOktaAssignmentRequest is the request for deleting an Okta assignment. type DeleteOktaAssignmentRequest struct { state protoimpl.MessageState `protogen:"hybrid.v1"` @@ -2821,7 +2716,7 @@ type DeleteOktaAssignmentRequest struct { func (x *DeleteOktaAssignmentRequest) Reset() { *x = DeleteOktaAssignmentRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2833,7 +2728,7 @@ func (x *DeleteOktaAssignmentRequest) String() string { func (*DeleteOktaAssignmentRequest) ProtoMessage() {} func (x *DeleteOktaAssignmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2893,7 +2788,7 @@ type DeleteAllOktaAssignmentsRequest struct { func (x *DeleteAllOktaAssignmentsRequest) Reset() { *x = DeleteAllOktaAssignmentsRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2905,7 +2800,7 @@ func (x *DeleteAllOktaAssignmentsRequest) String() string { func (*DeleteAllOktaAssignmentsRequest) ProtoMessage() {} func (x *DeleteAllOktaAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2941,7 +2836,7 @@ type GetAppsResponse_App struct { func (x *GetAppsResponse_App) Reset() { *x = GetAppsResponse_App{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2953,7 +2848,7 @@ func (x *GetAppsResponse_App) String() string { func (*GetAppsResponse_App) ProtoMessage() {} func (x *GetAppsResponse_App) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3017,7 +2912,7 @@ type GetGroupsResponse_Group struct { func (x *GetGroupsResponse_Group) Reset() { *x = GetGroupsResponse_Group{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[33] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3029,7 +2924,7 @@ func (x *GetGroupsResponse_Group) String() string { func (*GetGroupsResponse_Group) ProtoMessage() {} func (x *GetGroupsResponse_Group) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[33] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3207,15 +3102,11 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\x1bUpdateOktaAssignmentRequest\x127\n" + "\n" + "assignment\x18\x01 \x01(\v2\x17.types.OktaAssignmentV1R\n" + - "assignment\"\xc4\x01\n" + - "!UpdateOktaAssignmentStatusRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12H\n" + - "\x06status\x18\x02 \x01(\x0e20.types.OktaAssignmentSpecV1.OktaAssignmentStatusR\x06status\x12A\n" + - "\x0ftime_has_passed\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\rtimeHasPassed\"M\n" + + "assignment\"M\n" + "\x1bDeleteOktaAssignmentRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" + "\brevision\x18\x02 \x01(\tR\brevision\"!\n" + - "\x1fDeleteAllOktaAssignmentsRequest2\xb9\x10\n" + + "\x1fDeleteAllOktaAssignmentsRequest2\xce\x0f\n" + "\vOktaService\x12r\n" + "\x13ListOktaImportRules\x12,.teleport.okta.v1.ListOktaImportRulesRequest\x1a-.teleport.okta.v1.ListOktaImportRulesResponse\x12X\n" + "\x11GetOktaImportRule\x12*.teleport.okta.v1.GetOktaImportRuleRequest\x1a\x17.types.OktaImportRuleV1\x12^\n" + @@ -3226,8 +3117,7 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\x13ListOktaAssignments\x12,.teleport.okta.v1.ListOktaAssignmentsRequest\x1a-.teleport.okta.v1.ListOktaAssignmentsResponse\x12X\n" + "\x11GetOktaAssignment\x12*.teleport.okta.v1.GetOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12^\n" + "\x14CreateOktaAssignment\x12-.teleport.okta.v1.CreateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12^\n" + - "\x14UpdateOktaAssignment\x12-.teleport.okta.v1.UpdateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12i\n" + - "\x1aUpdateOktaAssignmentStatus\x123.teleport.okta.v1.UpdateOktaAssignmentStatusRequest\x1a\x16.google.protobuf.Empty\x12]\n" + + "\x14UpdateOktaAssignment\x12-.teleport.okta.v1.UpdateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12]\n" + "\x14DeleteOktaAssignment\x12-.teleport.okta.v1.DeleteOktaAssignmentRequest\x1a\x16.google.protobuf.Empty\x12e\n" + "\x18DeleteAllOktaAssignments\x121.teleport.okta.v1.DeleteAllOktaAssignmentsRequest\x1a\x16.google.protobuf.Empty\x12\x96\x01\n" + "\x1fConditionalUpdateOktaAssignment\x128.teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest\x1a9.teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse\x12u\n" + @@ -3238,122 +3128,116 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\aGetApps\x12 .teleport.okta.v1.GetAppsRequest\x1a!.teleport.okta.v1.GetAppsResponse\x12T\n" + "\tGetGroups\x12\".teleport.okta.v1.GetGroupsRequest\x1a#.teleport.okta.v1.GetGroupsResponseBLZJgithub.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1;oktav1b\x06proto3" -var file_teleport_okta_v1_okta_service_proto_msgTypes = make([]protoimpl.MessageInfo, 34) +var file_teleport_okta_v1_okta_service_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_teleport_okta_v1_okta_service_proto_goTypes = []any{ - (*ConditionalUpdateOktaAssignmentRequest)(nil), // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest - (*ConditionalUpdateOktaAssignmentResponse)(nil), // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse - (*UpsertOktaAssignmentResponse)(nil), // 2: teleport.okta.v1.UpsertOktaAssignmentResponse - (*UpsertOktaAssignmentRequest)(nil), // 3: teleport.okta.v1.UpsertOktaAssignmentRequest - (*GetAppsRequest)(nil), // 4: teleport.okta.v1.GetAppsRequest - (*GetAppsResponse)(nil), // 5: teleport.okta.v1.GetAppsResponse - (*GetGroupsRequest)(nil), // 6: teleport.okta.v1.GetGroupsRequest - (*GetGroupsResponse)(nil), // 7: teleport.okta.v1.GetGroupsResponse - (*CreateIntegrationRequest)(nil), // 8: teleport.okta.v1.CreateIntegrationRequest - (*UpdateIntegrationRequest)(nil), // 9: teleport.okta.v1.UpdateIntegrationRequest - (*AccessListSettings)(nil), // 10: teleport.okta.v1.AccessListSettings - (*OktaAPICredentials)(nil), // 11: teleport.okta.v1.OktaAPICredentials - (*CreateIntegrationResponse)(nil), // 12: teleport.okta.v1.CreateIntegrationResponse - (*UpdateIntegrationResponse)(nil), // 13: teleport.okta.v1.UpdateIntegrationResponse - (*ConnectorInfo)(nil), // 14: teleport.okta.v1.ConnectorInfo - (*ValidateClientCredentialsRequest)(nil), // 15: teleport.okta.v1.ValidateClientCredentialsRequest - (*ValidateClientCredentialsResponse)(nil), // 16: teleport.okta.v1.ValidateClientCredentialsResponse - (*ListOktaImportRulesRequest)(nil), // 17: teleport.okta.v1.ListOktaImportRulesRequest - (*ListOktaImportRulesResponse)(nil), // 18: teleport.okta.v1.ListOktaImportRulesResponse - (*GetOktaImportRuleRequest)(nil), // 19: teleport.okta.v1.GetOktaImportRuleRequest - (*CreateOktaImportRuleRequest)(nil), // 20: teleport.okta.v1.CreateOktaImportRuleRequest - (*UpdateOktaImportRuleRequest)(nil), // 21: teleport.okta.v1.UpdateOktaImportRuleRequest - (*DeleteOktaImportRuleRequest)(nil), // 22: teleport.okta.v1.DeleteOktaImportRuleRequest - (*DeleteAllOktaImportRulesRequest)(nil), // 23: teleport.okta.v1.DeleteAllOktaImportRulesRequest - (*ListOktaAssignmentsRequest)(nil), // 24: teleport.okta.v1.ListOktaAssignmentsRequest - (*ListOktaAssignmentsResponse)(nil), // 25: teleport.okta.v1.ListOktaAssignmentsResponse - (*GetOktaAssignmentRequest)(nil), // 26: teleport.okta.v1.GetOktaAssignmentRequest - (*CreateOktaAssignmentRequest)(nil), // 27: teleport.okta.v1.CreateOktaAssignmentRequest - (*UpdateOktaAssignmentRequest)(nil), // 28: teleport.okta.v1.UpdateOktaAssignmentRequest - (*UpdateOktaAssignmentStatusRequest)(nil), // 29: teleport.okta.v1.UpdateOktaAssignmentStatusRequest - (*DeleteOktaAssignmentRequest)(nil), // 30: teleport.okta.v1.DeleteOktaAssignmentRequest - (*DeleteAllOktaAssignmentsRequest)(nil), // 31: teleport.okta.v1.DeleteAllOktaAssignmentsRequest - (*GetAppsResponse_App)(nil), // 32: teleport.okta.v1.GetAppsResponse.App - (*GetGroupsResponse_Group)(nil), // 33: teleport.okta.v1.GetGroupsResponse.Group - (*types.OktaAssignmentV1)(nil), // 34: types.OktaAssignmentV1 - (*durationpb.Duration)(nil), // 35: google.protobuf.Duration - (*types.PluginV1)(nil), // 36: types.PluginV1 - (*types.OktaImportRuleV1)(nil), // 37: types.OktaImportRuleV1 - (types.OktaAssignmentSpecV1_OktaAssignmentStatus)(0), // 38: types.OktaAssignmentSpecV1.OktaAssignmentStatus - (*emptypb.Empty)(nil), // 39: google.protobuf.Empty + (*ConditionalUpdateOktaAssignmentRequest)(nil), // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest + (*ConditionalUpdateOktaAssignmentResponse)(nil), // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse + (*UpsertOktaAssignmentResponse)(nil), // 2: teleport.okta.v1.UpsertOktaAssignmentResponse + (*UpsertOktaAssignmentRequest)(nil), // 3: teleport.okta.v1.UpsertOktaAssignmentRequest + (*GetAppsRequest)(nil), // 4: teleport.okta.v1.GetAppsRequest + (*GetAppsResponse)(nil), // 5: teleport.okta.v1.GetAppsResponse + (*GetGroupsRequest)(nil), // 6: teleport.okta.v1.GetGroupsRequest + (*GetGroupsResponse)(nil), // 7: teleport.okta.v1.GetGroupsResponse + (*CreateIntegrationRequest)(nil), // 8: teleport.okta.v1.CreateIntegrationRequest + (*UpdateIntegrationRequest)(nil), // 9: teleport.okta.v1.UpdateIntegrationRequest + (*AccessListSettings)(nil), // 10: teleport.okta.v1.AccessListSettings + (*OktaAPICredentials)(nil), // 11: teleport.okta.v1.OktaAPICredentials + (*CreateIntegrationResponse)(nil), // 12: teleport.okta.v1.CreateIntegrationResponse + (*UpdateIntegrationResponse)(nil), // 13: teleport.okta.v1.UpdateIntegrationResponse + (*ConnectorInfo)(nil), // 14: teleport.okta.v1.ConnectorInfo + (*ValidateClientCredentialsRequest)(nil), // 15: teleport.okta.v1.ValidateClientCredentialsRequest + (*ValidateClientCredentialsResponse)(nil), // 16: teleport.okta.v1.ValidateClientCredentialsResponse + (*ListOktaImportRulesRequest)(nil), // 17: teleport.okta.v1.ListOktaImportRulesRequest + (*ListOktaImportRulesResponse)(nil), // 18: teleport.okta.v1.ListOktaImportRulesResponse + (*GetOktaImportRuleRequest)(nil), // 19: teleport.okta.v1.GetOktaImportRuleRequest + (*CreateOktaImportRuleRequest)(nil), // 20: teleport.okta.v1.CreateOktaImportRuleRequest + (*UpdateOktaImportRuleRequest)(nil), // 21: teleport.okta.v1.UpdateOktaImportRuleRequest + (*DeleteOktaImportRuleRequest)(nil), // 22: teleport.okta.v1.DeleteOktaImportRuleRequest + (*DeleteAllOktaImportRulesRequest)(nil), // 23: teleport.okta.v1.DeleteAllOktaImportRulesRequest + (*ListOktaAssignmentsRequest)(nil), // 24: teleport.okta.v1.ListOktaAssignmentsRequest + (*ListOktaAssignmentsResponse)(nil), // 25: teleport.okta.v1.ListOktaAssignmentsResponse + (*GetOktaAssignmentRequest)(nil), // 26: teleport.okta.v1.GetOktaAssignmentRequest + (*CreateOktaAssignmentRequest)(nil), // 27: teleport.okta.v1.CreateOktaAssignmentRequest + (*UpdateOktaAssignmentRequest)(nil), // 28: teleport.okta.v1.UpdateOktaAssignmentRequest + (*DeleteOktaAssignmentRequest)(nil), // 29: teleport.okta.v1.DeleteOktaAssignmentRequest + (*DeleteAllOktaAssignmentsRequest)(nil), // 30: teleport.okta.v1.DeleteAllOktaAssignmentsRequest + (*GetAppsResponse_App)(nil), // 31: teleport.okta.v1.GetAppsResponse.App + (*GetGroupsResponse_Group)(nil), // 32: teleport.okta.v1.GetGroupsResponse.Group + (*types.OktaAssignmentV1)(nil), // 33: types.OktaAssignmentV1 + (*durationpb.Duration)(nil), // 34: google.protobuf.Duration + (*types.PluginV1)(nil), // 35: types.PluginV1 + (*types.OktaImportRuleV1)(nil), // 36: types.OktaImportRuleV1 + (*emptypb.Empty)(nil), // 37: google.protobuf.Empty } var file_teleport_okta_v1_okta_service_proto_depIdxs = []int32{ - 34, // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 34, // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 - 34, // 2: teleport.okta.v1.UpsertOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 - 34, // 3: teleport.okta.v1.UpsertOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 + 33, // 2: teleport.okta.v1.UpsertOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 + 33, // 3: teleport.okta.v1.UpsertOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 11, // 4: teleport.okta.v1.GetAppsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 32, // 5: teleport.okta.v1.GetAppsResponse.apps:type_name -> teleport.okta.v1.GetAppsResponse.App + 31, // 5: teleport.okta.v1.GetAppsResponse.apps:type_name -> teleport.okta.v1.GetAppsResponse.App 11, // 6: teleport.okta.v1.GetGroupsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 33, // 7: teleport.okta.v1.GetGroupsResponse.groups:type_name -> teleport.okta.v1.GetGroupsResponse.Group + 32, // 7: teleport.okta.v1.GetGroupsResponse.groups:type_name -> teleport.okta.v1.GetGroupsResponse.Group 11, // 8: teleport.okta.v1.CreateIntegrationRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials 10, // 9: teleport.okta.v1.CreateIntegrationRequest.access_list_settings:type_name -> teleport.okta.v1.AccessListSettings - 35, // 10: teleport.okta.v1.CreateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration + 34, // 10: teleport.okta.v1.CreateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration 11, // 11: teleport.okta.v1.UpdateIntegrationRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials 10, // 12: teleport.okta.v1.UpdateIntegrationRequest.access_list_settings:type_name -> teleport.okta.v1.AccessListSettings - 35, // 13: teleport.okta.v1.UpdateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration - 36, // 14: teleport.okta.v1.CreateIntegrationResponse.plugin:type_name -> types.PluginV1 + 34, // 13: teleport.okta.v1.UpdateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration + 35, // 14: teleport.okta.v1.CreateIntegrationResponse.plugin:type_name -> types.PluginV1 14, // 15: teleport.okta.v1.CreateIntegrationResponse.connector_info:type_name -> teleport.okta.v1.ConnectorInfo - 36, // 16: teleport.okta.v1.UpdateIntegrationResponse.plugin:type_name -> types.PluginV1 + 35, // 16: teleport.okta.v1.UpdateIntegrationResponse.plugin:type_name -> types.PluginV1 14, // 17: teleport.okta.v1.UpdateIntegrationResponse.connector_info:type_name -> teleport.okta.v1.ConnectorInfo 11, // 18: teleport.okta.v1.ValidateClientCredentialsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 37, // 19: teleport.okta.v1.ListOktaImportRulesResponse.import_rules:type_name -> types.OktaImportRuleV1 - 37, // 20: teleport.okta.v1.CreateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 - 37, // 21: teleport.okta.v1.UpdateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 - 34, // 22: teleport.okta.v1.ListOktaAssignmentsResponse.assignments:type_name -> types.OktaAssignmentV1 - 34, // 23: teleport.okta.v1.CreateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 34, // 24: teleport.okta.v1.UpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 38, // 25: teleport.okta.v1.UpdateOktaAssignmentStatusRequest.status:type_name -> types.OktaAssignmentSpecV1.OktaAssignmentStatus - 35, // 26: teleport.okta.v1.UpdateOktaAssignmentStatusRequest.time_has_passed:type_name -> google.protobuf.Duration - 17, // 27: teleport.okta.v1.OktaService.ListOktaImportRules:input_type -> teleport.okta.v1.ListOktaImportRulesRequest - 19, // 28: teleport.okta.v1.OktaService.GetOktaImportRule:input_type -> teleport.okta.v1.GetOktaImportRuleRequest - 20, // 29: teleport.okta.v1.OktaService.CreateOktaImportRule:input_type -> teleport.okta.v1.CreateOktaImportRuleRequest - 21, // 30: teleport.okta.v1.OktaService.UpdateOktaImportRule:input_type -> teleport.okta.v1.UpdateOktaImportRuleRequest - 22, // 31: teleport.okta.v1.OktaService.DeleteOktaImportRule:input_type -> teleport.okta.v1.DeleteOktaImportRuleRequest - 23, // 32: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:input_type -> teleport.okta.v1.DeleteAllOktaImportRulesRequest - 24, // 33: teleport.okta.v1.OktaService.ListOktaAssignments:input_type -> teleport.okta.v1.ListOktaAssignmentsRequest - 26, // 34: teleport.okta.v1.OktaService.GetOktaAssignment:input_type -> teleport.okta.v1.GetOktaAssignmentRequest - 27, // 35: teleport.okta.v1.OktaService.CreateOktaAssignment:input_type -> teleport.okta.v1.CreateOktaAssignmentRequest - 28, // 36: teleport.okta.v1.OktaService.UpdateOktaAssignment:input_type -> teleport.okta.v1.UpdateOktaAssignmentRequest - 29, // 37: teleport.okta.v1.OktaService.UpdateOktaAssignmentStatus:input_type -> teleport.okta.v1.UpdateOktaAssignmentStatusRequest - 30, // 38: teleport.okta.v1.OktaService.DeleteOktaAssignment:input_type -> teleport.okta.v1.DeleteOktaAssignmentRequest - 31, // 39: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:input_type -> teleport.okta.v1.DeleteAllOktaAssignmentsRequest - 0, // 40: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:input_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest - 3, // 41: teleport.okta.v1.OktaService.UpsertOktaAssignment:input_type -> teleport.okta.v1.UpsertOktaAssignmentRequest - 15, // 42: teleport.okta.v1.OktaService.ValidateClientCredentials:input_type -> teleport.okta.v1.ValidateClientCredentialsRequest - 8, // 43: teleport.okta.v1.OktaService.CreateIntegration:input_type -> teleport.okta.v1.CreateIntegrationRequest - 9, // 44: teleport.okta.v1.OktaService.UpdateIntegration:input_type -> teleport.okta.v1.UpdateIntegrationRequest - 4, // 45: teleport.okta.v1.OktaService.GetApps:input_type -> teleport.okta.v1.GetAppsRequest - 6, // 46: teleport.okta.v1.OktaService.GetGroups:input_type -> teleport.okta.v1.GetGroupsRequest - 18, // 47: teleport.okta.v1.OktaService.ListOktaImportRules:output_type -> teleport.okta.v1.ListOktaImportRulesResponse - 37, // 48: teleport.okta.v1.OktaService.GetOktaImportRule:output_type -> types.OktaImportRuleV1 - 37, // 49: teleport.okta.v1.OktaService.CreateOktaImportRule:output_type -> types.OktaImportRuleV1 - 37, // 50: teleport.okta.v1.OktaService.UpdateOktaImportRule:output_type -> types.OktaImportRuleV1 - 39, // 51: teleport.okta.v1.OktaService.DeleteOktaImportRule:output_type -> google.protobuf.Empty - 39, // 52: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:output_type -> google.protobuf.Empty - 25, // 53: teleport.okta.v1.OktaService.ListOktaAssignments:output_type -> teleport.okta.v1.ListOktaAssignmentsResponse - 34, // 54: teleport.okta.v1.OktaService.GetOktaAssignment:output_type -> types.OktaAssignmentV1 - 34, // 55: teleport.okta.v1.OktaService.CreateOktaAssignment:output_type -> types.OktaAssignmentV1 - 34, // 56: teleport.okta.v1.OktaService.UpdateOktaAssignment:output_type -> types.OktaAssignmentV1 - 39, // 57: teleport.okta.v1.OktaService.UpdateOktaAssignmentStatus:output_type -> google.protobuf.Empty - 39, // 58: teleport.okta.v1.OktaService.DeleteOktaAssignment:output_type -> google.protobuf.Empty - 39, // 59: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:output_type -> google.protobuf.Empty - 1, // 60: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:output_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse - 2, // 61: teleport.okta.v1.OktaService.UpsertOktaAssignment:output_type -> teleport.okta.v1.UpsertOktaAssignmentResponse - 16, // 62: teleport.okta.v1.OktaService.ValidateClientCredentials:output_type -> teleport.okta.v1.ValidateClientCredentialsResponse - 12, // 63: teleport.okta.v1.OktaService.CreateIntegration:output_type -> teleport.okta.v1.CreateIntegrationResponse - 13, // 64: teleport.okta.v1.OktaService.UpdateIntegration:output_type -> teleport.okta.v1.UpdateIntegrationResponse - 5, // 65: teleport.okta.v1.OktaService.GetApps:output_type -> teleport.okta.v1.GetAppsResponse - 7, // 66: teleport.okta.v1.OktaService.GetGroups:output_type -> teleport.okta.v1.GetGroupsResponse - 47, // [47:67] is the sub-list for method output_type - 27, // [27:47] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 36, // 19: teleport.okta.v1.ListOktaImportRulesResponse.import_rules:type_name -> types.OktaImportRuleV1 + 36, // 20: teleport.okta.v1.CreateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 + 36, // 21: teleport.okta.v1.UpdateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 + 33, // 22: teleport.okta.v1.ListOktaAssignmentsResponse.assignments:type_name -> types.OktaAssignmentV1 + 33, // 23: teleport.okta.v1.CreateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 24: teleport.okta.v1.UpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 17, // 25: teleport.okta.v1.OktaService.ListOktaImportRules:input_type -> teleport.okta.v1.ListOktaImportRulesRequest + 19, // 26: teleport.okta.v1.OktaService.GetOktaImportRule:input_type -> teleport.okta.v1.GetOktaImportRuleRequest + 20, // 27: teleport.okta.v1.OktaService.CreateOktaImportRule:input_type -> teleport.okta.v1.CreateOktaImportRuleRequest + 21, // 28: teleport.okta.v1.OktaService.UpdateOktaImportRule:input_type -> teleport.okta.v1.UpdateOktaImportRuleRequest + 22, // 29: teleport.okta.v1.OktaService.DeleteOktaImportRule:input_type -> teleport.okta.v1.DeleteOktaImportRuleRequest + 23, // 30: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:input_type -> teleport.okta.v1.DeleteAllOktaImportRulesRequest + 24, // 31: teleport.okta.v1.OktaService.ListOktaAssignments:input_type -> teleport.okta.v1.ListOktaAssignmentsRequest + 26, // 32: teleport.okta.v1.OktaService.GetOktaAssignment:input_type -> teleport.okta.v1.GetOktaAssignmentRequest + 27, // 33: teleport.okta.v1.OktaService.CreateOktaAssignment:input_type -> teleport.okta.v1.CreateOktaAssignmentRequest + 28, // 34: teleport.okta.v1.OktaService.UpdateOktaAssignment:input_type -> teleport.okta.v1.UpdateOktaAssignmentRequest + 29, // 35: teleport.okta.v1.OktaService.DeleteOktaAssignment:input_type -> teleport.okta.v1.DeleteOktaAssignmentRequest + 30, // 36: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:input_type -> teleport.okta.v1.DeleteAllOktaAssignmentsRequest + 0, // 37: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:input_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest + 3, // 38: teleport.okta.v1.OktaService.UpsertOktaAssignment:input_type -> teleport.okta.v1.UpsertOktaAssignmentRequest + 15, // 39: teleport.okta.v1.OktaService.ValidateClientCredentials:input_type -> teleport.okta.v1.ValidateClientCredentialsRequest + 8, // 40: teleport.okta.v1.OktaService.CreateIntegration:input_type -> teleport.okta.v1.CreateIntegrationRequest + 9, // 41: teleport.okta.v1.OktaService.UpdateIntegration:input_type -> teleport.okta.v1.UpdateIntegrationRequest + 4, // 42: teleport.okta.v1.OktaService.GetApps:input_type -> teleport.okta.v1.GetAppsRequest + 6, // 43: teleport.okta.v1.OktaService.GetGroups:input_type -> teleport.okta.v1.GetGroupsRequest + 18, // 44: teleport.okta.v1.OktaService.ListOktaImportRules:output_type -> teleport.okta.v1.ListOktaImportRulesResponse + 36, // 45: teleport.okta.v1.OktaService.GetOktaImportRule:output_type -> types.OktaImportRuleV1 + 36, // 46: teleport.okta.v1.OktaService.CreateOktaImportRule:output_type -> types.OktaImportRuleV1 + 36, // 47: teleport.okta.v1.OktaService.UpdateOktaImportRule:output_type -> types.OktaImportRuleV1 + 37, // 48: teleport.okta.v1.OktaService.DeleteOktaImportRule:output_type -> google.protobuf.Empty + 37, // 49: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:output_type -> google.protobuf.Empty + 25, // 50: teleport.okta.v1.OktaService.ListOktaAssignments:output_type -> teleport.okta.v1.ListOktaAssignmentsResponse + 33, // 51: teleport.okta.v1.OktaService.GetOktaAssignment:output_type -> types.OktaAssignmentV1 + 33, // 52: teleport.okta.v1.OktaService.CreateOktaAssignment:output_type -> types.OktaAssignmentV1 + 33, // 53: teleport.okta.v1.OktaService.UpdateOktaAssignment:output_type -> types.OktaAssignmentV1 + 37, // 54: teleport.okta.v1.OktaService.DeleteOktaAssignment:output_type -> google.protobuf.Empty + 37, // 55: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:output_type -> google.protobuf.Empty + 1, // 56: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:output_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse + 2, // 57: teleport.okta.v1.OktaService.UpsertOktaAssignment:output_type -> teleport.okta.v1.UpsertOktaAssignmentResponse + 16, // 58: teleport.okta.v1.OktaService.ValidateClientCredentials:output_type -> teleport.okta.v1.ValidateClientCredentialsResponse + 12, // 59: teleport.okta.v1.OktaService.CreateIntegration:output_type -> teleport.okta.v1.CreateIntegrationResponse + 13, // 60: teleport.okta.v1.OktaService.UpdateIntegration:output_type -> teleport.okta.v1.UpdateIntegrationResponse + 5, // 61: teleport.okta.v1.OktaService.GetApps:output_type -> teleport.okta.v1.GetAppsResponse + 7, // 62: teleport.okta.v1.OktaService.GetGroups:output_type -> teleport.okta.v1.GetGroupsResponse + 44, // [44:63] is the sub-list for method output_type + 25, // [25:44] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_teleport_okta_v1_okta_service_proto_init() } @@ -3371,7 +3255,7 @@ func file_teleport_okta_v1_okta_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_okta_v1_okta_service_proto_rawDesc), len(file_teleport_okta_v1_okta_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 34, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/okta/v1/okta_service_protoopaque.pb.go b/api/gen/proto/go/teleport/okta/v1/okta_service_protoopaque.pb.go index 434963951542..8868290187fd 100644 --- a/api/gen/proto/go/teleport/okta/v1/okta_service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/okta/v1/okta_service_protoopaque.pb.go @@ -2630,107 +2630,6 @@ func (b0 UpdateOktaAssignmentRequest_builder) Build() *UpdateOktaAssignmentReque return m0 } -// UpdateOktaAssignmentStatusRequest is a request for updating an Okta assignment status. -type UpdateOktaAssignmentStatusRequest struct { - state protoimpl.MessageState `protogen:"opaque.v1"` - xxx_hidden_Name string `protobuf:"bytes,1,opt,name=name,proto3"` - xxx_hidden_Status types.OktaAssignmentSpecV1_OktaAssignmentStatus `protobuf:"varint,2,opt,name=status,proto3,enum=types.OktaAssignmentSpecV1_OktaAssignmentStatus"` - xxx_hidden_TimeHasPassed *durationpb.Duration `protobuf:"bytes,3,opt,name=time_has_passed,json=timeHasPassed,proto3"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateOktaAssignmentStatusRequest) Reset() { - *x = UpdateOktaAssignmentStatusRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateOktaAssignmentStatusRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateOktaAssignmentStatusRequest) ProtoMessage() {} - -func (x *UpdateOktaAssignmentStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -func (x *UpdateOktaAssignmentStatusRequest) GetName() string { - if x != nil { - return x.xxx_hidden_Name - } - return "" -} - -func (x *UpdateOktaAssignmentStatusRequest) GetStatus() types.OktaAssignmentSpecV1_OktaAssignmentStatus { - if x != nil { - return x.xxx_hidden_Status - } - return types.OktaAssignmentSpecV1_OktaAssignmentStatus(0) -} - -func (x *UpdateOktaAssignmentStatusRequest) GetTimeHasPassed() *durationpb.Duration { - if x != nil { - return x.xxx_hidden_TimeHasPassed - } - return nil -} - -func (x *UpdateOktaAssignmentStatusRequest) SetName(v string) { - x.xxx_hidden_Name = v -} - -func (x *UpdateOktaAssignmentStatusRequest) SetStatus(v types.OktaAssignmentSpecV1_OktaAssignmentStatus) { - x.xxx_hidden_Status = v -} - -func (x *UpdateOktaAssignmentStatusRequest) SetTimeHasPassed(v *durationpb.Duration) { - x.xxx_hidden_TimeHasPassed = v -} - -func (x *UpdateOktaAssignmentStatusRequest) HasTimeHasPassed() bool { - if x == nil { - return false - } - return x.xxx_hidden_TimeHasPassed != nil -} - -func (x *UpdateOktaAssignmentStatusRequest) ClearTimeHasPassed() { - x.xxx_hidden_TimeHasPassed = nil -} - -type UpdateOktaAssignmentStatusRequest_builder struct { - _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. - - // name is the name of the Okta assignment. - Name string - // status is the status to update the assignment to. - Status types.OktaAssignmentSpecV1_OktaAssignmentStatus - // timeHasPassed is the amount of time that must have passed since the last transition - // in order to set the new transition. - TimeHasPassed *durationpb.Duration -} - -func (b0 UpdateOktaAssignmentStatusRequest_builder) Build() *UpdateOktaAssignmentStatusRequest { - m0 := &UpdateOktaAssignmentStatusRequest{} - b, x := &b0, m0 - _, _ = b, x - x.xxx_hidden_Name = b.Name - x.xxx_hidden_Status = b.Status - x.xxx_hidden_TimeHasPassed = b.TimeHasPassed - return m0 -} - // DeleteOktaAssignmentRequest is the request for deleting an Okta assignment. type DeleteOktaAssignmentRequest struct { state protoimpl.MessageState `protogen:"opaque.v1"` @@ -2742,7 +2641,7 @@ type DeleteOktaAssignmentRequest struct { func (x *DeleteOktaAssignmentRequest) Reset() { *x = DeleteOktaAssignmentRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2754,7 +2653,7 @@ func (x *DeleteOktaAssignmentRequest) String() string { func (*DeleteOktaAssignmentRequest) ProtoMessage() {} func (x *DeleteOktaAssignmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2814,7 +2713,7 @@ type DeleteAllOktaAssignmentsRequest struct { func (x *DeleteAllOktaAssignmentsRequest) Reset() { *x = DeleteAllOktaAssignmentsRequest{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2826,7 +2725,7 @@ func (x *DeleteAllOktaAssignmentsRequest) String() string { func (*DeleteAllOktaAssignmentsRequest) ProtoMessage() {} func (x *DeleteAllOktaAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2860,7 +2759,7 @@ type GetAppsResponse_App struct { func (x *GetAppsResponse_App) Reset() { *x = GetAppsResponse_App{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2872,7 +2771,7 @@ func (x *GetAppsResponse_App) String() string { func (*GetAppsResponse_App) ProtoMessage() {} func (x *GetAppsResponse_App) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2934,7 +2833,7 @@ type GetGroupsResponse_Group struct { func (x *GetGroupsResponse_Group) Reset() { *x = GetGroupsResponse_Group{} - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[33] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2946,7 +2845,7 @@ func (x *GetGroupsResponse_Group) String() string { func (*GetGroupsResponse_Group) ProtoMessage() {} func (x *GetGroupsResponse_Group) ProtoReflect() protoreflect.Message { - mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[33] + mi := &file_teleport_okta_v1_okta_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3124,15 +3023,11 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\x1bUpdateOktaAssignmentRequest\x127\n" + "\n" + "assignment\x18\x01 \x01(\v2\x17.types.OktaAssignmentV1R\n" + - "assignment\"\xc4\x01\n" + - "!UpdateOktaAssignmentStatusRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12H\n" + - "\x06status\x18\x02 \x01(\x0e20.types.OktaAssignmentSpecV1.OktaAssignmentStatusR\x06status\x12A\n" + - "\x0ftime_has_passed\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\rtimeHasPassed\"M\n" + + "assignment\"M\n" + "\x1bDeleteOktaAssignmentRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" + "\brevision\x18\x02 \x01(\tR\brevision\"!\n" + - "\x1fDeleteAllOktaAssignmentsRequest2\xb9\x10\n" + + "\x1fDeleteAllOktaAssignmentsRequest2\xce\x0f\n" + "\vOktaService\x12r\n" + "\x13ListOktaImportRules\x12,.teleport.okta.v1.ListOktaImportRulesRequest\x1a-.teleport.okta.v1.ListOktaImportRulesResponse\x12X\n" + "\x11GetOktaImportRule\x12*.teleport.okta.v1.GetOktaImportRuleRequest\x1a\x17.types.OktaImportRuleV1\x12^\n" + @@ -3143,8 +3038,7 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\x13ListOktaAssignments\x12,.teleport.okta.v1.ListOktaAssignmentsRequest\x1a-.teleport.okta.v1.ListOktaAssignmentsResponse\x12X\n" + "\x11GetOktaAssignment\x12*.teleport.okta.v1.GetOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12^\n" + "\x14CreateOktaAssignment\x12-.teleport.okta.v1.CreateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12^\n" + - "\x14UpdateOktaAssignment\x12-.teleport.okta.v1.UpdateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12i\n" + - "\x1aUpdateOktaAssignmentStatus\x123.teleport.okta.v1.UpdateOktaAssignmentStatusRequest\x1a\x16.google.protobuf.Empty\x12]\n" + + "\x14UpdateOktaAssignment\x12-.teleport.okta.v1.UpdateOktaAssignmentRequest\x1a\x17.types.OktaAssignmentV1\x12]\n" + "\x14DeleteOktaAssignment\x12-.teleport.okta.v1.DeleteOktaAssignmentRequest\x1a\x16.google.protobuf.Empty\x12e\n" + "\x18DeleteAllOktaAssignments\x121.teleport.okta.v1.DeleteAllOktaAssignmentsRequest\x1a\x16.google.protobuf.Empty\x12\x96\x01\n" + "\x1fConditionalUpdateOktaAssignment\x128.teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest\x1a9.teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse\x12u\n" + @@ -3155,122 +3049,116 @@ const file_teleport_okta_v1_okta_service_proto_rawDesc = "" + "\aGetApps\x12 .teleport.okta.v1.GetAppsRequest\x1a!.teleport.okta.v1.GetAppsResponse\x12T\n" + "\tGetGroups\x12\".teleport.okta.v1.GetGroupsRequest\x1a#.teleport.okta.v1.GetGroupsResponseBLZJgithub.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1;oktav1b\x06proto3" -var file_teleport_okta_v1_okta_service_proto_msgTypes = make([]protoimpl.MessageInfo, 34) +var file_teleport_okta_v1_okta_service_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_teleport_okta_v1_okta_service_proto_goTypes = []any{ - (*ConditionalUpdateOktaAssignmentRequest)(nil), // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest - (*ConditionalUpdateOktaAssignmentResponse)(nil), // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse - (*UpsertOktaAssignmentResponse)(nil), // 2: teleport.okta.v1.UpsertOktaAssignmentResponse - (*UpsertOktaAssignmentRequest)(nil), // 3: teleport.okta.v1.UpsertOktaAssignmentRequest - (*GetAppsRequest)(nil), // 4: teleport.okta.v1.GetAppsRequest - (*GetAppsResponse)(nil), // 5: teleport.okta.v1.GetAppsResponse - (*GetGroupsRequest)(nil), // 6: teleport.okta.v1.GetGroupsRequest - (*GetGroupsResponse)(nil), // 7: teleport.okta.v1.GetGroupsResponse - (*CreateIntegrationRequest)(nil), // 8: teleport.okta.v1.CreateIntegrationRequest - (*UpdateIntegrationRequest)(nil), // 9: teleport.okta.v1.UpdateIntegrationRequest - (*AccessListSettings)(nil), // 10: teleport.okta.v1.AccessListSettings - (*OktaAPICredentials)(nil), // 11: teleport.okta.v1.OktaAPICredentials - (*CreateIntegrationResponse)(nil), // 12: teleport.okta.v1.CreateIntegrationResponse - (*UpdateIntegrationResponse)(nil), // 13: teleport.okta.v1.UpdateIntegrationResponse - (*ConnectorInfo)(nil), // 14: teleport.okta.v1.ConnectorInfo - (*ValidateClientCredentialsRequest)(nil), // 15: teleport.okta.v1.ValidateClientCredentialsRequest - (*ValidateClientCredentialsResponse)(nil), // 16: teleport.okta.v1.ValidateClientCredentialsResponse - (*ListOktaImportRulesRequest)(nil), // 17: teleport.okta.v1.ListOktaImportRulesRequest - (*ListOktaImportRulesResponse)(nil), // 18: teleport.okta.v1.ListOktaImportRulesResponse - (*GetOktaImportRuleRequest)(nil), // 19: teleport.okta.v1.GetOktaImportRuleRequest - (*CreateOktaImportRuleRequest)(nil), // 20: teleport.okta.v1.CreateOktaImportRuleRequest - (*UpdateOktaImportRuleRequest)(nil), // 21: teleport.okta.v1.UpdateOktaImportRuleRequest - (*DeleteOktaImportRuleRequest)(nil), // 22: teleport.okta.v1.DeleteOktaImportRuleRequest - (*DeleteAllOktaImportRulesRequest)(nil), // 23: teleport.okta.v1.DeleteAllOktaImportRulesRequest - (*ListOktaAssignmentsRequest)(nil), // 24: teleport.okta.v1.ListOktaAssignmentsRequest - (*ListOktaAssignmentsResponse)(nil), // 25: teleport.okta.v1.ListOktaAssignmentsResponse - (*GetOktaAssignmentRequest)(nil), // 26: teleport.okta.v1.GetOktaAssignmentRequest - (*CreateOktaAssignmentRequest)(nil), // 27: teleport.okta.v1.CreateOktaAssignmentRequest - (*UpdateOktaAssignmentRequest)(nil), // 28: teleport.okta.v1.UpdateOktaAssignmentRequest - (*UpdateOktaAssignmentStatusRequest)(nil), // 29: teleport.okta.v1.UpdateOktaAssignmentStatusRequest - (*DeleteOktaAssignmentRequest)(nil), // 30: teleport.okta.v1.DeleteOktaAssignmentRequest - (*DeleteAllOktaAssignmentsRequest)(nil), // 31: teleport.okta.v1.DeleteAllOktaAssignmentsRequest - (*GetAppsResponse_App)(nil), // 32: teleport.okta.v1.GetAppsResponse.App - (*GetGroupsResponse_Group)(nil), // 33: teleport.okta.v1.GetGroupsResponse.Group - (*types.OktaAssignmentV1)(nil), // 34: types.OktaAssignmentV1 - (*durationpb.Duration)(nil), // 35: google.protobuf.Duration - (*types.PluginV1)(nil), // 36: types.PluginV1 - (*types.OktaImportRuleV1)(nil), // 37: types.OktaImportRuleV1 - (types.OktaAssignmentSpecV1_OktaAssignmentStatus)(0), // 38: types.OktaAssignmentSpecV1.OktaAssignmentStatus - (*emptypb.Empty)(nil), // 39: google.protobuf.Empty + (*ConditionalUpdateOktaAssignmentRequest)(nil), // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest + (*ConditionalUpdateOktaAssignmentResponse)(nil), // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse + (*UpsertOktaAssignmentResponse)(nil), // 2: teleport.okta.v1.UpsertOktaAssignmentResponse + (*UpsertOktaAssignmentRequest)(nil), // 3: teleport.okta.v1.UpsertOktaAssignmentRequest + (*GetAppsRequest)(nil), // 4: teleport.okta.v1.GetAppsRequest + (*GetAppsResponse)(nil), // 5: teleport.okta.v1.GetAppsResponse + (*GetGroupsRequest)(nil), // 6: teleport.okta.v1.GetGroupsRequest + (*GetGroupsResponse)(nil), // 7: teleport.okta.v1.GetGroupsResponse + (*CreateIntegrationRequest)(nil), // 8: teleport.okta.v1.CreateIntegrationRequest + (*UpdateIntegrationRequest)(nil), // 9: teleport.okta.v1.UpdateIntegrationRequest + (*AccessListSettings)(nil), // 10: teleport.okta.v1.AccessListSettings + (*OktaAPICredentials)(nil), // 11: teleport.okta.v1.OktaAPICredentials + (*CreateIntegrationResponse)(nil), // 12: teleport.okta.v1.CreateIntegrationResponse + (*UpdateIntegrationResponse)(nil), // 13: teleport.okta.v1.UpdateIntegrationResponse + (*ConnectorInfo)(nil), // 14: teleport.okta.v1.ConnectorInfo + (*ValidateClientCredentialsRequest)(nil), // 15: teleport.okta.v1.ValidateClientCredentialsRequest + (*ValidateClientCredentialsResponse)(nil), // 16: teleport.okta.v1.ValidateClientCredentialsResponse + (*ListOktaImportRulesRequest)(nil), // 17: teleport.okta.v1.ListOktaImportRulesRequest + (*ListOktaImportRulesResponse)(nil), // 18: teleport.okta.v1.ListOktaImportRulesResponse + (*GetOktaImportRuleRequest)(nil), // 19: teleport.okta.v1.GetOktaImportRuleRequest + (*CreateOktaImportRuleRequest)(nil), // 20: teleport.okta.v1.CreateOktaImportRuleRequest + (*UpdateOktaImportRuleRequest)(nil), // 21: teleport.okta.v1.UpdateOktaImportRuleRequest + (*DeleteOktaImportRuleRequest)(nil), // 22: teleport.okta.v1.DeleteOktaImportRuleRequest + (*DeleteAllOktaImportRulesRequest)(nil), // 23: teleport.okta.v1.DeleteAllOktaImportRulesRequest + (*ListOktaAssignmentsRequest)(nil), // 24: teleport.okta.v1.ListOktaAssignmentsRequest + (*ListOktaAssignmentsResponse)(nil), // 25: teleport.okta.v1.ListOktaAssignmentsResponse + (*GetOktaAssignmentRequest)(nil), // 26: teleport.okta.v1.GetOktaAssignmentRequest + (*CreateOktaAssignmentRequest)(nil), // 27: teleport.okta.v1.CreateOktaAssignmentRequest + (*UpdateOktaAssignmentRequest)(nil), // 28: teleport.okta.v1.UpdateOktaAssignmentRequest + (*DeleteOktaAssignmentRequest)(nil), // 29: teleport.okta.v1.DeleteOktaAssignmentRequest + (*DeleteAllOktaAssignmentsRequest)(nil), // 30: teleport.okta.v1.DeleteAllOktaAssignmentsRequest + (*GetAppsResponse_App)(nil), // 31: teleport.okta.v1.GetAppsResponse.App + (*GetGroupsResponse_Group)(nil), // 32: teleport.okta.v1.GetGroupsResponse.Group + (*types.OktaAssignmentV1)(nil), // 33: types.OktaAssignmentV1 + (*durationpb.Duration)(nil), // 34: google.protobuf.Duration + (*types.PluginV1)(nil), // 35: types.PluginV1 + (*types.OktaImportRuleV1)(nil), // 36: types.OktaImportRuleV1 + (*emptypb.Empty)(nil), // 37: google.protobuf.Empty } var file_teleport_okta_v1_okta_service_proto_depIdxs = []int32{ - 34, // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 34, // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 - 34, // 2: teleport.okta.v1.UpsertOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 - 34, // 3: teleport.okta.v1.UpsertOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 0: teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 1: teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 + 33, // 2: teleport.okta.v1.UpsertOktaAssignmentResponse.assignment:type_name -> types.OktaAssignmentV1 + 33, // 3: teleport.okta.v1.UpsertOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 11, // 4: teleport.okta.v1.GetAppsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 32, // 5: teleport.okta.v1.GetAppsResponse.apps:type_name -> teleport.okta.v1.GetAppsResponse.App + 31, // 5: teleport.okta.v1.GetAppsResponse.apps:type_name -> teleport.okta.v1.GetAppsResponse.App 11, // 6: teleport.okta.v1.GetGroupsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 33, // 7: teleport.okta.v1.GetGroupsResponse.groups:type_name -> teleport.okta.v1.GetGroupsResponse.Group + 32, // 7: teleport.okta.v1.GetGroupsResponse.groups:type_name -> teleport.okta.v1.GetGroupsResponse.Group 11, // 8: teleport.okta.v1.CreateIntegrationRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials 10, // 9: teleport.okta.v1.CreateIntegrationRequest.access_list_settings:type_name -> teleport.okta.v1.AccessListSettings - 35, // 10: teleport.okta.v1.CreateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration + 34, // 10: teleport.okta.v1.CreateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration 11, // 11: teleport.okta.v1.UpdateIntegrationRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials 10, // 12: teleport.okta.v1.UpdateIntegrationRequest.access_list_settings:type_name -> teleport.okta.v1.AccessListSettings - 35, // 13: teleport.okta.v1.UpdateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration - 36, // 14: teleport.okta.v1.CreateIntegrationResponse.plugin:type_name -> types.PluginV1 + 34, // 13: teleport.okta.v1.UpdateIntegrationRequest.time_between_imports:type_name -> google.protobuf.Duration + 35, // 14: teleport.okta.v1.CreateIntegrationResponse.plugin:type_name -> types.PluginV1 14, // 15: teleport.okta.v1.CreateIntegrationResponse.connector_info:type_name -> teleport.okta.v1.ConnectorInfo - 36, // 16: teleport.okta.v1.UpdateIntegrationResponse.plugin:type_name -> types.PluginV1 + 35, // 16: teleport.okta.v1.UpdateIntegrationResponse.plugin:type_name -> types.PluginV1 14, // 17: teleport.okta.v1.UpdateIntegrationResponse.connector_info:type_name -> teleport.okta.v1.ConnectorInfo 11, // 18: teleport.okta.v1.ValidateClientCredentialsRequest.api_credentials:type_name -> teleport.okta.v1.OktaAPICredentials - 37, // 19: teleport.okta.v1.ListOktaImportRulesResponse.import_rules:type_name -> types.OktaImportRuleV1 - 37, // 20: teleport.okta.v1.CreateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 - 37, // 21: teleport.okta.v1.UpdateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 - 34, // 22: teleport.okta.v1.ListOktaAssignmentsResponse.assignments:type_name -> types.OktaAssignmentV1 - 34, // 23: teleport.okta.v1.CreateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 34, // 24: teleport.okta.v1.UpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 - 38, // 25: teleport.okta.v1.UpdateOktaAssignmentStatusRequest.status:type_name -> types.OktaAssignmentSpecV1.OktaAssignmentStatus - 35, // 26: teleport.okta.v1.UpdateOktaAssignmentStatusRequest.time_has_passed:type_name -> google.protobuf.Duration - 17, // 27: teleport.okta.v1.OktaService.ListOktaImportRules:input_type -> teleport.okta.v1.ListOktaImportRulesRequest - 19, // 28: teleport.okta.v1.OktaService.GetOktaImportRule:input_type -> teleport.okta.v1.GetOktaImportRuleRequest - 20, // 29: teleport.okta.v1.OktaService.CreateOktaImportRule:input_type -> teleport.okta.v1.CreateOktaImportRuleRequest - 21, // 30: teleport.okta.v1.OktaService.UpdateOktaImportRule:input_type -> teleport.okta.v1.UpdateOktaImportRuleRequest - 22, // 31: teleport.okta.v1.OktaService.DeleteOktaImportRule:input_type -> teleport.okta.v1.DeleteOktaImportRuleRequest - 23, // 32: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:input_type -> teleport.okta.v1.DeleteAllOktaImportRulesRequest - 24, // 33: teleport.okta.v1.OktaService.ListOktaAssignments:input_type -> teleport.okta.v1.ListOktaAssignmentsRequest - 26, // 34: teleport.okta.v1.OktaService.GetOktaAssignment:input_type -> teleport.okta.v1.GetOktaAssignmentRequest - 27, // 35: teleport.okta.v1.OktaService.CreateOktaAssignment:input_type -> teleport.okta.v1.CreateOktaAssignmentRequest - 28, // 36: teleport.okta.v1.OktaService.UpdateOktaAssignment:input_type -> teleport.okta.v1.UpdateOktaAssignmentRequest - 29, // 37: teleport.okta.v1.OktaService.UpdateOktaAssignmentStatus:input_type -> teleport.okta.v1.UpdateOktaAssignmentStatusRequest - 30, // 38: teleport.okta.v1.OktaService.DeleteOktaAssignment:input_type -> teleport.okta.v1.DeleteOktaAssignmentRequest - 31, // 39: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:input_type -> teleport.okta.v1.DeleteAllOktaAssignmentsRequest - 0, // 40: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:input_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest - 3, // 41: teleport.okta.v1.OktaService.UpsertOktaAssignment:input_type -> teleport.okta.v1.UpsertOktaAssignmentRequest - 15, // 42: teleport.okta.v1.OktaService.ValidateClientCredentials:input_type -> teleport.okta.v1.ValidateClientCredentialsRequest - 8, // 43: teleport.okta.v1.OktaService.CreateIntegration:input_type -> teleport.okta.v1.CreateIntegrationRequest - 9, // 44: teleport.okta.v1.OktaService.UpdateIntegration:input_type -> teleport.okta.v1.UpdateIntegrationRequest - 4, // 45: teleport.okta.v1.OktaService.GetApps:input_type -> teleport.okta.v1.GetAppsRequest - 6, // 46: teleport.okta.v1.OktaService.GetGroups:input_type -> teleport.okta.v1.GetGroupsRequest - 18, // 47: teleport.okta.v1.OktaService.ListOktaImportRules:output_type -> teleport.okta.v1.ListOktaImportRulesResponse - 37, // 48: teleport.okta.v1.OktaService.GetOktaImportRule:output_type -> types.OktaImportRuleV1 - 37, // 49: teleport.okta.v1.OktaService.CreateOktaImportRule:output_type -> types.OktaImportRuleV1 - 37, // 50: teleport.okta.v1.OktaService.UpdateOktaImportRule:output_type -> types.OktaImportRuleV1 - 39, // 51: teleport.okta.v1.OktaService.DeleteOktaImportRule:output_type -> google.protobuf.Empty - 39, // 52: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:output_type -> google.protobuf.Empty - 25, // 53: teleport.okta.v1.OktaService.ListOktaAssignments:output_type -> teleport.okta.v1.ListOktaAssignmentsResponse - 34, // 54: teleport.okta.v1.OktaService.GetOktaAssignment:output_type -> types.OktaAssignmentV1 - 34, // 55: teleport.okta.v1.OktaService.CreateOktaAssignment:output_type -> types.OktaAssignmentV1 - 34, // 56: teleport.okta.v1.OktaService.UpdateOktaAssignment:output_type -> types.OktaAssignmentV1 - 39, // 57: teleport.okta.v1.OktaService.UpdateOktaAssignmentStatus:output_type -> google.protobuf.Empty - 39, // 58: teleport.okta.v1.OktaService.DeleteOktaAssignment:output_type -> google.protobuf.Empty - 39, // 59: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:output_type -> google.protobuf.Empty - 1, // 60: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:output_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse - 2, // 61: teleport.okta.v1.OktaService.UpsertOktaAssignment:output_type -> teleport.okta.v1.UpsertOktaAssignmentResponse - 16, // 62: teleport.okta.v1.OktaService.ValidateClientCredentials:output_type -> teleport.okta.v1.ValidateClientCredentialsResponse - 12, // 63: teleport.okta.v1.OktaService.CreateIntegration:output_type -> teleport.okta.v1.CreateIntegrationResponse - 13, // 64: teleport.okta.v1.OktaService.UpdateIntegration:output_type -> teleport.okta.v1.UpdateIntegrationResponse - 5, // 65: teleport.okta.v1.OktaService.GetApps:output_type -> teleport.okta.v1.GetAppsResponse - 7, // 66: teleport.okta.v1.OktaService.GetGroups:output_type -> teleport.okta.v1.GetGroupsResponse - 47, // [47:67] is the sub-list for method output_type - 27, // [27:47] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 36, // 19: teleport.okta.v1.ListOktaImportRulesResponse.import_rules:type_name -> types.OktaImportRuleV1 + 36, // 20: teleport.okta.v1.CreateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 + 36, // 21: teleport.okta.v1.UpdateOktaImportRuleRequest.import_rule:type_name -> types.OktaImportRuleV1 + 33, // 22: teleport.okta.v1.ListOktaAssignmentsResponse.assignments:type_name -> types.OktaAssignmentV1 + 33, // 23: teleport.okta.v1.CreateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 33, // 24: teleport.okta.v1.UpdateOktaAssignmentRequest.assignment:type_name -> types.OktaAssignmentV1 + 17, // 25: teleport.okta.v1.OktaService.ListOktaImportRules:input_type -> teleport.okta.v1.ListOktaImportRulesRequest + 19, // 26: teleport.okta.v1.OktaService.GetOktaImportRule:input_type -> teleport.okta.v1.GetOktaImportRuleRequest + 20, // 27: teleport.okta.v1.OktaService.CreateOktaImportRule:input_type -> teleport.okta.v1.CreateOktaImportRuleRequest + 21, // 28: teleport.okta.v1.OktaService.UpdateOktaImportRule:input_type -> teleport.okta.v1.UpdateOktaImportRuleRequest + 22, // 29: teleport.okta.v1.OktaService.DeleteOktaImportRule:input_type -> teleport.okta.v1.DeleteOktaImportRuleRequest + 23, // 30: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:input_type -> teleport.okta.v1.DeleteAllOktaImportRulesRequest + 24, // 31: teleport.okta.v1.OktaService.ListOktaAssignments:input_type -> teleport.okta.v1.ListOktaAssignmentsRequest + 26, // 32: teleport.okta.v1.OktaService.GetOktaAssignment:input_type -> teleport.okta.v1.GetOktaAssignmentRequest + 27, // 33: teleport.okta.v1.OktaService.CreateOktaAssignment:input_type -> teleport.okta.v1.CreateOktaAssignmentRequest + 28, // 34: teleport.okta.v1.OktaService.UpdateOktaAssignment:input_type -> teleport.okta.v1.UpdateOktaAssignmentRequest + 29, // 35: teleport.okta.v1.OktaService.DeleteOktaAssignment:input_type -> teleport.okta.v1.DeleteOktaAssignmentRequest + 30, // 36: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:input_type -> teleport.okta.v1.DeleteAllOktaAssignmentsRequest + 0, // 37: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:input_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentRequest + 3, // 38: teleport.okta.v1.OktaService.UpsertOktaAssignment:input_type -> teleport.okta.v1.UpsertOktaAssignmentRequest + 15, // 39: teleport.okta.v1.OktaService.ValidateClientCredentials:input_type -> teleport.okta.v1.ValidateClientCredentialsRequest + 8, // 40: teleport.okta.v1.OktaService.CreateIntegration:input_type -> teleport.okta.v1.CreateIntegrationRequest + 9, // 41: teleport.okta.v1.OktaService.UpdateIntegration:input_type -> teleport.okta.v1.UpdateIntegrationRequest + 4, // 42: teleport.okta.v1.OktaService.GetApps:input_type -> teleport.okta.v1.GetAppsRequest + 6, // 43: teleport.okta.v1.OktaService.GetGroups:input_type -> teleport.okta.v1.GetGroupsRequest + 18, // 44: teleport.okta.v1.OktaService.ListOktaImportRules:output_type -> teleport.okta.v1.ListOktaImportRulesResponse + 36, // 45: teleport.okta.v1.OktaService.GetOktaImportRule:output_type -> types.OktaImportRuleV1 + 36, // 46: teleport.okta.v1.OktaService.CreateOktaImportRule:output_type -> types.OktaImportRuleV1 + 36, // 47: teleport.okta.v1.OktaService.UpdateOktaImportRule:output_type -> types.OktaImportRuleV1 + 37, // 48: teleport.okta.v1.OktaService.DeleteOktaImportRule:output_type -> google.protobuf.Empty + 37, // 49: teleport.okta.v1.OktaService.DeleteAllOktaImportRules:output_type -> google.protobuf.Empty + 25, // 50: teleport.okta.v1.OktaService.ListOktaAssignments:output_type -> teleport.okta.v1.ListOktaAssignmentsResponse + 33, // 51: teleport.okta.v1.OktaService.GetOktaAssignment:output_type -> types.OktaAssignmentV1 + 33, // 52: teleport.okta.v1.OktaService.CreateOktaAssignment:output_type -> types.OktaAssignmentV1 + 33, // 53: teleport.okta.v1.OktaService.UpdateOktaAssignment:output_type -> types.OktaAssignmentV1 + 37, // 54: teleport.okta.v1.OktaService.DeleteOktaAssignment:output_type -> google.protobuf.Empty + 37, // 55: teleport.okta.v1.OktaService.DeleteAllOktaAssignments:output_type -> google.protobuf.Empty + 1, // 56: teleport.okta.v1.OktaService.ConditionalUpdateOktaAssignment:output_type -> teleport.okta.v1.ConditionalUpdateOktaAssignmentResponse + 2, // 57: teleport.okta.v1.OktaService.UpsertOktaAssignment:output_type -> teleport.okta.v1.UpsertOktaAssignmentResponse + 16, // 58: teleport.okta.v1.OktaService.ValidateClientCredentials:output_type -> teleport.okta.v1.ValidateClientCredentialsResponse + 12, // 59: teleport.okta.v1.OktaService.CreateIntegration:output_type -> teleport.okta.v1.CreateIntegrationResponse + 13, // 60: teleport.okta.v1.OktaService.UpdateIntegration:output_type -> teleport.okta.v1.UpdateIntegrationResponse + 5, // 61: teleport.okta.v1.OktaService.GetApps:output_type -> teleport.okta.v1.GetAppsResponse + 7, // 62: teleport.okta.v1.OktaService.GetGroups:output_type -> teleport.okta.v1.GetGroupsResponse + 44, // [44:63] is the sub-list for method output_type + 25, // [25:44] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_teleport_okta_v1_okta_service_proto_init() } @@ -3288,7 +3176,7 @@ func file_teleport_okta_v1_okta_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_okta_v1_okta_service_proto_rawDesc), len(file_teleport_okta_v1_okta_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 34, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/teleport/okta/v1/okta_service.proto b/api/proto/teleport/okta/v1/okta_service.proto index 075562b0f5d8..4d527b9fbab8 100644 --- a/api/proto/teleport/okta/v1/okta_service.proto +++ b/api/proto/teleport/okta/v1/okta_service.proto @@ -45,8 +45,6 @@ service OktaService { rpc CreateOktaAssignment(CreateOktaAssignmentRequest) returns (types.OktaAssignmentV1); // UpdateOktaAssignment updates an existing Okta assignment resource. rpc UpdateOktaAssignment(UpdateOktaAssignmentRequest) returns (types.OktaAssignmentV1); - // UpdateOktaAssignmentStatus will update the status for an Okta assignment. - rpc UpdateOktaAssignmentStatus(UpdateOktaAssignmentStatusRequest) returns (google.protobuf.Empty); // DeleteOktaAssignment removes the specified Okta assignment resource. rpc DeleteOktaAssignment(DeleteOktaAssignmentRequest) returns (google.protobuf.Empty); // DeleteAllOktaAssignments removes all Okta assignments. @@ -342,17 +340,6 @@ message UpdateOktaAssignmentRequest { types.OktaAssignmentV1 assignment = 1; } -// UpdateOktaAssignmentStatusRequest is a request for updating an Okta assignment status. -message UpdateOktaAssignmentStatusRequest { - // name is the name of the Okta assignment. - string name = 1; - // status is the status to update the assignment to. - types.OktaAssignmentSpecV1.OktaAssignmentStatus status = 2; - // timeHasPassed is the amount of time that must have passed since the last transition - // in order to set the new transition. - google.protobuf.Duration time_has_passed = 3; -} - // DeleteOktaAssignmentRequest is the request for deleting an Okta assignment. message DeleteOktaAssignmentRequest { // name is the name of the Okta assignment. diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index e5dcba853dd6..84d2598146a6 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -22,7 +22,6 @@ import ( "context" "io" "iter" - "time" "github.com/gravitational/trace" "google.golang.org/grpc" @@ -1111,10 +1110,6 @@ type OktaAccessPoint interface { // UpdateOktaAssignment updates an existing Okta assignment resource. UpdateOktaAssignment(context.Context, types.OktaAssignment) (types.OktaAssignment, error) - // UpdateOktaAssignmentStatus will update the status for an Okta assignment if the given time has passed - // since the last transition. - UpdateOktaAssignmentStatus(ctx context.Context, name, status string, timeHasPassed time.Duration) error - // DeleteOktaAssignment removes the specified Okta assignment resource. DeleteOktaAssignment(ctx context.Context, name string) error @@ -1991,12 +1986,6 @@ func (w *OktaWrapper) UpdateOktaAssignment(ctx context.Context, assignment types return w.NoCache.UpdateOktaAssignment(ctx, assignment) } -// UpdateOktaAssignmentStatus will update the status for an Okta assignment if the given time has passed -// since the last transition. -func (w *OktaWrapper) UpdateOktaAssignmentStatus(ctx context.Context, name, status string, timeHasPassed time.Duration) error { - return w.NoCache.UpdateOktaAssignmentStatus(ctx, name, status, timeHasPassed) -} - // DeleteOktaAssignment removes the specified Okta assignment resource. func (w *OktaWrapper) DeleteOktaAssignment(ctx context.Context, name string) error { return w.NoCache.DeleteOktaAssignment(ctx, name) diff --git a/lib/services/local/okta.go b/lib/services/local/okta.go index e9cca039b639..f69b96d7cdb1 100644 --- a/lib/services/local/okta.go +++ b/lib/services/local/okta.go @@ -20,7 +20,6 @@ package local import ( "context" - "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -177,30 +176,6 @@ func (o *OktaService) ConditionalUpdateOktaAssignment(ctx context.Context, assig return updated, trace.Wrap(err) } -// UpdateOktaAssignmentStatus will update the status for an Okta assignment if the given time has passed -// since the last transition. -func (o *OktaService) UpdateOktaAssignmentStatus(ctx context.Context, name, status string, timeHasPassed time.Duration) error { - _, err := o.assignmentSvc.UpdateAndSwapResource(ctx, name, func(currentAssignment types.OktaAssignment) error { - // Only update the status if the given duration has passed. - sinceLastTransition := o.clock.Since(currentAssignment.GetLastTransition()) - if sinceLastTransition < timeHasPassed { - return trace.BadParameter("only %s has passed since last transition (want at least %s)", sinceLastTransition, timeHasPassed) - } - - if err := currentAssignment.SetStatus(status); err != nil { - return trace.Wrap(err) - } - currentAssignment.SetLastTransition(o.clock.Now()) - - return nil - }) - if err != nil { - return trace.Wrap(err) - } - - return nil -} - // DeleteOktaAssignment removes the specified Okta assignment resource. func (o *OktaService) DeleteOktaAssignment(ctx context.Context, name string) error { return o.assignmentSvc.DeleteResource(ctx, name) diff --git a/lib/services/local/okta_test.go b/lib/services/local/okta_test.go index 6aaf4203c3a2..9019665ba567 100644 --- a/lib/services/local/okta_test.go +++ b/lib/services/local/okta_test.go @@ -397,25 +397,6 @@ func TestOktaAssignmentCRUD(t *testing.T) { cmpopts.IgnoreFields(types.Metadata{}, "Revision"), )) - // Fail to update the status for an assignment due to a bad transition. - err = service.UpdateOktaAssignmentStatus(ctx, assignment1.GetName(), constants.OktaAssignmentStatusPending, 0) - require.ErrorIs(t, err, trace.BadParameter("invalid transition: processing -> pending")) - - // Fail to update the status because not enough time has passed. - err = service.UpdateOktaAssignmentStatus(ctx, assignment1.GetName(), constants.OktaAssignmentStatusPending, time.Hour) - require.ErrorContains(t, err, "only 0s has passed since last transition") - require.True(t, trace.IsBadParameter(err)) - - // Successfully update the status for an assignment. - require.NoError(t, assignment1.SetStatus(constants.OktaAssignmentStatusSuccessful)) - err = service.UpdateOktaAssignmentStatus(ctx, assignment1.GetName(), constants.OktaAssignmentStatusSuccessful, 0) - require.NoError(t, err) - assignment, err = service.GetOktaAssignment(ctx, assignment1.GetName()) - require.NoError(t, err) - require.Empty(t, cmp.Diff(assignment1, assignment, - cmpopts.IgnoreFields(types.Metadata{}, "Revision"), - )) - // Delete an assignment err = service.DeleteOktaAssignment(ctx, assignment1.GetName()) require.NoError(t, err) diff --git a/lib/services/okta.go b/lib/services/okta.go index f6daa320fc78..0ddb6f0fd784 100644 --- a/lib/services/okta.go +++ b/lib/services/okta.go @@ -20,7 +20,6 @@ package services import ( "context" - "time" "github.com/gravitational/trace" @@ -75,9 +74,6 @@ type OktaAssignments interface { ConditionalUpdateOktaAssignment(context.Context, types.OktaAssignment) (types.OktaAssignment, error) // UpsertOktaAssignment upsert an Okta assignment. UpsertOktaAssignment(context.Context, types.OktaAssignment) (types.OktaAssignment, error) - // UpdateOktaAssignmentStatus will update the status for an Okta assignment if the given time has passed - // since the last transition. - UpdateOktaAssignmentStatus(ctx context.Context, name, status string, timeHasPassed time.Duration) error // DeleteOktaAssignment removes the specified Okta assignment resource. DeleteOktaAssignment(ctx context.Context, name string) error // ConditionalDeleteOktaAssignment removes the specified Okta assignment resource, protected by optimistic locking. From 86b5c3d6605d5688b7b27b6f52536059bec09727 Mon Sep 17 00:00:00 2001 From: Pawel Kopiczko Date: Mon, 3 Aug 2026 12:33:47 +0100 Subject: [PATCH 105/347] Okta: Remove OktaAssignmentWatcher (#66817) --- lib/services/watcher.go | 178 ----------------------------- lib/services/watcher_test.go | 216 ----------------------------------- 2 files changed, 394 deletions(-) diff --git a/lib/services/watcher.go b/lib/services/watcher.go index ddc8760e691c..4454f0c7167a 100644 --- a/lib/services/watcher.go +++ b/lib/services/watcher.go @@ -1782,184 +1782,6 @@ func (p *accessRequestCollector) processEventsAndUpdateCurrent(ctx context.Conte func (*accessRequestCollector) notifyStale() {} -// OktaAssignmentWatcherConfig is a OktaAssignmentWatcher configuration. -type OktaAssignmentWatcherConfig struct { - // OktaAssignments is responsible for fetching Okta assignments. - OktaAssignments OktaAssignmentsGetter - // OktaAssignmentsC receives up-to-date list of all Okta assignment resources. - OktaAssignmentsC chan types.OktaAssignments - // RWCfg is the resource watcher configuration. - RWCfg ResourceWatcherConfig - // PageSize is the number of Okta assignments to list at a time. - PageSize int -} - -// CheckAndSetDefaults checks parameters and sets default values. -func (cfg *OktaAssignmentWatcherConfig) CheckAndSetDefaults() error { - if err := cfg.RWCfg.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) - } - if cfg.OktaAssignments == nil { - assignments, ok := cfg.RWCfg.Client.(OktaAssignmentsGetter) - if !ok { - return trace.BadParameter("missing parameter OktaAssignments and Client not usable as OktaAssignments") - } - cfg.OktaAssignments = assignments - } - if cfg.OktaAssignmentsC == nil { - cfg.OktaAssignmentsC = make(chan types.OktaAssignments) - } - return nil -} - -// NewOktaAssignmentWatcher returns a new instance of OktaAssignmentWatcher. The context here will be used to -// exit early from the resource watcher if needed. -func NewOktaAssignmentWatcher(ctx context.Context, cfg OktaAssignmentWatcherConfig) (*OktaAssignmentWatcher, error) { - if err := cfg.CheckAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) - } - collector := &oktaAssignmentCollector{ - logger: cfg.RWCfg.Logger, - cfg: cfg, - initializationC: make(chan struct{}), - } - watcher, err := newResourceWatcher(ctx, collector, cfg.RWCfg) - if err != nil { - return nil, trace.Wrap(err) - } - return &OktaAssignmentWatcher{ - resourceWatcher: watcher, - collector: collector, - }, nil -} - -// OktaAssignmentWatcher is built on top of resourceWatcher to monitor Okta assignment resources. -type OktaAssignmentWatcher struct { - resourceWatcher *resourceWatcher - collector *oktaAssignmentCollector -} - -// CollectorChan is the channel that collects the Okta assignments. -func (o *OktaAssignmentWatcher) CollectorChan() chan types.OktaAssignments { - return o.collector.cfg.OktaAssignmentsC -} - -// Close closes the underlying resource watcher -func (o *OktaAssignmentWatcher) Close() { - o.resourceWatcher.Close() -} - -// Done returns the channel that signals watcher closer. -func (o *OktaAssignmentWatcher) Done() <-chan struct{} { - return o.resourceWatcher.Done() -} - -// oktaAssignmentCollector accompanies resourceWatcher when monitoring Okta assignment resources. -type oktaAssignmentCollector struct { - // OktaAssignmentWatcherConfig is the watcher configuration. - cfg OktaAssignmentWatcherConfig - logger *slog.Logger - // current holds a map of the currently known Okta assignment resources. - current map[string]types.OktaAssignment - // initializationC is used to check that the watcher has been initialized properly. - initializationC chan struct{} - // mu guards "current" - mu sync.RWMutex - once sync.Once -} - -// resourceKinds specifies the resource kind to watch. -func (*oktaAssignmentCollector) resourceKinds() []types.WatchKind { - return []types.WatchKind{{Kind: types.KindOktaAssignment}} -} - -// initializationChan is used to check if the initial state sync has been completed. -func (c *oktaAssignmentCollector) initializationChan() <-chan struct{} { - return c.initializationC -} - -// getResourcesAndUpdateCurrent refreshes the list of current resources. -func (c *oktaAssignmentCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { - var oktaAssignments []types.OktaAssignment - var nextToken string - for { - var oktaAssignmentsPage []types.OktaAssignment - var err error - oktaAssignmentsPage, nextToken, err = c.cfg.OktaAssignments.ListOktaAssignments(ctx, c.cfg.PageSize, nextToken) - if err != nil { - return trace.Wrap(err) - } - - oktaAssignments = append(oktaAssignments, oktaAssignmentsPage...) - if nextToken == "" { - break - } - } - - newCurrent := make(map[string]types.OktaAssignment, len(oktaAssignments)) - for _, oktaAssignment := range oktaAssignments { - newCurrent[oktaAssignment.GetName()] = oktaAssignment.Copy() - } - c.mu.Lock() - c.current = newCurrent - c.defineCollectorAsInitialized() - c.mu.Unlock() - - select { - case <-ctx.Done(): - return trace.Wrap(ctx.Err()) - case c.cfg.OktaAssignmentsC <- oktaAssignments: - } - - return nil -} - -func (c *oktaAssignmentCollector) defineCollectorAsInitialized() { - c.once.Do(func() { - close(c.initializationC) - }) -} - -// processEventsAndUpdateCurrent is called when a watcher event is received. -func (c *oktaAssignmentCollector) processEventsAndUpdateCurrent(ctx context.Context, events []types.Event) { - c.mu.Lock() - defer c.mu.Unlock() - - for _, event := range events { - if event.Resource == nil || event.Resource.GetKind() != types.KindOktaAssignment { - c.logger.WarnContext(ctx, "Received unexpected event", "event", logutils.StringerAttr(event)) - continue - } - switch event.Type { - case types.OpDelete: - delete(c.current, event.Resource.GetName()) - resources := resourcesToSlice(c.current, types.OktaAssignment.Copy) - select { - case <-ctx.Done(): - case c.cfg.OktaAssignmentsC <- resources: - } - case types.OpPut: - oktaAssignment, ok := event.Resource.(types.OktaAssignment) - if !ok { - c.logger.WarnContext(ctx, "Received unexpected resource type", "resource", event.Resource.GetKind()) - continue - } - c.current[oktaAssignment.GetName()] = oktaAssignment - resources := resourcesToSlice(c.current, types.OktaAssignment.Copy) - - select { - case <-ctx.Done(): - case c.cfg.OktaAssignmentsC <- resources: - } - - default: - c.logger.WarnContext(ctx, "Received unsupported event type", "event_type", event.Type) - } - } -} - -func (*oktaAssignmentCollector) notifyStale() {} - // GitServerWatcherConfig is the config for Git server watcher. type GitServerWatcherConfig struct { GitServerGetter diff --git a/lib/services/watcher_test.go b/lib/services/watcher_test.go index 19db16f66ba4..ad5d0696cac6 100644 --- a/lib/services/watcher_test.go +++ b/lib/services/watcher_test.go @@ -24,7 +24,6 @@ import ( "errors" "fmt" "slices" - "sort" "strings" "sync" "testing" @@ -1501,221 +1500,6 @@ func newAccessRequest(t *testing.T, name string) types.AccessRequest { return accessRequest } -// TestOktaAssignmentWatcher tests that Okta assignment resource watcher properly receives -// and dispatches updates to Okta assignment resources. -func TestOktaAssignmentWatcher(t *testing.T) { - t.Parallel() - - ctx := context.Background() - clock := clockwork.NewFakeClock() - - bk, err := memory.New(memory.Config{ - Context: ctx, - Clock: clock, - }) - require.NoError(t, err) - - type client struct { - services.OktaAssignments - types.Events - } - - oktaService, err := local.NewOktaService(bk, clock) - require.NoError(t, err) - w, err := services.NewOktaAssignmentWatcher(ctx, services.OktaAssignmentWatcherConfig{ - RWCfg: services.ResourceWatcherConfig{ - Component: "test", - MaxRetryPeriod: 200 * time.Millisecond, - Client: &client{ - OktaAssignments: oktaService, - Events: local.NewEventsService(bk), - }, - }, - OktaAssignments: oktaService, - PageSize: 1, // Set page size to 1 to exercise pagination logic. - OktaAssignmentsC: make(chan types.OktaAssignments, 10), - }) - require.NoError(t, err) - t.Cleanup(w.Close) - - // Initially there are no assignments so watcher should send an empty list. - select { - case changeset := <-w.CollectorChan(): - require.Empty(t, changeset, "initial assignment list should be empty") - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(2 * time.Second): - t.Fatal("Timeout waiting for the initial empty event.") - } - - // Add an assignment. - a1 := newOktaAssignment(t, uuid.NewString()) - _, err = oktaService.CreateOktaAssignment(ctx, a1) - require.NoError(t, err) - - // The first event is always the current list of assignments. - select { - case changeset := <-w.CollectorChan(): - expected := types.OktaAssignments{a1} - sortedChangeset := changeset - sort.Sort(expected) - sort.Sort(sortedChangeset) - - require.Empty(t, - cmp.Diff(expected, - changeset, - cmpopts.IgnoreFields(types.Metadata{}, "Revision")), - "should be no differences in the changeset after adding the first assignment") - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(2 * time.Second): - t.Fatal("Timeout waiting for the first event.") - } - - // Add a second assignment. - a2 := newOktaAssignment(t, uuid.NewString()) - _, err = oktaService.CreateOktaAssignment(ctx, a2) - require.NoError(t, err) - - // Watcher should detect the assignment list change. - select { - case changeset := <-w.CollectorChan(): - expected := types.OktaAssignments{a1, a2} - sort.Sort(expected) - sort.Sort(changeset) - - require.Empty(t, - cmp.Diff( - expected, - changeset, - cmpopts.IgnoreFields(types.Metadata{}, "Revision")), - "should be no difference in the changeset after adding the second assignment") - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(2 * time.Second): - t.Fatal("Timeout waiting for the second event.") - } - - // Change the second assignment. - a2.SetExpiry(time.Now().Add(30 * time.Minute)) - _, err = oktaService.UpdateOktaAssignment(ctx, a2) - require.NoError(t, err) - - // Watcher should detect the assignment list change. - select { - case changeset := <-w.CollectorChan(): - expected := types.OktaAssignments{a1, a2} - sort.Sort(expected) - sort.Sort(changeset) - - require.Empty(t, - cmp.Diff( - expected, - changeset, - cmpopts.IgnoreFields(types.Metadata{}, "Revision")), - "should be no difference in the changeset after update") - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(2 * time.Second): - t.Fatal("Timeout waiting for the updated event.") - } - - // Delete the first assignment. - require.NoError(t, oktaService.DeleteOktaAssignment(ctx, a1.GetName())) - - // Watcher should detect the Okta assignment list change. - select { - case changeset := <-w.CollectorChan(): - expected := types.OktaAssignments{a2} - sort.Sort(expected) - sort.Sort(changeset) - - require.Empty(t, - cmp.Diff( - expected, - changeset, - cmpopts.IgnoreFields(types.Metadata{}, "Revision")), - "should be no difference in the changeset after deleting the first assignment") - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(2 * time.Second): - t.Fatal("Timeout waiting for the delete event.") - } -} - -// TestOktaAssignmentWatcherRace ensures there are no races when editing OktaAssignment resources -// collected from the watcher. -func TestOktaAssignmentWatcherRace(t *testing.T) { - t.Parallel() - - ctx := t.Context() - clock := clockwork.NewFakeClock() - - bk, err := memory.New(memory.Config{ - Context: ctx, - Clock: clock, - }) - require.NoError(t, err) - - oktaService, err := local.NewOktaService(bk, clock) - require.NoError(t, err) - - for range 10 { - a1 := newOktaAssignment(t, uuid.NewString()) - _, err = oktaService.CreateOktaAssignment(ctx, a1) - require.NoError(t, err) - } - - type client struct { - services.OktaAssignments - types.Events - } - - w, err := services.NewOktaAssignmentWatcher(ctx, services.OktaAssignmentWatcherConfig{ - RWCfg: services.ResourceWatcherConfig{ - Component: "test", - Client: &client{ - OktaAssignments: oktaService, - Events: local.NewEventsService(bk), - }, - }, - }) - require.NoError(t, err) - t.Cleanup(w.Close) - - select { - case changeset := <-w.CollectorChan(): - for _, a := range changeset { - a.SetLastTransition(a.GetLastTransition().Add(1)) - _, _ = oktaService.UpdateOktaAssignment(ctx, a) - } - case <-w.Done(): - t.Fatal("Watcher has unexpectedly exited.") - case <-time.After(30 * time.Second): - t.Fatal("Timeout processing the event.") - } -} - -func newOktaAssignment(t *testing.T, name string) types.OktaAssignment { - assignment, err := types.NewOktaAssignment( - types.Metadata{ - Name: name, - }, - types.OktaAssignmentSpecV1{ - User: "test-user@test.user", - Targets: []*types.OktaAssignmentTargetV1{ - { - Type: types.OktaAssignmentTargetV1_APPLICATION, - Id: "123456", - }, - }, - Status: types.OktaAssignmentSpecV1_PENDING, - }, - ) - require.NoError(t, err) - return assignment -} - func TestGitServerWatcher(t *testing.T) { t.Parallel() From d875508a5dfa95776877ede562e2939f2f6383f0 Mon Sep 17 00:00:00 2001 From: matheus Date: Mon, 3 Aug 2026 09:07:27 -0300 Subject: [PATCH 106/347] Add new CIR fields to audit events (#69231) * Add new CIR fields to audit events * Improve clarity --- .../teleport/legacy/types/events/events.proto | 14 + api/types/events/events.pb.go | 3280 +++++++++-------- docs/pages/reference/audit-events.mdx | 2 + lib/events/events_test.go | 1 + .../teleport/src/Audit/fixtures/index.ts | 2 + .../teleport/src/services/audit/makeEvent.ts | 21 +- .../teleport/src/services/audit/types.ts | 2 + 7 files changed, 1727 insertions(+), 1595 deletions(-) diff --git a/api/proto/teleport/legacy/types/events/events.proto b/api/proto/teleport/legacy/types/events/events.proto index 4b64e1005987..e8f449ecb11e 100644 --- a/api/proto/teleport/legacy/types/events/events.proto +++ b/api/proto/teleport/legacy/types/events/events.proto @@ -10042,6 +10042,20 @@ message ClientIPRestrictionsUpdate { // ClientIPRestrictions is the new Client IP Restrictions allowlist. repeated string ClientIPRestrictions = 6 [(gogoproto.jsontag) = "client_ip_restrictions"]; + + // Mode is the new Client IP Restrictions enforcement mode ("draft" or + // "enforced"), recorded exactly as accepted by the server. Empty when the + // request did not set a mode (treated as enforced), for deletions, and for + // updates made through APIs that predate the mode field. + string Mode = 7 [(gogoproto.jsontag) = "mode,omitempty"]; + + // EnforcementExpires is the time at which the Client IP Restrictions + // enforcement lapses. Zero if no expiry is set. + google.protobuf.Timestamp EnforcementExpires = 8 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "enforcement_expires,omitempty" + ]; } // AppAuthConfigCreate is emitted when a AppAuthConfig is created. diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 97e2c676a46d..d74cef3b9446 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -19997,9 +19997,17 @@ type ClientIPRestrictionsUpdate struct { Status `protobuf:"bytes,5,opt,name=Status,proto3,embedded=Status" json:""` // ClientIPRestrictions is the new Client IP Restrictions allowlist. ClientIPRestrictions []string `protobuf:"bytes,6,rep,name=ClientIPRestrictions,proto3" json:"client_ip_restrictions"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // Mode is the new Client IP Restrictions enforcement mode ("draft" or + // "enforced"), recorded exactly as accepted by the server. Empty when the + // request did not set a mode (treated as enforced), for deletions, and for + // updates made through APIs that predate the mode field. + Mode string `protobuf:"bytes,7,opt,name=Mode,proto3" json:"mode,omitempty"` + // EnforcementExpires is the time at which the Client IP Restrictions + // enforcement lapses. Zero if no expiry is set. + EnforcementExpires time.Time `protobuf:"bytes,8,opt,name=EnforcementExpires,proto3,stdtime" json:"enforcement_expires,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ClientIPRestrictionsUpdate) Reset() { *m = ClientIPRestrictionsUpdate{} } @@ -22129,1594 +22137,1598 @@ func init() { } var fileDescriptor_007ba1c3d6266d56 = []byte{ - // 25381 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x7b, 0x7c, 0x24, 0xc7, - 0x79, 0x18, 0x88, 0x79, 0x60, 0x00, 0x7c, 0x78, 0xd7, 0xbe, 0x86, 0xcb, 0x25, 0x87, 0x6c, 0x92, - 0xab, 0x5d, 0x89, 0xdc, 0x25, 0x97, 0x0f, 0x89, 0x22, 0x25, 0x72, 0x00, 0x2c, 0x08, 0xec, 0x2e, - 0x16, 0xa3, 0x1e, 0xec, 0x2e, 0x25, 0x59, 0x1a, 0x37, 0xa6, 0x0b, 0x40, 0x13, 0x33, 0xdd, 0xa3, - 0xee, 0x1e, 0x60, 0xc1, 0x5c, 0x2e, 0x7e, 0xdb, 0xb2, 0x25, 0x59, 0x27, 0xc5, 0x91, 0x63, 0x27, - 0x39, 0xdb, 0x71, 0x2e, 0x8e, 0xed, 0xd8, 0x71, 0x2e, 0x17, 0xbf, 0x1d, 0xdb, 0x4a, 0xce, 0xf2, - 0xfb, 0x91, 0xd8, 0xe7, 0xb3, 0x1d, 0xd8, 0xe7, 0xc4, 0x97, 0x3b, 0x5c, 0x92, 0x73, 0xfc, 0x88, - 0x1d, 0x27, 0xf6, 0xdd, 0xaf, 0xbe, 0xaa, 0xee, 0xae, 0xae, 0xee, 0x19, 0x3c, 0x69, 0x2c, 0xb8, - 0xf8, 0x67, 0x17, 0xf3, 0x7d, 0x5f, 0x7d, 0xdd, 0xfd, 0xd5, 0x57, 0x55, 0x5f, 0x55, 0x7d, 0x0f, - 0xb8, 0xe8, 0xd3, 0x06, 0x6d, 0x39, 0xae, 0x7f, 0xb9, 0x41, 0x97, 0x8d, 0xfa, 0xc6, 0x65, 0x7f, - 0xa3, 0x45, 0xbd, 0xcb, 0x74, 0x8d, 0xda, 0x7e, 0xf0, 0xdf, 0xa5, 0x96, 0xeb, 0xf8, 0x0e, 0x29, - 0xf0, 0x5f, 0x67, 0x4f, 0x2e, 0x3b, 0xcb, 0x0e, 0x82, 0x2e, 0xb3, 0xbf, 0x38, 0xf6, 0xec, 0xb9, - 0x65, 0xc7, 0x59, 0x6e, 0xd0, 0xcb, 0xf8, 0x6b, 0xb1, 0xbd, 0x74, 0xd9, 0xf3, 0xdd, 0x76, 0xdd, - 0x17, 0xd8, 0x92, 0x8a, 0xf5, 0xad, 0x26, 0xf5, 0x7c, 0xa3, 0xd9, 0x12, 0x04, 0x0f, 0xab, 0x04, - 0xeb, 0xae, 0xd1, 0x6a, 0x51, 0x57, 0x3c, 0xfc, 0xec, 0x3b, 0xc2, 0xf7, 0x34, 0xea, 0x75, 0xea, - 0x79, 0x0d, 0xcb, 0xf3, 0x2f, 0xaf, 0x3d, 0x23, 0xfd, 0x12, 0x84, 0x8f, 0xa6, 0x7f, 0x10, 0xfe, - 0x2b, 0x48, 0x9e, 0x4a, 0x27, 0x09, 0x9e, 0xa8, 0x3c, 0x5a, 0xfb, 0x4c, 0x16, 0xfa, 0xe7, 0xa8, - 0x6f, 0x98, 0x86, 0x6f, 0x90, 0x73, 0xd0, 0x3b, 0x6b, 0x9b, 0xf4, 0x6e, 0x31, 0xf3, 0x48, 0xe6, - 0x42, 0x6e, 0xa2, 0xb0, 0xb5, 0x59, 0xca, 0x52, 0x4b, 0xe7, 0x40, 0xf2, 0x10, 0xe4, 0x17, 0x36, - 0x5a, 0xb4, 0x98, 0x7d, 0x24, 0x73, 0x61, 0x60, 0x62, 0x60, 0x6b, 0xb3, 0xd4, 0x8b, 0x42, 0xd3, - 0x11, 0x4c, 0x1e, 0x85, 0xec, 0xec, 0x54, 0x31, 0x87, 0xc8, 0xf1, 0xad, 0xcd, 0xd2, 0x70, 0xdb, - 0x32, 0x9f, 0x74, 0x9a, 0x96, 0x4f, 0x9b, 0x2d, 0x7f, 0x43, 0xcf, 0xce, 0x4e, 0x91, 0xf3, 0x90, - 0x9f, 0x74, 0x4c, 0x5a, 0xcc, 0x23, 0x11, 0xd9, 0xda, 0x2c, 0x8d, 0xd4, 0x1d, 0x93, 0x4a, 0x54, - 0x88, 0x27, 0xaf, 0x42, 0x7e, 0xc1, 0x6a, 0xd2, 0x62, 0xef, 0x23, 0x99, 0x0b, 0x83, 0x57, 0xce, - 0x5e, 0xe2, 0xe2, 0xbb, 0x14, 0x88, 0xef, 0xd2, 0x42, 0x20, 0xdf, 0x89, 0xb1, 0x2f, 0x6c, 0x96, - 0x7a, 0xb6, 0x36, 0x4b, 0x79, 0x26, 0xf2, 0x4f, 0xff, 0x76, 0x29, 0xa3, 0x63, 0x4b, 0xf2, 0x32, - 0x0c, 0x4e, 0x36, 0xda, 0x9e, 0x4f, 0xdd, 0x9b, 0x46, 0x93, 0x16, 0x0b, 0xf8, 0xc0, 0xb3, 0x5b, - 0x9b, 0xa5, 0xd3, 0x75, 0x0e, 0xae, 0xd9, 0x46, 0x53, 0x7e, 0xb0, 0x4c, 0xae, 0xfd, 0x40, 0x06, - 0x46, 0xab, 0xd4, 0xf3, 0x2c, 0xc7, 0x0e, 0x65, 0xf3, 0x04, 0x0c, 0x08, 0xd0, 0xec, 0x14, 0xca, - 0x67, 0x60, 0xa2, 0x6f, 0x6b, 0xb3, 0x94, 0xf3, 0x2c, 0x53, 0x8f, 0x30, 0xe4, 0x69, 0xe8, 0xbb, - 0x63, 0xf9, 0x2b, 0x73, 0xd3, 0x65, 0x21, 0xa7, 0xd3, 0x5b, 0x9b, 0x25, 0xb2, 0x6e, 0xf9, 0x2b, - 0xb5, 0xe6, 0x92, 0x21, 0x3d, 0x30, 0x20, 0x23, 0x37, 0x60, 0xac, 0xe2, 0x5a, 0x6b, 0x86, 0x4f, - 0xaf, 0xd3, 0x8d, 0x8a, 0xd3, 0xb0, 0xea, 0x1b, 0x42, 0x8a, 0x8f, 0x6c, 0x6d, 0x96, 0xce, 0xb5, - 0x38, 0xae, 0xb6, 0x4a, 0x37, 0x6a, 0x2d, 0xc4, 0x4a, 0x4c, 0x12, 0x2d, 0xb5, 0x5f, 0x04, 0x18, - 0xba, 0xe5, 0x51, 0x37, 0x7c, 0xef, 0xf3, 0x90, 0x67, 0xbf, 0xc5, 0x2b, 0xa3, 0xcc, 0xdb, 0x1e, - 0x75, 0x65, 0x99, 0x33, 0x3c, 0xb9, 0x08, 0xbd, 0x37, 0x9c, 0x65, 0xcb, 0x16, 0xaf, 0x7d, 0x62, - 0x6b, 0xb3, 0x34, 0xda, 0x60, 0x00, 0x89, 0x92, 0x53, 0x90, 0xf7, 0xc3, 0xd0, 0x6c, 0x93, 0xe9, - 0x90, 0x63, 0x1b, 0xbe, 0xe3, 0x8a, 0xb7, 0x45, 0xe9, 0x5a, 0x12, 0x5c, 0x6a, 0x18, 0xa3, 0x27, - 0xef, 0x05, 0x28, 0xdf, 0xa9, 0xea, 0x4e, 0x83, 0x96, 0xf5, 0x9b, 0x42, 0x19, 0xb0, 0xb5, 0xb1, - 0xee, 0xd5, 0x5c, 0xa7, 0x41, 0x6b, 0x86, 0x2b, 0x3f, 0x56, 0xa2, 0x26, 0x57, 0x61, 0xa4, 0x8c, - 0xa3, 0x42, 0xa7, 0x1f, 0x6b, 0x53, 0xcf, 0xf7, 0x8a, 0xbd, 0x8f, 0xe4, 0x2e, 0x0c, 0x4c, 0x3c, - 0xb4, 0xb5, 0x59, 0x7a, 0x80, 0x8f, 0x97, 0x9a, 0x2b, 0x50, 0x12, 0x0b, 0xa5, 0x11, 0x99, 0x80, - 0xe1, 0xf2, 0x9b, 0x6d, 0x97, 0xce, 0x9a, 0xd4, 0xf6, 0x2d, 0x7f, 0x43, 0x68, 0xc8, 0xb9, 0xad, - 0xcd, 0x52, 0xd1, 0x60, 0x88, 0x9a, 0x25, 0x30, 0x12, 0x93, 0x78, 0x13, 0x32, 0x0f, 0xe3, 0xaf, - 0x4d, 0x56, 0xaa, 0xd4, 0x5d, 0xb3, 0xea, 0xb4, 0x5c, 0xaf, 0x3b, 0x6d, 0xdb, 0x2f, 0xf6, 0x21, - 0x9f, 0x47, 0xb7, 0x36, 0x4b, 0x0f, 0x2d, 0xd7, 0x5b, 0x35, 0x8f, 0x63, 0x6b, 0x06, 0x47, 0x4b, - 0xcc, 0x92, 0x6d, 0xc9, 0x87, 0x60, 0x78, 0xc1, 0x65, 0x5a, 0x68, 0x4e, 0x51, 0x06, 0x2f, 0xf6, - 0xa3, 0xfe, 0x9f, 0xbe, 0x24, 0x66, 0x2a, 0x0e, 0x0d, 0x7a, 0x96, 0xbf, 0xac, 0xcf, 0x1b, 0xd4, - 0x4c, 0xc4, 0xc9, 0x2f, 0x1b, 0x63, 0x45, 0x28, 0x14, 0xd9, 0xc7, 0x5b, 0x2e, 0x35, 0x13, 0xda, - 0x36, 0x80, 0xef, 0x7c, 0x71, 0x6b, 0xb3, 0xf4, 0x84, 0x2b, 0x68, 0x6a, 0x5d, 0xd5, 0xae, 0x23, - 0x2b, 0x72, 0x15, 0xfa, 0x99, 0x36, 0x5d, 0xb7, 0x6c, 0xb3, 0x08, 0x8f, 0x64, 0x2e, 0x8c, 0x5c, - 0x19, 0x0b, 0xde, 0x3e, 0x80, 0x4f, 0x9c, 0xd9, 0xda, 0x2c, 0x9d, 0x60, 0x3a, 0x58, 0x5b, 0xb5, - 0x6c, 0x79, 0x8a, 0x08, 0x9b, 0xb2, 0x51, 0x34, 0xe1, 0xf8, 0x38, 0x74, 0x07, 0xa3, 0x51, 0xb4, - 0xe8, 0xf8, 0xea, 0xb0, 0x0d, 0xc8, 0xc8, 0x24, 0x0c, 0x4f, 0x38, 0xfe, 0xac, 0xed, 0xf9, 0x86, - 0x5d, 0xa7, 0xb3, 0x53, 0xc5, 0x21, 0x6c, 0x87, 0x6a, 0xc1, 0xda, 0x59, 0x02, 0x53, 0x8b, 0x4d, - 0x4a, 0xf1, 0x36, 0x64, 0x0e, 0x80, 0xbd, 0xc2, 0xbc, 0x6b, 0xb1, 0x81, 0x30, 0x8c, 0xef, 0x4f, - 0xe4, 0xf7, 0xe7, 0x98, 0x89, 0x07, 0xb6, 0x36, 0x4b, 0xa7, 0xf0, 0x0b, 0x1c, 0x04, 0xc8, 0xba, - 0x1a, 0x91, 0x91, 0x17, 0x60, 0x80, 0xfd, 0x62, 0xaa, 0xeb, 0x15, 0x47, 0x50, 0x4d, 0x8b, 0x5b, - 0x9b, 0xa5, 0x93, 0xd8, 0x92, 0xe9, 0xb9, 0xac, 0xa1, 0x11, 0x29, 0xf9, 0x6c, 0x86, 0xbf, 0xc7, - 0x82, 0x6b, 0x58, 0xbe, 0x57, 0x1c, 0x45, 0x2d, 0x38, 0x75, 0x29, 0x9c, 0xb9, 0x6f, 0x18, 0x8b, - 0xb4, 0x71, 0xdb, 0x68, 0xb4, 0xa9, 0x37, 0xf1, 0x45, 0x6c, 0x02, 0xfc, 0x8d, 0xcd, 0xd2, 0x4b, - 0xcb, 0x96, 0xbf, 0xd2, 0x5e, 0xbc, 0x54, 0x77, 0x9a, 0x97, 0x97, 0x5d, 0x63, 0xcd, 0xf2, 0x0d, - 0xdf, 0x72, 0x6c, 0xa3, 0x71, 0x39, 0x5a, 0x5b, 0x5a, 0x96, 0xb2, 0x18, 0x5c, 0xe2, 0x4f, 0x08, - 0xbf, 0xc6, 0xc7, 0x9f, 0xea, 0xd7, 0x70, 0x22, 0x32, 0x0b, 0xa3, 0xec, 0x97, 0x3c, 0xad, 0x8e, - 0xa1, 0x8c, 0x4b, 0x5b, 0x9b, 0xa5, 0x07, 0xb1, 0x7d, 0x87, 0xb9, 0x55, 0x6d, 0x47, 0x9e, 0x82, - 0x01, 0xaf, 0xee, 0xb4, 0x68, 0xad, 0x65, 0xd9, 0xc5, 0x71, 0xfc, 0xbc, 0x50, 0x4d, 0xaa, 0x0c, - 0x51, 0xb1, 0x6c, 0xbd, 0xdf, 0x13, 0x7f, 0x91, 0xa7, 0xa0, 0x30, 0x41, 0x8d, 0xe6, 0xec, 0x54, - 0x91, 0xe0, 0x03, 0x4f, 0x6d, 0x6d, 0x96, 0xc6, 0x17, 0xa9, 0xd1, 0x8c, 0x77, 0xa6, 0x20, 0x22, - 0x73, 0x30, 0x36, 0xe1, 0xf8, 0xc8, 0x67, 0x7e, 0x49, 0xf4, 0xe5, 0x89, 0x68, 0x58, 0x32, 0x6d, - 0xe0, 0x4f, 0x77, 0x96, 0x92, 0xfd, 0x97, 0x68, 0xaa, 0xfd, 0x49, 0x1e, 0x46, 0xd8, 0x40, 0x95, - 0xe6, 0xd4, 0x32, 0x5b, 0x1e, 0x18, 0x84, 0x7d, 0x8d, 0xd7, 0x32, 0xea, 0x54, 0x4c, 0xaf, 0xa8, - 0xda, 0x76, 0x00, 0x94, 0x45, 0xa0, 0xd0, 0x93, 0x8b, 0xd0, 0xcf, 0x41, 0xb3, 0x53, 0x62, 0xc6, - 0x1d, 0xde, 0xda, 0x2c, 0x0d, 0x78, 0x08, 0xab, 0x59, 0xa6, 0x1e, 0xa2, 0xd9, 0x94, 0xc7, 0xff, - 0x9e, 0x71, 0x3c, 0x9f, 0x31, 0x17, 0x13, 0x2e, 0xea, 0xb6, 0x68, 0xb0, 0x22, 0x50, 0xf2, 0x94, - 0x17, 0x6f, 0x44, 0x5e, 0x04, 0xe0, 0x90, 0xb2, 0x69, 0xba, 0x62, 0xd6, 0x45, 0x45, 0x16, 0x2c, - 0x0c, 0xd3, 0x94, 0xa7, 0x6c, 0x89, 0x98, 0x34, 0x61, 0x88, 0xff, 0x42, 0xcd, 0xe3, 0x53, 0xee, - 0xe0, 0x95, 0x0b, 0x61, 0x97, 0xc5, 0xa4, 0x73, 0x49, 0x26, 0xbd, 0x6a, 0xfb, 0xee, 0xc6, 0x44, - 0x49, 0xac, 0xd2, 0x67, 0xc4, 0xa3, 0x1a, 0x88, 0x93, 0xd7, 0x07, 0xb9, 0x0d, 0x5b, 0xbc, 0xa7, - 0x1d, 0x77, 0xdd, 0x70, 0x4d, 0x6a, 0x4e, 0x6c, 0xc8, 0x8b, 0xf7, 0x52, 0x00, 0xae, 0x2d, 0xca, - 0xf3, 0x91, 0x4c, 0xce, 0x66, 0x02, 0xce, 0xad, 0xda, 0x5e, 0xc4, 0x79, 0xa8, 0x2f, 0x21, 0x2d, - 0xaf, 0xbd, 0xa8, 0xce, 0x3d, 0xf1, 0x36, 0x6c, 0x7d, 0xe0, 0x80, 0xdb, 0xd4, 0x65, 0x2b, 0x3b, - 0x4e, 0xc5, 0x62, 0x7d, 0x10, 0x4c, 0xd6, 0x38, 0x26, 0xc9, 0x43, 0x34, 0x39, 0xfb, 0x0a, 0x8c, - 0x27, 0x44, 0x41, 0xc6, 0x20, 0xb7, 0x4a, 0x37, 0xb8, 0xba, 0xe8, 0xec, 0x4f, 0x72, 0x12, 0x7a, - 0xd7, 0xd8, 0x58, 0xe6, 0x6a, 0xa0, 0xf3, 0x1f, 0xef, 0xcd, 0xbe, 0x27, 0xc3, 0xcc, 0x10, 0x32, - 0xe9, 0xd8, 0x36, 0xad, 0xfb, 0xb2, 0x25, 0xf2, 0x02, 0x0c, 0xdc, 0x70, 0xea, 0x46, 0x03, 0xfb, - 0x91, 0xeb, 0x1d, 0x4e, 0x2b, 0xac, 0x03, 0x2f, 0x35, 0x18, 0x46, 0x9e, 0x56, 0x42, 0x52, 0xa6, - 0x00, 0x3a, 0x6d, 0x3a, 0x3e, 0xc5, 0x86, 0xd9, 0x48, 0x01, 0xb0, 0xa1, 0x8b, 0x28, 0x59, 0x01, - 0x22, 0x62, 0x72, 0x19, 0xfa, 0x2b, 0xcc, 0xf8, 0xaa, 0x3b, 0x0d, 0xa1, 0x7c, 0x68, 0x1f, 0xa0, - 0x41, 0x26, 0x4f, 0xe0, 0x01, 0x91, 0x36, 0x03, 0x23, 0x93, 0x0d, 0x8b, 0xda, 0xbe, 0xfc, 0xd6, - 0x6c, 0x1a, 0x28, 0x2f, 0x53, 0xdb, 0x97, 0xdf, 0x1a, 0x27, 0x0e, 0x83, 0x41, 0xd5, 0xc9, 0x10, - 0x49, 0xb5, 0x5f, 0xc8, 0xc1, 0x03, 0xd7, 0xdb, 0x8b, 0xd4, 0xb5, 0xa9, 0x4f, 0x3d, 0x31, 0x8b, - 0x84, 0x5c, 0x6f, 0xc2, 0x78, 0x02, 0x29, 0xb8, 0xa3, 0xf5, 0xb4, 0x1a, 0x22, 0x83, 0xc9, 0x49, - 0x5e, 0x82, 0x13, 0x4d, 0xc9, 0x0c, 0x8c, 0x46, 0x40, 0xf6, 0x12, 0x5e, 0x31, 0x8b, 0x13, 0xf7, - 0xc3, 0x5b, 0x9b, 0xa5, 0xb3, 0x12, 0x37, 0xf6, 0xda, 0xb2, 0x06, 0xab, 0xcd, 0xc8, 0x75, 0x18, - 0x8b, 0x40, 0xaf, 0xb9, 0x4e, 0xbb, 0xe5, 0x15, 0x73, 0xc8, 0x0a, 0xe7, 0x4b, 0x89, 0xd5, 0x32, - 0x22, 0xe5, 0x39, 0x48, 0x6d, 0x48, 0xbe, 0x22, 0x23, 0x73, 0x13, 0xa3, 0x30, 0x8f, 0xa3, 0xf0, - 0xdd, 0xc1, 0x28, 0xec, 0x28, 0xa4, 0x4b, 0x6a, 0x4b, 0x31, 0x28, 0x95, 0xd7, 0x48, 0x0c, 0xca, - 0xc4, 0x13, 0xcf, 0x4e, 0xc2, 0xa9, 0x54, 0x5e, 0xbb, 0xd2, 0xea, 0xdf, 0xcb, 0xc9, 0x5c, 0x2a, - 0x8e, 0x19, 0x76, 0xe6, 0xbc, 0xdc, 0x99, 0x15, 0xc7, 0xc4, 0x35, 0x26, 0x13, 0xcd, 0xdc, 0xd2, - 0xcb, 0xb6, 0x1c, 0x53, 0x5d, 0x65, 0x92, 0x6d, 0xc9, 0x47, 0xe1, 0x74, 0x02, 0xc8, 0xa7, 0x6b, - 0xae, 0xfd, 0xe7, 0xb7, 0x36, 0x4b, 0x5a, 0x0a, 0x57, 0x75, 0xf6, 0xee, 0xc0, 0x85, 0x18, 0x70, - 0x46, 0x92, 0xba, 0x63, 0xfb, 0x86, 0x65, 0x8b, 0xa5, 0x91, 0x8f, 0x92, 0x77, 0x6c, 0x6d, 0x96, - 0x1e, 0x93, 0x75, 0x30, 0xa0, 0x51, 0x5f, 0xbe, 0x13, 0x1f, 0x62, 0x42, 0x31, 0x05, 0x35, 0xdb, - 0x34, 0x96, 0x83, 0x6d, 0xd4, 0x85, 0xad, 0xcd, 0xd2, 0xe3, 0xa9, 0xcf, 0xb0, 0x18, 0x95, 0x6c, - 0xb6, 0x75, 0xe2, 0x44, 0x74, 0x20, 0x11, 0xee, 0xa6, 0x63, 0x52, 0xfc, 0x86, 0x5e, 0xe4, 0xaf, - 0x6d, 0x6d, 0x96, 0x1e, 0x96, 0xf8, 0xdb, 0x8e, 0x49, 0xd5, 0xd7, 0x4f, 0x69, 0xad, 0xfd, 0x4e, - 0x0e, 0x4e, 0xdf, 0xb1, 0x6c, 0xd3, 0x59, 0xf7, 0xa6, 0xa8, 0xb7, 0xea, 0x3b, 0xad, 0xb0, 0xa3, - 0x3f, 0x0c, 0xa7, 0xe2, 0x18, 0x61, 0x08, 0x8b, 0xce, 0x7e, 0x62, 0x6b, 0xb3, 0xf4, 0xe8, 0x3a, - 0x27, 0xa8, 0x99, 0x9c, 0x22, 0xb0, 0xa4, 0xa5, 0x87, 0xa6, 0xf3, 0x60, 0xab, 0x87, 0x80, 0x48, - 0xf3, 0x1c, 0xae, 0x1e, 0x01, 0x2b, 0x65, 0xa5, 0x93, 0xc9, 0xc9, 0x73, 0x50, 0x98, 0x72, 0x9a, - 0x86, 0x65, 0x8b, 0x1e, 0xc4, 0x19, 0x3f, 0x7c, 0x17, 0xc4, 0xc8, 0x26, 0x07, 0xa7, 0x65, 0xcf, - 0x14, 0x2f, 0x83, 0x7b, 0x2d, 0x69, 0x4b, 0x13, 0x34, 0x55, 0xf6, 0x5c, 0x32, 0x39, 0x69, 0xc3, - 0xb0, 0x78, 0x85, 0xd8, 0xfa, 0xfa, 0x4c, 0x30, 0xb2, 0xd3, 0xa5, 0x78, 0x29, 0xd6, 0x86, 0x8f, - 0x69, 0x7c, 0xdb, 0xe0, 0x33, 0x13, 0x03, 0x3a, 0xfe, 0x94, 0xb3, 0xaf, 0x02, 0x49, 0xb2, 0xd8, - 0xdd, 0x02, 0x95, 0x83, 0x87, 0xab, 0xe5, 0xb9, 0x1b, 0xb3, 0x66, 0xb0, 0x95, 0xa9, 0xb8, 0xce, - 0x9a, 0x65, 0x4a, 0x13, 0xf4, 0x22, 0x9c, 0x51, 0x50, 0x57, 0x71, 0xf7, 0x14, 0x6e, 0xa2, 0x51, - 0x7d, 0x83, 0x6d, 0x52, 0x4b, 0xd0, 0xd4, 0xf8, 0x16, 0x2b, 0x6e, 0xdf, 0x75, 0x62, 0xc4, 0x86, - 0xa1, 0x82, 0xaa, 0xae, 0x38, 0xae, 0x5f, 0x6f, 0xfb, 0xa2, 0xf7, 0x71, 0x18, 0x26, 0x9e, 0xe1, - 0x09, 0xa2, 0x2e, 0x8f, 0x08, 0xf8, 0x90, 0x8f, 0x67, 0x60, 0xac, 0xec, 0xfb, 0xae, 0xb5, 0xd8, - 0xf6, 0xe9, 0x9c, 0xd1, 0x6a, 0x59, 0xf6, 0x32, 0x4e, 0xe7, 0x83, 0x57, 0x5e, 0x0e, 0xcd, 0xa0, - 0xae, 0x92, 0xb8, 0xa4, 0x36, 0x97, 0x66, 0x61, 0x23, 0x40, 0xd5, 0x9a, 0x1c, 0x27, 0xcf, 0xc2, - 0x6a, 0x3b, 0x36, 0x0b, 0xa7, 0xf2, 0xda, 0x55, 0xd7, 0x7d, 0x26, 0x07, 0xe7, 0xe6, 0x57, 0x7d, - 0x43, 0xa7, 0x9e, 0xd3, 0x76, 0xeb, 0xd4, 0xbb, 0xd5, 0x32, 0x0d, 0x9f, 0x46, 0x93, 0x71, 0x09, - 0x7a, 0xcb, 0xa6, 0x49, 0x4d, 0x64, 0xd7, 0xcb, 0x8f, 0x7b, 0x0c, 0x06, 0xd0, 0x39, 0x9c, 0x3c, - 0x01, 0x7d, 0xa2, 0x0d, 0x72, 0xef, 0x9d, 0x18, 0xdc, 0xda, 0x2c, 0xf5, 0xb5, 0x39, 0x48, 0x0f, - 0x70, 0x8c, 0x6c, 0x8a, 0x36, 0x28, 0x23, 0xcb, 0x45, 0x64, 0x26, 0x07, 0xe9, 0x01, 0x8e, 0x7c, - 0x00, 0x46, 0x90, 0x6d, 0xf8, 0x3e, 0x62, 0x79, 0x3b, 0x19, 0x48, 0x57, 0x7e, 0x59, 0x6e, 0x7d, - 0xe0, 0xdb, 0xd4, 0xdc, 0xa0, 0x81, 0xae, 0x30, 0x20, 0x77, 0x60, 0x4c, 0xbc, 0x44, 0xc4, 0xb4, - 0xb7, 0x0b, 0x53, 0xdc, 0x56, 0x88, 0xf7, 0x97, 0xd8, 0x26, 0x98, 0x30, 0xc6, 0xe2, 0xb5, 0x23, - 0xc6, 0x85, 0xed, 0x18, 0x8b, 0x2f, 0x96, 0x19, 0xab, 0x4c, 0xb4, 0x0f, 0xc2, 0x90, 0xdc, 0x90, - 0x9c, 0xc6, 0x23, 0x35, 0x3e, 0x4e, 0xf0, 0x30, 0xce, 0x32, 0xf1, 0x1c, 0xed, 0x19, 0x9c, 0xe2, - 0xea, 0xae, 0xd5, 0x62, 0x86, 0xa1, 0x50, 0xf2, 0xd1, 0xad, 0xcd, 0xd2, 0xa0, 0x19, 0x81, 0x75, - 0x99, 0x46, 0xfb, 0xcf, 0x19, 0x38, 0xcd, 0x78, 0x97, 0x3d, 0xcf, 0x5a, 0xb6, 0x9b, 0xb2, 0x65, - 0xf6, 0x24, 0x14, 0xaa, 0xf8, 0x3c, 0xf1, 0xa4, 0x93, 0x5b, 0x9b, 0xa5, 0x31, 0xfe, 0x06, 0xf2, - 0x54, 0xc7, 0x69, 0xc2, 0xf3, 0xa4, 0xec, 0x36, 0xe7, 0x49, 0x6c, 0xd7, 0xe2, 0x1b, 0xae, 0x6f, - 0xd9, 0xcb, 0x55, 0xdf, 0xf0, 0xdb, 0x5e, 0x6c, 0xd7, 0x22, 0x30, 0x35, 0x0f, 0x51, 0xb1, 0x5d, - 0x4b, 0xac, 0x11, 0x79, 0x05, 0x86, 0xae, 0xda, 0x66, 0xc4, 0x84, 0x4f, 0xad, 0x0f, 0xb2, 0xcd, - 0x04, 0x45, 0x78, 0x92, 0x45, 0xac, 0x81, 0xf6, 0xed, 0x59, 0x28, 0xf2, 0xc3, 0x9f, 0x1b, 0x96, - 0xe7, 0xcf, 0xd1, 0xe6, 0xa2, 0x34, 0x3b, 0x4d, 0x07, 0xa7, 0x49, 0x0c, 0x27, 0x99, 0x1b, 0x68, - 0xed, 0x89, 0xd3, 0xa4, 0x86, 0xe5, 0x25, 0x8e, 0x1d, 0x94, 0x56, 0x64, 0x16, 0xfa, 0x38, 0x67, - 0x6e, 0x2e, 0x0e, 0x5e, 0x29, 0x06, 0x8a, 0xa0, 0x3e, 0x9a, 0x2b, 0x43, 0x93, 0x13, 0xcb, 0x07, - 0x19, 0xa2, 0x3d, 0xdb, 0x66, 0x47, 0x6d, 0x16, 0x2c, 0xbf, 0x11, 0xac, 0xf3, 0x7c, 0xa6, 0x90, - 0xde, 0xc9, 0x67, 0x48, 0xd9, 0x04, 0x55, 0xda, 0x91, 0x77, 0xc2, 0xb8, 0x4c, 0x8f, 0x9b, 0x5e, - 0xbe, 0xa8, 0xeb, 0xa3, 0x46, 0x48, 0x8b, 0xbb, 0x5d, 0xed, 0xf3, 0x39, 0x18, 0x53, 0xdf, 0x95, - 0xdc, 0x81, 0xfe, 0x6b, 0x8e, 0x65, 0x53, 0x73, 0xde, 0x46, 0xc1, 0x74, 0x3f, 0x8b, 0x0d, 0x76, - 0x79, 0x27, 0xde, 0xc0, 0x36, 0x35, 0x79, 0x6f, 0x84, 0x47, 0xb3, 0x21, 0x33, 0xf2, 0x21, 0x18, - 0x60, 0xbb, 0x8b, 0x35, 0xe4, 0x9c, 0xdd, 0x96, 0xf3, 0x23, 0x82, 0xf3, 0x49, 0x97, 0x37, 0x4a, - 0xb2, 0x8e, 0xd8, 0x31, 0x75, 0xd6, 0xa9, 0xe1, 0x39, 0xc1, 0x0a, 0x8e, 0xea, 0xec, 0x22, 0x44, - 0x56, 0x67, 0x4e, 0xc3, 0x36, 0x45, 0xfc, 0x63, 0xb1, 0xf7, 0xa5, 0x5d, 0x31, 0xef, 0x22, 0xb5, - 0xe3, 0x25, 0x62, 0x62, 0xc3, 0xa8, 0xe8, 0xc7, 0x15, 0xab, 0x85, 0xfb, 0x49, 0x14, 0xee, 0xc8, - 0x95, 0xf3, 0x97, 0x82, 0x33, 0x97, 0x4b, 0xd2, 0x09, 0xfe, 0xda, 0x33, 0x97, 0xe6, 0x42, 0x72, - 0x3c, 0x08, 0xc3, 0xa1, 0xa0, 0xb0, 0x90, 0x95, 0xac, 0x19, 0x23, 0x27, 0x8f, 0xc2, 0x90, 0x78, - 0x29, 0xde, 0x93, 0xb8, 0x2f, 0xd6, 0x07, 0x39, 0x8c, 0xf7, 0xe2, 0x57, 0x65, 0xe1, 0xa9, 0xa8, - 0x17, 0x75, 0xba, 0x66, 0xd1, 0xf5, 0xe8, 0xa1, 0xe2, 0xd4, 0x8e, 0x0d, 0x7e, 0x6f, 0x72, 0xc5, - 0xb0, 0x97, 0xa9, 0x49, 0x2e, 0x42, 0x2f, 0x3f, 0x9f, 0xca, 0xe0, 0xde, 0x04, 0x27, 0x56, 0xf5, - 0x68, 0x8a, 0x53, 0x10, 0x07, 0x0a, 0xe2, 0x44, 0x8a, 0xeb, 0x78, 0x39, 0xa9, 0xe3, 0x3b, 0x78, - 0xa2, 0x38, 0x73, 0xe2, 0xab, 0x1f, 0xf6, 0x4d, 0xe2, 0xd4, 0x49, 0x3c, 0xe6, 0xec, 0x8b, 0x30, - 0x28, 0x11, 0xef, 0x6a, 0x79, 0xfb, 0xa1, 0x5e, 0x79, 0xd4, 0x07, 0xaf, 0x25, 0x46, 0xfd, 0x65, - 0x36, 0x5a, 0x3d, 0x8f, 0x99, 0xd0, 0x99, 0xe8, 0x40, 0xa9, 0xc9, 0x41, 0xf1, 0x31, 0x89, 0x20, - 0xf2, 0x2c, 0xf4, 0x73, 0x16, 0xe1, 0x61, 0x0d, 0x1e, 0xf4, 0xb8, 0x08, 0x8b, 0x1b, 0x29, 0x21, - 0x21, 0xf9, 0xce, 0x0c, 0x3c, 0xd4, 0x55, 0x12, 0xa8, 0x9f, 0x83, 0x57, 0x9e, 0xdf, 0x93, 0x18, - 0x27, 0x9e, 0xda, 0xda, 0x2c, 0x5d, 0x94, 0x94, 0xc7, 0x95, 0x68, 0x6a, 0x75, 0x4e, 0x24, 0xbd, - 0x57, 0xf7, 0x57, 0x61, 0x3b, 0x25, 0xfe, 0xd0, 0x69, 0x3c, 0x3c, 0xb7, 0xeb, 0x1b, 0xc1, 0x4b, - 0xe6, 0xa3, 0x9d, 0x92, 0xf8, 0xde, 0xa5, 0x80, 0x24, 0xe5, 0x31, 0x1d, 0xb8, 0x90, 0x3a, 0x9c, - 0xe1, 0x98, 0x29, 0x63, 0x63, 0x7e, 0x69, 0xce, 0xb1, 0xfd, 0x95, 0xe0, 0x01, 0xbd, 0xf2, 0xe9, - 0x33, 0x3e, 0xc0, 0x34, 0x36, 0x6a, 0xce, 0x52, 0xad, 0xc9, 0xa8, 0x52, 0x9e, 0xd1, 0x89, 0x13, - 0x5b, 0x72, 0xc4, 0x34, 0x10, 0x4c, 0xc6, 0x85, 0xe8, 0x6e, 0x20, 0x98, 0x3a, 0x92, 0x53, 0xaf, - 0xd2, 0x28, 0x6d, 0x06, 0xee, 0xdb, 0xe3, 0x0c, 0xfc, 0x1c, 0x9c, 0xc6, 0xb1, 0xca, 0x96, 0xfd, - 0xd8, 0xe3, 0x8b, 0xfd, 0xec, 0xcd, 0xf4, 0x93, 0x1c, 0x1b, 0x7f, 0x01, 0x6d, 0x16, 0x86, 0x6e, - 0x38, 0xf5, 0xd5, 0x50, 0x5f, 0x5f, 0x84, 0xc2, 0x82, 0xe1, 0x2e, 0x53, 0x1f, 0x3b, 0x63, 0xf0, - 0xca, 0xf8, 0x25, 0x7e, 0xe1, 0xc7, 0x88, 0x38, 0x62, 0x62, 0x44, 0xcc, 0x90, 0x05, 0x1f, 0x7f, - 0xeb, 0xa2, 0x81, 0xf6, 0x63, 0x05, 0x18, 0x12, 0x97, 0x53, 0xb8, 0xb0, 0x92, 0xf7, 0x46, 0xd7, - 0x7d, 0x62, 0x4a, 0x0f, 0x4f, 0x5e, 0xc3, 0x8b, 0x85, 0x21, 0xc6, 0xec, 0x97, 0x36, 0x4b, 0x99, - 0xad, 0xcd, 0x52, 0x8f, 0xde, 0x2f, 0x1d, 0xe1, 0x44, 0x4b, 0xbf, 0x64, 0xeb, 0xc8, 0xd7, 0x4d, - 0x4a, 0x5b, 0x6e, 0x0a, 0xbc, 0x02, 0x7d, 0xe2, 0x1d, 0x84, 0xca, 0x9f, 0x89, 0x4e, 0x0e, 0x63, - 0x97, 0x6c, 0x4a, 0xeb, 0xa0, 0x15, 0x79, 0x19, 0x0a, 0xfc, 0x24, 0x4d, 0x08, 0xe0, 0x74, 0xfa, - 0xc9, 0xa3, 0xd2, 0x5c, 0xb4, 0x21, 0x33, 0x00, 0xd1, 0x29, 0x5a, 0x78, 0xa7, 0x28, 0x38, 0x24, - 0xcf, 0xd7, 0x14, 0x2e, 0x52, 0x5b, 0xf2, 0x02, 0x0c, 0x2d, 0x50, 0xb7, 0x69, 0xd9, 0x46, 0xa3, - 0x6a, 0xbd, 0x19, 0x5c, 0x2b, 0xa2, 0x0d, 0xe4, 0x59, 0x6f, 0xca, 0x9a, 0x10, 0xa3, 0x23, 0x1f, - 0x49, 0x3b, 0xa5, 0xea, 0xc3, 0x17, 0x79, 0x74, 0xdb, 0xe3, 0x1b, 0xe5, 0x7d, 0x52, 0x0e, 0xad, - 0x3e, 0x00, 0xc3, 0xb1, 0x03, 0x0a, 0x71, 0x6f, 0xf4, 0x50, 0x92, 0xb5, 0x74, 0xda, 0xa2, 0xb0, - 0x8d, 0x73, 0x60, 0x43, 0x69, 0xd6, 0xb6, 0x7c, 0xcb, 0x68, 0x4c, 0x3a, 0xcd, 0xa6, 0x61, 0x9b, - 0xc5, 0x81, 0x68, 0x28, 0x59, 0x1c, 0x53, 0xab, 0x73, 0x94, 0x3c, 0x94, 0xe2, 0x8d, 0xc8, 0x75, - 0x18, 0x13, 0x7d, 0xa8, 0xd3, 0xba, 0xe3, 0x32, 0xb3, 0x0c, 0xaf, 0x85, 0xc4, 0x58, 0xf2, 0x38, - 0xae, 0xe6, 0x06, 0x48, 0x79, 0xdf, 0xa3, 0x36, 0x64, 0xd3, 0xf6, 0xac, 0xbd, 0x66, 0xb1, 0x9d, - 0xc4, 0x10, 0xbe, 0x0c, 0x4e, 0xdb, 0x16, 0x07, 0xc9, 0xd3, 0xb6, 0xa0, 0x92, 0x2c, 0x81, 0xe1, - 0xed, 0x2d, 0x81, 0x6b, 0xf9, 0xfe, 0xc1, 0xb1, 0x21, 0xf5, 0xa2, 0x50, 0xfb, 0x07, 0x39, 0x18, - 0x14, 0x6f, 0xc2, 0xac, 0x97, 0xe3, 0xf1, 0xb3, 0x9f, 0xf1, 0x93, 0x3a, 0x0e, 0x0a, 0x07, 0x35, - 0x0e, 0xb4, 0x4f, 0x64, 0xc3, 0xc9, 0xae, 0xe2, 0x5a, 0xf6, 0xfe, 0x26, 0xbb, 0xf3, 0x00, 0x93, - 0x2b, 0x6d, 0x7b, 0x95, 0x3b, 0x44, 0x64, 0x23, 0x87, 0x88, 0xba, 0xa5, 0x4b, 0x18, 0xf2, 0x10, - 0xe4, 0xa7, 0x18, 0x7f, 0xd6, 0x33, 0x43, 0x13, 0x03, 0x5f, 0xe0, 0x9c, 0x32, 0x4f, 0xe9, 0x08, - 0x66, 0xdb, 0xe8, 0x89, 0x0d, 0x9f, 0xf2, 0x8d, 0x4b, 0x8e, 0x6f, 0xa3, 0x17, 0x19, 0x40, 0xe7, - 0x70, 0xf2, 0x1c, 0x8c, 0x4f, 0xd1, 0x86, 0xb1, 0x31, 0x67, 0x35, 0x1a, 0x96, 0x47, 0xeb, 0x8e, - 0x6d, 0x7a, 0x28, 0x64, 0xf1, 0xb8, 0xa6, 0xa7, 0x27, 0x09, 0x88, 0x06, 0x85, 0xf9, 0xa5, 0x25, - 0x8f, 0xfa, 0x28, 0xbe, 0xdc, 0x04, 0xb0, 0xb9, 0xdf, 0x41, 0x88, 0x2e, 0x30, 0xda, 0xbf, 0xcb, - 0xb0, 0x7d, 0x2a, 0x1e, 0xf0, 0x44, 0x83, 0x68, 0x3f, 0x22, 0xb9, 0x18, 0xd9, 0x4d, 0x59, 0xfc, - 0xda, 0x51, 0xf1, 0xb5, 0x7d, 0xc2, 0x76, 0x8a, 0x2c, 0xa6, 0xd4, 0xaf, 0xca, 0x6d, 0xf7, 0x55, - 0x2f, 0xc0, 0xe0, 0xc2, 0x54, 0x65, 0x22, 0x78, 0x48, 0x1e, 0x1f, 0x72, 0x52, 0x3c, 0x64, 0xc8, - 0x37, 0x5b, 0x8b, 0xb5, 0xe0, 0x49, 0x32, 0xa1, 0xf6, 0xed, 0x39, 0x38, 0x23, 0xbe, 0x74, 0xb2, - 0x61, 0xb5, 0x16, 0x1d, 0xc3, 0x35, 0x75, 0x5a, 0xa7, 0xd6, 0x1a, 0x3d, 0x9a, 0x03, 0x36, 0x3e, - 0xe4, 0xf2, 0xfb, 0x18, 0x72, 0x57, 0xe2, 0xa7, 0xa1, 0xdc, 0xd8, 0x1a, 0x63, 0xe2, 0x94, 0x4f, - 0x43, 0xe3, 0x67, 0xa0, 0x1a, 0x14, 0x6e, 0x50, 0x7b, 0xd9, 0x5f, 0x41, 0xe5, 0xea, 0xe5, 0xca, - 0xd5, 0x40, 0x88, 0x2e, 0x30, 0x12, 0x5f, 0xdc, 0x38, 0xf5, 0x25, 0xf9, 0xb2, 0x9d, 0x93, 0x2e, - 0x13, 0x69, 0xdf, 0x9a, 0x83, 0x93, 0x6a, 0x37, 0x55, 0xa9, 0x6d, 0x1e, 0xf7, 0xd1, 0xbd, 0xd3, - 0x47, 0xdf, 0x91, 0x87, 0x07, 0x83, 0x03, 0xf5, 0x15, 0xc3, 0xa5, 0xe6, 0x94, 0xe5, 0xd2, 0xba, - 0xef, 0xb8, 0x1b, 0x47, 0xd8, 0x7e, 0x3c, 0xb8, 0xae, 0x7a, 0x0e, 0x0a, 0xe2, 0x20, 0x8a, 0xaf, - 0x83, 0x23, 0xe1, 0x9b, 0x20, 0x34, 0xb1, 0x82, 0xf2, 0x43, 0x2c, 0xa5, 0x83, 0x0b, 0x3b, 0xe9, - 0xe0, 0xf7, 0xc0, 0x70, 0x28, 0x7a, 0xa9, 0xfb, 0xd0, 0xd8, 0x34, 0x03, 0x04, 0xef, 0xc0, 0x38, - 0x21, 0x3e, 0x2d, 0x00, 0xcc, 0x4e, 0xa1, 0x31, 0x38, 0x2c, 0x9e, 0x16, 0xb6, 0xb3, 0x4c, 0x5d, - 0x26, 0x52, 0x55, 0x65, 0x60, 0x27, 0xaa, 0xf2, 0x99, 0x5e, 0x38, 0x9b, 0xae, 0x2a, 0x3a, 0x35, - 0xcc, 0x63, 0x4d, 0x79, 0x7b, 0x6a, 0xca, 0xa3, 0x90, 0xaf, 0x18, 0xfe, 0x8a, 0x50, 0x11, 0x74, - 0x5a, 0x59, 0xb2, 0x1a, 0xb4, 0xd6, 0x32, 0xfc, 0x15, 0x1d, 0x51, 0xd2, 0xdc, 0x04, 0xc8, 0x31, - 0x6d, 0x6e, 0x8a, 0x0c, 0x98, 0xc1, 0x47, 0x32, 0x17, 0xf2, 0x69, 0x06, 0x8c, 0xaa, 0x94, 0x43, - 0x3b, 0x51, 0xca, 0xcf, 0xf6, 0x76, 0x9a, 0xbf, 0xee, 0xb8, 0x96, 0x4f, 0x8f, 0xb5, 0xf2, 0x58, - 0x2b, 0x0f, 0x41, 0x2b, 0x7f, 0x25, 0x0b, 0xc3, 0xe1, 0x7e, 0xf6, 0x0d, 0x5a, 0x3f, 0x9c, 0x75, - 0x34, 0xda, 0x06, 0xe6, 0xf6, 0xbd, 0x0d, 0xdc, 0x8f, 0x12, 0x6a, 0xe1, 0xbe, 0x9c, 0x9b, 0x3a, - 0x28, 0x65, 0xbe, 0x2f, 0x0f, 0xcf, 0xe5, 0x1f, 0x85, 0xbe, 0x39, 0xe3, 0xae, 0xd5, 0x6c, 0x37, - 0xc5, 0x0e, 0x07, 0x9d, 0xad, 0x9b, 0xc6, 0x5d, 0x3d, 0x80, 0x6b, 0xff, 0x2a, 0x03, 0x23, 0x42, - 0xa8, 0x82, 0xf9, 0xbe, 0xa4, 0x1a, 0x49, 0x27, 0xbb, 0x6f, 0xe9, 0xe4, 0xf6, 0x2e, 0x1d, 0xed, - 0x6f, 0xe5, 0xa0, 0x38, 0x6d, 0x35, 0xe8, 0x82, 0x6b, 0xd8, 0xde, 0x12, 0x75, 0xc5, 0x51, 0xc4, - 0x55, 0xc6, 0x6a, 0x5f, 0x1f, 0x28, 0x4d, 0x43, 0xd9, 0x3d, 0x4d, 0x43, 0xef, 0x82, 0x01, 0xf1, - 0x32, 0xa1, 0xa3, 0x3f, 0x8e, 0x34, 0x37, 0x00, 0xea, 0x11, 0x9e, 0x11, 0x97, 0x5b, 0x2d, 0xd7, - 0x59, 0xa3, 0x2e, 0xbf, 0xcb, 0x15, 0xc4, 0x46, 0x00, 0xd4, 0x23, 0xbc, 0xc4, 0x99, 0x06, 0xf6, - 0xaf, 0xcc, 0x99, 0xba, 0x7a, 0x84, 0x27, 0x17, 0xa0, 0xff, 0x86, 0x53, 0x47, 0xf7, 0x56, 0x31, - 0x15, 0x0d, 0x6d, 0x6d, 0x96, 0xfa, 0x1b, 0x02, 0xa6, 0x87, 0x58, 0x46, 0x39, 0xe5, 0xac, 0xdb, - 0x0d, 0xc7, 0xe0, 0x5e, 0x80, 0xfd, 0x9c, 0xd2, 0x14, 0x30, 0x3d, 0xc4, 0x32, 0x4a, 0x26, 0x73, - 0xf4, 0xae, 0xec, 0x8f, 0x78, 0x2e, 0x09, 0x98, 0x1e, 0x62, 0xb5, 0xef, 0xcd, 0x33, 0xed, 0xf5, - 0xac, 0x37, 0xef, 0xfb, 0xb5, 0x24, 0x1a, 0x30, 0xbd, 0x7b, 0x18, 0x30, 0xf7, 0xcd, 0x59, 0xaa, - 0xf6, 0x27, 0x7d, 0x00, 0x42, 0xfa, 0x57, 0x8f, 0x37, 0xbb, 0xfb, 0xd3, 0x9a, 0x29, 0x18, 0xbf, - 0x6a, 0xaf, 0x18, 0x76, 0x9d, 0x9a, 0xd1, 0x89, 0x72, 0x01, 0x87, 0x36, 0x86, 0x08, 0x50, 0x81, - 0x8c, 0x8e, 0x94, 0xf5, 0x64, 0x03, 0xf2, 0x0c, 0x0c, 0xce, 0xda, 0x3e, 0x75, 0x8d, 0xba, 0x6f, - 0xad, 0x51, 0x31, 0x35, 0xa0, 0xff, 0x84, 0x15, 0x81, 0x75, 0x99, 0x86, 0x3c, 0x07, 0x43, 0x15, - 0xc3, 0xf5, 0xad, 0xba, 0xd5, 0x32, 0x6c, 0x5f, 0xdc, 0xdf, 0xf0, 0x65, 0xbf, 0x25, 0xc1, 0xf5, - 0x18, 0x15, 0xf9, 0x08, 0x0c, 0xe0, 0xb6, 0x19, 0xa3, 0x99, 0x06, 0xb6, 0xbd, 0xe7, 0x7e, 0x2c, - 0xf2, 0x93, 0xe6, 0x07, 0xe3, 0xe8, 0x27, 0xa1, 0x5e, 0x75, 0x87, 0x1c, 0xc9, 0xeb, 0xd0, 0x77, - 0xd5, 0x36, 0x91, 0x39, 0x6c, 0xcb, 0x5c, 0x13, 0xcc, 0x4f, 0x47, 0xcc, 0x9d, 0x96, 0xc2, 0x3b, - 0x60, 0x97, 0x3e, 0xca, 0x06, 0xdf, 0xba, 0x51, 0x36, 0xf4, 0x16, 0xdc, 0x58, 0x0c, 0x1f, 0xd4, - 0x8d, 0xc5, 0xc8, 0x1e, 0x6f, 0x2c, 0xb4, 0x1f, 0xc8, 0xc0, 0xe0, 0x44, 0x65, 0x3a, 0x1c, 0xbe, - 0x0f, 0x40, 0xae, 0x22, 0x1c, 0x7a, 0xf2, 0xdc, 0xa0, 0x69, 0x59, 0xa6, 0xce, 0x60, 0xe4, 0x22, - 0xf4, 0x4f, 0xa2, 0x23, 0xb0, 0xb8, 0x62, 0xce, 0xf3, 0x05, 0xb0, 0x8e, 0x30, 0x8c, 0x07, 0x08, - 0xd0, 0xe4, 0x09, 0xe8, 0xab, 0xb8, 0xce, 0xb2, 0x6b, 0x34, 0xc5, 0x22, 0x8c, 0x1e, 0x55, 0x2d, - 0x0e, 0xd2, 0x03, 0x1c, 0x79, 0x19, 0x46, 0xca, 0x6d, 0xd3, 0xf2, 0xa3, 0xa8, 0xb5, 0x3c, 0xda, - 0xbd, 0x78, 0x0b, 0x62, 0x30, 0x4c, 0x2d, 0xf8, 0x1a, 0xcb, 0xd4, 0x15, 0x5a, 0xed, 0xb3, 0x99, - 0x60, 0xa7, 0xc0, 0x9e, 0x57, 0x6d, 0xe3, 0xad, 0x08, 0xbe, 0x79, 0x3f, 0x7f, 0x9e, 0xc7, 0x41, - 0x7a, 0x80, 0x23, 0x17, 0xa1, 0xf7, 0xaa, 0xeb, 0x3a, 0xae, 0x1c, 0x40, 0x46, 0x19, 0x40, 0xf6, - 0x24, 0x40, 0x0a, 0xf2, 0x6e, 0x18, 0xe4, 0x53, 0x16, 0x3f, 0xe7, 0xcd, 0x75, 0xbb, 0x84, 0x97, - 0x29, 0xb5, 0xcf, 0xe7, 0x24, 0x93, 0x8f, 0x77, 0xd8, 0x7d, 0x78, 0x21, 0xf3, 0x2c, 0xe4, 0x26, - 0x2a, 0xd3, 0x62, 0xfe, 0x3c, 0x11, 0x34, 0x95, 0x14, 0x4d, 0x69, 0xc7, 0xa8, 0xc9, 0x39, 0xc8, - 0x57, 0x98, 0xf2, 0x15, 0x50, 0xb9, 0xfa, 0xb7, 0x36, 0x4b, 0xf9, 0x16, 0xd3, 0x3e, 0x84, 0x22, - 0x96, 0xed, 0x9f, 0xf8, 0x26, 0x8d, 0x63, 0xa3, 0xad, 0xd3, 0x39, 0xc8, 0x97, 0xdd, 0xe5, 0x35, - 0x31, 0xe9, 0x21, 0xd6, 0x70, 0x97, 0xd7, 0x74, 0x84, 0x92, 0xcb, 0x00, 0x3a, 0xf5, 0xdb, 0xae, - 0x8d, 0xb1, 0x9d, 0x03, 0x78, 0x1c, 0x89, 0x93, 0xa9, 0x8b, 0xd0, 0x5a, 0xdd, 0x31, 0xa9, 0x2e, - 0x91, 0x68, 0x7f, 0x2f, 0xba, 0x53, 0x9b, 0xb2, 0xbc, 0xd5, 0xe3, 0x2e, 0xdc, 0x45, 0x17, 0x1a, - 0xe2, 0xc4, 0x37, 0xd9, 0x49, 0x25, 0xe8, 0x9d, 0x6e, 0x18, 0xcb, 0x1e, 0xf6, 0xa1, 0x70, 0xd8, - 0x5c, 0x62, 0x00, 0x9d, 0xc3, 0x95, 0x7e, 0xea, 0xdf, 0xbe, 0x9f, 0x3e, 0xd7, 0x1b, 0x8e, 0xb6, - 0x9b, 0xd4, 0x5f, 0x77, 0xdc, 0xe3, 0xae, 0xda, 0x69, 0x57, 0x9d, 0x87, 0xbe, 0xaa, 0x5b, 0x97, - 0x4e, 0x4b, 0x70, 0x3b, 0xe1, 0xb9, 0x75, 0x7e, 0x52, 0x12, 0x20, 0x19, 0xdd, 0x94, 0xe7, 0x23, - 0x5d, 0x5f, 0x44, 0x67, 0x7a, 0xbe, 0xa0, 0x13, 0x48, 0x41, 0x57, 0x71, 0x5c, 0x5f, 0x74, 0x5c, - 0x48, 0xd7, 0x72, 0x5c, 0x5f, 0x0f, 0x90, 0xe4, 0x5d, 0x00, 0x0b, 0x93, 0x95, 0x20, 0x68, 0x69, - 0x20, 0x72, 0xb8, 0x15, 0xd1, 0x4a, 0xba, 0x84, 0x26, 0x0b, 0x30, 0x30, 0xdf, 0xa2, 0x2e, 0xdf, - 0x49, 0xf1, 0x68, 0xcd, 0x77, 0x28, 0xa2, 0x15, 0xfd, 0x7e, 0x49, 0xfc, 0x1f, 0x92, 0xf3, 0xd5, - 0xc9, 0x09, 0x7e, 0xea, 0x11, 0x23, 0xf2, 0x6e, 0x28, 0x94, 0xb9, 0x99, 0x38, 0x88, 0x2c, 0x43, - 0x91, 0xe1, 0x0e, 0x96, 0xa3, 0xf8, 0x96, 0xdf, 0xc0, 0xbf, 0x75, 0x41, 0xae, 0x5d, 0x84, 0x31, - 0xf5, 0x31, 0x64, 0x10, 0xfa, 0x26, 0xe7, 0x6f, 0xde, 0xbc, 0x3a, 0xb9, 0x30, 0xd6, 0x43, 0xfa, - 0x21, 0x5f, 0xbd, 0x7a, 0x73, 0x6a, 0x2c, 0xa3, 0x7d, 0xb7, 0x34, 0x83, 0x30, 0xd5, 0x3a, 0xbe, - 0x95, 0xdf, 0xd7, 0xf5, 0xd3, 0x18, 0x5e, 0x45, 0xe3, 0x81, 0x43, 0xd3, 0xf2, 0x7d, 0x6a, 0x8a, - 0x55, 0x02, 0xaf, 0x6a, 0xfd, 0xbb, 0x7a, 0x02, 0x4f, 0x9e, 0x84, 0x61, 0x84, 0x89, 0x5b, 0x56, - 0xbe, 0xbd, 0x16, 0x0d, 0xdc, 0xbb, 0x7a, 0x1c, 0xa9, 0xfd, 0x6c, 0x74, 0x31, 0x7f, 0x83, 0x1a, - 0x47, 0xf5, 0x52, 0xf6, 0x1e, 0xe9, 0x2f, 0xed, 0x87, 0x7a, 0x79, 0x28, 0x1d, 0x0f, 0xc6, 0x3f, - 0x0c, 0x51, 0x46, 0xa7, 0xc8, 0xb9, 0x5d, 0x9c, 0x22, 0x3f, 0x09, 0x85, 0x39, 0xea, 0xaf, 0x38, - 0x81, 0x4f, 0x21, 0x1a, 0xa2, 0x4d, 0x84, 0xc8, 0xee, 0x38, 0x9c, 0x86, 0xac, 0x02, 0x09, 0x22, - 0xed, 0xc3, 0x68, 0x87, 0xe0, 0xd4, 0xfa, 0x4c, 0x62, 0x9b, 0x53, 0xc5, 0x7c, 0x1c, 0x18, 0xab, - 0x74, 0x32, 0x8c, 0xa6, 0x90, 0x9c, 0xfc, 0xfe, 0x6c, 0xb3, 0x54, 0xe0, 0x34, 0x7a, 0x0a, 0x5b, - 0xf2, 0x01, 0x18, 0x98, 0x9b, 0x2e, 0x8b, 0xa8, 0x7b, 0xee, 0x90, 0xf2, 0x40, 0x28, 0xc5, 0x00, - 0x11, 0x8a, 0x04, 0xe3, 0x16, 0x9b, 0x4b, 0x46, 0x32, 0xe8, 0x3e, 0xe2, 0xc2, 0xb4, 0x85, 0x47, - 0x40, 0x8a, 0xc3, 0x89, 0x50, 0x5b, 0xe2, 0x71, 0x91, 0xaa, 0xac, 0x38, 0x56, 0xd1, 0x96, 0xfe, - 0x7d, 0x8c, 0xee, 0x79, 0x18, 0x2f, 0xb7, 0x5a, 0x0d, 0x8b, 0x9a, 0xa8, 0x2f, 0x7a, 0xbb, 0x41, - 0x3d, 0xe1, 0xcc, 0x85, 0x41, 0x75, 0x06, 0x47, 0xd6, 0x30, 0xd7, 0x43, 0xcd, 0x6d, 0xc7, 0x5d, - 0x7f, 0x93, 0x6d, 0x31, 0xb5, 0x06, 0x67, 0xef, 0xb8, 0xb3, 0x53, 0xc2, 0x9d, 0x8b, 0xa7, 0xd6, - 0x08, 0xc0, 0x71, 0x97, 0x58, 0x99, 0x5c, 0xfb, 0x9d, 0x2c, 0x9c, 0x9e, 0x74, 0xa9, 0xe1, 0xd3, - 0xb9, 0xe9, 0x72, 0xb9, 0x8d, 0xce, 0x9b, 0x8d, 0x06, 0xb5, 0x97, 0x0f, 0x67, 0x52, 0x78, 0x09, - 0x46, 0xc2, 0x17, 0x40, 0x17, 0x6a, 0x39, 0xbc, 0xb5, 0x1e, 0x60, 0xb8, 0xc3, 0xb5, 0xae, 0x90, - 0x92, 0xeb, 0x70, 0x22, 0x84, 0x94, 0x1b, 0x0d, 0x67, 0x5d, 0xa7, 0x6d, 0x8f, 0xbb, 0xad, 0xf4, - 0x73, 0x27, 0xf2, 0x88, 0x83, 0xc1, 0xf0, 0x35, 0x97, 0x11, 0xe8, 0x69, 0xad, 0xc8, 0x0c, 0xf4, - 0x4f, 0x37, 0x9c, 0x75, 0xcc, 0xb0, 0xd2, 0x1b, 0x5f, 0x38, 0xe7, 0xa6, 0xcb, 0x01, 0x8a, 0x3b, - 0x1e, 0x2f, 0x31, 0x5e, 0xfe, 0x46, 0x4b, 0x56, 0xbd, 0xb0, 0xb5, 0xb6, 0x95, 0x83, 0x33, 0xb7, - 0x8d, 0x86, 0x65, 0x46, 0x42, 0xd6, 0xa9, 0xd7, 0x72, 0x6c, 0x8f, 0x1e, 0xa1, 0xd9, 0x22, 0x36, - 0x24, 0xf3, 0x07, 0x32, 0x24, 0x93, 0x9d, 0xdd, 0xbb, 0xef, 0xce, 0x2e, 0xec, 0xbb, 0xb3, 0xfb, - 0xf6, 0xd5, 0xd9, 0x9f, 0xcb, 0xc2, 0x58, 0x10, 0x2f, 0x24, 0xe7, 0x7c, 0x91, 0x82, 0x59, 0xf0, - 0x4c, 0x55, 0x89, 0x63, 0x40, 0x3c, 0xa9, 0x42, 0xdf, 0xd5, 0xbb, 0x2d, 0xcb, 0xa5, 0xde, 0x0e, - 0x82, 0x30, 0x1e, 0x12, 0xe7, 0x47, 0xe3, 0x94, 0x37, 0x49, 0x1c, 0x1d, 0x71, 0x30, 0x06, 0x7a, - 0xf3, 0x88, 0xa9, 0x89, 0x20, 0x91, 0x0d, 0x0f, 0xf4, 0x16, 0x91, 0x55, 0xb1, 0xc8, 0xfd, 0x88, - 0x94, 0x3c, 0x06, 0xb9, 0x85, 0x85, 0x1b, 0x62, 0x6d, 0xc0, 0x04, 0x42, 0xbe, 0x2f, 0x47, 0xb2, - 0x33, 0x2c, 0xb9, 0x08, 0xbd, 0x9e, 0xda, 0x73, 0x08, 0x90, 0x0f, 0x19, 0x10, 0xa0, 0xfd, 0x56, - 0x96, 0x67, 0xd1, 0xe0, 0xb3, 0xcd, 0xa1, 0x68, 0xfe, 0x04, 0xf4, 0x07, 0x7d, 0x23, 0x74, 0x3f, - 0x8c, 0x0b, 0x52, 0xfb, 0x4c, 0x7d, 0x76, 0x18, 0x03, 0x56, 0x0a, 0x02, 0x34, 0xf8, 0x1d, 0x0a, - 0x6e, 0xeb, 0x30, 0x40, 0x23, 0x08, 0xcb, 0x78, 0x17, 0x0c, 0x84, 0x13, 0xac, 0x7c, 0x77, 0x12, - 0xce, 0xc6, 0x7a, 0x84, 0x57, 0xd6, 0x95, 0xc2, 0x3e, 0xac, 0x90, 0x40, 0xbc, 0xbc, 0x03, 0x8f, - 0xc5, 0x7b, 0xc0, 0xe2, 0xfd, 0x94, 0x10, 0x2f, 0x8f, 0x11, 0x3c, 0xb2, 0xe2, 0x3d, 0xb0, 0x7b, - 0x03, 0xed, 0x57, 0x32, 0x40, 0xd8, 0x6b, 0x55, 0x0c, 0xcf, 0x5b, 0x77, 0x5c, 0x93, 0x07, 0x7d, - 0x1c, 0x8a, 0x60, 0x0e, 0xee, 0xae, 0xf7, 0x6b, 0x01, 0x4e, 0xc4, 0x1c, 0xce, 0x8f, 0xf8, 0x64, - 0x75, 0x31, 0x3e, 0x9a, 0xba, 0x45, 0x93, 0x3d, 0x2e, 0x5f, 0x26, 0xf7, 0xc6, 0x42, 0x5c, 0xa5, - 0x5b, 0xe4, 0xa7, 0x60, 0x48, 0xfc, 0x60, 0x66, 0x41, 0x70, 0x4b, 0x88, 0xa3, 0xd4, 0x63, 0x00, - 0x3d, 0x86, 0x26, 0xcf, 0xc3, 0x00, 0x1b, 0x30, 0xcb, 0x98, 0x96, 0xac, 0x2f, 0x8a, 0xd4, 0x32, - 0x03, 0xa0, 0xbc, 0xf4, 0x84, 0x94, 0x52, 0xa0, 0x40, 0xff, 0x0e, 0x42, 0x06, 0x3f, 0x0a, 0x83, - 0x65, 0xdb, 0x76, 0x78, 0x82, 0x25, 0x4f, 0x5c, 0xeb, 0x74, 0xdc, 0x92, 0x3c, 0x86, 0x19, 0x56, - 0x22, 0xfa, 0xd4, 0x3d, 0x89, 0xcc, 0x90, 0x5c, 0x09, 0xa2, 0xcd, 0xa8, 0x2b, 0xac, 0x6b, 0xbc, - 0xda, 0x72, 0x05, 0x2c, 0x19, 0x6c, 0x86, 0x9d, 0x37, 0x5c, 0x71, 0x9d, 0x96, 0xe3, 0x51, 0x93, - 0x0b, 0x6a, 0x30, 0xca, 0x5e, 0xd0, 0x12, 0x08, 0x8c, 0x94, 0x8d, 0xa5, 0x08, 0x8b, 0x35, 0x21, - 0x4b, 0x70, 0x32, 0xb8, 0x64, 0x0f, 0x63, 0x92, 0x67, 0xa7, 0x3c, 0x0c, 0xb6, 0x18, 0x8c, 0xf2, - 0x60, 0x45, 0xa8, 0x89, 0x87, 0x83, 0x2b, 0xa5, 0x20, 0xa8, 0xb9, 0x66, 0x99, 0x72, 0x57, 0xa7, - 0xf2, 0x23, 0x5f, 0x0c, 0x83, 0x73, 0xc6, 0xdd, 0xa9, 0xb6, 0x38, 0x78, 0x1a, 0xde, 0xf9, 0xcd, - 0x55, 0xd3, 0xb8, 0x5b, 0x33, 0x45, 0x3b, 0xc5, 0xfc, 0x90, 0x59, 0x92, 0x1a, 0x9c, 0xae, 0xb8, - 0x4e, 0xd3, 0xf1, 0xa9, 0xa9, 0x84, 0xf7, 0x8e, 0x46, 0xf9, 0x00, 0x5a, 0x82, 0xa2, 0xd6, 0x25, - 0xce, 0xb7, 0x03, 0x1b, 0xd2, 0x84, 0xd1, 0xb2, 0xe7, 0xb5, 0x9b, 0x34, 0xba, 0xdd, 0x1b, 0xdb, - 0xf6, 0x33, 0xde, 0x21, 0x1c, 0xd9, 0x1f, 0x34, 0xb0, 0x29, 0xbf, 0xdc, 0xab, 0xf9, 0x96, 0xfc, - 0x44, 0xfc, 0x16, 0x95, 0x37, 0xeb, 0xdd, 0x40, 0x80, 0x98, 0x7c, 0xa4, 0x38, 0x8e, 0xc3, 0x0b, - 0x7b, 0x37, 0x14, 0x3d, 0x26, 0x2e, 0x91, 0x7b, 0x37, 0xd6, 0x84, 0x7c, 0x14, 0xce, 0x26, 0x7a, - 0x83, 0x7f, 0x15, 0xeb, 0x63, 0x12, 0x8f, 0x5a, 0x56, 0x09, 0x26, 0xf2, 0xac, 0x0b, 0xf4, 0x2e, - 0x1c, 0xc8, 0xa3, 0x30, 0xe4, 0xb5, 0x17, 0xf9, 0xf1, 0x50, 0x6d, 0x71, 0x83, 0x67, 0xdc, 0xd2, - 0x07, 0x43, 0xd8, 0xc4, 0xc6, 0xb5, 0x7c, 0xff, 0xc8, 0xd8, 0xa8, 0x7e, 0x26, 0x29, 0x53, 0x0c, - 0x96, 0xd3, 0xfe, 0x76, 0x56, 0x99, 0x0c, 0xb9, 0x45, 0xb9, 0xaf, 0xc9, 0x50, 0x9e, 0xd4, 0xb2, - 0x7b, 0x9c, 0xd4, 0x1e, 0x4f, 0xba, 0xbd, 0xa4, 0xcc, 0x54, 0x5f, 0x0c, 0x23, 0x41, 0x0b, 0x7c, - 0xef, 0x8d, 0x70, 0xb5, 0xeb, 0xac, 0x11, 0xe7, 0x84, 0x46, 0x8c, 0xa1, 0x49, 0xbd, 0xa1, 0xa8, - 0x81, 0xc2, 0x4f, 0xfb, 0xd1, 0x0c, 0x40, 0x34, 0x8e, 0xc8, 0x53, 0xf1, 0x14, 0x97, 0x99, 0xe8, - 0x2a, 0x50, 0x64, 0x3a, 0x8a, 0xe5, 0xb4, 0x24, 0xe7, 0x20, 0x8f, 0xd9, 0xb0, 0xb2, 0xd1, 0xe5, - 0xc1, 0xaa, 0x65, 0x9b, 0x3a, 0x42, 0x19, 0x56, 0x4a, 0x5b, 0x83, 0x58, 0xf4, 0x7b, 0xe1, 0xfb, - 0x84, 0x29, 0x18, 0xad, 0xb6, 0x17, 0x65, 0x7d, 0x92, 0x53, 0x9c, 0x78, 0xed, 0xc5, 0x30, 0xab, - 0x41, 0x2c, 0xe5, 0x59, 0xbc, 0x89, 0xf6, 0x35, 0xd2, 0x56, 0x25, 0xd0, 0x1b, 0x72, 0x01, 0xb2, - 0x96, 0x29, 0xba, 0x35, 0x6d, 0x8a, 0xe1, 0x8a, 0x97, 0xb5, 0x4c, 0x32, 0x07, 0x27, 0xda, 0xf6, - 0xaa, 0xed, 0xac, 0xdb, 0xb5, 0xba, 0x63, 0x7b, 0xbe, 0x6b, 0x58, 0xb6, 0xef, 0x85, 0x17, 0xdf, - 0xc1, 0x32, 0xc7, 0x49, 0x26, 0x23, 0x8a, 0x99, 0x1e, 0x9d, 0xb4, 0x13, 0x50, 0xf2, 0x2a, 0x0c, - 0x1a, 0xeb, 0x98, 0x2d, 0xc7, 0x73, 0x1a, 0x54, 0xdc, 0x6d, 0x87, 0x17, 0xd0, 0xe5, 0x3b, 0xd5, - 0x49, 0x8e, 0x89, 0x73, 0x02, 0x63, 0xdd, 0x13, 0x08, 0xf2, 0x4e, 0xc8, 0x79, 0xde, 0x8a, 0xb8, - 0xba, 0x8e, 0x0e, 0x03, 0xab, 0x33, 0xf1, 0x26, 0x8c, 0x68, 0x62, 0x18, 0x06, 0xa5, 0x97, 0xd6, - 0x4e, 0x02, 0x49, 0xbe, 0xa8, 0xb6, 0x0a, 0xa7, 0x52, 0x9f, 0x4b, 0xce, 0xc3, 0x68, 0x90, 0x16, - 0x93, 0xbd, 0x71, 0x5b, 0x64, 0xd0, 0x1a, 0xd6, 0x87, 0x19, 0xb8, 0xec, 0xda, 0xde, 0x24, 0x26, - 0x90, 0x7c, 0x27, 0x8c, 0x47, 0x74, 0x2d, 0xbe, 0x60, 0xf0, 0xfc, 0x55, 0xfa, 0x68, 0x40, 0x59, - 0xe1, 0x60, 0xed, 0x43, 0x30, 0x12, 0x7f, 0x55, 0x36, 0x82, 0xf1, 0x10, 0x28, 0xfe, 0x88, 0x41, - 0x0e, 0xe3, 0x0f, 0x78, 0x02, 0x46, 0x04, 0x49, 0x9c, 0xfb, 0x30, 0x87, 0x06, 0xbc, 0xbf, 0x37, - 0xa3, 0x8c, 0xe4, 0x43, 0xb4, 0x62, 0x77, 0x34, 0x7a, 0xb5, 0x5f, 0xce, 0xc1, 0x60, 0xc5, 0x71, - 0x7d, 0x91, 0x48, 0xee, 0x68, 0x9b, 0x95, 0xd2, 0x89, 0x4b, 0x7e, 0x17, 0x27, 0x2e, 0xe7, 0x20, - 0x2f, 0xc5, 0x9f, 0xf0, 0x5b, 0x5e, 0xd3, 0x74, 0x75, 0x84, 0xbe, 0xc5, 0xb1, 0x7b, 0x49, 0x8f, - 0x90, 0xbe, 0x7d, 0xfb, 0x5d, 0xfd, 0x58, 0x16, 0xe0, 0xf5, 0x67, 0x9e, 0xb9, 0x9f, 0xbb, 0x74, - 0x5f, 0xce, 0x56, 0xda, 0x37, 0x67, 0x60, 0x54, 0xb8, 0x59, 0x48, 0x69, 0x90, 0xfb, 0x02, 0xff, - 0x1a, 0x79, 0xc5, 0xe1, 0x20, 0x3d, 0xc0, 0x31, 0x1b, 0xf6, 0xea, 0x5d, 0xcb, 0xc7, 0x9b, 0x66, - 0x29, 0x0f, 0x32, 0x15, 0x30, 0xd9, 0x86, 0x0d, 0xe8, 0xc8, 0x53, 0x81, 0x03, 0x49, 0x2e, 0x32, - 0xdc, 0x59, 0x83, 0xab, 0xa9, 0x4e, 0x24, 0xda, 0x3f, 0xcd, 0x43, 0xfe, 0xea, 0x5d, 0x5a, 0x3f, - 0xe2, 0x1d, 0x2b, 0x5d, 0x4b, 0xe5, 0xf7, 0x79, 0x2d, 0xb5, 0x17, 0x87, 0xba, 0x57, 0xa2, 0xfe, - 0x2c, 0xc4, 0x1f, 0xaf, 0xf4, 0xbc, 0xfa, 0xf8, 0xa0, 0xa7, 0x8f, 0x9e, 0x3f, 0xe6, 0xbf, 0xc8, - 0x41, 0xae, 0x3a, 0x59, 0x39, 0xd6, 0x9b, 0x43, 0xd5, 0x9b, 0xee, 0x1e, 0x47, 0x5a, 0xe8, 0x44, - 0xd0, 0x1f, 0x85, 0x08, 0x28, 0xfe, 0x02, 0x7f, 0x94, 0x83, 0x91, 0xea, 0xf4, 0x42, 0x45, 0xba, - 0xc7, 0xbb, 0xce, 0xdd, 0xb8, 0xd1, 0xa1, 0x98, 0x77, 0xe9, 0xb9, 0x84, 0xf9, 0x7d, 0x6b, 0xd6, - 0xf6, 0x5f, 0x78, 0x0e, 0xd3, 0x27, 0xe3, 0x31, 0x33, 0x0f, 0x14, 0xf1, 0xac, 0x37, 0xe9, 0xb7, - 0x60, 0x92, 0xa2, 0x80, 0x01, 0x79, 0x09, 0x72, 0xb7, 0x84, 0x37, 0x5e, 0x27, 0x3e, 0xcf, 0x5e, - 0xe1, 0x7c, 0xd8, 0x24, 0x98, 0x6b, 0x5b, 0x26, 0x72, 0x60, 0xad, 0x58, 0xe3, 0xd7, 0x84, 0xc1, - 0xb1, 0xa3, 0xc6, 0xcb, 0x41, 0xe3, 0xd7, 0x66, 0xa7, 0x48, 0x15, 0x06, 0x2b, 0xd4, 0x6d, 0x5a, - 0xd8, 0x51, 0xc1, 0x8c, 0xdf, 0x9d, 0xc9, 0x89, 0xad, 0xcd, 0xd2, 0x60, 0x2b, 0x6a, 0x84, 0xcc, - 0x64, 0x2e, 0xe4, 0x83, 0x00, 0xdc, 0x26, 0xdb, 0x61, 0x6a, 0xfd, 0x87, 0xf0, 0xe0, 0x82, 0xef, - 0x8d, 0x53, 0x36, 0xa9, 0x12, 0x33, 0xb2, 0x0a, 0x63, 0x73, 0x8e, 0x69, 0x2d, 0x59, 0xdc, 0xef, - 0x1e, 0x1f, 0x50, 0xd8, 0xde, 0xdb, 0x95, 0xed, 0x85, 0x9b, 0x52, 0xbb, 0xb4, 0xc7, 0x24, 0x18, - 0x6b, 0xff, 0xac, 0x17, 0xf2, 0xac, 0xdb, 0x8f, 0xc7, 0xef, 0x7e, 0xc6, 0x6f, 0x19, 0xc6, 0xee, - 0x38, 0xee, 0xaa, 0x65, 0x2f, 0x87, 0x61, 0x54, 0xe2, 0x70, 0x0d, 0xfd, 0x30, 0xd7, 0x39, 0xae, - 0x16, 0x46, 0x5c, 0xe9, 0x09, 0xf2, 0x6d, 0x46, 0xf0, 0x8b, 0x00, 0x3c, 0x07, 0x0d, 0xd2, 0xf4, - 0x47, 0x89, 0xb5, 0x78, 0x86, 0x1a, 0x8c, 0xcc, 0x92, 0x13, 0x6b, 0x45, 0xc4, 0xe4, 0x62, 0xe0, - 0xc9, 0x36, 0x80, 0x0e, 0xab, 0x78, 0x8a, 0x88, 0x9e, 0x6c, 0xb2, 0x11, 0xc0, 0x7d, 0xda, 0x2a, - 0x00, 0x92, 0x77, 0x00, 0x28, 0x82, 0x88, 0x4d, 0x0e, 0x22, 0x49, 0x72, 0x8a, 0x73, 0x80, 0x2e, - 0xf1, 0x20, 0x2f, 0x28, 0xee, 0x4b, 0x24, 0xc6, 0xad, 0xa3, 0xf7, 0x52, 0xe4, 0xfe, 0x3a, 0xb4, - 0x9d, 0xfb, 0xab, 0xf6, 0x5d, 0x39, 0x18, 0x64, 0xdc, 0xaa, 0xed, 0x66, 0xd3, 0x70, 0x37, 0x8e, - 0x15, 0x79, 0x3f, 0x8a, 0x5c, 0x83, 0x71, 0x39, 0x5a, 0x8a, 0x19, 0xbe, 0x41, 0x36, 0xc6, 0xf0, - 0xa8, 0x47, 0x25, 0x10, 0x17, 0xab, 0x6c, 0xde, 0xf7, 0x05, 0x18, 0x0f, 0x47, 0x3d, 0x3d, 0xc9, - 0x4b, 0xfb, 0x86, 0x0c, 0x8c, 0xa9, 0xd0, 0x50, 0xf7, 0x33, 0xa9, 0xba, 0xff, 0x24, 0x0c, 0x08, - 0x07, 0x28, 0xc3, 0x14, 0xde, 0xdc, 0x23, 0x5b, 0x9b, 0x25, 0xc0, 0xc4, 0x1f, 0x35, 0x97, 0x1a, - 0xa6, 0x1e, 0x11, 0x90, 0xe7, 0x61, 0x08, 0x7f, 0xdc, 0x71, 0x2d, 0xdf, 0xa7, 0xbc, 0x33, 0xf2, - 0xfc, 0x06, 0x94, 0x37, 0x58, 0xe7, 0x08, 0x3d, 0x46, 0xa6, 0xfd, 0x4c, 0x16, 0x06, 0xaa, 0xed, - 0x45, 0x6f, 0xc3, 0xf3, 0x69, 0xf3, 0x88, 0xeb, 0x50, 0x70, 0xfc, 0x94, 0x4f, 0x3d, 0x7e, 0x7a, - 0x2c, 0x18, 0x5a, 0xd2, 0xf5, 0x5b, 0xb8, 0x31, 0x08, 0x7c, 0xca, 0x23, 0x2d, 0x2a, 0xec, 0x61, - 0xab, 0xf3, 0x8f, 0xb3, 0x30, 0xc6, 0x5d, 0x6f, 0xa6, 0x2c, 0xaf, 0x7e, 0x00, 0xd1, 0x84, 0x87, - 0x2f, 0xd3, 0xfd, 0xb9, 0xab, 0xed, 0x20, 0x46, 0x53, 0xfb, 0xca, 0x1c, 0x0c, 0x96, 0xdb, 0xfe, - 0x4a, 0xd9, 0xc7, 0xf9, 0xed, 0x78, 0x87, 0xbd, 0x87, 0x70, 0xa6, 0x21, 0xa3, 0xd5, 0xaa, 0x35, - 0x03, 0xa9, 0x15, 0xe2, 0x4e, 0xc6, 0xe5, 0x56, 0xab, 0x03, 0x83, 0x41, 0x23, 0x42, 0x69, 0x3f, - 0x9d, 0xe1, 0xa5, 0x39, 0x16, 0x9c, 0x55, 0x6a, 0x1f, 0xc0, 0x0d, 0xe2, 0x41, 0x1c, 0x9a, 0x07, - 0xfd, 0x99, 0xdb, 0x5d, 0x7f, 0xe2, 0xbd, 0xb7, 0xee, 0x34, 0xe8, 0xd1, 0xfe, 0x8c, 0x03, 0xbc, - 0xf7, 0x0e, 0x04, 0x72, 0x00, 0x7e, 0x16, 0x6f, 0x2f, 0x81, 0x1c, 0xc0, 0x99, 0xf2, 0xdb, 0x43, - 0x20, 0x9f, 0xcf, 0xc0, 0xc0, 0x84, 0xe3, 0x1f, 0xf1, 0x81, 0x2f, 0xbe, 0xe2, 0x68, 0xab, 0x79, - 0xf0, 0x15, 0x47, 0x5b, 0x37, 0xb5, 0x6f, 0xcc, 0xc2, 0x49, 0x51, 0x3e, 0x4c, 0x9c, 0xc3, 0x1d, - 0x4f, 0xc7, 0x62, 0xb0, 0x25, 0x45, 0x73, 0x3c, 0x0f, 0x09, 0xd1, 0x7c, 0x26, 0x03, 0x27, 0xd0, - 0x57, 0xd6, 0x44, 0x43, 0x24, 0xfc, 0xba, 0x93, 0xc0, 0x3d, 0xea, 0x78, 0x8a, 0x69, 0x9d, 0xff, - 0x20, 0x25, 0x18, 0x7c, 0xc3, 0xb1, 0xec, 0x1a, 0x0f, 0x00, 0x10, 0x19, 0x9c, 0x81, 0x81, 0x44, - 0x00, 0xc0, 0x43, 0x00, 0x6d, 0xcf, 0x58, 0xa6, 0xb5, 0xa6, 0x63, 0x8a, 0x8b, 0x69, 0x7d, 0x00, - 0x21, 0x73, 0x8e, 0x49, 0xc9, 0x13, 0x30, 0x62, 0x60, 0x2e, 0x7b, 0x6a, 0x8a, 0x7c, 0xd8, 0xb8, - 0x79, 0xd0, 0x87, 0x03, 0x28, 0xcf, 0x88, 0xfd, 0x8b, 0x59, 0x18, 0x97, 0x5e, 0x2a, 0xd2, 0xe3, - 0xe6, 0x2e, 0x3b, 0xab, 0x29, 0x75, 0x96, 0xbb, 0xc7, 0xce, 0x72, 0xa5, 0xce, 0x6a, 0xef, 0xb2, - 0xb3, 0xda, 0x71, 0x57, 0xea, 0xdd, 0xd8, 0xa8, 0x37, 0x60, 0x48, 0x64, 0x21, 0xf6, 0x99, 0x0c, - 0x84, 0xa5, 0xfa, 0x60, 0xac, 0xe2, 0x5a, 0xbc, 0xcf, 0x54, 0x6b, 0xd3, 0x8b, 0x48, 0x54, 0x89, - 0x46, 0x13, 0xf6, 0xb1, 0x44, 0xf7, 0x28, 0xd1, 0x2f, 0x8d, 0x4b, 0x34, 0x9a, 0x50, 0xee, 0x1f, - 0x89, 0x6a, 0xdf, 0x91, 0x83, 0x93, 0x58, 0x32, 0xc5, 0xb3, 0x1c, 0xfb, 0x6d, 0xb0, 0x91, 0x21, - 0xf5, 0xb8, 0x2b, 0xe4, 0x5c, 0x8a, 0x2b, 0xe4, 0x9f, 0x6d, 0x96, 0x5e, 0xd8, 0x45, 0xd9, 0xc6, - 0x4b, 0x55, 0x3c, 0xea, 0x61, 0x5c, 0x03, 0x27, 0x4a, 0x0f, 0xe0, 0x5a, 0x38, 0x63, 0x8a, 0x9d, - 0x7a, 0x75, 0x6b, 0xb3, 0x74, 0x4a, 0x9a, 0x5a, 0xf7, 0xfe, 0xbc, 0x88, 0xb5, 0x2e, 0x3d, 0x46, - 0xfb, 0x97, 0x19, 0x78, 0x20, 0xbe, 0x04, 0xbe, 0x1d, 0x36, 0x9e, 0x7f, 0x33, 0x0b, 0xa7, 0x5e, - 0x43, 0xe1, 0x84, 0xee, 0xdc, 0xc7, 0x46, 0x8f, 0x58, 0xd9, 0x53, 0x64, 0x73, 0xbc, 0x1d, 0xed, - 0x2c, 0x9b, 0x63, 0x8b, 0x50, 0xc8, 0xe6, 0xe7, 0x33, 0x70, 0x62, 0x7e, 0x76, 0x6a, 0xf2, 0x6d, - 0x32, 0xa2, 0x92, 0xdf, 0x73, 0xc4, 0x77, 0xab, 0x89, 0xef, 0x39, 0xe2, 0xfb, 0xd6, 0xcf, 0x64, - 0xe1, 0x44, 0xb5, 0x3c, 0x77, 0xe3, 0xed, 0x32, 0x83, 0x4f, 0xca, 0xb1, 0x47, 0xc1, 0x29, 0xbe, - 0xb0, 0x05, 0xe4, 0xcf, 0xbc, 0x7d, 0xa5, 0x73, 0x4c, 0x52, 0x52, 0x28, 0x47, 0x7c, 0xea, 0x3e, - 0x10, 0xa1, 0x30, 0xcd, 0x8f, 0x51, 0x1f, 0x71, 0xcd, 0xff, 0xe7, 0x05, 0x18, 0xbc, 0xde, 0x5e, - 0xa4, 0xc2, 0xa3, 0xf5, 0xbe, 0xbe, 0xba, 0xba, 0x02, 0x83, 0x42, 0x0c, 0x78, 0x45, 0x2b, 0xa5, - 0x53, 0x17, 0xe9, 0x24, 0x79, 0x56, 0x58, 0x99, 0x88, 0x9c, 0x83, 0xfc, 0x6d, 0xea, 0x2e, 0xca, - 0xa9, 0x75, 0xd6, 0xa8, 0xbb, 0xa8, 0x23, 0x94, 0xdc, 0x90, 0x1c, 0xd7, 0x2b, 0xb3, 0x58, 0x93, - 0x57, 0x78, 0x3d, 0x60, 0x91, 0xe1, 0x30, 0x04, 0xc3, 0x68, 0x59, 0xbc, 0x9a, 0xaf, 0x9c, 0x15, - 0x4c, 0x6d, 0x49, 0x6e, 0xc2, 0x78, 0x2c, 0x34, 0x03, 0x0b, 0xd2, 0xf6, 0xa7, 0xb0, 0x4b, 0x2b, - 0x45, 0x9b, 0x6c, 0x4a, 0x5e, 0x81, 0xa1, 0x00, 0x88, 0x1e, 0xfe, 0x03, 0x51, 0x89, 0xbc, 0x90, - 0x95, 0x52, 0x5a, 0x2c, 0xd6, 0x40, 0x66, 0x80, 0xb7, 0xb0, 0x90, 0xc2, 0x40, 0x09, 0x8c, 0x89, - 0x35, 0x20, 0xcf, 0x23, 0x03, 0x8c, 0x30, 0x47, 0x8f, 0xcf, 0x41, 0xcc, 0x3b, 0x83, 0x37, 0xd8, - 0xae, 0x80, 0xf3, 0xec, 0x42, 0x31, 0x32, 0x32, 0x0f, 0x10, 0x79, 0xe6, 0x09, 0x3f, 0xfa, 0x5d, - 0xfb, 0x0c, 0x4a, 0x2c, 0x64, 0x57, 0x84, 0xe1, 0xbd, 0xb8, 0x22, 0x68, 0x5f, 0xc8, 0xc1, 0xa0, - 0x74, 0xe9, 0x46, 0x9e, 0x82, 0x42, 0xb9, 0xd5, 0xba, 0xa5, 0xcf, 0xca, 0x85, 0xc2, 0x8c, 0x56, - 0xab, 0xd6, 0x76, 0x2d, 0x39, 0x32, 0x8c, 0x13, 0x91, 0x49, 0x18, 0x2e, 0xb7, 0x5a, 0x95, 0xf6, - 0x62, 0xc3, 0xaa, 0x4b, 0xc5, 0x67, 0x31, 0x05, 0x1d, 0x6b, 0xd5, 0x42, 0x8c, 0x5a, 0x7f, 0x36, - 0xde, 0x86, 0x7c, 0x14, 0x13, 0xa7, 0x8a, 0x4a, 0xb0, 0xbc, 0xc4, 0xa8, 0x96, 0x72, 0x21, 0x78, - 0x29, 0x24, 0x12, 0xa5, 0x5f, 0x83, 0x1a, 0x79, 0xec, 0x41, 0x89, 0xd2, 0xaf, 0x11, 0x4b, 0xf2, - 0x34, 0xf4, 0x95, 0x5b, 0x2d, 0xe9, 0xba, 0x1d, 0x3d, 0x73, 0x59, 0x2b, 0xa5, 0x8f, 0x03, 0x32, - 0xf1, 0x59, 0xc2, 0x41, 0xc7, 0x71, 0x7d, 0x1c, 0x52, 0xc3, 0xd1, 0x67, 0x05, 0x1e, 0x3d, 0x8e, - 0x9c, 0xab, 0x50, 0x8f, 0xb7, 0x61, 0x8f, 0x0d, 0x0a, 0xb2, 0x17, 0xa2, 0xc7, 0xa6, 0x54, 0x62, - 0x0f, 0xc8, 0xce, 0xbe, 0x0c, 0x23, 0xf1, 0x6f, 0xdc, 0x55, 0x05, 0xb8, 0x3f, 0xcd, 0xa0, 0x1c, - 0x8f, 0x78, 0x40, 0xe5, 0xb3, 0x90, 0x2b, 0xb7, 0x5a, 0x62, 0x1a, 0xdc, 0xc1, 0xbd, 0x30, 0xa3, - 0x0e, 0x3e, 0xfd, 0x88, 0x47, 0x66, 0xef, 0xe9, 0xd3, 0x3f, 0xcf, 0x3f, 0xfd, 0x68, 0x47, 0x4d, - 0x6b, 0xdf, 0x94, 0x87, 0xd1, 0x72, 0xab, 0x75, 0x5c, 0xb8, 0xed, 0xa0, 0x52, 0x5c, 0x3d, 0x03, - 0x20, 0xcd, 0xca, 0x7d, 0x61, 0x8a, 0x89, 0x41, 0x69, 0x46, 0x2e, 0x66, 0x74, 0x89, 0x28, 0x50, - 0xbf, 0xfe, 0xdd, 0xa8, 0x5f, 0xe4, 0x04, 0x38, 0xb0, 0xdb, 0x1c, 0x98, 0xb0, 0xe3, 0x1c, 0x98, - 0x5f, 0x9a, 0xc3, 0xe9, 0xf8, 0xa8, 0x67, 0x14, 0xbe, 0x57, 0x54, 0x43, 0xf4, 0x73, 0x61, 0x57, - 0xd3, 0xcc, 0x4f, 0xe4, 0xe4, 0x01, 0x8a, 0xd5, 0xc0, 0x8e, 0x7b, 0xa1, 0x77, 0x5f, 0x16, 0xff, - 0x88, 0x2c, 0x4c, 0x91, 0xa7, 0x34, 0xa8, 0x45, 0x2d, 0xd2, 0xd4, 0xd6, 0x19, 0x0a, 0x93, 0xd5, - 0xc6, 0x69, 0x83, 0x3e, 0xec, 0xdb, 0x55, 0x1f, 0x6e, 0x66, 0x31, 0x33, 0x56, 0x98, 0xb4, 0x77, - 0xff, 0x1b, 0xa7, 0xcb, 0x00, 0xfc, 0x36, 0x23, 0x0c, 0x7b, 0x1a, 0xe6, 0x09, 0x36, 0x79, 0x89, - 0x6a, 0x91, 0x60, 0x33, 0x22, 0x09, 0xbd, 0x48, 0x73, 0xa9, 0x5e, 0xa4, 0x17, 0xa1, 0x5f, 0x37, - 0xd6, 0x3f, 0xd0, 0xa6, 0x22, 0x96, 0x38, 0xc8, 0x89, 0x6f, 0xac, 0xd7, 0x3e, 0xc6, 0x80, 0x7a, - 0x88, 0x26, 0x5a, 0x98, 0x5a, 0x4d, 0xf2, 0xd6, 0xe3, 0xc7, 0xff, 0x61, 0x42, 0xb5, 0xbd, 0x28, - 0x3a, 0x79, 0x2f, 0xe4, 0xca, 0x77, 0xaa, 0x42, 0xb2, 0x67, 0xa5, 0xe8, 0x58, 0x21, 0xaf, 0x8e, - 0x6d, 0xef, 0x54, 0xb5, 0xef, 0xcd, 0xc3, 0xc9, 0x48, 0xc0, 0x37, 0x6e, 0xcc, 0x49, 0x32, 0xde, - 0xf3, 0xcd, 0xd6, 0xb3, 0x90, 0x33, 0x5a, 0x2d, 0x31, 0x50, 0x76, 0xf2, 0x15, 0x46, 0xab, 0x45, - 0x9e, 0x83, 0x82, 0xb7, 0x8b, 0x0c, 0x54, 0x5e, 0x18, 0x0f, 0xc9, 0x7a, 0x43, 0x76, 0x4a, 0xe5, - 0xbd, 0xd3, 0x12, 0x11, 0x2a, 0xcd, 0x8e, 0x22, 0xe7, 0xff, 0x93, 0x0b, 0xd0, 0xdf, 0x72, 0x9d, - 0x35, 0xcb, 0xa4, 0xb1, 0x34, 0xa0, 0x01, 0x4c, 0x0f, 0xff, 0x62, 0x0b, 0x47, 0xd3, 0x31, 0x69, - 0x43, 0xac, 0x4d, 0xb8, 0x70, 0x20, 0x40, 0x5e, 0x38, 0x10, 0x40, 0x5e, 0x86, 0xd1, 0xa0, 0x2c, - 0x82, 0x59, 0xe3, 0x8d, 0xfa, 0xa3, 0x46, 0x0a, 0x4a, 0x1f, 0x09, 0x01, 0x73, 0xd8, 0xfa, 0x3a, - 0x8c, 0x5b, 0x76, 0xab, 0xed, 0xf3, 0x8b, 0x4b, 0x11, 0xe9, 0x3b, 0x80, 0x15, 0x36, 0x30, 0xc1, - 0x75, 0x02, 0x29, 0x47, 0x74, 0x23, 0x92, 0x5f, 0xf6, 0x60, 0x38, 0xf0, 0x4d, 0x20, 0x4e, 0xdb, - 0x57, 0xb9, 0x01, 0x72, 0xc3, 0xad, 0x6c, 0x12, 0x2b, 0xef, 0x8c, 0x39, 0x36, 0xe2, 0xa7, 0x5d, - 0x07, 0x98, 0x59, 0x58, 0xa8, 0xcc, 0x50, 0x83, 0xc9, 0xe4, 0x1c, 0xa0, 0x13, 0xb0, 0xec, 0x63, - 0xcd, 0x9d, 0x82, 0xd9, 0xbf, 0xa4, 0x14, 0xb3, 0xfc, 0x79, 0x4a, 0x10, 0x04, 0x88, 0x4d, 0x80, - 0xf6, 0x0d, 0x79, 0x38, 0x15, 0xa9, 0x1f, 0xe3, 0x7b, 0x10, 0xfa, 0x17, 0xdc, 0x8a, 0x66, 0x77, - 0x79, 0x2b, 0xfa, 0x0a, 0xf4, 0x79, 0x7b, 0x9a, 0xa9, 0x45, 0xab, 0x40, 0xf1, 0xf3, 0xbb, 0x52, - 0xfc, 0xa7, 0x00, 0x82, 0x33, 0x0f, 0x2b, 0x50, 0x54, 0xf4, 0x44, 0x8f, 0xa0, 0x7a, 0x58, 0xbe, - 0xc3, 0x94, 0x74, 0xba, 0xd0, 0x51, 0xa7, 0x1f, 0x80, 0x5c, 0xdb, 0x0d, 0xf4, 0x14, 0x73, 0x98, - 0xb7, 0xdd, 0x86, 0xce, 0xfe, 0x21, 0xcf, 0xc2, 0xd0, 0x8a, 0xef, 0xb7, 0x6a, 0x22, 0xd7, 0xac, - 0x50, 0x4b, 0x3c, 0x79, 0x91, 0xe1, 0xfa, 0x20, 0xfb, 0x15, 0x24, 0xa1, 0x7d, 0x11, 0xfa, 0x56, - 0xb0, 0xbf, 0x79, 0x56, 0x42, 0x29, 0x0b, 0x40, 0xa4, 0x0a, 0x3c, 0xc8, 0x54, 0x90, 0xe9, 0xc1, - 0x1f, 0xe4, 0x39, 0x88, 0x26, 0x43, 0x61, 0x40, 0xf1, 0xba, 0xdc, 0x01, 0x50, 0x0e, 0x33, 0x75, - 0xc5, 0x5c, 0xa9, 0xfd, 0x64, 0x16, 0x1e, 0x4e, 0xd5, 0x8b, 0x09, 0xc7, 0xdc, 0x08, 0x97, 0xf2, - 0x3d, 0x2b, 0x88, 0xd4, 0xd1, 0xd9, 0x3d, 0x75, 0x74, 0xbc, 0xcf, 0x72, 0xdb, 0xf5, 0xd9, 0xd3, - 0x30, 0x28, 0xd6, 0x45, 0x2c, 0x54, 0xca, 0xcb, 0x8c, 0xe2, 0xaa, 0x23, 0x81, 0x75, 0xa8, 0x47, - 0x15, 0x4b, 0x1f, 0x87, 0x3e, 0xcb, 0xab, 0x35, 0x0c, 0x8f, 0x6f, 0xe4, 0x45, 0x3e, 0x77, 0x01, - 0xd2, 0x0b, 0x96, 0x77, 0xc3, 0xf0, 0x30, 0xc2, 0x21, 0x74, 0x49, 0x1e, 0xe2, 0xa3, 0x8f, 0xfd, - 0xd6, 0xf1, 0x5f, 0xed, 0x0f, 0xf2, 0x70, 0x5a, 0x15, 0x62, 0x94, 0x62, 0xf0, 0xf0, 0x84, 0x27, - 0x46, 0x49, 0x6e, 0x1f, 0xa3, 0x24, 0xbf, 0x03, 0x89, 0x4b, 0x0b, 0xba, 0x38, 0x0c, 0x49, 0xae, - 0xf3, 0x5e, 0xb4, 0xce, 0x47, 0x2d, 0x7c, 0x7a, 0xd7, 0x17, 0x83, 0x4b, 0x6e, 0xc1, 0xc0, 0x41, - 0x8b, 0x05, 0x7a, 0xd7, 0x4f, 0x0c, 0xa5, 0xbe, 0x5d, 0x0e, 0xa5, 0xfe, 0x5d, 0x0e, 0xa5, 0x2b, - 0x30, 0xe4, 0x51, 0xdb, 0xc4, 0x08, 0xbc, 0x5a, 0xd3, 0x13, 0x2b, 0x02, 0x3e, 0x4f, 0x86, 0xeb, - 0xc0, 0x7e, 0x2d, 0x58, 0x4d, 0x3a, 0x87, 0x6d, 0xd6, 0x0d, 0xcb, 0x0f, 0xdb, 0x40, 0xd4, 0x46, - 0x86, 0xeb, 0xc0, 0x7e, 0x89, 0x36, 0x2f, 0xb1, 0xc5, 0x0b, 0xb3, 0x02, 0x87, 0xcd, 0x06, 0xb1, - 0x99, 0x58, 0xbc, 0x62, 0x28, 0x7d, 0x58, 0x00, 0x78, 0x63, 0xed, 0xf3, 0x59, 0x28, 0xa5, 0x2b, - 0xdd, 0xf1, 0xd0, 0xdd, 0xf9, 0xd0, 0xfd, 0xd2, 0x2c, 0x90, 0xa4, 0x01, 0x47, 0x5e, 0x80, 0x01, - 0x84, 0x2e, 0xb3, 0xcf, 0xcf, 0x44, 0x69, 0x1b, 0x8d, 0x75, 0xaf, 0xe6, 0x22, 0x34, 0x76, 0x9c, - 0x18, 0x90, 0x92, 0x17, 0x01, 0xca, 0x77, 0xaa, 0x6c, 0x43, 0x60, 0xd5, 0x83, 0xc5, 0x18, 0xe3, - 0xfc, 0x58, 0x43, 0x8f, 0x83, 0x63, 0xa1, 0x76, 0x21, 0x31, 0x9e, 0x44, 0xde, 0xa9, 0xce, 0x38, - 0x9e, 0x2f, 0x64, 0xc5, 0x4f, 0x22, 0xd7, 0xbd, 0xda, 0x8a, 0xe3, 0xf9, 0xb1, 0x93, 0x48, 0x4e, - 0x46, 0xa6, 0x61, 0xa4, 0x7c, 0xa7, 0xca, 0xd3, 0x23, 0x99, 0xba, 0xd3, 0x08, 0x8e, 0x30, 0x1f, - 0xde, 0xda, 0x2c, 0x9d, 0x65, 0x0d, 0x79, 0x6a, 0x25, 0xb3, 0xe6, 0x3a, 0xb1, 0xc2, 0xfc, 0x4a, - 0x2b, 0xed, 0x93, 0xfd, 0x30, 0x36, 0x65, 0xf8, 0xc6, 0xa2, 0xe1, 0x51, 0xe9, 0xfe, 0x66, 0x34, - 0x80, 0x05, 0x9f, 0x23, 0xc9, 0xc1, 0x5c, 0x4c, 0xf9, 0x1a, 0xb5, 0x01, 0x79, 0x29, 0xe2, 0x5b, - 0x71, 0x1d, 0xdf, 0xa9, 0x3b, 0x0d, 0x21, 0x13, 0xec, 0x57, 0x73, 0xb1, 0xd6, 0x12, 0x60, 0x3d, - 0x41, 0x48, 0x9e, 0x84, 0xc1, 0x00, 0x76, 0x4b, 0x9f, 0x15, 0x32, 0xc1, 0x35, 0xd8, 0x5c, 0xac, - 0xb5, 0x5d, 0x4b, 0x97, 0xd1, 0xe4, 0x45, 0x18, 0x0a, 0x7e, 0x4a, 0x87, 0xb9, 0x78, 0x82, 0x60, - 0x2e, 0x26, 0xce, 0xeb, 0x65, 0x52, 0xb9, 0x29, 0x6e, 0x3b, 0x7b, 0x63, 0x4d, 0x99, 0xbd, 0x92, - 0xd6, 0x14, 0x77, 0x9c, 0x1f, 0x83, 0x91, 0xe0, 0xb7, 0x38, 0xa2, 0xe6, 0xb1, 0x76, 0x4f, 0x06, - 0x43, 0x45, 0x15, 0xeb, 0xa5, 0x38, 0x39, 0x3f, 0xac, 0x7e, 0x50, 0x1c, 0x56, 0x9f, 0x30, 0x17, - 0x93, 0x67, 0xd5, 0xca, 0x03, 0xc8, 0x2c, 0x8c, 0x07, 0x90, 0x48, 0x43, 0xfb, 0xa2, 0x3b, 0x0a, - 0x73, 0xb1, 0x96, 0xaa, 0xa4, 0xc9, 0x56, 0xa4, 0x01, 0xe7, 0x62, 0x40, 0xd3, 0x5b, 0xb1, 0x96, - 0x7c, 0x71, 0xc1, 0x20, 0x6a, 0x0d, 0x0e, 0x4c, 0x5c, 0xd8, 0xda, 0x2c, 0x3d, 0x1e, 0x72, 0xe5, - 0x34, 0x35, 0x91, 0x54, 0x29, 0x9e, 0xda, 0xb8, 0x2b, 0x37, 0x52, 0x85, 0x93, 0x01, 0xfe, 0xb5, - 0xc9, 0x4a, 0xc5, 0x75, 0xde, 0xa0, 0x75, 0x7f, 0x76, 0x4a, 0x1c, 0x0e, 0xa1, 0xb9, 0x6d, 0x2e, - 0xd6, 0x96, 0xeb, 0x2d, 0xa6, 0x14, 0x0c, 0x17, 0x67, 0x9e, 0xda, 0x98, 0xdc, 0x86, 0x53, 0x12, - 0x7c, 0xd6, 0xf6, 0x7c, 0xc3, 0xae, 0xd3, 0x30, 0x11, 0x33, 0x9a, 0xdd, 0x82, 0xab, 0x25, 0x90, - 0x71, 0xb6, 0xe9, 0xcd, 0xc9, 0xcb, 0x30, 0x1c, 0x20, 0xb8, 0xdf, 0xda, 0x20, 0xfa, 0xad, 0xe1, - 0x90, 0x34, 0x17, 0x6b, 0x6a, 0x16, 0xbf, 0x38, 0xb1, 0xac, 0x51, 0x98, 0xd4, 0x76, 0x28, 0xa6, - 0x51, 0x4a, 0xf6, 0xda, 0x18, 0x29, 0x79, 0x25, 0xd2, 0xa8, 0x79, 0xd7, 0x5a, 0xb6, 0x82, 0x6a, - 0xfd, 0x67, 0x84, 0x7e, 0x38, 0x08, 0x4c, 0xd3, 0x0f, 0x4e, 0x7e, 0xb6, 0x0c, 0x27, 0x52, 0x74, - 0x6c, 0x57, 0x97, 0x05, 0x9f, 0xc8, 0x46, 0x2f, 0x71, 0xc4, 0x6f, 0x0c, 0x26, 0xa0, 0x3f, 0xf8, - 0x12, 0xb1, 0x59, 0x28, 0x76, 0x1a, 0x9a, 0x2a, 0x8f, 0x00, 0x1f, 0x13, 0xc7, 0x11, 0xbf, 0x45, - 0x38, 0x08, 0x71, 0x7c, 0x21, 0x13, 0x89, 0xe3, 0x88, 0xdf, 0x2c, 0x7c, 0x67, 0x6f, 0x34, 0x27, - 0x1d, 0x5f, 0x2f, 0x1c, 0xd4, 0xe9, 0x65, 0xe4, 0xf2, 0x5c, 0xd8, 0x85, 0x13, 0xb9, 0xac, 0x9a, - 0x7d, 0x7b, 0x53, 0x4d, 0xf2, 0x32, 0x0c, 0x56, 0x1c, 0xcf, 0x5f, 0x76, 0xa9, 0x57, 0x09, 0x6b, - 0xe5, 0x62, 0x56, 0xbe, 0x96, 0x00, 0xd7, 0x5a, 0xf1, 0x64, 0xfc, 0x12, 0xb9, 0x54, 0xa3, 0x60, - 0x60, 0x0f, 0x35, 0x0a, 0x6a, 0x00, 0x93, 0xe5, 0xf9, 0x35, 0xea, 0xba, 0x96, 0x19, 0x14, 0xa0, - 0x7b, 0x3c, 0xe4, 0x10, 0x62, 0x26, 0xa9, 0xeb, 0xf3, 0x0c, 0x1e, 0x74, 0x8a, 0xfa, 0x86, 0xd5, - 0xf0, 0x44, 0xba, 0x74, 0xa3, 0xe6, 0x08, 0x12, 0xd9, 0x3e, 0x8c, 0x1a, 0x6a, 0xbf, 0x96, 0x4b, - 0x28, 0x2b, 0x3f, 0xee, 0x3c, 0x92, 0xca, 0x7a, 0x00, 0x33, 0x11, 0xb9, 0x12, 0x2d, 0xd2, 0xfc, - 0x5c, 0xb8, 0x57, 0x2a, 0x1a, 0xb4, 0x28, 0x8e, 0x85, 0xe3, 0x24, 0xe4, 0xc3, 0x70, 0x26, 0x06, - 0xa8, 0x18, 0xae, 0xd1, 0xa4, 0x3e, 0xdb, 0x25, 0x16, 0xa2, 0x32, 0x10, 0x41, 0xeb, 0x5a, 0x2b, - 0x44, 0x4b, 0xbd, 0xd2, 0x89, 0x83, 0xa4, 0xf9, 0x7d, 0xbb, 0x70, 0xf6, 0xff, 0x5c, 0x2e, 0xb2, - 0xc3, 0xe2, 0xe5, 0xdc, 0x74, 0xea, 0xb5, 0x1b, 0xfe, 0xfd, 0xdb, 0xc1, 0x7b, 0xab, 0xcf, 0x3d, - 0x03, 0xa3, 0xe5, 0xa5, 0x25, 0x5a, 0xf7, 0x83, 0x22, 0x97, 0x9e, 0x28, 0xe0, 0xc3, 0xf7, 0x45, - 0x02, 0x25, 0x8a, 0x16, 0xca, 0xfd, 0xaa, 0x36, 0xd3, 0x7e, 0x3d, 0x0f, 0xc5, 0x70, 0x5f, 0x12, - 0x26, 0x0f, 0x3a, 0x44, 0x1b, 0xe0, 0x9e, 0xe8, 0x15, 0x0b, 0xc6, 0x23, 0x61, 0x88, 0xac, 0x2d, - 0xc5, 0x5e, 0xdc, 0xf7, 0x94, 0x54, 0x66, 0x11, 0x21, 0xdf, 0xea, 0x9c, 0x15, 0x5b, 0x1d, 0x12, - 0x25, 0x67, 0xaa, 0x79, 0x9c, 0x85, 0x9e, 0xe4, 0x4a, 0x3e, 0x95, 0x81, 0x93, 0x41, 0xa7, 0xcc, - 0x2f, 0x32, 0x9b, 0x1f, 0x8f, 0xc6, 0x83, 0x6d, 0xd6, 0x7b, 0x3b, 0x3f, 0x8e, 0x77, 0xd2, 0xa5, - 0xb4, 0xc6, 0xfc, 0x4d, 0xc2, 0x6c, 0xcd, 0xa1, 0x42, 0x38, 0x48, 0xc3, 0x0f, 0xe6, 0x3d, 0x3d, - 0xf5, 0xb9, 0x67, 0x5f, 0x83, 0x07, 0x3a, 0xb2, 0xdc, 0xce, 0xc6, 0xee, 0x95, 0x6d, 0xec, 0x5f, - 0xcd, 0x44, 0x13, 0x91, 0x22, 0x24, 0x72, 0x09, 0x20, 0x02, 0x89, 0x5d, 0x37, 0x1e, 0x9c, 0x44, - 0x42, 0xd3, 0x25, 0x0a, 0x32, 0x0f, 0x05, 0x21, 0x96, 0x2c, 0x8a, 0xe5, 0x5d, 0xdb, 0xf4, 0xc2, - 0x25, 0x59, 0x0e, 0xb8, 0xa3, 0x16, 0xdf, 0x2c, 0xd8, 0x9c, 0x7d, 0x11, 0x06, 0xf7, 0xfa, 0x5d, - 0x9f, 0xca, 0x01, 0x91, 0xb7, 0xc8, 0x87, 0xb8, 0x7f, 0x38, 0xc2, 0x53, 0xd8, 0x05, 0xe8, 0x67, - 0x9f, 0x80, 0x17, 0x3e, 0xd2, 0x55, 0x59, 0x5b, 0xc0, 0xf4, 0x10, 0x1b, 0x55, 0x6c, 0xe8, 0x4b, - 0xaf, 0xd8, 0xa0, 0x7d, 0x43, 0x0e, 0x4e, 0xcb, 0x1d, 0x32, 0x45, 0xb1, 0x86, 0xef, 0x71, 0xa7, - 0xbc, 0x85, 0x9d, 0xa2, 0x41, 0x81, 0xef, 0x8c, 0x44, 0x31, 0x65, 0x7e, 0x6a, 0x85, 0x10, 0x5d, - 0x60, 0xb4, 0x7f, 0x93, 0x85, 0xe1, 0xd0, 0xfa, 0x34, 0x5c, 0xef, 0x3e, 0xee, 0x8e, 0xf7, 0xc0, - 0x30, 0xe6, 0xdc, 0x6f, 0x52, 0x9b, 0xe7, 0xa5, 0xef, 0x95, 0xaa, 0x9f, 0x07, 0x08, 0x3c, 0xc3, - 0xd3, 0xe3, 0x84, 0x4c, 0xfb, 0xb9, 0xe5, 0x27, 0x55, 0x42, 0xe0, 0x66, 0x1f, 0x87, 0x6b, 0xdf, - 0x96, 0x83, 0xa1, 0x40, 0xca, 0x13, 0xd6, 0x51, 0xf5, 0x0e, 0x3a, 0x5c, 0x21, 0x5f, 0x06, 0xa8, - 0x38, 0xae, 0x6f, 0x34, 0x6e, 0x46, 0x9a, 0x8f, 0xe7, 0xb7, 0x2d, 0x84, 0xf2, 0x36, 0x12, 0x09, - 0xae, 0x5f, 0x91, 0x59, 0xcd, 0x27, 0x26, 0xbe, 0x7e, 0x85, 0x50, 0x5d, 0xa2, 0xd0, 0x7e, 0x38, - 0x0b, 0xa3, 0x41, 0x27, 0x5d, 0xbd, 0x4b, 0xeb, 0xed, 0xfb, 0x79, 0x6e, 0x8a, 0x4b, 0xbb, 0x77, - 0x5b, 0x69, 0x6b, 0x7f, 0x28, 0x4d, 0x24, 0x93, 0x0d, 0xe7, 0x78, 0x22, 0xf9, 0xcb, 0xd0, 0x71, - 0xed, 0x2b, 0x72, 0x70, 0x32, 0x90, 0xfa, 0x74, 0xdb, 0xc6, 0x93, 0x8f, 0x49, 0xa3, 0xd1, 0xb8, - 0x9f, 0x77, 0xe3, 0x83, 0x81, 0x20, 0xe6, 0x45, 0x11, 0x9b, 0x61, 0x7e, 0xff, 0xb9, 0x24, 0xc0, - 0x35, 0xc7, 0x32, 0x75, 0x99, 0x88, 0xbc, 0x02, 0x43, 0xc1, 0xcf, 0xb2, 0xbb, 0x1c, 0x6c, 0xc1, - 0xf1, 0x1e, 0x23, 0x6c, 0x64, 0xb8, 0xb1, 0x54, 0x97, 0xb1, 0x06, 0xda, 0x37, 0xe7, 0xe0, 0xec, - 0x1d, 0xcb, 0x36, 0x9d, 0x75, 0x4f, 0x3a, 0x59, 0x91, 0xf3, 0x73, 0x57, 0xdb, 0x68, 0xa7, 0xcb, - 0xf9, 0xb9, 0x3d, 0x0e, 0xd2, 0x03, 0x1c, 0x79, 0x1e, 0x86, 0xaa, 0xd4, 0xb5, 0x8c, 0xc6, 0xcd, - 0x76, 0x73, 0x91, 0x06, 0x81, 0x0a, 0x18, 0xb1, 0xe1, 0x21, 0xbc, 0x66, 0x23, 0x42, 0x8f, 0x91, - 0x91, 0x07, 0x20, 0x77, 0xab, 0x72, 0x53, 0xdc, 0x4c, 0x71, 0xe7, 0x8f, 0x96, 0xad, 0x33, 0x18, - 0xf9, 0x00, 0x9c, 0x9a, 0xd4, 0x6f, 0x4c, 0x59, 0x1e, 0xcf, 0xa4, 0x69, 0x39, 0x76, 0xc5, 0xc1, - 0x02, 0x0d, 0xf9, 0xe8, 0x0b, 0xeb, 0x6e, 0xa3, 0x66, 0x4a, 0x14, 0xb5, 0x16, 0x92, 0xe8, 0xe9, - 0x2d, 0xc9, 0x45, 0xe8, 0xbf, 0x4e, 0x37, 0x6e, 0xa1, 0x7f, 0x6c, 0x2f, 0x86, 0x94, 0xa0, 0x03, - 0xdc, 0x2a, 0xdd, 0xa8, 0x61, 0x1e, 0x10, 0x3d, 0x44, 0x93, 0x09, 0x18, 0xbb, 0x7a, 0xd7, 0xa7, - 0xb6, 0x49, 0xcd, 0xb0, 0x09, 0x13, 0x6d, 0x6f, 0x90, 0x77, 0x9c, 0xe3, 0x6a, 0x51, 0xdb, 0x04, - 0x3d, 0xf2, 0xb0, 0x57, 0x0c, 0xbb, 0x2e, 0xf1, 0xe8, 0x8b, 0x2e, 0x41, 0xa8, 0xc0, 0xc5, 0x78, - 0x28, 0xf4, 0xda, 0x0f, 0xf7, 0x87, 0xbd, 0x33, 0x45, 0xbd, 0x55, 0xdf, 0x79, 0x1b, 0x38, 0x71, - 0x1f, 0x58, 0x3c, 0xf0, 0x1e, 0xad, 0xd0, 0x0f, 0xc0, 0x29, 0x55, 0xa4, 0x6e, 0x58, 0xa8, 0x55, - 0x68, 0xd6, 0x3a, 0x27, 0xa8, 0x99, 0x9c, 0x22, 0xb8, 0xaa, 0xd5, 0xd3, 0x5b, 0xb2, 0x91, 0x2b, - 0x20, 0x92, 0x43, 0x38, 0x8e, 0xdc, 0x80, 0x01, 0x56, 0x3e, 0x90, 0x89, 0xc8, 0x3b, 0xa1, 0x30, - 0xe5, 0x34, 0x0d, 0x2b, 0xf0, 0x6b, 0xc2, 0x39, 0x36, 0x7c, 0x2e, 0x62, 0x74, 0x41, 0xc1, 0xf8, - 0x8b, 0x07, 0x63, 0x97, 0x0d, 0x44, 0xfc, 0x83, 0x06, 0xcc, 0x86, 0xd6, 0x65, 0x22, 0xe2, 0xc0, - 0xb0, 0x78, 0x9c, 0xb8, 0x58, 0x05, 0xdc, 0xda, 0x3e, 0x1f, 0xc8, 0xa8, 0xb3, 0x5a, 0x5d, 0x8a, - 0xb5, 0xe3, 0x9b, 0x5c, 0x7c, 0xbb, 0xe0, 0x63, 0xf8, 0x15, 0xab, 0x1e, 0xe7, 0x2f, 0x09, 0x01, - 0x97, 0x80, 0xc1, 0xa4, 0x10, 0x70, 0x0d, 0x90, 0x89, 0xc8, 0x55, 0x18, 0xc7, 0x32, 0x96, 0xe1, - 0x46, 0x97, 0xa9, 0xc4, 0x10, 0x9a, 0xfc, 0x78, 0x5f, 0xc7, 0x2b, 0x5f, 0xb2, 0x8f, 0xab, 0xd5, - 0x05, 0x5a, 0x4f, 0xb6, 0x60, 0xf3, 0xc8, 0xcd, 0x1b, 0x65, 0xbc, 0xe8, 0xeb, 0xe7, 0xf3, 0x88, - 0xdd, 0x30, 0x74, 0x06, 0x23, 0x77, 0x60, 0x88, 0xcd, 0x6b, 0xe1, 0x30, 0x19, 0x41, 0x4d, 0xd1, - 0x14, 0x29, 0xa4, 0x4c, 0x7d, 0xc2, 0x73, 0x22, 0x42, 0xe8, 0x31, 0x46, 0xca, 0x71, 0xf5, 0xe8, - 0x81, 0x1f, 0x57, 0x9f, 0x7d, 0x15, 0x48, 0xb2, 0x23, 0x76, 0x75, 0x0d, 0xf9, 0xd7, 0xe0, 0x5c, - 0xb7, 0x17, 0x61, 0xbb, 0x2c, 0xdc, 0xca, 0x72, 0xc7, 0x84, 0xfe, 0x28, 0x0f, 0xf1, 0x1a, 0xd5, - 0xc5, 0xff, 0xe4, 0x25, 0x18, 0x15, 0x41, 0x0d, 0x6c, 0x9e, 0x5a, 0x31, 0xbc, 0x15, 0x31, 0xb9, - 0xa3, 0x87, 0x8d, 0x82, 0xd2, 0x87, 0x39, 0xe0, 0x3a, 0xdd, 0x98, 0x31, 0xbc, 0x15, 0xed, 0x6f, - 0xf4, 0x42, 0xf1, 0x86, 0x65, 0xb7, 0xef, 0x1e, 0x4f, 0x5e, 0x07, 0x34, 0x79, 0x29, 0x33, 0x4d, - 0x61, 0x27, 0x33, 0xcd, 0x93, 0x30, 0x80, 0x9d, 0x80, 0x12, 0xeb, 0x8b, 0x0e, 0xc3, 0x1a, 0x0c, - 0xc8, 0x67, 0x8e, 0x88, 0x80, 0x34, 0xd5, 0x79, 0x83, 0xfb, 0x7e, 0x3d, 0x1b, 0xbc, 0x5e, 0xa7, - 0xfe, 0x3c, 0x98, 0x59, 0x63, 0x60, 0x07, 0xb3, 0xc6, 0x01, 0x8c, 0x8c, 0x5f, 0xca, 0x47, 0x87, - 0x6c, 0x47, 0x3d, 0xf2, 0xe5, 0x20, 0x4c, 0xcf, 0x8f, 0xc0, 0x40, 0x54, 0x7a, 0x6e, 0xfb, 0x5c, - 0xfe, 0x8f, 0x89, 0xa3, 0xdf, 0x33, 0x41, 0x28, 0x05, 0xd6, 0x9e, 0x53, 0x52, 0xed, 0x47, 0x1c, - 0xc9, 0xeb, 0xd0, 0x77, 0x95, 0xfb, 0xf9, 0xed, 0x20, 0x8f, 0x7f, 0x58, 0x9e, 0x2f, 0x62, 0xee, - 0xb4, 0x12, 0xd5, 0x81, 0x39, 0x3b, 0x65, 0x3c, 0xf6, 0xed, 0x63, 0x3c, 0xbe, 0x1f, 0x86, 0x2a, - 0x86, 0xeb, 0x5b, 0x75, 0xab, 0x65, 0x30, 0x3b, 0xb3, 0x1f, 0x4d, 0x35, 0x7e, 0xf7, 0x29, 0xc1, - 0x65, 0x43, 0x5a, 0xa6, 0xd7, 0xfe, 0x30, 0x03, 0xe3, 0x89, 0x42, 0xd2, 0xe4, 0x59, 0x00, 0x0e, - 0x91, 0x8a, 0xaa, 0x71, 0x6f, 0xfc, 0xb0, 0xb8, 0xb4, 0xd8, 0x1a, 0x45, 0x64, 0xe4, 0x32, 0xf4, - 0xf3, 0x5f, 0xa2, 0x5c, 0x44, 0xb2, 0x49, 0xbb, 0x6d, 0x99, 0x7a, 0x48, 0x14, 0x3d, 0x05, 0x9d, - 0x65, 0x72, 0xa9, 0x4d, 0xfc, 0x8d, 0x56, 0xf8, 0x14, 0x74, 0x94, 0x99, 0x84, 0xe1, 0xb9, 0xe9, - 0xf2, 0x6d, 0xcb, 0x98, 0x70, 0x9d, 0x75, 0x4f, 0x5c, 0x7a, 0xf7, 0xf3, 0x30, 0x5c, 0xd6, 0x6e, - 0xcd, 0x32, 0x6a, 0x8b, 0x1c, 0x25, 0x3b, 0xea, 0xc4, 0xda, 0x68, 0x9f, 0xcc, 0xc2, 0x50, 0xf8, - 0xd5, 0x65, 0xf3, 0xb0, 0x86, 0x50, 0x41, 0x14, 0xf6, 0xce, 0x6d, 0x57, 0xd8, 0x5b, 0x99, 0x55, - 0x45, 0x25, 0xef, 0x83, 0x4b, 0x51, 0xf3, 0xe9, 0x2c, 0x8c, 0x86, 0x4f, 0x3d, 0x44, 0xe7, 0x8e, - 0x7b, 0x48, 0x24, 0x9f, 0xca, 0x40, 0x71, 0xc2, 0x6a, 0x34, 0x2c, 0x7b, 0x79, 0xd6, 0x5e, 0x72, - 0xdc, 0x26, 0xda, 0x6c, 0x87, 0x77, 0x07, 0xa8, 0x7d, 0x75, 0x06, 0xc6, 0xc5, 0x0b, 0x4d, 0x1a, - 0xae, 0x79, 0x78, 0x17, 0x2c, 0xea, 0x9b, 0x1c, 0x9e, 0xbe, 0x68, 0x3f, 0x98, 0x05, 0xb8, 0xe1, - 0xd4, 0x57, 0x8f, 0x78, 0x86, 0x9b, 0x97, 0xa0, 0xc0, 0x73, 0x04, 0x08, 0x8d, 0x1d, 0x17, 0x99, - 0x5c, 0xd8, 0xa7, 0x71, 0xc4, 0xc4, 0x98, 0x58, 0x5e, 0x0a, 0x3c, 0xcd, 0x40, 0x31, 0xa3, 0x8b, - 0x26, 0xec, 0xa1, 0x8c, 0x4e, 0xac, 0x7f, 0xe1, 0x43, 0x19, 0x2c, 0xfe, 0xd0, 0xad, 0xcd, 0x52, - 0xbe, 0xe1, 0xd4, 0x57, 0x75, 0xa4, 0xd7, 0xfe, 0x5b, 0x86, 0xcb, 0xee, 0x88, 0xe7, 0xe9, 0x0a, - 0x3e, 0x3f, 0xbf, 0xcb, 0xcf, 0xff, 0xda, 0x0c, 0x9c, 0xd4, 0x69, 0x9d, 0xed, 0x66, 0x36, 0x26, - 0x1d, 0x93, 0xbe, 0x46, 0x6d, 0xea, 0x1e, 0xd6, 0x88, 0xfa, 0x91, 0x0c, 0x8c, 0xc9, 0x2f, 0x73, - 0xcb, 0xa3, 0x87, 0xb3, 0x26, 0x45, 0xe6, 0x7d, 0x6e, 0x17, 0x9e, 0x2c, 0x7f, 0xd1, 0x07, 0xc5, - 0xd4, 0x8d, 0xf9, 0x91, 0xb5, 0x4e, 0x3b, 0x9e, 0xb6, 0xe4, 0x0f, 0xea, 0xb4, 0xa5, 0x77, 0x77, - 0xa7, 0x2d, 0x85, 0xdd, 0x9e, 0xb6, 0xf4, 0xed, 0xe4, 0xb4, 0x65, 0xbb, 0x5d, 0x53, 0xa7, 0x4e, - 0xdd, 0xe3, 0xae, 0x29, 0x66, 0xaf, 0x0f, 0xbc, 0x95, 0xf6, 0x3a, 0x1c, 0xac, 0xbd, 0xbe, 0x97, - 0x43, 0xa2, 0x0b, 0x6c, 0x52, 0xac, 0x3b, 0xae, 0x49, 0x4d, 0x71, 0x36, 0x84, 0xd7, 0xc6, 0xae, - 0x80, 0xe9, 0x21, 0x96, 0x3c, 0xa7, 0xd8, 0xf0, 0xc3, 0x68, 0xc3, 0x23, 0x7b, 0xd9, 0x86, 0x8f, - 0x5b, 0xee, 0x8a, 0xa9, 0x33, 0xb2, 0x77, 0x53, 0xe7, 0x00, 0x36, 0xa6, 0x7f, 0x5c, 0x80, 0xd3, - 0x29, 0x3b, 0xec, 0x23, 0x3b, 0xfc, 0x0f, 0xee, 0xbc, 0x64, 0x2f, 0xa3, 0x3e, 0x76, 0xf2, 0x51, - 0xd8, 0xee, 0xe4, 0x63, 0x55, 0x1d, 0xc3, 0x7d, 0x38, 0x86, 0x9f, 0xe9, 0x72, 0xf2, 0x71, 0x40, - 0x23, 0xb8, 0xff, 0xad, 0x1c, 0xc1, 0x03, 0x6f, 0xe9, 0x08, 0x86, 0xdd, 0x8e, 0xe0, 0xc1, 0x5d, - 0x8d, 0xe0, 0xa1, 0x9d, 0x8c, 0xe0, 0x03, 0x18, 0x77, 0xff, 0x25, 0x0b, 0xe3, 0xd2, 0x09, 0xe9, - 0x01, 0x98, 0xc1, 0x65, 0x18, 0x95, 0x18, 0xe2, 0xc6, 0x3c, 0x1b, 0x05, 0xa2, 0xd4, 0xa9, 0xeb, - 0xab, 0x71, 0x2c, 0x2a, 0x3d, 0x7b, 0xfc, 0xac, 0x49, 0x6d, 0xdf, 0xf2, 0x37, 0xc4, 0xf0, 0x0b, - 0x1f, 0x1f, 0xc0, 0xb9, 0x20, 0x2d, 0xf1, 0x4b, 0x0f, 0xe9, 0x25, 0x5f, 0xec, 0xfc, 0x1e, 0x7c, - 0xb1, 0x57, 0xe1, 0xa4, 0xf4, 0x32, 0xe5, 0xb6, 0xbf, 0xe2, 0xb8, 0xec, 0x2d, 0x7a, 0x45, 0xe9, - 0xc8, 0x80, 0x57, 0x0a, 0x8d, 0x38, 0xde, 0x8e, 0x30, 0x35, 0x23, 0x40, 0xe9, 0xa9, 0x4c, 0xb5, - 0xef, 0xc9, 0xa4, 0x3f, 0x8d, 0x9c, 0x87, 0x3c, 0xca, 0x2d, 0x13, 0x2d, 0xd9, 0x8a, 0xc8, 0x10, - 0x4f, 0x9e, 0x0c, 0x17, 0xf7, 0x6c, 0x94, 0xe5, 0x83, 0x2f, 0xea, 0x72, 0x56, 0x35, 0xb1, 0xbc, - 0xbf, 0x0a, 0x43, 0xe2, 0xda, 0xf2, 0x3a, 0xdd, 0x08, 0x8b, 0x78, 0x9f, 0xdb, 0xda, 0x2c, 0x15, - 0xc5, 0xbd, 0x26, 0x9e, 0x67, 0xc7, 0xdc, 0xdc, 0x63, 0x2d, 0xb4, 0xef, 0xce, 0xc0, 0x79, 0x9d, - 0xda, 0x74, 0xdd, 0x58, 0x6c, 0xc8, 0xe7, 0xea, 0xc2, 0xf2, 0x65, 0xf3, 0x99, 0xe5, 0x35, 0x0d, - 0xbf, 0xbe, 0xb2, 0x2f, 0x0d, 0x9a, 0x86, 0x21, 0x79, 0x82, 0xde, 0xc5, 0xe4, 0x1d, 0x6b, 0xa7, - 0xfd, 0x76, 0x1e, 0xfa, 0x26, 0x1c, 0xff, 0x9a, 0x63, 0xd9, 0xfb, 0x7a, 0x9f, 0xc8, 0xa4, 0xcd, - 0xee, 0xe2, 0xc4, 0xfa, 0x69, 0x7c, 0x38, 0xce, 0x15, 0x52, 0x54, 0xe6, 0xa2, 0xe3, 0x27, 0xf2, - 0xc3, 0x09, 0x32, 0xd6, 0x9d, 0x22, 0xfb, 0x48, 0x3e, 0xea, 0x4e, 0x35, 0xf9, 0x78, 0x98, 0x87, - 0xe4, 0x05, 0x18, 0xc0, 0x94, 0x0f, 0x92, 0xbb, 0x02, 0x06, 0x58, 0xf2, 0x24, 0x11, 0xca, 0x33, - 0x22, 0x52, 0xf2, 0xe1, 0x58, 0xa9, 0xc7, 0xa0, 0xe8, 0xac, 0x3a, 0x49, 0x56, 0x7d, 0xb7, 0x5d, - 0xf7, 0x27, 0xb4, 0x4e, 0xb5, 0x1e, 0xff, 0x6c, 0xb3, 0x54, 0xe0, 0x34, 0xb1, 0xaa, 0x8f, 0xcf, - 0x72, 0x4f, 0x37, 0x7c, 0xa7, 0xbe, 0x68, 0xd4, 0xe3, 0x45, 0x96, 0xf2, 0x4a, 0x21, 0xa1, 0xb2, - 0x56, 0xf6, 0xef, 0x63, 0xad, 0x9c, 0x84, 0xe1, 0x09, 0xc7, 0x97, 0x82, 0xf9, 0x06, 0xa2, 0xc4, - 0x81, 0x4c, 0xf2, 0xe9, 0x91, 0x7c, 0xf1, 0x36, 0xe4, 0x22, 0xf4, 0x62, 0x15, 0x01, 0x31, 0xc5, - 0xe3, 0x79, 0x22, 0x96, 0x19, 0x90, 0x73, 0x88, 0xf0, 0xaa, 0x18, 0xdf, 0xd2, 0x0b, 0x43, 0x41, - 0xcb, 0x43, 0x52, 0xb3, 0xa7, 0xa0, 0x30, 0xe3, 0x48, 0x45, 0xf9, 0x31, 0x56, 0x70, 0xc5, 0xf1, - 0x94, 0x20, 0x48, 0x41, 0xc4, 0x3a, 0xe8, 0xa6, 0x63, 0xca, 0x91, 0xae, 0xd8, 0x41, 0xb6, 0x63, - 0x26, 0x72, 0x53, 0x86, 0x84, 0x6c, 0x3e, 0xc2, 0x20, 0x61, 0xc9, 0x29, 0x46, 0x09, 0x0c, 0x46, - 0xbc, 0xa4, 0xc0, 0x85, 0xdd, 0x2a, 0x70, 0xdf, 0x5e, 0x15, 0xb8, 0xff, 0x60, 0x15, 0xf8, 0x83, - 0x30, 0x84, 0x4f, 0xba, 0x7a, 0xb7, 0x65, 0xb9, 0xd4, 0xdb, 0x81, 0x11, 0xf1, 0x80, 0x30, 0x22, - 0x86, 0xf9, 0x7b, 0x53, 0xde, 0x10, 0x6d, 0x87, 0x18, 0x2b, 0x45, 0xcd, 0x61, 0x1f, 0x6a, 0x7e, - 0x12, 0x7a, 0x79, 0xa9, 0x96, 0x41, 0xbe, 0x9c, 0xe3, 0x8f, 0xa8, 0x3e, 0xcc, 0x90, 0x54, 0x1f, - 0x46, 0xfb, 0xb5, 0x0c, 0xf4, 0xdd, 0xb2, 0x57, 0x6d, 0x67, 0x7d, 0x7f, 0xda, 0xf9, 0x2c, 0x0c, - 0x0a, 0x36, 0xd2, 0x92, 0x8e, 0x8e, 0x2e, 0x6d, 0x0e, 0xae, 0x21, 0x27, 0x5d, 0xa6, 0x22, 0x2f, - 0x87, 0x8d, 0x26, 0xc3, 0xe2, 0x33, 0xfc, 0x6a, 0x21, 0x68, 0x54, 0x8f, 0x57, 0xb1, 0x97, 0xc9, - 0xc9, 0x39, 0xc8, 0x4f, 0xb1, 0x57, 0x95, 0x12, 0x07, 0xf1, 0xf8, 0x7b, 0x06, 0xd5, 0x3e, 0x91, - 0x87, 0x11, 0xe5, 0xd2, 0xe1, 0x9d, 0x30, 0x20, 0x0e, 0xfd, 0xad, 0xa0, 0xac, 0x3e, 0x7a, 0xba, - 0x84, 0x40, 0xbd, 0x9f, 0xff, 0x39, 0x6b, 0x92, 0xf7, 0x43, 0x9f, 0xe3, 0xa1, 0x25, 0x82, 0xdf, - 0x32, 0x12, 0x0d, 0xb7, 0xf9, 0x2a, 0x7b, 0x77, 0x3e, 0x90, 0x04, 0x89, 0xac, 0xbd, 0x8e, 0x87, - 0x9f, 0xf6, 0x1c, 0x0c, 0x18, 0x9e, 0x47, 0xfd, 0x9a, 0x6f, 0x2c, 0xcb, 0x95, 0xf6, 0x43, 0xa0, - 0x3c, 0x92, 0x10, 0xb8, 0x60, 0x2c, 0x93, 0x57, 0x61, 0xb8, 0xee, 0x52, 0xb4, 0x55, 0x8c, 0x46, - 0x94, 0x25, 0x43, 0x78, 0xf5, 0x48, 0x08, 0x79, 0x0d, 0x8e, 0x10, 0xb3, 0x26, 0xb9, 0x0d, 0xc3, - 0xe2, 0x73, 0x78, 0x40, 0x2f, 0x0e, 0xca, 0x91, 0x68, 0x75, 0xe4, 0x22, 0xe1, 0x21, 0xbd, 0x22, - 0xae, 0x5b, 0x26, 0x97, 0xf9, 0x9a, 0x12, 0x29, 0x99, 0x07, 0xb2, 0x4e, 0x17, 0xd1, 0x66, 0x61, - 0xcf, 0xe2, 0x85, 0xa2, 0x71, 0x1c, 0xf7, 0xf3, 0x60, 0xe8, 0x24, 0x56, 0x8e, 0x11, 0x5f, 0xa7, - 0x8b, 0xe5, 0x18, 0x92, 0xdc, 0x81, 0x53, 0xc9, 0x26, 0xec, 0x93, 0xb9, 0xdb, 0xc7, 0x63, 0x5b, - 0x9b, 0xa5, 0x52, 0x2a, 0x81, 0xc4, 0xf6, 0x44, 0x82, 0xed, 0xac, 0x79, 0x2d, 0xdf, 0xdf, 0x37, - 0xd6, 0xaf, 0x8f, 0xb0, 0xb6, 0x81, 0xdd, 0x6e, 0x99, 0xda, 0xcf, 0x66, 0x98, 0x7d, 0xce, 0x3e, - 0xe8, 0x2a, 0x13, 0xc4, 0xbe, 0x12, 0x58, 0x9c, 0x0f, 0xf3, 0x5c, 0xa5, 0xce, 0xc4, 0x61, 0x66, - 0xab, 0x4b, 0x50, 0x30, 0xe5, 0xcb, 0x86, 0xd3, 0xf1, 0x4e, 0x08, 0x9e, 0xa3, 0x0b, 0x2a, 0x72, - 0x41, 0x24, 0x3d, 0xca, 0x77, 0x36, 0x68, 0x78, 0x9a, 0x23, 0xed, 0x4b, 0xb2, 0x30, 0x24, 0x7d, - 0xcd, 0x95, 0x7d, 0x7d, 0xce, 0x7b, 0x77, 0xf6, 0x9a, 0x81, 0xb3, 0x39, 0x1e, 0x41, 0x05, 0xaf, - 0x1c, 0xa5, 0xfc, 0xea, 0xdd, 0x45, 0xca, 0xaf, 0x20, 0xbb, 0x53, 0x61, 0x77, 0xd9, 0x9d, 0xae, - 0xe5, 0xfb, 0xb3, 0x63, 0xb9, 0x6b, 0xf9, 0xfe, 0xfc, 0x58, 0x2f, 0x16, 0x84, 0xc0, 0x22, 0xb2, - 0xfc, 0x48, 0xd3, 0x5e, 0xb2, 0x96, 0x8f, 0x78, 0x48, 0xf9, 0xc1, 0x16, 0xcb, 0x50, 0x64, 0x73, - 0xc4, 0xe3, 0xcb, 0xdf, 0x52, 0xd9, 0x1c, 0xed, 0x60, 0xf3, 0x03, 0x94, 0xcd, 0xaf, 0x67, 0xa0, - 0x98, 0x2a, 0x9b, 0xf2, 0x21, 0xf9, 0x1f, 0x1f, 0x58, 0xae, 0x7b, 0xed, 0x77, 0xb3, 0x30, 0x3e, - 0x6b, 0xfb, 0x74, 0x99, 0x6f, 0x44, 0x8f, 0xf8, 0x54, 0x71, 0x1d, 0x06, 0xa5, 0x8f, 0x11, 0x7d, - 0x1e, 0xd6, 0x45, 0x93, 0x50, 0x9d, 0xea, 0xa2, 0x49, 0x24, 0x07, 0x17, 0xa0, 0xaf, 0x0a, 0xf9, - 0x88, 0xcf, 0x39, 0x47, 0x43, 0xc8, 0x47, 0x7c, 0xf2, 0xba, 0x47, 0x85, 0xfc, 0xe9, 0x1c, 0x9c, - 0x48, 0x79, 0x38, 0x39, 0x1f, 0xa5, 0xd7, 0xcf, 0x44, 0x91, 0x7a, 0x41, 0x7a, 0xfd, 0x30, 0xa9, - 0x3e, 0x59, 0xc0, 0x9c, 0x5b, 0xf3, 0xb3, 0x53, 0x93, 0x42, 0xaa, 0x9a, 0x94, 0xd4, 0x95, 0x81, - 0xd3, 0xbe, 0x2c, 0xcc, 0xcb, 0xe5, 0x58, 0x66, 0x5d, 0xc9, 0xcb, 0xc5, 0xda, 0x90, 0x2f, 0x82, - 0x81, 0xf2, 0x9b, 0x6d, 0x97, 0x22, 0xdf, 0x5c, 0xdc, 0xb5, 0x36, 0x44, 0xa4, 0x71, 0xe6, 0x29, - 0xc6, 0x18, 0x85, 0xca, 0x3b, 0x62, 0x48, 0xe6, 0xa1, 0xf0, 0x9a, 0xe5, 0xcf, 0xb4, 0x17, 0x45, - 0x2f, 0x84, 0x35, 0x22, 0x38, 0x34, 0x8d, 0x2f, 0xee, 0xe0, 0x79, 0xad, 0x3b, 0x79, 0x0f, 0xc4, - 0x1b, 0x90, 0x1b, 0xd0, 0x5b, 0xbe, 0x53, 0xd5, 0xcb, 0xa2, 0x27, 0x1e, 0x91, 0xf3, 0xda, 0x96, - 0x3b, 0xb2, 0xc3, 0x74, 0x50, 0x86, 0x7c, 0x98, 0x82, 0xf4, 0xda, 0x27, 0x33, 0x70, 0xb6, 0xb3, - 0xf0, 0xc8, 0xd3, 0xd0, 0xa7, 0x3b, 0x0d, 0x5a, 0xd6, 0x6f, 0x8a, 0x9e, 0x41, 0x69, 0xb2, 0x4d, - 0x6e, 0xcd, 0x70, 0xe5, 0xbd, 0x48, 0x40, 0x46, 0xde, 0x07, 0x83, 0xb3, 0x9e, 0xd7, 0xa6, 0x6e, - 0xf5, 0xd9, 0x5b, 0xfa, 0xac, 0xd8, 0xb2, 0xe2, 0x96, 0xc8, 0x42, 0x70, 0xcd, 0x7b, 0x56, 0x29, - 0x40, 0x21, 0xd3, 0x6b, 0x5f, 0x93, 0x81, 0x73, 0xdd, 0x84, 0x4e, 0x9e, 0x85, 0xfe, 0x05, 0x6a, - 0x1b, 0xb6, 0x3f, 0x3b, 0x25, 0x5e, 0x09, 0x77, 0x80, 0x3e, 0xc2, 0xe2, 0x1b, 0x99, 0x90, 0x90, - 0x35, 0xe2, 0x67, 0xcd, 0xa1, 0x8f, 0x1b, 0x3f, 0x17, 0x47, 0x98, 0xd2, 0x28, 0x20, 0xd4, 0x3e, - 0x0c, 0x0f, 0x74, 0xec, 0x23, 0xf2, 0x7e, 0x18, 0x9a, 0x77, 0x97, 0x0d, 0xdb, 0x7a, 0x93, 0x0f, - 0xb1, 0x4c, 0xb4, 0xcb, 0x76, 0x24, 0xb8, 0xbc, 0xf3, 0x93, 0xe9, 0xb5, 0x45, 0x28, 0x76, 0xea, - 0x30, 0x32, 0x0d, 0x23, 0x58, 0xa2, 0xb0, 0x6c, 0xd7, 0x57, 0x1c, 0x37, 0x92, 0x3d, 0x26, 0x44, - 0xf0, 0x19, 0xa6, 0x66, 0x20, 0x4a, 0xe9, 0x03, 0xa5, 0x95, 0xf6, 0x0b, 0x59, 0x18, 0xaa, 0x34, - 0xda, 0xcb, 0xd6, 0x41, 0x54, 0x8e, 0xdd, 0x6b, 0xee, 0x58, 0xb9, 0x9a, 0x67, 0x6e, 0x8f, 0xd5, - 0x3c, 0x5f, 0x86, 0x42, 0x0b, 0xbf, 0x43, 0xbd, 0x5e, 0xe0, 0x5f, 0xd7, 0xe9, 0x7a, 0x81, 0xb7, - 0x61, 0xf3, 0x57, 0x7d, 0x1f, 0xf3, 0x57, 0xd4, 0x56, 0x12, 0xe8, 0x01, 0x14, 0x8e, 0x3d, 0x16, - 0xa8, 0x2c, 0xd0, 0x03, 0xa8, 0x1b, 0x7b, 0x2c, 0x50, 0x5b, 0xfb, 0x27, 0x59, 0x18, 0x89, 0x3f, - 0x92, 0x3c, 0x0d, 0x83, 0xfc, 0x31, 0xfc, 0xdc, 0x2d, 0x23, 0x05, 0x4b, 0x46, 0x60, 0x1d, 0xf8, - 0x0f, 0x71, 0x80, 0x38, 0xba, 0x62, 0x78, 0xb5, 0xe8, 0x04, 0x8c, 0xbb, 0x15, 0xf5, 0xf3, 0x53, - 0x79, 0x05, 0xa5, 0x8f, 0xac, 0x18, 0xde, 0x64, 0xf4, 0x9b, 0x5c, 0x05, 0xe2, 0xd2, 0xb6, 0x47, - 0xe3, 0x0c, 0xb8, 0xbb, 0x2f, 0x5f, 0x3d, 0x12, 0x58, 0x7d, 0x9c, 0xc3, 0x64, 0x36, 0x1f, 0x09, - 0x5f, 0x1b, 0x95, 0xa1, 0xb7, 0xfb, 0x89, 0xf3, 0x63, 0x5b, 0x9b, 0xa5, 0x53, 0x12, 0x7d, 0xfa, - 0x91, 0x33, 0x27, 0x98, 0x32, 0x7c, 0x83, 0x1f, 0x7a, 0x04, 0x1d, 0xa0, 0xfd, 0xa7, 0xbf, 0x9f, - 0x81, 0xde, 0x79, 0x9b, 0xce, 0x2f, 0x91, 0x67, 0x60, 0x80, 0x69, 0xcc, 0x0d, 0x87, 0x75, 0x66, - 0x46, 0xf8, 0xf5, 0x49, 0xaa, 0x84, 0x88, 0x99, 0x1e, 0x3d, 0xa2, 0x22, 0xcf, 0x01, 0x44, 0xb9, - 0x33, 0x84, 0xfa, 0x11, 0xb9, 0x0d, 0xc7, 0xcc, 0xf4, 0xe8, 0x12, 0x5d, 0xd0, 0x4a, 0x64, 0x1e, - 0xc8, 0x25, 0x5b, 0x71, 0x4c, 0xd0, 0x4a, 0x0c, 0x90, 0x1b, 0x40, 0xd8, 0xaf, 0x8a, 0xe1, 0x79, - 0xeb, 0x8e, 0x6b, 0x4e, 0xae, 0x18, 0xf6, 0x32, 0x55, 0xb7, 0xa7, 0x49, 0x8a, 0x99, 0x1e, 0x3d, - 0xa5, 0x1d, 0x79, 0x2f, 0x0c, 0xc9, 0x51, 0x15, 0xaa, 0x33, 0xa2, 0x8c, 0x9b, 0xe9, 0xd1, 0x63, - 0xb4, 0xe4, 0xdd, 0x30, 0x28, 0x7e, 0x5f, 0x73, 0x84, 0xa7, 0x93, 0x94, 0xb5, 0x58, 0x42, 0xcd, - 0xf4, 0xe8, 0x32, 0xa5, 0xf4, 0xd0, 0x8a, 0x6b, 0xd9, 0xbe, 0xf0, 0xa3, 0x57, 0x1f, 0x8a, 0x38, - 0xe9, 0xa1, 0xf8, 0x9b, 0xbc, 0x0f, 0x86, 0xc3, 0x9a, 0x07, 0x18, 0x27, 0xca, 0x6f, 0x22, 0x4e, - 0x29, 0x8d, 0x39, 0x72, 0xa6, 0x47, 0x8f, 0x53, 0x93, 0x0b, 0x50, 0xd0, 0xa9, 0x67, 0xbd, 0x19, - 0xf8, 0x29, 0x8c, 0x48, 0x03, 0xdd, 0x7a, 0x93, 0x49, 0x49, 0xe0, 0x59, 0xef, 0x44, 0x5e, 0x17, - 0xe2, 0xde, 0x80, 0x28, 0x4f, 0xb9, 0x6a, 0x9b, 0xac, 0x77, 0x24, 0xaf, 0x99, 0x57, 0xa3, 0x4a, - 0x10, 0x3c, 0x25, 0x14, 0x5e, 0x16, 0xc4, 0x72, 0xbb, 0xc9, 0xd8, 0x99, 0x1e, 0x5d, 0xa1, 0x97, - 0xa4, 0x3a, 0x65, 0x79, 0xab, 0xa2, 0xae, 0x98, 0x2a, 0x55, 0x86, 0x92, 0xa4, 0xca, 0x7e, 0x4a, - 0x8f, 0xbe, 0x49, 0xfd, 0x75, 0xc7, 0x5d, 0x15, 0x55, 0xc4, 0xd4, 0x47, 0x0b, 0xac, 0xf4, 0x68, - 0x01, 0x91, 0x1f, 0x1d, 0xc5, 0xae, 0x25, 0x1e, 0x6d, 0xf8, 0x86, 0xfc, 0x68, 0x7e, 0xd4, 0x19, - 0x74, 0xd2, 0x0d, 0x6a, 0xac, 0x05, 0xe1, 0x69, 0x6a, 0x87, 0x22, 0x4e, 0xea, 0x50, 0xfc, 0xcd, - 0x1e, 0x5a, 0x71, 0x5c, 0x7f, 0xda, 0x71, 0xd7, 0x0d, 0xd7, 0x2c, 0x8e, 0xc5, 0x1f, 0x2a, 0xa1, - 0xd8, 0x43, 0xa5, 0x9f, 0xac, 0x83, 0x5e, 0x7f, 0xe6, 0x99, 0xa0, 0xdd, 0x78, 0xbc, 0x83, 0x22, - 0x0c, 0xeb, 0xa0, 0xe8, 0x17, 0x29, 0x41, 0xae, 0x3a, 0x59, 0x29, 0x12, 0x24, 0x1f, 0x0c, 0xdf, - 0x70, 0xb2, 0x32, 0xd3, 0xa3, 0x33, 0x0c, 0xd1, 0x20, 0x7f, 0xf5, 0x2e, 0xad, 0x17, 0x4f, 0x20, - 0xc5, 0x50, 0x40, 0xc1, 0x60, 0x33, 0x3d, 0x3a, 0xe2, 0xd8, 0x14, 0x51, 0x6d, 0x2f, 0x7a, 0x58, - 0xd0, 0xba, 0x78, 0x32, 0x3e, 0x45, 0x84, 0x08, 0x36, 0x45, 0x84, 0x3f, 0xc8, 0x34, 0x8c, 0x71, - 0xbb, 0x12, 0x0f, 0x82, 0x70, 0xba, 0x2e, 0x9e, 0x8a, 0xaf, 0x35, 0x2a, 0x7e, 0xa6, 0x47, 0x4f, - 0xb4, 0x61, 0xe2, 0x2a, 0xb7, 0xfd, 0x95, 0xb2, 0x8f, 0x93, 0x5b, 0xf1, 0xb4, 0x92, 0x2a, 0x3c, - 0x42, 0x31, 0x71, 0x49, 0x3f, 0xc9, 0x3c, 0x9c, 0x28, 0xd7, 0xeb, 0xd4, 0xf3, 0x44, 0xea, 0x64, - 0x31, 0x59, 0x9d, 0x89, 0xef, 0x0c, 0x53, 0x48, 0x66, 0x7a, 0xf4, 0xb4, 0x96, 0x64, 0x12, 0x46, - 0xd9, 0x84, 0x22, 0x55, 0xcf, 0x2e, 0x16, 0xe3, 0x4e, 0x5b, 0x0a, 0x7a, 0xa6, 0x47, 0x57, 0x5b, - 0xb0, 0x4e, 0x64, 0xfb, 0x06, 0xd1, 0xfe, 0x81, 0x78, 0x27, 0x46, 0x18, 0xd6, 0x89, 0xd1, 0xaf, - 0xa0, 0x95, 0x98, 0x39, 0xcf, 0x26, 0x5b, 0x45, 0x33, 0x67, 0xf4, 0x8b, 0xe8, 0x70, 0x32, 0x5e, - 0xf8, 0x5b, 0x3c, 0xf5, 0xc1, 0xb8, 0x97, 0x49, 0x1a, 0xcd, 0x4c, 0x8f, 0x9e, 0xda, 0x36, 0xc9, - 0x53, 0xbc, 0xd3, 0xb9, 0x6e, 0x3c, 0xc3, 0xb7, 0x4b, 0x6d, 0x4b, 0x8c, 0x2e, 0x05, 0xca, 0x8b, - 0x0f, 0xc5, 0xf7, 0x90, 0x1d, 0x09, 0x67, 0x7a, 0xf4, 0x2e, 0x65, 0xce, 0x6f, 0x75, 0xa8, 0x16, - 0x5e, 0x7c, 0x18, 0xd9, 0x3f, 0x24, 0x6d, 0x51, 0x93, 0x44, 0x33, 0x3d, 0x7a, 0x87, 0x5a, 0xe3, - 0xb7, 0x3a, 0x14, 0x93, 0x2e, 0x96, 0xba, 0xb2, 0x0d, 0xe5, 0xd1, 0xa1, 0x14, 0xf5, 0x7c, 0x6a, - 0x1d, 0xe6, 0xe2, 0x23, 0x71, 0xd5, 0x4d, 0x21, 0x61, 0xaa, 0x9b, 0x56, 0xc1, 0x79, 0x3e, 0xb5, - 0x70, 0x70, 0xf1, 0xd1, 0x2e, 0x0c, 0xc3, 0x77, 0x4c, 0x2d, 0x39, 0x3c, 0x9f, 0x5a, 0xb9, 0xb7, - 0xa8, 0xc5, 0x19, 0xa6, 0x90, 0x30, 0x86, 0x69, 0x35, 0x7f, 0xe7, 0x53, 0x0b, 0xbc, 0x16, 0x1f, - 0xeb, 0xc2, 0x30, 0x7a, 0xc3, 0xb4, 0xd2, 0xb0, 0xef, 0x8e, 0x55, 0x58, 0x2d, 0x3e, 0x1e, 0x9f, - 0x37, 0x24, 0x14, 0x9b, 0x37, 0xe4, 0x5a, 0xac, 0x93, 0x89, 0x62, 0x6e, 0xc5, 0x27, 0xe2, 0xc3, - 0x5c, 0x41, 0xb3, 0x61, 0xae, 0x96, 0x7f, 0x9b, 0x4c, 0x14, 0x9c, 0x2a, 0x9e, 0xef, 0xc4, 0x04, - 0xd1, 0x71, 0x26, 0x3c, 0x39, 0xfe, 0x6c, 0x4a, 0xc5, 0xa3, 0xe2, 0x3b, 0xe2, 0xf1, 0x46, 0x09, - 0x82, 0x99, 0x1e, 0x3d, 0xa5, 0x4e, 0x92, 0x9e, 0x9e, 0x47, 0xb6, 0x78, 0x21, 0x3e, 0x6c, 0xd3, - 0x68, 0xd8, 0xb0, 0x4d, 0xcd, 0x41, 0x7b, 0x23, 0x2d, 0xc6, 0xb3, 0x78, 0x31, 0x6e, 0x98, 0x25, - 0x29, 0x98, 0x61, 0x96, 0x12, 0x1b, 0xaa, 0xa7, 0x27, 0x0f, 0x2d, 0xbe, 0xb3, 0xeb, 0x1b, 0x22, - 0x4d, 0xca, 0x1b, 0xf2, 0x5c, 0x9a, 0x91, 0xed, 0x74, 0xab, 0xd5, 0x70, 0x0c, 0xb3, 0xf8, 0xae, - 0x54, 0xdb, 0x89, 0x23, 0x25, 0xdb, 0x89, 0x03, 0xd8, 0x2a, 0x2f, 0xc7, 0xde, 0x15, 0x9f, 0x8c, - 0xaf, 0xf2, 0x32, 0x8e, 0xad, 0xf2, 0xb1, 0x38, 0xbd, 0xc9, 0x44, 0x9c, 0x5a, 0xf1, 0xa9, 0xb8, - 0x02, 0x28, 0x68, 0xa6, 0x00, 0x6a, 0x64, 0xdb, 0x47, 0x3b, 0x47, 0x76, 0x15, 0x2f, 0xc5, 0xcf, - 0xc2, 0x3a, 0xd1, 0xcd, 0xf4, 0xe8, 0x9d, 0xa3, 0xc3, 0x66, 0x53, 0x02, 0xb5, 0x8a, 0x97, 0xe3, - 0x0a, 0x96, 0x20, 0x60, 0x0a, 0x96, 0x0c, 0xef, 0x9a, 0x4d, 0x89, 0xb4, 0x2a, 0x3e, 0xdd, 0x91, - 0x55, 0xf8, 0xcd, 0x29, 0xf1, 0x59, 0xcf, 0xc9, 0xa1, 0x52, 0xc5, 0x67, 0xe2, 0x8b, 0x5d, 0x84, - 0x61, 0x8b, 0x9d, 0x14, 0x52, 0xf5, 0x9c, 0x1c, 0x24, 0x54, 0xbc, 0x92, 0x6c, 0x15, 0x2d, 0x91, - 0x52, 0x30, 0x91, 0x9e, 0x1e, 0x5b, 0x53, 0x7c, 0x36, 0xae, 0x75, 0x69, 0x34, 0x4c, 0xeb, 0x52, - 0xe3, 0x72, 0xa6, 0x93, 0x21, 0x32, 0xc5, 0xe7, 0xd4, 0x5d, 0x76, 0x1c, 0xcf, 0x2c, 0x9f, 0x44, - 0x58, 0xcd, 0xab, 0x6a, 0x92, 0xf3, 0xe2, 0xf3, 0xca, 0xbd, 0x7a, 0x0c, 0xcb, 0xec, 0x5b, 0x25, - 0x29, 0xfa, 0xab, 0x6a, 0x5e, 0xf0, 0xe2, 0x0b, 0xe9, 0x1c, 0x42, 0x5d, 0x51, 0xf3, 0x88, 0xbf, - 0xaa, 0xa6, 0xd2, 0x2e, 0xbe, 0x3b, 0x9d, 0x43, 0x28, 0x5d, 0x35, 0xf5, 0xf6, 0x33, 0x52, 0x5d, - 0xd7, 0xe2, 0x7b, 0xe2, 0xa6, 0x63, 0x88, 0x60, 0xa6, 0x63, 0x54, 0xfd, 0xf5, 0x19, 0xa9, 0x1e, - 0x6a, 0xf1, 0xc5, 0x44, 0x93, 0xf0, 0x65, 0xa5, 0xaa, 0xa9, 0xcf, 0x48, 0x75, 0x44, 0x8b, 0xef, - 0x4d, 0x34, 0x09, 0xdf, 0x4e, 0xaa, 0x36, 0x6a, 0x76, 0x4b, 0xfd, 0x52, 0x7c, 0x29, 0x35, 0x8f, - 0x45, 0x0a, 0xe5, 0x4c, 0x8f, 0xde, 0x2d, 0x85, 0xcc, 0x47, 0x3b, 0x07, 0x1c, 0x15, 0x5f, 0x8e, - 0x0f, 0xe1, 0x4e, 0x74, 0x6c, 0x08, 0x77, 0x0c, 0x5a, 0x7a, 0x9f, 0x92, 0xa4, 0xaf, 0xf8, 0xbe, - 0xf8, 0x14, 0x17, 0x43, 0xb2, 0x29, 0x4e, 0x4d, 0xe9, 0x17, 0xcb, 0x3e, 0x57, 0x7c, 0x7f, 0x7c, - 0x8a, 0x93, 0x71, 0x6c, 0x8a, 0x8b, 0x65, 0xaa, 0x9b, 0x4c, 0x24, 0x45, 0x2b, 0xbe, 0x12, 0x9f, - 0xe2, 0x14, 0x34, 0x9b, 0xe2, 0xd4, 0x34, 0x6a, 0xef, 0x53, 0x72, 0x83, 0x15, 0x5f, 0x4d, 0x7f, - 0x7f, 0x44, 0xca, 0xef, 0xcf, 0x33, 0x89, 0xe9, 0xe9, 0x49, 0xae, 0x8a, 0xe5, 0xf8, 0xf8, 0x4d, - 0xa3, 0x61, 0xe3, 0x37, 0x35, 0x41, 0x96, 0xba, 0x71, 0x10, 0x5a, 0x35, 0xd1, 0x65, 0xe3, 0x10, - 0x99, 0x22, 0x29, 0xe0, 0xd8, 0x1e, 0x99, 0x6f, 0x84, 0x26, 0x3b, 0xec, 0x91, 0x83, 0x6d, 0x90, - 0x42, 0xcf, 0x66, 0xd7, 0x84, 0xf7, 0x7c, 0x71, 0x2a, 0x3e, 0xbb, 0x26, 0x08, 0xd8, 0xec, 0x9a, - 0xf4, 0xb9, 0x9f, 0x86, 0x31, 0xa1, 0x45, 0x3c, 0x28, 0xc0, 0xb2, 0x97, 0x8b, 0x57, 0x95, 0xb4, - 0x06, 0x0a, 0x9e, 0xcd, 0x4e, 0x2a, 0x0c, 0xd7, 0x6b, 0x0e, 0x9b, 0x6c, 0x58, 0xad, 0x45, 0xc7, - 0x70, 0xcd, 0x2a, 0xb5, 0xcd, 0xe2, 0xb4, 0xb2, 0x5e, 0xa7, 0xd0, 0xe0, 0x7a, 0x9d, 0x02, 0xc7, - 0xdc, 0xd7, 0x0a, 0x5c, 0xe7, 0x25, 0x85, 0x8a, 0xaf, 0x21, 0xdb, 0x52, 0x27, 0xb6, 0x82, 0x6c, - 0xa6, 0x47, 0xef, 0xc4, 0x81, 0xd9, 0xea, 0x73, 0x1b, 0xd5, 0x0f, 0xdc, 0x08, 0xf3, 0xaa, 0x55, - 0x5c, 0xda, 0x32, 0x5c, 0x5a, 0x9c, 0x89, 0xdb, 0xea, 0xa9, 0x44, 0xcc, 0x56, 0x4f, 0x45, 0x24, - 0xd9, 0x06, 0x63, 0x61, 0xb6, 0x1b, 0xdb, 0x68, 0x44, 0xa4, 0xb7, 0x66, 0xb3, 0x53, 0x1c, 0xc1, - 0x04, 0x74, 0xc3, 0xb1, 0x97, 0xf1, 0xa4, 0xe2, 0x5a, 0x7c, 0x76, 0xea, 0x4c, 0xc9, 0x66, 0xa7, - 0xce, 0x58, 0xa6, 0xea, 0x71, 0x2c, 0x1f, 0x83, 0xd7, 0xe3, 0xaa, 0x9e, 0x42, 0xc2, 0x54, 0x3d, - 0x05, 0x9c, 0x64, 0xa8, 0x53, 0x8f, 0xfa, 0xc5, 0x1b, 0xdd, 0x18, 0x22, 0x49, 0x92, 0x21, 0x82, - 0x93, 0x0c, 0xa7, 0xa9, 0x5f, 0x5f, 0x29, 0xce, 0x75, 0x63, 0x88, 0x24, 0x49, 0x86, 0x08, 0x66, - 0x9b, 0xcd, 0x38, 0x78, 0xa2, 0xdd, 0x58, 0x0d, 0xfa, 0xec, 0x66, 0x7c, 0xb3, 0xd9, 0x91, 0x90, - 0x6d, 0x36, 0x3b, 0x22, 0xc9, 0xd7, 0xec, 0x38, 0x7e, 0xa1, 0x38, 0x8f, 0x0f, 0xbc, 0x14, 0xd9, - 0x05, 0x3b, 0x69, 0x35, 0xd3, 0xa3, 0xef, 0x34, 0x3e, 0xe2, 0x5d, 0xa1, 0x57, 0x6e, 0xb1, 0x82, - 0x8f, 0x1a, 0x0d, 0xcf, 0x2a, 0x38, 0x78, 0xa6, 0x47, 0x0f, 0xfd, 0x76, 0xdf, 0x0d, 0x83, 0xf8, - 0x51, 0xb3, 0xb6, 0xe5, 0x4f, 0x4d, 0x14, 0x3f, 0x10, 0xdf, 0x32, 0x49, 0x28, 0xb6, 0x65, 0x92, - 0x7e, 0xb2, 0x49, 0x1c, 0x7f, 0xf2, 0x29, 0x66, 0x6a, 0xa2, 0xa8, 0xc7, 0x27, 0xf1, 0x18, 0x92, - 0x4d, 0xe2, 0x31, 0x40, 0xf8, 0xdc, 0x29, 0xd7, 0x69, 0x4d, 0x4d, 0x14, 0xab, 0x29, 0xcf, 0xe5, - 0xa8, 0xf0, 0xb9, 0xfc, 0x67, 0xf8, 0xdc, 0xea, 0x4a, 0xdb, 0x9f, 0x62, 0xdf, 0xb8, 0x90, 0xf2, - 0xdc, 0x00, 0x19, 0x3e, 0x37, 0x00, 0xb0, 0xa9, 0x10, 0x01, 0x15, 0xd7, 0x61, 0x93, 0xf6, 0x75, - 0xab, 0xd1, 0x28, 0xde, 0x8a, 0x4f, 0x85, 0x2a, 0x9e, 0x4d, 0x85, 0x2a, 0x8c, 0x99, 0x9e, 0xfc, - 0xad, 0xe8, 0x62, 0x7b, 0xb9, 0x78, 0x3b, 0x6e, 0x7a, 0x46, 0x18, 0x66, 0x7a, 0x46, 0xbf, 0x70, - 0x77, 0xc1, 0x7e, 0xe9, 0x74, 0xc9, 0xa5, 0xde, 0x4a, 0xf1, 0x8e, 0xb2, 0xbb, 0x90, 0x70, 0xb8, - 0xbb, 0x90, 0x7e, 0x93, 0x65, 0x78, 0x30, 0xb6, 0xd0, 0x04, 0xb7, 0x36, 0x55, 0x6a, 0xb8, 0xf5, - 0x95, 0xe2, 0xeb, 0xc8, 0xea, 0xb1, 0xd4, 0xa5, 0x2a, 0x4e, 0x3a, 0xd3, 0xa3, 0x77, 0xe3, 0x84, - 0xdb, 0xf2, 0x0f, 0xdc, 0xe0, 0x15, 0x38, 0xf4, 0xca, 0x64, 0xb0, 0x09, 0xfd, 0xa0, 0xb2, 0x2d, - 0x4f, 0x92, 0xe0, 0xb6, 0x3c, 0x09, 0x26, 0x2d, 0x78, 0x58, 0xd9, 0xaa, 0xcd, 0x19, 0x0d, 0xb6, - 0x2f, 0xa1, 0x66, 0xc5, 0xa8, 0xaf, 0x52, 0xbf, 0xf8, 0x21, 0xe4, 0x7d, 0xbe, 0xc3, 0x86, 0x4f, - 0xa1, 0x9e, 0xe9, 0xd1, 0xb7, 0xe1, 0x47, 0x34, 0xc8, 0x57, 0xa7, 0x17, 0x2a, 0xc5, 0x0f, 0xc7, - 0xcf, 0x37, 0x19, 0x6c, 0xa6, 0x47, 0x47, 0x1c, 0xb3, 0xd2, 0x6e, 0xb5, 0x96, 0x5d, 0xc3, 0xa4, - 0xdc, 0xd0, 0x42, 0xdb, 0x4d, 0x18, 0xa0, 0x5f, 0x14, 0xb7, 0xd2, 0x3a, 0xd1, 0x31, 0x2b, 0xad, - 0x13, 0x8e, 0x29, 0x6a, 0xac, 0x06, 0x78, 0xf1, 0x23, 0x71, 0x45, 0x8d, 0x21, 0x99, 0xa2, 0xc6, - 0x2b, 0x86, 0xbf, 0x0e, 0xa7, 0xc3, 0xfd, 0xbc, 0x58, 0x7f, 0x79, 0xa7, 0x15, 0x3f, 0x8a, 0x7c, - 0x1e, 0x4e, 0x5c, 0x06, 0xc4, 0xa8, 0x66, 0x7a, 0xf4, 0x0e, 0xed, 0xd9, 0x8a, 0x7b, 0xbd, 0xbd, - 0x48, 0x5d, 0x9b, 0xfa, 0xcc, 0xa4, 0x92, 0x4f, 0x09, 0xbf, 0x38, 0xbe, 0xe2, 0x76, 0x20, 0x63, - 0x2b, 0x6e, 0x07, 0x54, 0x2a, 0x73, 0x21, 0x54, 0x63, 0x1b, 0xe6, 0xa1, 0x4c, 0x3b, 0x71, 0x48, - 0x65, 0x2e, 0x2c, 0xb5, 0xc5, 0x6d, 0x98, 0x87, 0xd6, 0x5a, 0x27, 0x0e, 0xe4, 0x02, 0x14, 0xaa, - 0xd5, 0x39, 0xbd, 0x6d, 0x17, 0xeb, 0x8a, 0x3b, 0x32, 0x42, 0x67, 0x7a, 0x74, 0x81, 0x67, 0x66, - 0xd0, 0xd5, 0x86, 0xe1, 0xf9, 0x56, 0xdd, 0xc3, 0x11, 0x13, 0x8c, 0x10, 0x33, 0x6e, 0x06, 0xa5, - 0xd1, 0x30, 0x33, 0x28, 0x0d, 0xce, 0xec, 0xc5, 0x49, 0xc3, 0xf3, 0x0c, 0xdb, 0x74, 0x8d, 0x09, - 0x5c, 0x26, 0xa8, 0x12, 0x63, 0x18, 0xc3, 0x32, 0x7b, 0x31, 0x0e, 0xc1, 0xc3, 0xf7, 0x00, 0x12, - 0x98, 0x39, 0x4b, 0xca, 0xe1, 0xbb, 0x82, 0xc7, 0xc3, 0x77, 0x05, 0x86, 0x76, 0x67, 0x00, 0xd3, - 0xe9, 0xb2, 0xc5, 0x44, 0x54, 0x5c, 0x56, 0xec, 0x4e, 0x95, 0x00, 0xed, 0x4e, 0x15, 0x18, 0x7b, - 0xa5, 0x60, 0xb9, 0x5d, 0xe9, 0xf0, 0x4a, 0xd1, 0x2a, 0x9b, 0x68, 0xc3, 0xd6, 0xef, 0x68, 0x70, - 0x4c, 0x6d, 0xd8, 0x46, 0xd3, 0x99, 0x9a, 0x08, 0xa4, 0x6e, 0xc5, 0xd7, 0xef, 0x8e, 0x84, 0x6c, - 0xfd, 0xee, 0x88, 0x64, 0xb3, 0x6b, 0xb0, 0xd1, 0x5a, 0x31, 0x5c, 0x6a, 0x4e, 0x59, 0x2e, 0x9e, - 0x2c, 0x6e, 0xf0, 0xad, 0xe1, 0x1b, 0xf1, 0xd9, 0xb5, 0x0b, 0x29, 0x9b, 0x5d, 0xbb, 0xa0, 0x99, - 0x91, 0x97, 0x8e, 0xd6, 0xa9, 0x61, 0x16, 0x57, 0xe3, 0x46, 0x5e, 0x67, 0x4a, 0x66, 0xe4, 0x75, - 0xc6, 0x76, 0xfe, 0x9c, 0x3b, 0xae, 0xe5, 0xd3, 0x62, 0x63, 0x27, 0x9f, 0x83, 0xa4, 0x9d, 0x3f, - 0x07, 0xd1, 0x6c, 0x43, 0xa8, 0x76, 0x48, 0x33, 0xbe, 0x21, 0x4c, 0x76, 0x83, 0xda, 0x82, 0x59, - 0x2c, 0x22, 0x98, 0xb2, 0x68, 0xc7, 0x2d, 0x16, 0x01, 0x66, 0x16, 0x4b, 0x14, 0x6e, 0x19, 0x8b, - 0x8b, 0x2b, 0x3a, 0xf1, 0x35, 0x54, 0xc6, 0xb1, 0x35, 0x34, 0x16, 0x43, 0xf7, 0xee, 0x58, 0x20, - 0x47, 0xb1, 0x15, 0xb7, 0x3a, 0x24, 0x14, 0xb3, 0x3a, 0xe4, 0x90, 0x8f, 0x49, 0x18, 0xc5, 0x5b, - 0x70, 0xbd, 0x1d, 0xde, 0xe3, 0x7c, 0x2c, 0xfe, 0x99, 0x0a, 0x9a, 0x7d, 0xa6, 0x02, 0x8a, 0x31, - 0x11, 0xd3, 0x96, 0xdb, 0x81, 0x49, 0x74, 0x3e, 0xa8, 0x80, 0xc8, 0x0d, 0x20, 0xd5, 0xf2, 0xdc, - 0x8d, 0x59, 0xb3, 0x22, 0x5f, 0x91, 0x79, 0xf1, 0x13, 0xd8, 0x24, 0xc5, 0x4c, 0x8f, 0x9e, 0xd2, - 0x8e, 0xbc, 0x01, 0xe7, 0x04, 0x54, 0x64, 0x02, 0xa9, 0x88, 0x72, 0xe7, 0xe2, 0x23, 0xfd, 0xb8, - 0xa3, 0x60, 0x37, 0xda, 0x99, 0x1e, 0xbd, 0x2b, 0xaf, 0xce, 0xcf, 0x12, 0xeb, 0x43, 0x7b, 0x27, - 0xcf, 0x0a, 0x17, 0x89, 0xae, 0xbc, 0x3a, 0x3f, 0x4b, 0xc8, 0x7d, 0x6d, 0x27, 0xcf, 0x0a, 0x3b, - 0xa1, 0x2b, 0x2f, 0xe2, 0x41, 0xa9, 0x1b, 0xbe, 0xdc, 0x68, 0x14, 0xd7, 0xf1, 0x71, 0xef, 0xd8, - 0xc9, 0xe3, 0xca, 0x68, 0x70, 0x6e, 0xc7, 0x91, 0xcd, 0xd2, 0xf3, 0x2d, 0x6a, 0x57, 0x63, 0x0b, - 0xd0, 0xdd, 0xf8, 0x2c, 0x9d, 0x20, 0x60, 0xb3, 0x74, 0x02, 0xc8, 0x06, 0x94, 0x1c, 0x0f, 0x54, - 0xdc, 0x88, 0x0f, 0x28, 0x19, 0xc7, 0x06, 0x54, 0x2c, 0x76, 0x68, 0x1e, 0x4e, 0xcc, 0xaf, 0xfa, - 0x46, 0x60, 0x41, 0x7a, 0xa2, 0x2b, 0xdf, 0x54, 0x2e, 0x99, 0x92, 0x24, 0x78, 0xc9, 0x94, 0x04, - 0xb3, 0x31, 0xc2, 0xc0, 0xd5, 0x0d, 0xbb, 0x3e, 0x6d, 0x58, 0x8d, 0xb6, 0x4b, 0x8b, 0x7f, 0x25, - 0x3e, 0x46, 0x14, 0x34, 0x1b, 0x23, 0x0a, 0x88, 0x2d, 0xd0, 0x0c, 0x54, 0xf6, 0x3c, 0x6b, 0xd9, - 0x16, 0xfb, 0xca, 0x76, 0xc3, 0x2f, 0xfe, 0x77, 0xf1, 0x05, 0x3a, 0x8d, 0x86, 0x2d, 0xd0, 0x69, - 0x70, 0x3c, 0x75, 0x62, 0xbd, 0xc0, 0x16, 0x0f, 0xf9, 0xae, 0xf2, 0xaf, 0x2a, 0xa7, 0x4e, 0x29, - 0x34, 0x78, 0xea, 0x94, 0x02, 0x67, 0xeb, 0x23, 0xb7, 0xc9, 0x6e, 0x58, 0xe1, 0x5d, 0xf5, 0x7f, - 0x1f, 0x5f, 0x1f, 0x55, 0x3c, 0x5b, 0x1f, 0x55, 0x58, 0x9c, 0x8f, 0xe8, 0x82, 0xbf, 0xd6, 0x89, - 0x4f, 0x28, 0xff, 0x44, 0x1b, 0xf2, 0x9a, 0xcc, 0x47, 0x8c, 0x94, 0x2f, 0xc9, 0x74, 0x62, 0x14, - 0x0e, 0x8f, 0x44, 0xa3, 0x38, 0x23, 0x9d, 0xae, 0x59, 0x74, 0xbd, 0xf8, 0xa5, 0x1d, 0x19, 0x71, - 0x82, 0x38, 0x23, 0x0e, 0x23, 0x1f, 0x84, 0xd3, 0x11, 0x6c, 0x8e, 0x36, 0x17, 0xc3, 0x99, 0xe9, - 0xcb, 0x32, 0x71, 0x33, 0x38, 0x9d, 0x8c, 0x99, 0xc1, 0xe9, 0x98, 0x34, 0xd6, 0x42, 0x74, 0x5f, - 0xbe, 0x0d, 0xeb, 0x50, 0x82, 0x1d, 0x18, 0xa4, 0xb1, 0x16, 0xd2, 0xfc, 0x8a, 0x6d, 0x58, 0x87, - 0x32, 0xed, 0xc0, 0x80, 0x7c, 0x3c, 0x03, 0xe7, 0xd3, 0x51, 0xe5, 0x46, 0x63, 0xda, 0x71, 0x23, - 0x5c, 0xf1, 0x2b, 0x33, 0xf1, 0x83, 0x86, 0x9d, 0x35, 0x9b, 0xe9, 0xd1, 0x77, 0xf8, 0x00, 0xf2, - 0x7e, 0x18, 0x2e, 0xb7, 0x4d, 0xcb, 0xc7, 0x8b, 0x37, 0x66, 0x38, 0x7f, 0x55, 0x46, 0xd9, 0xe2, - 0xc8, 0x58, 0xdc, 0xe2, 0xc8, 0x00, 0x72, 0x0d, 0xc6, 0xab, 0xb4, 0xde, 0xc6, 0x34, 0x16, 0xb4, - 0xe5, 0xb8, 0x3e, 0xe3, 0xf1, 0xd5, 0x99, 0xf8, 0x24, 0x96, 0xa0, 0x60, 0x93, 0x58, 0x02, 0x48, - 0x6e, 0x27, 0x6e, 0xe5, 0x45, 0x67, 0x7e, 0x4d, 0xa6, 0xeb, 0xb5, 0x7c, 0xd8, 0x97, 0xe9, 0xcd, - 0x49, 0x45, 0xb9, 0x45, 0x17, 0x5c, 0x3f, 0x9e, 0xe9, 0x72, 0x8d, 0x2e, 0xcd, 0x70, 0x49, 0x30, - 0xe3, 0x18, 0xbb, 0xbb, 0x16, 0x1c, 0xbf, 0x36, 0xd3, 0xe5, 0xda, 0x3b, 0xe2, 0x98, 0x02, 0x26, - 0xcf, 0x73, 0x4f, 0x11, 0xc1, 0xe8, 0xeb, 0x32, 0x49, 0x57, 0x91, 0xb0, 0xbd, 0x44, 0xc8, 0x9a, - 0xdd, 0xf2, 0x42, 0xa5, 0xff, 0x44, 0x26, 0xe9, 0x9b, 0x17, 0x35, 0x8b, 0x7e, 0x11, 0x0a, 0x67, - 0xaf, 0xde, 0xf5, 0xa9, 0x6b, 0x1b, 0x0d, 0xec, 0xce, 0xaa, 0xef, 0xb8, 0xc6, 0x32, 0xbd, 0x6a, - 0x1b, 0x8b, 0x0d, 0x5a, 0xfc, 0x64, 0x26, 0x6e, 0xc1, 0x76, 0x26, 0x65, 0x16, 0x6c, 0x67, 0x2c, - 0x59, 0x81, 0x07, 0xd3, 0xb0, 0x53, 0x96, 0x87, 0xcf, 0xf9, 0x54, 0x26, 0x6e, 0xc2, 0x76, 0xa1, - 0x65, 0x26, 0x6c, 0x17, 0x34, 0xb9, 0x02, 0x03, 0x13, 0x4e, 0x30, 0xfd, 0x7e, 0xbd, 0xe2, 0x0c, - 0x19, 0x62, 0x66, 0x7a, 0xf4, 0x88, 0x4c, 0xb4, 0x11, 0x83, 0xfa, 0xd3, 0xc9, 0x36, 0xd1, 0xe5, - 0x53, 0xf8, 0x43, 0xb4, 0x11, 0xe2, 0xfe, 0x1f, 0x92, 0x6d, 0xa2, 0x3b, 0xae, 0xf0, 0x07, 0x9b, - 0x49, 0xf8, 0x13, 0xe7, 0xa6, 0xcb, 0xcc, 0x6e, 0x9b, 0x5c, 0x31, 0x1a, 0x0d, 0x6a, 0x2f, 0xd3, - 0xe2, 0x67, 0x94, 0x99, 0x24, 0x9d, 0x8c, 0xcd, 0x24, 0xe9, 0x18, 0xf2, 0x45, 0x70, 0xe6, 0xb6, - 0xd1, 0xb0, 0xcc, 0x08, 0x17, 0x94, 0xea, 0x2f, 0x7e, 0x36, 0x13, 0xdf, 0x4d, 0x77, 0xa0, 0x63, - 0xbb, 0xe9, 0x0e, 0x28, 0x32, 0x07, 0x04, 0x97, 0xd1, 0x70, 0xb6, 0x60, 0xeb, 0x73, 0xf1, 0xaf, - 0x67, 0xe2, 0x76, 0x6a, 0x92, 0x84, 0xd9, 0xa9, 0x49, 0x28, 0xa9, 0x75, 0xae, 0x79, 0x58, 0xfc, - 0x86, 0x4c, 0xfc, 0xb4, 0xa6, 0x13, 0xe1, 0x4c, 0x8f, 0xde, 0xb9, 0x70, 0xe2, 0x6b, 0x30, 0x56, - 0xad, 0xcc, 0x4e, 0x4f, 0x5f, 0xad, 0xde, 0x9e, 0x9d, 0xc2, 0x50, 0x0d, 0xb3, 0xf8, 0x37, 0x94, - 0x15, 0x4b, 0x25, 0x60, 0x2b, 0x96, 0x0a, 0x23, 0x2f, 0xc1, 0x10, 0x7b, 0x7f, 0x36, 0x60, 0xf0, - 0x93, 0x3f, 0x97, 0x89, 0x9b, 0x53, 0x32, 0x92, 0x99, 0x53, 0xf2, 0x6f, 0x52, 0x85, 0x93, 0x4c, - 0x8a, 0x15, 0x97, 0x2e, 0x51, 0x97, 0xda, 0xf5, 0x60, 0x4c, 0x7f, 0x63, 0x26, 0x6e, 0x65, 0xa4, - 0x11, 0x31, 0x2b, 0x23, 0x0d, 0x4e, 0x56, 0xe1, 0x9c, 0x7a, 0x12, 0x24, 0xc7, 0xf5, 0x16, 0xff, - 0x66, 0x46, 0x31, 0x86, 0xbb, 0x10, 0xa3, 0x31, 0xdc, 0x05, 0x4f, 0x6c, 0x78, 0x48, 0x1c, 0xab, - 0x08, 0x87, 0x4b, 0xf5, 0x69, 0xdf, 0xc4, 0x9f, 0xf6, 0x44, 0xe4, 0x10, 0xd8, 0x85, 0x7a, 0xa6, - 0x47, 0xef, 0xce, 0x8e, 0xe9, 0x59, 0xb2, 0xb2, 0x5f, 0xf1, 0x9b, 0x33, 0xe9, 0x1e, 0x29, 0x31, - 0x37, 0xe5, 0xb4, 0x92, 0x80, 0x1f, 0xec, 0x54, 0x97, 0xae, 0xf8, 0xb7, 0x94, 0xf1, 0x96, 0x4e, - 0xc6, 0xc6, 0x5b, 0x87, 0xc2, 0x76, 0xd7, 0x60, 0x9c, 0x2b, 0x75, 0xc5, 0xc0, 0x61, 0x68, 0x2f, - 0x53, 0xb3, 0xf8, 0xb7, 0x95, 0xd5, 0x2e, 0x41, 0x81, 0xae, 0x3d, 0x2a, 0x90, 0x4d, 0xdd, 0xd5, - 0x96, 0x61, 0xdb, 0x78, 0xcc, 0x5a, 0xfc, 0x3b, 0xca, 0xd4, 0x1d, 0xa1, 0xd0, 0x71, 0x37, 0xfc, - 0xc5, 0x34, 0xa1, 0x5b, 0x4d, 0xd7, 0xe2, 0xff, 0xa8, 0x68, 0x42, 0x37, 0x62, 0xa6, 0x09, 0x5d, - 0x0b, 0xc4, 0xde, 0xee, 0x10, 0x63, 0x5f, 0xfc, 0x16, 0x65, 0x45, 0x4e, 0xa5, 0x62, 0x2b, 0x72, - 0x7a, 0x88, 0xfe, 0xed, 0x0e, 0xf1, 0xe9, 0xc5, 0x6f, 0xed, 0xce, 0x37, 0x5a, 0xe9, 0xd3, 0xc3, - 0xdb, 0x6f, 0x77, 0x88, 0xed, 0x2e, 0x7e, 0x5b, 0x77, 0xbe, 0x91, 0x63, 0x5f, 0x7a, 0x68, 0x78, - 0xad, 0x73, 0x5c, 0x74, 0xf1, 0xef, 0xaa, 0x53, 0x57, 0x07, 0x42, 0x9c, 0xba, 0x3a, 0x05, 0x57, - 0x2f, 0xc2, 0x03, 0x5c, 0x43, 0x5e, 0x73, 0x8d, 0xd6, 0x4a, 0x95, 0xfa, 0xbe, 0x65, 0x2f, 0x07, - 0x3b, 0xb1, 0x6f, 0xcf, 0x28, 0xc7, 0x63, 0x9d, 0x28, 0xf1, 0x78, 0xac, 0x13, 0x92, 0x29, 0x6f, - 0x22, 0x02, 0xba, 0xf8, 0xf7, 0x14, 0xe5, 0x4d, 0x50, 0x30, 0xe5, 0x4d, 0x06, 0x4e, 0x5f, 0x4b, - 0x09, 0xf4, 0x2d, 0xfe, 0x4f, 0x9d, 0x79, 0x85, 0xef, 0x97, 0x12, 0x1f, 0x7c, 0x2d, 0x25, 0x9e, - 0xb5, 0xf8, 0xf7, 0x3b, 0xf3, 0x8a, 0x7c, 0x90, 0x92, 0x61, 0xb0, 0x1f, 0x84, 0xd3, 0x7c, 0x36, - 0x9f, 0xa6, 0x26, 0x8d, 0x7d, 0xe8, 0x77, 0x28, 0x63, 0x3f, 0x9d, 0x0c, 0x8f, 0xdc, 0x53, 0x31, - 0x69, 0xac, 0xc5, 0xbb, 0xfe, 0x83, 0x6d, 0x58, 0x47, 0x1b, 0x82, 0x74, 0x0c, 0x5b, 0x6f, 0xe4, - 0xe8, 0xb7, 0xe2, 0x77, 0x2a, 0xeb, 0x8d, 0x8c, 0x44, 0x77, 0x0e, 0x39, 0x54, 0xee, 0xa5, 0x78, - 0xa4, 0x57, 0xf1, 0xbb, 0x52, 0x1b, 0x87, 0x1d, 0x10, 0x0f, 0x0b, 0x7b, 0x29, 0x1e, 0xd5, 0x54, - 0xfc, 0xee, 0xd4, 0xc6, 0xe1, 0x07, 0xc4, 0x43, 0xa0, 0xd8, 0x16, 0xa9, 0xed, 0x3b, 0x9c, 0x55, - 0x6c, 0x7a, 0xf8, 0x87, 0xea, 0x16, 0x29, 0x95, 0x0c, 0xb7, 0x48, 0xa9, 0x98, 0x34, 0xd6, 0xe2, - 0xf3, 0xbe, 0x67, 0x1b, 0xd6, 0xd2, 0xc6, 0x2e, 0x15, 0x93, 0xc6, 0x5a, 0x7c, 0xfc, 0xf7, 0x6e, - 0xc3, 0x5a, 0xda, 0xd8, 0xa5, 0x62, 0x98, 0x39, 0x16, 0x61, 0x6e, 0x53, 0xd7, 0x8b, 0xd4, 0xef, - 0x1f, 0x29, 0xe6, 0x58, 0x07, 0x3a, 0x66, 0x8e, 0x75, 0x40, 0xa5, 0x72, 0x17, 0x42, 0xf9, 0xbe, - 0xed, 0xb8, 0x47, 0xf7, 0x32, 0x1d, 0x50, 0xa9, 0xdc, 0x85, 0x5c, 0xfe, 0xf1, 0x76, 0xdc, 0xa3, - 0x8b, 0x99, 0x0e, 0x28, 0x66, 0x14, 0x55, 0x7d, 0xc3, 0xb7, 0xea, 0x33, 0x8e, 0xe7, 0x4b, 0x8b, - 0xfc, 0xff, 0xac, 0x18, 0x45, 0x69, 0x44, 0xcc, 0x28, 0x4a, 0x83, 0x27, 0x99, 0x0a, 0x69, 0xfc, - 0x93, 0xae, 0x4c, 0x23, 0x4b, 0x2b, 0x0d, 0x9e, 0x64, 0x2a, 0x84, 0xf0, 0xbf, 0x74, 0x65, 0x1a, - 0x79, 0xca, 0xa7, 0xc1, 0x99, 0x65, 0x3a, 0xe9, 0x3a, 0xeb, 0xf6, 0x35, 0xba, 0x4e, 0x1b, 0xe2, - 0xd3, 0xff, 0xa9, 0x62, 0x99, 0xaa, 0x04, 0x78, 0x8b, 0xa2, 0xc0, 0xe2, 0x8c, 0xc4, 0xe7, 0x7e, - 0x7f, 0x47, 0x46, 0xd1, 0x31, 0x91, 0x0a, 0x8b, 0x33, 0x12, 0x9f, 0xf8, 0x03, 0x1d, 0x19, 0x45, - 0xc7, 0x44, 0x2a, 0x8c, 0x94, 0x61, 0x04, 0x63, 0x25, 0x0c, 0x2f, 0xf0, 0xfc, 0xfc, 0x91, 0x4c, - 0xfc, 0xd6, 0x2b, 0x8e, 0x9e, 0xe9, 0xd1, 0x95, 0x06, 0x32, 0x0b, 0xf1, 0x49, 0x3f, 0xda, 0x81, - 0x45, 0xe4, 0xef, 0x18, 0x87, 0xc8, 0x2c, 0xc4, 0xc7, 0xfc, 0x58, 0x07, 0x16, 0x91, 0xc3, 0x63, - 0x1c, 0x42, 0xde, 0x03, 0x83, 0xd5, 0xe9, 0x85, 0x4a, 0x50, 0x77, 0xfc, 0x9f, 0x65, 0x94, 0xa8, - 0xa2, 0x08, 0x87, 0x51, 0x45, 0xd1, 0x4f, 0xf2, 0x7e, 0x18, 0x9e, 0x74, 0x6c, 0xdf, 0xa8, 0x07, - 0x1b, 0xd0, 0x1f, 0x57, 0xce, 0x50, 0x62, 0xd8, 0x99, 0x1e, 0x3d, 0x4e, 0x2e, 0xb5, 0x17, 0xef, - 0xfe, 0x13, 0xe9, 0xed, 0xc3, 0x57, 0x8f, 0x93, 0xb3, 0x19, 0xed, 0x8e, 0xe3, 0xae, 0x36, 0x1c, - 0xc3, 0x0c, 0xf2, 0x9c, 0x8a, 0x17, 0xf9, 0x49, 0x65, 0x46, 0x4b, 0x27, 0x63, 0x33, 0x5a, 0x3a, - 0x26, 0x8d, 0xb5, 0xe8, 0xa2, 0xcf, 0x6f, 0xc3, 0x3a, 0x9a, 0x87, 0xd3, 0x31, 0x69, 0xac, 0xc5, - 0xe7, 0xff, 0xf3, 0x6d, 0x58, 0x47, 0xf3, 0x70, 0x3a, 0x86, 0x99, 0xd6, 0xaf, 0x59, 0x7e, 0x10, - 0xd8, 0xf6, 0x2f, 0x14, 0xd3, 0x3a, 0x42, 0x31, 0xd3, 0x3a, 0xfa, 0x45, 0x28, 0x9c, 0x0d, 0x43, - 0x25, 0xa3, 0xbd, 0xeb, 0xac, 0xbd, 0xc6, 0xf6, 0xc7, 0xc5, 0xff, 0x55, 0x39, 0x15, 0xe9, 0x4c, - 0x3a, 0xd3, 0xa3, 0x77, 0x61, 0x44, 0x2a, 0x8a, 0x9f, 0x22, 0x4f, 0x00, 0x58, 0xfc, 0xa9, 0x4c, - 0x17, 0x47, 0x45, 0x4e, 0x93, 0x70, 0x54, 0xe4, 0x60, 0x31, 0x67, 0x2d, 0x36, 0xe8, 0xad, 0x9b, - 0xb3, 0xaf, 0x4b, 0xb3, 0xeb, 0x17, 0x92, 0x73, 0x56, 0x82, 0x48, 0xcc, 0x59, 0x09, 0x38, 0xf9, - 0xf2, 0x0c, 0x3c, 0xae, 0xca, 0xf7, 0xf5, 0xe7, 0x9f, 0x7e, 0x51, 0xa7, 0x6b, 0x4e, 0x5d, 0xb6, - 0xac, 0x7e, 0x9a, 0x3f, 0xe5, 0xc9, 0x4e, 0xdd, 0x95, 0xd6, 0x68, 0xa6, 0x47, 0xdf, 0x11, 0xf3, - 0x1d, 0xbc, 0x85, 0x50, 0x9a, 0x9f, 0xd9, 0xd5, 0x5b, 0x84, 0x2a, 0xb4, 0x23, 0xe6, 0x3b, 0x78, - 0x0b, 0x31, 0x2a, 0x7e, 0x76, 0x57, 0x6f, 0x11, 0x8e, 0x91, 0x1d, 0x31, 0xc7, 0xdd, 0xe7, 0x9d, - 0xea, 0xec, 0x64, 0xe8, 0xeb, 0xb3, 0x61, 0xd7, 0x8b, 0x3f, 0xa7, 0xee, 0x3e, 0x55, 0x0a, 0xdc, - 0x7d, 0xaa, 0x40, 0xb6, 0xdc, 0xcf, 0x50, 0xa3, 0xc1, 0xb6, 0xa3, 0xb4, 0xbe, 0x1a, 0x33, 0xde, - 0x7e, 0x5e, 0x59, 0xee, 0x3b, 0xd0, 0xb1, 0xe5, 0xbe, 0x03, 0x2a, 0x95, 0xbb, 0x90, 0xd0, 0x2f, - 0x6c, 0xc7, 0x3d, 0x32, 0x55, 0x3a, 0xa0, 0x52, 0xb9, 0x0b, 0x2d, 0xf8, 0xc5, 0xed, 0xb8, 0x47, - 0xa6, 0x4a, 0x07, 0x14, 0xf9, 0xfa, 0x0c, 0x5c, 0x48, 0xeb, 0x0e, 0x9e, 0xfb, 0x23, 0x2c, 0x1b, - 0xc8, 0x65, 0xf5, 0x4b, 0xfc, 0x79, 0x4f, 0x77, 0xeb, 0xef, 0xb4, 0x86, 0x33, 0x3d, 0xfa, 0x8e, - 0x1f, 0xb2, 0xc3, 0x37, 0x12, 0x12, 0xf8, 0xe5, 0x5d, 0xbf, 0x51, 0x28, 0x92, 0x1d, 0x3f, 0x04, - 0x27, 0x1c, 0x6b, 0xd9, 0xf3, 0x1d, 0x97, 0x56, 0x9c, 0x86, 0x55, 0x0f, 0xd6, 0x9b, 0x5f, 0x51, - 0x27, 0x9c, 0x14, 0x22, 0x9c, 0x70, 0x52, 0xe0, 0x49, 0xa6, 0x42, 0x63, 0xfe, 0x65, 0x57, 0xa6, - 0x92, 0x39, 0x97, 0x02, 0x4f, 0x32, 0x15, 0x62, 0xfa, 0x57, 0x5d, 0x99, 0x4a, 0xe6, 0x5c, 0x0a, - 0x9c, 0xd8, 0xf0, 0x50, 0x64, 0xe8, 0x96, 0x97, 0xa9, 0xed, 0xeb, 0x4e, 0xa3, 0xe1, 0xb4, 0xfd, - 0x05, 0xd7, 0x5a, 0x5e, 0xa6, 0x6e, 0xf1, 0x57, 0x95, 0x03, 0xb2, 0xae, 0xd4, 0x33, 0x3d, 0x7a, - 0x77, 0x76, 0xc4, 0x87, 0x52, 0x3a, 0xc1, 0xb4, 0xe3, 0xd6, 0xe9, 0x94, 0x63, 0xd3, 0xe2, 0xaf, - 0x65, 0xe2, 0xd7, 0xd3, 0xdb, 0xd0, 0xcf, 0xf4, 0xe8, 0xdb, 0xb1, 0x24, 0x1f, 0x83, 0x87, 0xd3, - 0x49, 0xd8, 0x7f, 0x8b, 0x46, 0x7d, 0xb5, 0xf8, 0xbf, 0x65, 0xe2, 0x3e, 0x7f, 0xdd, 0xc9, 0x67, - 0x7a, 0xf4, 0x6d, 0x18, 0x92, 0x29, 0x18, 0x9d, 0x9b, 0xac, 0xc4, 0x02, 0x3a, 0x7e, 0x3d, 0xa3, - 0x84, 0x5f, 0xc5, 0xf1, 0x18, 0x7e, 0x15, 0x07, 0x31, 0x7b, 0x2a, 0x02, 0x5d, 0xb5, 0xcd, 0xe2, - 0xff, 0xae, 0xd8, 0x53, 0x31, 0x2c, 0xfa, 0x97, 0xca, 0x00, 0x36, 0xcf, 0x46, 0x80, 0xe0, 0x5e, - 0xfe, 0x37, 0x94, 0x79, 0x36, 0x41, 0xc1, 0xe6, 0xd9, 0x04, 0x90, 0x59, 0x39, 0x11, 0xf0, 0xa6, - 0x23, 0x5c, 0x7e, 0x2d, 0xc7, 0x2e, 0xfe, 0xa6, 0x62, 0xe5, 0xa4, 0x93, 0x31, 0x2b, 0x27, 0x1d, - 0xc3, 0x54, 0x7b, 0xc2, 0x69, 0xdb, 0xe6, 0x75, 0xba, 0xd1, 0x32, 0x2c, 0x37, 0x88, 0x43, 0x2a, - 0xfe, 0x96, 0xa2, 0xda, 0x69, 0x44, 0x4c, 0xb5, 0xd3, 0xe0, 0x09, 0xa6, 0x8e, 0xcf, 0xdf, 0xf6, - 0x5f, 0x77, 0x63, 0x2a, 0x88, 0x12, 0x4c, 0x05, 0x9c, 0x7c, 0x22, 0x03, 0xef, 0x90, 0x11, 0xd7, - 0x1c, 0xcb, 0x46, 0x0f, 0xec, 0xdb, 0xd4, 0x0d, 0xbf, 0x67, 0xda, 0xb0, 0x1a, 0xd4, 0x2c, 0x6e, - 0xf2, 0x07, 0x5d, 0x4e, 0x7b, 0x50, 0x97, 0x76, 0x33, 0x3d, 0xfa, 0x4e, 0x1f, 0x41, 0x16, 0xe1, - 0x81, 0x48, 0xa4, 0xcc, 0x32, 0xa3, 0x76, 0xb5, 0x7a, 0xb5, 0xea, 0xbb, 0xd4, 0x68, 0x16, 0x7f, - 0x5b, 0x39, 0x6c, 0xeb, 0x48, 0x89, 0xbe, 0xe4, 0x9d, 0x90, 0x64, 0x15, 0xce, 0x45, 0xc8, 0xc0, - 0x2e, 0x5c, 0x58, 0xa8, 0x04, 0xea, 0xf4, 0x3b, 0xca, 0x31, 0x6d, 0x37, 0xe2, 0x99, 0x1e, 0xbd, - 0x2b, 0x33, 0xbc, 0xf8, 0x98, 0x9c, 0x9d, 0x63, 0xef, 0x60, 0xd9, 0xcb, 0xdc, 0x2f, 0xea, 0xff, - 0x50, 0x2f, 0x3e, 0x14, 0x02, 0xbc, 0xf8, 0x50, 0x60, 0x78, 0x9b, 0x3b, 0x39, 0x3b, 0x17, 0x58, - 0x0a, 0x9c, 0xd3, 0xef, 0xaa, 0xb7, 0xb9, 0x2a, 0x05, 0xde, 0xe6, 0xaa, 0x40, 0x66, 0x4d, 0x8b, - 0x04, 0x55, 0x15, 0x9d, 0x7a, 0xbe, 0x6b, 0x61, 0xa4, 0x4e, 0x70, 0xa6, 0xf9, 0x6f, 0x14, 0x6b, - 0xba, 0x33, 0x29, 0xb3, 0xa6, 0x3b, 0x63, 0xd1, 0x9a, 0x6e, 0xb5, 0xf0, 0x5a, 0x4c, 0x36, 0x62, - 0xfe, 0xad, 0x6a, 0x4d, 0x27, 0x69, 0xd0, 0x9a, 0x4e, 0x82, 0x13, 0x1c, 0xc5, 0x1b, 0xff, 0x5e, - 0x37, 0x8e, 0xd1, 0xe5, 0x6e, 0x0a, 0x38, 0xc1, 0x51, 0xac, 0x41, 0xff, 0x67, 0x37, 0x8e, 0x52, - 0x68, 0x52, 0x12, 0x9c, 0xe0, 0x88, 0x5a, 0xbe, 0x51, 0xfc, 0x77, 0xdd, 0x38, 0x72, 0x9a, 0x04, - 0x47, 0x0e, 0x66, 0x3a, 0x74, 0xdb, 0xa6, 0x7e, 0x4c, 0x88, 0xff, 0x97, 0xa2, 0x43, 0x2a, 0x01, - 0xd3, 0x21, 0x15, 0x16, 0x67, 0x24, 0x64, 0xf7, 0x7f, 0x77, 0x64, 0x14, 0x1d, 0x51, 0xa8, 0xb0, - 0x38, 0x23, 0x21, 0xb2, 0xad, 0x8e, 0x8c, 0xa2, 0x23, 0x0a, 0x15, 0x46, 0x6e, 0xc3, 0xa9, 0xc0, - 0xb2, 0x89, 0xbb, 0x4a, 0xff, 0x3f, 0xca, 0xad, 0x40, 0x2a, 0xd5, 0x4c, 0x8f, 0x9e, 0xde, 0x3c, - 0x85, 0xaf, 0xf8, 0xdc, 0x7f, 0xdf, 0x9d, 0x6f, 0x74, 0x8b, 0x91, 0x8a, 0x48, 0xe1, 0x2b, 0xbe, - 0xfe, 0x3f, 0x74, 0xe7, 0x1b, 0xdd, 0x62, 0xa4, 0x22, 0xd8, 0xdc, 0x3e, 0x6b, 0x8b, 0x7b, 0xc5, - 0x39, 0xc7, 0x0c, 0x4f, 0xa2, 0xfe, 0xa3, 0x32, 0xb7, 0xa7, 0x11, 0xb1, 0xb9, 0x3d, 0x0d, 0x9e, - 0x64, 0x2a, 0x64, 0xf0, 0xff, 0x76, 0x65, 0x1a, 0x59, 0x6d, 0x69, 0xf0, 0x24, 0x53, 0x21, 0x80, - 0xdf, 0xef, 0xca, 0x34, 0xb2, 0xda, 0xd2, 0xe0, 0x4c, 0xac, 0x21, 0xbc, 0x4a, 0xeb, 0x2e, 0x0d, - 0x8e, 0x6b, 0xfe, 0x93, 0x22, 0xd6, 0x54, 0x2a, 0x26, 0xd6, 0x54, 0x44, 0x0a, 0x5f, 0x21, 0x82, - 0x3f, 0xe8, 0xce, 0x37, 0x52, 0x83, 0x54, 0x44, 0x0a, 0x5f, 0x21, 0x85, 0x3f, 0xec, 0xce, 0x37, - 0x52, 0x83, 0x54, 0x44, 0x8c, 0x6f, 0xcc, 0x7a, 0xff, 0xa3, 0x4e, 0x7c, 0x15, 0xf3, 0x3d, 0xbd, - 0x79, 0x0a, 0x5f, 0x21, 0x87, 0x3f, 0xee, 0xce, 0x37, 0x45, 0x0e, 0x31, 0x13, 0x3e, 0xc9, 0x57, - 0xc8, 0xe1, 0x3f, 0x77, 0xe7, 0x9b, 0x22, 0x87, 0x98, 0x15, 0x8f, 0xae, 0x4b, 0xdc, 0x6c, 0xc4, - 0x83, 0x3c, 0xeb, 0x4d, 0x6a, 0x16, 0xff, 0x24, 0xe1, 0xba, 0xa4, 0x50, 0x70, 0xd7, 0x25, 0x05, - 0x88, 0x8b, 0x1d, 0x75, 0xfd, 0xb0, 0x46, 0x4f, 0xb0, 0x5f, 0xe2, 0x2b, 0xe8, 0x9f, 0xaa, 0x8b, - 0x5d, 0x47, 0x52, 0x5c, 0xec, 0x3a, 0x62, 0xd1, 0xb7, 0x20, 0x74, 0x7f, 0xbf, 0x71, 0x63, 0x2e, - 0xb0, 0x26, 0xfe, 0x8b, 0xea, 0x5b, 0x90, 0x42, 0x84, 0xbe, 0x05, 0x29, 0x70, 0xc6, 0x54, 0xa7, - 0xbe, 0x6b, 0xd1, 0x35, 0xa3, 0x21, 0x4f, 0x0b, 0x7f, 0x96, 0x51, 0x83, 0xe9, 0x93, 0x44, 0x3c, - 0x98, 0x3e, 0x09, 0x4f, 0x32, 0x15, 0xba, 0xf0, 0x5f, 0xbb, 0x32, 0x8d, 0xa6, 0x85, 0x34, 0x78, - 0x92, 0xa9, 0x50, 0x84, 0xff, 0xd6, 0x95, 0x69, 0x34, 0x2d, 0xa4, 0xc1, 0xc9, 0x65, 0xe8, 0xc7, - 0x34, 0x9e, 0x7a, 0xdb, 0x2e, 0xfe, 0xb9, 0x92, 0xc9, 0x2f, 0x40, 0xcc, 0xf4, 0xe8, 0x21, 0x11, - 0xa9, 0x75, 0x2e, 0xde, 0x5f, 0xfc, 0x0b, 0xe5, 0x32, 0xb8, 0x13, 0xe1, 0x4c, 0x8f, 0xde, 0x91, - 0x09, 0xdb, 0x33, 0xa4, 0xd7, 0x54, 0x2b, 0xfe, 0x7f, 0xca, 0x9e, 0x21, 0x9d, 0x8c, 0xed, 0x19, - 0x3a, 0x14, 0xcb, 0xab, 0x41, 0xf1, 0xaa, 0xbd, 0x66, 0xb9, 0x8e, 0xcd, 0x63, 0x61, 0x9d, 0x25, - 0x2b, 0x74, 0x3a, 0xfb, 0x92, 0x6c, 0xfc, 0xdd, 0x3b, 0x11, 0xb2, 0x77, 0xef, 0x84, 0x63, 0x83, - 0x6a, 0x82, 0x1a, 0x4d, 0x2f, 0x66, 0x47, 0x7c, 0x69, 0x56, 0x49, 0x28, 0xa1, 0x52, 0x60, 0x42, - 0x09, 0x15, 0xa8, 0xf0, 0x12, 0x6f, 0xf9, 0x65, 0x9d, 0x79, 0x45, 0x97, 0xcc, 0x09, 0xa0, 0xc2, - 0x4b, 0xe8, 0xcd, 0x97, 0x77, 0xe6, 0x25, 0x25, 0xba, 0x50, 0x81, 0x78, 0x77, 0xd2, 0x30, 0x3c, - 0xcf, 0x5a, 0xb2, 0x42, 0x53, 0xe2, 0x2b, 0xb2, 0x6a, 0x8e, 0xac, 0x38, 0x01, 0xcf, 0x91, 0x15, - 0x87, 0xc5, 0x19, 0x89, 0xef, 0xfb, 0xca, 0x8e, 0x8c, 0xa4, 0xdb, 0x1c, 0x05, 0x16, 0x67, 0x24, - 0x3e, 0xee, 0xab, 0x3a, 0x32, 0x92, 0x6e, 0x73, 0x14, 0x18, 0xce, 0x89, 0x75, 0xa7, 0x45, 0x4d, - 0xd9, 0x3d, 0xfa, 0xab, 0x15, 0x31, 0x25, 0x28, 0x70, 0x4e, 0x54, 0x81, 0x0a, 0xaf, 0xc0, 0x95, - 0xb3, 0x33, 0xaf, 0xa8, 0xfb, 0x12, 0x40, 0x85, 0x97, 0xf8, 0xc2, 0x8f, 0x77, 0xe6, 0x15, 0x75, - 0x5f, 0x02, 0x48, 0x5e, 0x82, 0x21, 0x3c, 0x7c, 0x08, 0x32, 0xb2, 0x7d, 0xad, 0x92, 0x86, 0x53, - 0x46, 0xce, 0xf4, 0xe8, 0x31, 0x62, 0xb6, 0x18, 0x45, 0x93, 0xa8, 0xbc, 0xa1, 0xfb, 0xba, 0x6c, - 0x7c, 0x31, 0x4a, 0xa5, 0x62, 0x8b, 0x51, 0x2a, 0x02, 0x0f, 0x5b, 0xd2, 0x10, 0x13, 0x8e, 0xb9, - 0xc1, 0xf3, 0x10, 0x7d, 0x22, 0xab, 0x1c, 0xb6, 0x74, 0x25, 0xc7, 0xc3, 0x96, 0xae, 0x14, 0x78, - 0x11, 0xae, 0x50, 0x08, 0xdf, 0xc1, 0x4f, 0x66, 0x95, 0x8b, 0xf0, 0x54, 0x32, 0xbc, 0x08, 0x4f, - 0xc5, 0xe0, 0x81, 0x55, 0x2a, 0x26, 0xfa, 0x9c, 0x4f, 0x65, 0x95, 0x03, 0xab, 0xee, 0xf4, 0x78, - 0x60, 0xd5, 0x9d, 0x84, 0x2d, 0xc2, 0x11, 0x09, 0x2f, 0x5e, 0x3d, 0x65, 0x19, 0x8d, 0x29, 0x6a, - 0x5b, 0xd4, 0x2c, 0x7e, 0xbd, 0x9a, 0x88, 0xbb, 0x23, 0x29, 0x5b, 0x84, 0x3b, 0x63, 0x27, 0xfa, - 0xa0, 0x17, 0x57, 0xe3, 0x6b, 0x85, 0xfe, 0x1f, 0xcc, 0x8c, 0xfd, 0x50, 0xe6, 0x5a, 0xa1, 0xff, - 0x87, 0x32, 0x63, 0x3f, 0xcc, 0xfe, 0xff, 0xe1, 0xcc, 0xd8, 0x8f, 0x64, 0xf4, 0x07, 0x94, 0x53, - 0xae, 0x4a, 0xc3, 0x10, 0x63, 0x22, 0x15, 0xc5, 0x7f, 0xa6, 0xa2, 0xb8, 0xc6, 0x6a, 0xdf, 0x92, - 0x81, 0x21, 0x7e, 0xae, 0x20, 0xaa, 0x7a, 0x9d, 0x85, 0x7e, 0x9e, 0xeb, 0x28, 0x48, 0x2d, 0xad, - 0x87, 0xbf, 0xc9, 0x79, 0x18, 0xb9, 0x61, 0x78, 0x3e, 0xbe, 0xe2, 0xac, 0x6d, 0xd2, 0xbb, 0x98, - 0xe7, 0x33, 0xa7, 0x2b, 0x50, 0x72, 0x83, 0xd3, 0xf1, 0x76, 0x58, 0x11, 0x33, 0xb7, 0x6d, 0x31, - 0xab, 0xfe, 0x2f, 0x6c, 0x96, 0x7a, 0xb0, 0x76, 0x95, 0xd2, 0x56, 0xfb, 0xd9, 0x0c, 0x24, 0xb2, - 0x30, 0xed, 0x3d, 0x23, 0xfd, 0x3c, 0x8c, 0x2a, 0x85, 0x54, 0x45, 0xb2, 0xd2, 0x1d, 0xd6, 0x59, - 0x55, 0x5b, 0x93, 0x77, 0x84, 0x49, 0x32, 0x6f, 0xe9, 0x37, 0x44, 0xa1, 0xb2, 0xbe, 0xad, 0xcd, - 0x52, 0xae, 0xed, 0x36, 0x74, 0x09, 0x25, 0x8a, 0xcb, 0xfc, 0xda, 0x89, 0xa8, 0xc4, 0x24, 0x39, - 0x2f, 0xd2, 0xe3, 0x4b, 0xe5, 0x16, 0xdb, 0x1e, 0x75, 0xe5, 0xf2, 0x66, 0x98, 0x0e, 0xff, 0xfd, - 0x30, 0x34, 0xdb, 0x6c, 0x51, 0xd7, 0x73, 0x6c, 0xc3, 0x77, 0x5c, 0x91, 0xbe, 0x1b, 0x13, 0x6d, - 0x5b, 0x12, 0x5c, 0x4e, 0xb4, 0x2d, 0xd3, 0x93, 0x8b, 0xd0, 0xab, 0x63, 0x81, 0xae, 0x1c, 0x16, - 0xf7, 0xc4, 0x14, 0xb6, 0x58, 0xa4, 0x4b, 0xce, 0x85, 0x8e, 0x14, 0x8c, 0xf4, 0x96, 0x67, 0x60, - 0x3a, 0xd5, 0x90, 0xb4, 0xcd, 0x00, 0x32, 0x29, 0x52, 0x90, 0x27, 0xa1, 0x80, 0xf7, 0x90, 0x5e, - 0xb1, 0x17, 0x69, 0x31, 0xc7, 0x7a, 0x03, 0x21, 0x72, 0xca, 0x76, 0x4e, 0x43, 0xae, 0xc3, 0x58, - 0x14, 0x5b, 0xfb, 0x9a, 0xeb, 0xb4, 0x5b, 0x5e, 0xb1, 0x80, 0xed, 0x4a, 0x5b, 0x9b, 0xa5, 0x07, - 0x57, 0x43, 0x5c, 0x6d, 0x19, 0x91, 0x12, 0x8b, 0x44, 0x43, 0x32, 0x03, 0xa3, 0x11, 0x8c, 0x89, - 0x88, 0x97, 0x8e, 0x15, 0xe9, 0xc1, 0x25, 0x5e, 0x4c, 0x9c, 0x32, 0x2b, 0xb5, 0x19, 0x99, 0x85, - 0xbe, 0xa0, 0xe2, 0xda, 0xf6, 0x35, 0x61, 0x4f, 0x88, 0x8a, 0x6b, 0x7d, 0x72, 0xad, 0xb5, 0xa0, - 0x3d, 0x99, 0x86, 0x11, 0xdd, 0x69, 0xfb, 0x74, 0xc1, 0x11, 0x9b, 0x63, 0x51, 0x04, 0x10, 0xdf, - 0xc9, 0x65, 0x98, 0x9a, 0xef, 0xd4, 0xea, 0x1c, 0x27, 0xa7, 0x2c, 0x8f, 0xb7, 0x22, 0x37, 0x61, - 0x3c, 0x11, 0x85, 0x2c, 0x4a, 0x02, 0x62, 0x3d, 0x2c, 0xe9, 0xf3, 0x92, 0xcc, 0x92, 0x4d, 0xc9, - 0x57, 0x65, 0xa0, 0xb0, 0xe0, 0x1a, 0x96, 0xef, 0x89, 0x4c, 0xac, 0xa7, 0x2e, 0xad, 0xbb, 0x46, - 0x8b, 0xe9, 0xc7, 0x25, 0xac, 0xde, 0x7a, 0xdb, 0x68, 0xb4, 0xa9, 0x37, 0x71, 0x87, 0x7d, 0xdd, - 0x6f, 0x6c, 0x96, 0x5e, 0xe2, 0x69, 0xf7, 0x2f, 0xd5, 0x9d, 0xe6, 0xe5, 0x65, 0xd7, 0x58, 0xb3, - 0xf8, 0x59, 0xab, 0xd1, 0xb8, 0xec, 0xd3, 0x06, 0x86, 0x54, 0x5c, 0x36, 0x5a, 0xd6, 0x65, 0xac, - 0xf3, 0x7f, 0x39, 0xe4, 0xc4, 0x9f, 0xc0, 0x54, 0xc0, 0xc7, 0xbf, 0x64, 0x15, 0xe0, 0x38, 0x72, - 0x13, 0x40, 0x7c, 0x6a, 0xb9, 0xd5, 0x12, 0x69, 0x5d, 0xa5, 0x40, 0x84, 0x00, 0xc3, 0x15, 0x3b, - 0x14, 0x98, 0xd1, 0x92, 0x2a, 0xe3, 0xea, 0x12, 0x07, 0xa6, 0x05, 0x0b, 0xe2, 0x8d, 0x02, 0x31, - 0x0d, 0x4b, 0x49, 0xe2, 0x05, 0x2a, 0x45, 0x48, 0x6a, 0x33, 0xb2, 0x08, 0xa3, 0x82, 0x6f, 0xe0, - 0xb4, 0x2a, 0x52, 0xbf, 0x9e, 0x51, 0x5e, 0x2f, 0x40, 0x73, 0xa5, 0x0d, 0xdf, 0xd1, 0x14, 0x60, - 0xf9, 0x19, 0x4a, 0x0b, 0x32, 0x01, 0xc3, 0xc1, 0xdf, 0x37, 0x8d, 0x26, 0xf5, 0x8a, 0xa3, 0xa8, - 0xb1, 0x58, 0x06, 0x35, 0x68, 0x8f, 0xc5, 0x07, 0x65, 0xd1, 0xc5, 0x9b, 0xc8, 0x3c, 0xb8, 0xd6, - 0x8f, 0xa5, 0xf0, 0x50, 0x75, 0x3e, 0xde, 0x84, 0x4c, 0xc2, 0x70, 0x98, 0x55, 0xee, 0xd6, 0xad, - 0xd9, 0x29, 0xcc, 0x1b, 0x2b, 0x4a, 0x55, 0x36, 0x97, 0x8c, 0x9a, 0xa8, 0xda, 0xd6, 0x6e, 0xc7, - 0x4b, 0x55, 0xc6, 0xda, 0x48, 0xc5, 0x04, 0x78, 0x22, 0x59, 0xa5, 0x98, 0x40, 0x2b, 0xa5, 0x98, - 0x40, 0x85, 0xbc, 0x0f, 0x06, 0xcb, 0x77, 0xaa, 0xa2, 0x48, 0x82, 0x57, 0x3c, 0x81, 0xef, 0x8e, - 0x65, 0x11, 0xb0, 0x32, 0x83, 0x28, 0xa8, 0x20, 0xbf, 0xba, 0x4c, 0x4f, 0xae, 0xc2, 0x48, 0xcc, - 0x45, 0xc1, 0x2b, 0x9e, 0x44, 0x0e, 0xf8, 0xe6, 0x06, 0x62, 0x6a, 0xae, 0x40, 0xc9, 0xc3, 0x2b, - 0xde, 0x88, 0x69, 0xcd, 0x94, 0xe5, 0x19, 0x8d, 0x86, 0xb3, 0xae, 0x53, 0xac, 0xc7, 0x80, 0x59, - 0x68, 0xfb, 0xb9, 0xd6, 0x98, 0x02, 0x55, 0x73, 0x39, 0x4e, 0xee, 0x51, 0xa5, 0x19, 0x79, 0x03, - 0x48, 0x99, 0xfd, 0xa6, 0x66, 0x70, 0x3e, 0x3d, 0x3b, 0xe5, 0x15, 0x4f, 0x63, 0x0d, 0x6b, 0xa2, - 0xa6, 0x4f, 0x9f, 0x9d, 0x9a, 0x38, 0x2f, 0xa6, 0x8f, 0x87, 0x0d, 0xde, 0xaa, 0x16, 0xa4, 0x4e, - 0xaf, 0x59, 0xa6, 0xfc, 0xc6, 0x29, 0x5c, 0xc9, 0x3a, 0x9c, 0xa9, 0xb8, 0x74, 0xcd, 0x72, 0xda, - 0x5e, 0xb0, 0x7c, 0x04, 0xf3, 0xd6, 0x99, 0x6d, 0xe7, 0xad, 0x47, 0xc5, 0x83, 0x4f, 0xb5, 0x5c, - 0xba, 0x56, 0x0b, 0x6a, 0x17, 0xc7, 0x2a, 0x46, 0x76, 0xe2, 0xce, 0xc4, 0x85, 0xfb, 0x53, 0x01, - 0xb7, 0xa8, 0x57, 0x2c, 0x46, 0x53, 0x2d, 0xaf, 0xfc, 0x61, 0x85, 0x38, 0x59, 0x5c, 0x4a, 0x33, - 0xa2, 0x03, 0x79, 0x6d, 0x32, 0x88, 0x59, 0x2d, 0xd7, 0xeb, 0x4e, 0xdb, 0xf6, 0xbd, 0xe2, 0x03, - 0xc8, 0x4c, 0x63, 0x62, 0x59, 0xae, 0xb7, 0x6a, 0x1e, 0x47, 0xd7, 0x0c, 0x81, 0x97, 0xc5, 0x92, - 0x6c, 0x4d, 0x6e, 0xc0, 0x58, 0xc5, 0x45, 0x0f, 0xfa, 0xeb, 0x74, 0x83, 0x9f, 0xc1, 0x60, 0x32, - 0x5c, 0x31, 0x55, 0xb6, 0x38, 0x0e, 0xcb, 0x0b, 0xb7, 0x10, 0x2b, 0x2f, 0x2b, 0x6a, 0x4b, 0xb9, - 0x72, 0xee, 0x83, 0x3b, 0xab, 0x9c, 0x4b, 0x61, 0x4c, 0x44, 0xbc, 0xde, 0xf5, 0xa9, 0xcd, 0x96, - 0x7a, 0x4f, 0x24, 0xbe, 0x2d, 0x2a, 0x11, 0xb2, 0x21, 0x9e, 0x4f, 0x1d, 0x62, 0x94, 0xd1, 0x10, - 0x2c, 0xbf, 0x98, 0xda, 0x24, 0x59, 0x5e, 0xf6, 0xa1, 0x3d, 0x94, 0x97, 0x7d, 0x01, 0x06, 0xae, - 0x39, 0x16, 0x0f, 0x31, 0xc5, 0x2c, 0xb7, 0xa2, 0xec, 0xe9, 0x1b, 0x8e, 0x65, 0xd7, 0xb0, 0x86, - 0xa8, 0x5c, 0xbd, 0x25, 0x24, 0x25, 0x57, 0xa1, 0x1f, 0xb7, 0x3a, 0x15, 0xcb, 0x16, 0x59, 0x6c, - 0xc7, 0x62, 0xfb, 0xa2, 0x8a, 0x65, 0xf3, 0xd1, 0x8f, 0xd5, 0x40, 0x6b, 0xad, 0x58, 0xad, 0xc7, - 0xb0, 0x29, 0xf9, 0x10, 0x14, 0x15, 0xbd, 0xe6, 0x03, 0x93, 0x8d, 0x99, 0x47, 0x70, 0xcc, 0x24, - 0x4a, 0x0e, 0x04, 0x04, 0x13, 0x79, 0xa6, 0xc0, 0x7a, 0xc7, 0xf6, 0xe4, 0x0a, 0x0c, 0xe2, 0xb7, - 0xf2, 0x30, 0x2e, 0xcc, 0x62, 0xdb, 0xcf, 0x6b, 0x98, 0x73, 0xe9, 0x70, 0xb8, 0x2e, 0x13, 0x91, - 0x39, 0x18, 0x9b, 0x70, 0x7c, 0x7c, 0xbd, 0xf9, 0x25, 0x5e, 0x8b, 0x12, 0xb3, 0xd5, 0x0e, 0x4c, - 0x3c, 0xba, 0xb5, 0x59, 0x7a, 0x88, 0x35, 0xe4, 0x1f, 0xe4, 0x2c, 0x25, 0x4b, 0x58, 0x26, 0x9a, - 0x6a, 0x5f, 0x91, 0x8d, 0xc4, 0x44, 0x4a, 0x41, 0x25, 0x5f, 0x6e, 0xd9, 0x0d, 0x6c, 0x6d, 0x96, - 0x78, 0xad, 0x54, 0x51, 0xbf, 0x97, 0x2c, 0xc0, 0x60, 0x14, 0x43, 0xec, 0x15, 0xb3, 0xf8, 0xfd, - 0x8f, 0xaa, 0x62, 0xbd, 0x24, 0xd1, 0xf0, 0x3a, 0xf7, 0x58, 0xb3, 0xc0, 0x88, 0xa0, 0xba, 0xcc, - 0x86, 0x3c, 0x0a, 0x43, 0x3c, 0xd7, 0x76, 0xcd, 0x8d, 0xcc, 0x3d, 0x7d, 0x90, 0xc3, 0xd0, 0xbe, - 0x3b, 0xfb, 0x51, 0x18, 0x53, 0x99, 0xa6, 0x94, 0x6d, 0x7f, 0x4e, 0x2e, 0xdb, 0x2e, 0xfb, 0x9f, - 0x8b, 0x17, 0xb3, 0xa9, 0x29, 0x71, 0x91, 0xcb, 0xba, 0xbf, 0x08, 0xa7, 0xd3, 0x89, 0x98, 0x4c, - 0xb8, 0x11, 0x9a, 0xc1, 0x11, 0x8f, 0x32, 0xc1, 0xd7, 0x14, 0xa6, 0xa7, 0xf6, 0x1f, 0x73, 0xb2, - 0x7d, 0x40, 0xce, 0x41, 0x1e, 0x47, 0x62, 0x26, 0x2a, 0xc2, 0x8a, 0x75, 0xee, 0xf3, 0xa2, 0x64, - 0xf5, 0x40, 0x70, 0x6b, 0x19, 0x94, 0xb3, 0x19, 0xd9, 0xda, 0x2c, 0x41, 0x54, 0x98, 0x53, 0x8f, - 0x08, 0xc8, 0x65, 0x80, 0x4a, 0x7b, 0xb1, 0x61, 0xd5, 0xcb, 0xa6, 0xe9, 0x8a, 0xa2, 0xa9, 0xbc, - 0xfc, 0x03, 0x42, 0x6b, 0x86, 0x69, 0xba, 0xba, 0x44, 0xc2, 0x14, 0x2a, 0x88, 0x2c, 0x8a, 0x0a, - 0x16, 0xa3, 0x42, 0x09, 0x6b, 0x42, 0xd4, 0xdc, 0x97, 0x88, 0xc8, 0x7b, 0x01, 0xa2, 0xe5, 0x4a, - 0xec, 0x04, 0xd0, 0x94, 0x91, 0x57, 0x37, 0xd9, 0x94, 0x89, 0xa8, 0xd9, 0xc2, 0x2e, 0x4f, 0x97, - 0x1b, 0xa2, 0x8e, 0x31, 0x2e, 0xec, 0xb1, 0x39, 0x56, 0x9e, 0xc0, 0xe2, 0x4d, 0xc8, 0x3c, 0x8c, - 0x27, 0x66, 0x48, 0x51, 0xde, 0x18, 0x35, 0x3a, 0x65, 0x7a, 0x95, 0x0d, 0xc7, 0x44, 0x5b, 0xf2, - 0x18, 0xe4, 0x6e, 0xe9, 0xb3, 0xa2, 0x6c, 0x2a, 0xaf, 0xb8, 0x1b, 0x2b, 0x5a, 0xc4, 0xb0, 0xe4, - 0x45, 0x00, 0xbe, 0x8f, 0xad, 0x38, 0xae, 0x8f, 0x16, 0xef, 0x30, 0x2f, 0x48, 0xef, 0x23, 0xb4, - 0xc6, 0xcc, 0x2c, 0xf9, 0xa3, 0x23, 0x62, 0xed, 0xcb, 0xb2, 0x09, 0xb3, 0x8b, 0x09, 0x5e, 0xbc, - 0x85, 0xd4, 0xf9, 0x28, 0xf8, 0xe0, 0xd5, 0xb9, 0xe0, 0x25, 0x22, 0x72, 0x01, 0xfa, 0x2b, 0x6c, - 0xd1, 0xab, 0x3b, 0x0d, 0xa1, 0x0a, 0x58, 0x3b, 0xab, 0x25, 0x60, 0x7a, 0x88, 0x25, 0x57, 0x78, - 0x95, 0x70, 0x5b, 0xa9, 0x8d, 0xde, 0x16, 0x30, 0xb5, 0x48, 0x38, 0x83, 0xb1, 0x36, 0xa1, 0x51, - 0x98, 0x8f, 0xda, 0xa4, 0x98, 0x7c, 0x21, 0x5d, 0xb4, 0xe1, 0xea, 0xdd, 0x6e, 0xc3, 0xa5, 0xfd, - 0x78, 0x26, 0xb9, 0x84, 0x90, 0xe7, 0x92, 0xf5, 0x84, 0x71, 0x86, 0x0d, 0x81, 0xf2, 0x53, 0xc3, - 0xca, 0xc2, 0xb1, 0xca, 0xc0, 0xd9, 0x3d, 0x57, 0x06, 0xce, 0xed, 0xb2, 0x32, 0xb0, 0xf6, 0x5f, - 0xf3, 0x5d, 0x93, 0x8f, 0x1d, 0x4a, 0x05, 0xb9, 0x17, 0x61, 0x98, 0x3f, 0xbd, 0xec, 0x25, 0xb6, - 0xbe, 0x3c, 0xb7, 0x52, 0xcd, 0xe0, 0xa3, 0xd2, 0xd3, 0xe3, 0x94, 0xe4, 0x15, 0x18, 0x0a, 0x3e, - 0x00, 0x2b, 0x4e, 0x4b, 0x95, 0x92, 0x43, 0x83, 0x4d, 0xa9, 0xcd, 0x1c, 0x6b, 0x40, 0x9e, 0x87, - 0x01, 0x34, 0xd7, 0x5b, 0x46, 0x3d, 0x28, 0x5d, 0xce, 0x6b, 0x9d, 0x07, 0x40, 0x79, 0x9d, 0x0d, - 0x29, 0xc9, 0x47, 0xa0, 0x80, 0xdb, 0x31, 0xbe, 0x2f, 0x96, 0x1c, 0x55, 0xba, 0xc8, 0x96, 0x6f, - 0xe0, 0xc4, 0xe2, 0xc0, 0x37, 0xe0, 0x08, 0x88, 0x6d, 0xc0, 0x11, 0x42, 0x16, 0xe0, 0x44, 0xc5, - 0xa5, 0x26, 0xe6, 0x05, 0xbc, 0x7a, 0xb7, 0xe5, 0xf2, 0xc9, 0x51, 0x4c, 0x10, 0x68, 0x7f, 0xb5, - 0x02, 0x34, 0xb3, 0x0c, 0x05, 0x5e, 0xae, 0x89, 0x9c, 0xd2, 0x9c, 0x19, 0xe5, 0xfc, 0x4d, 0xae, - 0xd3, 0x8d, 0x75, 0xc7, 0x35, 0xd9, 0x36, 0x3a, 0x34, 0xca, 0x85, 0xa0, 0x57, 0x05, 0x4a, 0x36, - 0xca, 0xe3, 0x8d, 0xce, 0xbe, 0x08, 0x83, 0xd2, 0x97, 0xa4, 0xac, 0x48, 0x27, 0xe5, 0x15, 0x69, - 0x40, 0x5e, 0x71, 0xbe, 0x2f, 0xdb, 0x21, 0x8d, 0xe7, 0xa1, 0xe8, 0xdd, 0x2b, 0xd0, 0x27, 0x96, - 0x1d, 0x71, 0xe8, 0xb5, 0xc3, 0x73, 0xa5, 0xa0, 0x15, 0x99, 0x50, 0xa6, 0x1b, 0xd9, 0x92, 0x14, - 0xf0, 0x4e, 0x2f, 0x1f, 0x4e, 0x3f, 0x25, 0xe8, 0xe5, 0xa9, 0xd2, 0x7b, 0x23, 0xf3, 0xe3, 0x63, - 0x98, 0x57, 0x81, 0xc3, 0xb5, 0x3f, 0xca, 0x76, 0xc8, 0x51, 0x7a, 0xff, 0xca, 0x8c, 0x2d, 0x3c, - 0x81, 0x30, 0x66, 0xa7, 0x50, 0x72, 0xc3, 0x62, 0xe1, 0x09, 0xc0, 0xcc, 0xa8, 0x90, 0x89, 0xc8, - 0x25, 0x80, 0x8a, 0xe1, 0x1a, 0x4d, 0xea, 0xb3, 0xbd, 0x38, 0x3f, 0xcd, 0x42, 0x2b, 0xa4, 0x15, - 0x42, 0x75, 0x89, 0x42, 0xfb, 0x8e, 0x5c, 0xb7, 0x1c, 0xae, 0xc7, 0xb2, 0xdf, 0x8d, 0xec, 0xaf, - 0xc0, 0x60, 0x28, 0xd9, 0xd9, 0x29, 0xb4, 0x97, 0x44, 0x9b, 0x50, 0xf8, 0xd8, 0x46, 0x22, 0x22, - 0x17, 0xf9, 0xbb, 0x56, 0xad, 0x37, 0x29, 0xce, 0x7b, 0xc3, 0xa2, 0x4a, 0xbf, 0xe1, 0x1b, 0x35, - 0xcf, 0x7a, 0x93, 0xea, 0x21, 0x5a, 0xfb, 0xc9, 0x6c, 0x6a, 0x22, 0xdc, 0xe3, 0x3e, 0xda, 0x45, - 0x1f, 0xa5, 0x08, 0x91, 0xa7, 0xf0, 0x3d, 0x16, 0xe2, 0x2e, 0x84, 0xf8, 0xfb, 0xd9, 0xd4, 0x84, - 0xc7, 0xc7, 0x42, 0xdc, 0xcd, 0x6c, 0xf1, 0x24, 0x0c, 0xe8, 0xce, 0xba, 0x37, 0x89, 0x7b, 0x22, - 0x3e, 0x57, 0xe0, 0x44, 0xed, 0x3a, 0xeb, 0x5e, 0x0d, 0x77, 0x3b, 0x7a, 0x44, 0xa0, 0xfd, 0x69, - 0xb6, 0x4b, 0x4a, 0xe8, 0x63, 0xc1, 0xbf, 0x95, 0x4b, 0xe4, 0xf7, 0x67, 0x63, 0x29, 0xa7, 0xef, - 0x5f, 0x61, 0x5f, 0x06, 0xa8, 0xd6, 0x57, 0x68, 0xd3, 0xc0, 0x7d, 0x70, 0x6f, 0x74, 0x64, 0xe1, - 0x21, 0x94, 0x6f, 0x83, 0x25, 0x12, 0xed, 0x07, 0xb3, 0x4a, 0xce, 0xed, 0x63, 0xd9, 0xed, 0x58, - 0x76, 0xa1, 0xd6, 0x89, 0x34, 0xe2, 0xc7, 0x92, 0xdb, 0xa9, 0xe4, 0xbe, 0x26, 0xab, 0x64, 0x5c, - 0xbf, 0x6f, 0x65, 0xc7, 0x06, 0x60, 0x32, 0x13, 0xfc, 0x7d, 0xab, 0x49, 0x4f, 0xc2, 0x80, 0x90, - 0x43, 0xb8, 0x54, 0xf0, 0x79, 0x9f, 0x03, 0xf1, 0x80, 0x36, 0x24, 0xd0, 0xbe, 0x32, 0x0b, 0xf1, - 0x4c, 0xf8, 0xf7, 0xa9, 0x0e, 0x7d, 0x7f, 0x36, 0x5e, 0x03, 0xe0, 0xfe, 0xd5, 0x9f, 0x4b, 0x00, - 0xd5, 0xf6, 0x62, 0x5d, 0x44, 0xda, 0xf7, 0x4a, 0x27, 0xfc, 0x21, 0x54, 0x97, 0x28, 0xb4, 0x3f, - 0xcf, 0xa6, 0x16, 0x26, 0xb8, 0x7f, 0x05, 0xf8, 0x2c, 0x9e, 0x8a, 0xd7, 0xed, 0x68, 0x22, 0xc7, - 0x43, 0x48, 0x36, 0xfe, 0xd4, 0xeb, 0xcc, 0x90, 0x90, 0xbc, 0x27, 0xc5, 0x5c, 0xc3, 0x4b, 0xc2, - 0xc8, 0x5c, 0x93, 0x0f, 0xf3, 0x25, 0xc3, 0xed, 0x67, 0xb2, 0xdb, 0xd5, 0x71, 0xb8, 0x9f, 0x57, - 0xd5, 0xbe, 0x8a, 0xb1, 0x81, 0xf5, 0x06, 0x59, 0x4f, 0x0c, 0x4d, 0x9c, 0xda, 0xda, 0x2c, 0x8d, - 0xb7, 0x38, 0x48, 0xbe, 0x56, 0x16, 0x54, 0xda, 0xef, 0xf5, 0xa6, 0x17, 0x11, 0xb8, 0x7f, 0x45, - 0x78, 0x0e, 0xf2, 0x15, 0xc3, 0x5f, 0x11, 0x9a, 0x8c, 0xb7, 0x81, 0x2d, 0xc3, 0x5f, 0xd1, 0x11, - 0x4a, 0x2e, 0x42, 0xbf, 0x6e, 0xac, 0xf3, 0x33, 0x4f, 0x7e, 0x73, 0x86, 0x07, 0x3b, 0xae, 0xb1, - 0x5e, 0xe3, 0xe7, 0x9e, 0x21, 0x9a, 0x68, 0x50, 0x98, 0xa3, 0xfe, 0x8a, 0x63, 0x8a, 0x93, 0x6f, - 0xd8, 0xda, 0x2c, 0x15, 0x9a, 0x08, 0xd1, 0x05, 0x86, 0x3d, 0x6c, 0xc2, 0x31, 0x37, 0xf0, 0xe6, - 0x6b, 0x88, 0x3f, 0x6c, 0xd1, 0x31, 0x37, 0x74, 0x84, 0x92, 0x8f, 0x67, 0xa0, 0x6f, 0x86, 0x1a, - 0x26, 0x1b, 0x21, 0x03, 0xdd, 0x1c, 0xaa, 0x5e, 0x3f, 0x18, 0x87, 0xaa, 0xf1, 0x15, 0xfe, 0x30, - 0x59, 0x51, 0xc4, 0xf3, 0xc9, 0x6b, 0xd0, 0x3f, 0x69, 0xf8, 0x74, 0xd9, 0x71, 0x37, 0xd0, 0x45, - 0x6c, 0x24, 0x72, 0x13, 0x8e, 0xe9, 0x4f, 0x40, 0xc4, 0x6f, 0xc6, 0xea, 0xe2, 0x97, 0x1e, 0x36, - 0x66, 0x62, 0xe1, 0x37, 0x73, 0xe8, 0x23, 0x26, 0xc4, 0xc2, 0xaf, 0xf0, 0x74, 0x81, 0x89, 0x8e, - 0x95, 0x87, 0xd2, 0x8f, 0x95, 0xd1, 0x7a, 0x44, 0x37, 0xd2, 0x49, 0xc7, 0xa4, 0xe8, 0x8b, 0x35, - 0x2c, 0xac, 0x47, 0x84, 0xd6, 0xea, 0x8e, 0xc9, 0xac, 0xc7, 0x90, 0x44, 0xfb, 0xed, 0x5e, 0x48, - 0x4d, 0x39, 0x7e, 0xac, 0xe4, 0xc7, 0x4a, 0x1e, 0x29, 0xf9, 0x54, 0x42, 0xc9, 0xcf, 0x26, 0x93, - 0xd8, 0xdf, 0xa3, 0x1a, 0xfe, 0xb9, 0x7c, 0xa2, 0x04, 0xc6, 0xfd, 0xbd, 0xbb, 0x8c, 0xa4, 0xd7, - 0xbb, 0xad, 0xf4, 0xc2, 0x01, 0x51, 0xd8, 0x76, 0x40, 0xf4, 0xed, 0x74, 0x40, 0xf4, 0x77, 0x1c, - 0x10, 0x91, 0x82, 0x0c, 0x74, 0x54, 0x90, 0x59, 0x31, 0x68, 0x40, 0xc8, 0x50, 0xf5, 0xb9, 0xab, - 0xfa, 0x6e, 0xbb, 0xee, 0xa3, 0xdf, 0xc6, 0x08, 0x1b, 0x4d, 0x91, 0x3a, 0xff, 0xd9, 0x66, 0xa9, - 0xc0, 0xb1, 0x7c, 0x84, 0x69, 0x3f, 0x9b, 0xef, 0x52, 0xb7, 0xe6, 0x50, 0x74, 0xe4, 0x59, 0xc8, - 0x95, 0x5b, 0x2d, 0xa1, 0x1f, 0x27, 0xa4, 0x68, 0x86, 0x0e, 0xad, 0x18, 0x35, 0x79, 0x2f, 0xe4, - 0xca, 0x77, 0xaa, 0x42, 0x25, 0xce, 0x4a, 0xb9, 0x82, 0xc4, 0x97, 0x74, 0x6c, 0x7b, 0xa7, 0x4a, - 0x5e, 0x8e, 0xca, 0x60, 0xae, 0xb4, 0xed, 0x55, 0xb1, 0x51, 0x14, 0x9e, 0xe4, 0x81, 0x27, 0x4f, - 0x9d, 0xa1, 0xd8, 0x76, 0x51, 0xa1, 0x55, 0xb4, 0xa9, 0xb0, 0x73, 0x6d, 0xea, 0xdb, 0x56, 0x9b, - 0xfa, 0x77, 0xaa, 0x4d, 0x03, 0x3b, 0xd0, 0x26, 0xd8, 0x56, 0x9b, 0x06, 0xf7, 0xaf, 0x4d, 0x2d, - 0x38, 0x9b, 0xac, 0x35, 0x16, 0x6a, 0x84, 0x0e, 0x24, 0x89, 0x15, 0x8e, 0x25, 0x78, 0xf5, 0xdf, - 0xe6, 0xd8, 0xda, 0x3a, 0xa2, 0x6b, 0x1e, 0xc3, 0xcb, 0xae, 0x97, 0xc9, 0xd6, 0xda, 0xf7, 0x65, - 0x3b, 0x97, 0x48, 0x3b, 0x9a, 0x53, 0xdc, 0x17, 0xa7, 0x4a, 0x29, 0xaf, 0x24, 0x67, 0xeb, 0x28, - 0x65, 0x85, 0x6d, 0x9a, 0xcc, 0x1c, 0x38, 0x9b, 0x8c, 0x6f, 0x0c, 0x3f, 0xfc, 0x03, 0x40, 0x92, - 0x58, 0xd1, 0x4b, 0xe8, 0xc1, 0x45, 0x23, 0x6c, 0xad, 0xc5, 0xd1, 0x72, 0x27, 0x25, 0x1b, 0x63, - 0x27, 0x75, 0x8c, 0xa8, 0x3c, 0xaa, 0x9d, 0x94, 0x22, 0x24, 0xa5, 0x93, 0x3a, 0x0b, 0x59, 0xed, - 0xa4, 0x14, 0x99, 0x7d, 0x6f, 0xb6, 0x53, 0x71, 0xbd, 0x7d, 0x49, 0xec, 0x89, 0xa4, 0xc7, 0x22, - 0xc6, 0x09, 0x79, 0x71, 0x57, 0xc5, 0x69, 0x18, 0x92, 0x85, 0x28, 0xa4, 0xb4, 0x13, 0x01, 0xc7, - 0xda, 0x91, 0x97, 0x61, 0x30, 0x08, 0x33, 0x8b, 0x9c, 0x98, 0xd0, 0x1d, 0x31, 0x98, 0x58, 0x15, - 0x1f, 0x26, 0x99, 0x9c, 0x3c, 0x09, 0x85, 0x69, 0x2c, 0x0d, 0x2f, 0xcf, 0xc8, 0xbc, 0x58, 0xbc, - 0xec, 0x5a, 0xc4, 0x69, 0xb4, 0x7f, 0xdb, 0x9f, 0x12, 0xf0, 0xbe, 0x2f, 0x61, 0x49, 0x6a, 0x92, - 0xdd, 0x93, 0x9a, 0x3c, 0x07, 0x05, 0x3e, 0xf3, 0x0b, 0x01, 0x8e, 0xc8, 0xd9, 0x55, 0xdb, 0x9e, - 0xd2, 0x4c, 0xd0, 0xee, 0x53, 0x68, 0x2f, 0xc0, 0x00, 0x86, 0x7f, 0x4b, 0x67, 0xe7, 0x78, 0x76, - 0xd2, 0x64, 0x40, 0xf5, 0xcc, 0x25, 0x22, 0x25, 0x6b, 0x40, 0xa2, 0x7c, 0x0a, 0x6c, 0x9e, 0xa0, - 0x66, 0x99, 0xdf, 0x52, 0x76, 0x77, 0xeb, 0x7f, 0x67, 0x10, 0x4f, 0x60, 0x05, 0xad, 0xf9, 0xc4, - 0x4d, 0xcd, 0x9a, 0xdc, 0x4d, 0xe8, 0xdf, 0x9f, 0xf2, 0x04, 0xb2, 0x01, 0x27, 0x42, 0xe8, 0xb4, - 0x65, 0x5b, 0xde, 0x0a, 0x3e, 0xb8, 0x6f, 0xdb, 0x07, 0xbf, 0x4b, 0x3c, 0xb8, 0x14, 0x3d, 0x78, - 0x49, 0xb4, 0x4f, 0x3e, 0x39, 0xed, 0x19, 0xe4, 0x3a, 0x8c, 0x55, 0x57, 0x1c, 0xd7, 0x9f, 0xa2, - 0x5e, 0xdd, 0xb5, 0x5a, 0x98, 0x08, 0x8a, 0x2f, 0xba, 0xe8, 0x1d, 0xef, 0x31, 0x5c, 0xcd, 0x8c, - 0x90, 0xb2, 0xeb, 0xb5, 0xda, 0x90, 0xc9, 0x5d, 0xb7, 0xbc, 0xd5, 0x1b, 0x74, 0x8d, 0x36, 0xc4, - 0x8a, 0x8c, 0x72, 0x77, 0x2d, 0x6f, 0xb5, 0xd6, 0x60, 0x50, 0x59, 0xee, 0x21, 0x29, 0x79, 0x19, - 0x0a, 0xfc, 0xb8, 0x50, 0x98, 0x73, 0x52, 0xf5, 0x6d, 0x06, 0xed, 0xa0, 0x62, 0xa2, 0x0d, 0xf9, - 0x48, 0x5a, 0x98, 0xd6, 0x60, 0x3c, 0xc5, 0x53, 0x82, 0xa0, 0x03, 0xcf, 0x94, 0xa8, 0x2d, 0xd9, - 0xde, 0x1e, 0xda, 0xa3, 0xbd, 0xfd, 0x01, 0x18, 0x8e, 0x18, 0x57, 0x1c, 0x13, 0x37, 0x2c, 0x52, - 0x24, 0x71, 0x0c, 0xd9, 0x81, 0x5b, 0x9c, 0x03, 0x99, 0x86, 0x3e, 0x11, 0xfb, 0x2f, 0x22, 0xa4, - 0x1e, 0x4e, 0x2f, 0x56, 0xdf, 0x69, 0x7c, 0x0a, 0x74, 0xcc, 0x13, 0x77, 0x74, 0x67, 0x9e, 0xb8, - 0xda, 0x8f, 0x67, 0xe0, 0x04, 0x7b, 0x1b, 0x45, 0x96, 0xe4, 0x0d, 0x00, 0x06, 0x16, 0xce, 0x93, - 0x19, 0x74, 0x9e, 0x7c, 0x97, 0xfc, 0x8d, 0x4a, 0x83, 0x4b, 0x11, 0x35, 0x77, 0x9c, 0x7c, 0x28, - 0x88, 0x8f, 0x59, 0x6d, 0x2f, 0xd2, 0x5a, 0xc2, 0x83, 0x52, 0xe2, 0x7e, 0xf6, 0x7d, 0x3c, 0xf2, - 0x70, 0xaf, 0xce, 0x8a, 0xdf, 0x93, 0xed, 0x58, 0x5e, 0xf5, 0x50, 0x96, 0xe3, 0x09, 0xe8, 0x0f, - 0x1c, 0x4a, 0xc5, 0x44, 0x99, 0x08, 0xc2, 0xe8, 0xf4, 0xec, 0x00, 0x4f, 0x3e, 0x9c, 0xda, 0x2b, - 0x62, 0x49, 0x7e, 0xb0, 0x4b, 0x3f, 0x28, 0x1c, 0xd3, 0xb8, 0xa4, 0x0b, 0xec, 0x10, 0xed, 0x97, - 0x7b, 0x5e, 0x60, 0xbf, 0x9c, 0xe9, 0x58, 0x06, 0xf7, 0xa8, 0x0a, 0x4c, 0xfb, 0x4c, 0x6f, 0x50, - 0x7d, 0x77, 0x5f, 0x9f, 0xf0, 0x24, 0x0c, 0x88, 0x84, 0xcc, 0xf1, 0x98, 0x11, 0x71, 0x85, 0x84, - 0x57, 0x92, 0x21, 0x01, 0xdb, 0x5e, 0x4a, 0x01, 0x57, 0x52, 0xcc, 0x88, 0x14, 0x6c, 0xa5, 0x4b, - 0x24, 0x6c, 0x03, 0x79, 0xf5, 0xae, 0xe5, 0xe3, 0x6e, 0x94, 0xf5, 0x65, 0x8e, 0x6f, 0x20, 0xe9, - 0x5d, 0xcb, 0xe7, 0x7b, 0xd1, 0x10, 0xcd, 0x36, 0x87, 0xc2, 0x3a, 0xe9, 0x8d, 0x36, 0x87, 0x7c, - 0xdb, 0x1a, 0xda, 0x22, 0x4f, 0xc2, 0x80, 0x08, 0xc4, 0x10, 0xee, 0x8d, 0xe2, 0x6d, 0x45, 0xe8, - 0x06, 0xbe, 0x6d, 0x48, 0xc0, 0x38, 0xea, 0x74, 0x39, 0x72, 0xe8, 0x46, 0x8e, 0x2e, 0x42, 0x74, - 0x81, 0x21, 0x57, 0x60, 0xa4, 0xea, 0x1b, 0xb6, 0x69, 0xb8, 0xe6, 0x7c, 0xdb, 0x6f, 0xb5, 0x7d, - 0xf9, 0x30, 0xc4, 0xf3, 0x4d, 0xa7, 0xed, 0xeb, 0x0a, 0x05, 0x79, 0x1a, 0x86, 0x03, 0xc8, 0x55, - 0xd7, 0x75, 0x5c, 0x79, 0xc7, 0xeb, 0xf9, 0x26, 0x75, 0x5d, 0x3d, 0x4e, 0x40, 0xde, 0x03, 0xc3, - 0xb3, 0x76, 0x98, 0x5f, 0x58, 0xbf, 0x21, 0xf6, 0xbf, 0x18, 0xdd, 0x6e, 0x85, 0x88, 0x5a, 0xdb, - 0x6d, 0xe8, 0x71, 0x42, 0xf2, 0x3c, 0x0c, 0x55, 0x1a, 0x86, 0xcf, 0xcc, 0x4c, 0x34, 0xa1, 0x06, - 0xa3, 0xc0, 0x93, 0x96, 0x80, 0x73, 0x07, 0x84, 0x18, 0x99, 0xdc, 0x0c, 0xad, 0xb6, 0xa1, 0x94, - 0x66, 0xcc, 0x6c, 0xd3, 0x63, 0x64, 0xe4, 0xfd, 0x30, 0x1a, 0xfc, 0x16, 0x15, 0x05, 0x44, 0xf4, - 0x30, 0xda, 0xba, 0x61, 0xcb, 0x35, 0x8e, 0xd3, 0x55, 0x62, 0xed, 0x37, 0x33, 0x22, 0x66, 0x27, - 0xd4, 0xaf, 0x97, 0x60, 0xa4, 0xda, 0x5e, 0x0c, 0xed, 0x92, 0x20, 0x83, 0x82, 0x08, 0x3a, 0x90, - 0x30, 0xfc, 0x34, 0x23, 0x46, 0xca, 0xc4, 0x16, 0xe8, 0x3b, 0x06, 0xb9, 0x0b, 0x05, 0x45, 0xb1, - 0x85, 0x61, 0x07, 0x18, 0x1a, 0xaf, 0xc7, 0x09, 0x99, 0xa2, 0x46, 0x91, 0xa2, 0xb2, 0xa2, 0x4a, - 0xe1, 0xa5, 0xba, 0x44, 0x22, 0xe9, 0x4a, 0xbe, 0x93, 0xae, 0x68, 0xb7, 0x44, 0xd8, 0xe7, 0xed, - 0xb9, 0xf0, 0xf3, 0x1e, 0x82, 0xfc, 0xed, 0xb9, 0xf0, 0xa3, 0xf0, 0x6c, 0x74, 0xad, 0xc9, 0x78, - 0x23, 0x98, 0x3c, 0x06, 0x85, 0xdb, 0x73, 0xd8, 0x6f, 0xfc, 0xcd, 0x07, 0xb7, 0x36, 0x4b, 0x7d, - 0x6b, 0xa2, 0xc7, 0x04, 0x4a, 0xfb, 0x6c, 0x3e, 0xca, 0x89, 0xb4, 0xaf, 0xb1, 0x3c, 0xab, 0x08, - 0x5f, 0xcc, 0x4b, 0xa7, 0x62, 0xf9, 0x95, 0x3a, 0x99, 0x34, 0xf1, 0x6e, 0x9b, 0x4f, 0x7c, 0xaa, - 0xba, 0x35, 0x55, 0xd0, 0x6a, 0x4a, 0x08, 0x55, 0x50, 0xea, 0x44, 0xd0, 0xdb, 0x79, 0x22, 0x78, - 0x09, 0x46, 0xb8, 0x5b, 0x21, 0x4f, 0xd1, 0xe4, 0x07, 0xfb, 0x06, 0x54, 0x19, 0x1a, 0x60, 0x6a, - 0xe8, 0x5f, 0xa7, 0x2b, 0xa4, 0x29, 0xe3, 0xb9, 0xb0, 0xfb, 0xf1, 0xdc, 0xb7, 0xdd, 0x78, 0x7e, - 0x16, 0xfa, 0xcb, 0x95, 0x59, 0x4e, 0xdc, 0x2f, 0x05, 0x15, 0xb5, 0xac, 0x1a, 0x65, 0x40, 0xd9, - 0x54, 0x0b, 0x08, 0xd9, 0xfe, 0x51, 0x4c, 0x70, 0x03, 0xd2, 0x89, 0x1e, 0x42, 0xe4, 0xfd, 0x23, - 0xa7, 0xd1, 0xb6, 0xb2, 0xc9, 0xea, 0xe4, 0xf7, 0xef, 0x29, 0xf9, 0xc5, 0x78, 0xf4, 0x06, 0xaa, - 0x02, 0x9e, 0x40, 0xca, 0xc1, 0x63, 0xfc, 0x20, 0xf2, 0x0a, 0xf4, 0x5f, 0xa7, 0x1b, 0x3c, 0xd0, - 0xa8, 0x10, 0x59, 0xd1, 0xab, 0x02, 0x26, 0x77, 0x4d, 0x40, 0xa7, 0xfd, 0x68, 0x36, 0x59, 0x77, - 0xfd, 0xfe, 0x15, 0xf6, 0xd3, 0xd0, 0x87, 0xa2, 0x9c, 0x0d, 0x7c, 0x4c, 0x50, 0x80, 0x28, 0xee, - 0x78, 0xc8, 0x5b, 0x40, 0xa6, 0x7d, 0x6b, 0x41, 0x2d, 0xc6, 0x7f, 0xff, 0x4a, 0xef, 0x25, 0x18, - 0x9c, 0x74, 0x6c, 0x0f, 0x33, 0x14, 0xd7, 0x03, 0x85, 0xc5, 0xe8, 0xcf, 0x7a, 0x04, 0x96, 0x8f, - 0x4b, 0x24, 0xea, 0xbd, 0x28, 0x2f, 0xdb, 0xea, 0xa3, 0xc8, 0x71, 0xa1, 0xef, 0x8b, 0xb6, 0xfa, - 0x8b, 0x0c, 0xa8, 0x1e, 0xce, 0x44, 0xa4, 0xe4, 0x16, 0xf4, 0x4f, 0xae, 0x58, 0x0d, 0xd3, 0xa5, - 0x36, 0x06, 0xa8, 0x49, 0x7b, 0xf4, 0x78, 0x5f, 0x5e, 0xc2, 0x7f, 0x91, 0x96, 0xbf, 0x4e, 0x5d, - 0x34, 0x8b, 0x65, 0xb4, 0x10, 0xb0, 0xb3, 0xdf, 0x90, 0x05, 0x88, 0x1a, 0x90, 0x47, 0x20, 0x1b, - 0xae, 0x8f, 0xe8, 0x17, 0x1d, 0xd3, 0xa0, 0x2c, 0xda, 0x88, 0x62, 0x6c, 0x67, 0xb7, 0x1d, 0xdb, - 0xb7, 0xa0, 0xc0, 0xaf, 0x58, 0x31, 0x74, 0x51, 0xca, 0x67, 0xd5, 0xf1, 0x85, 0x2f, 0x21, 0x3d, - 0x9f, 0x6b, 0x71, 0xcb, 0x19, 0x9b, 0x6b, 0x39, 0xb3, 0xb3, 0x75, 0xe8, 0xc5, 0xbf, 0xc8, 0x79, - 0xc8, 0xa3, 0x14, 0x33, 0x78, 0x71, 0x82, 0x76, 0x86, 0x22, 0x3f, 0xc4, 0xb3, 0x6e, 0x9a, 0x74, - 0x6c, 0x5f, 0xc4, 0xa9, 0x67, 0x2e, 0x0c, 0x09, 0xb9, 0x08, 0x58, 0x4c, 0x2e, 0x02, 0xa6, 0xfd, - 0x54, 0x16, 0xc6, 0xc3, 0xd7, 0x64, 0x16, 0x05, 0x1e, 0x69, 0xdc, 0xb7, 0xc3, 0xe4, 0x45, 0x00, - 0x4c, 0x8f, 0xc5, 0xe4, 0x19, 0xc4, 0x04, 0xe3, 0x28, 0x41, 0x46, 0xa8, 0xb6, 0xb1, 0xf3, 0x86, - 0x88, 0x58, 0xfb, 0xe9, 0x0c, 0x44, 0x45, 0xfe, 0x0f, 0xe0, 0xa0, 0x40, 0xde, 0x8e, 0x65, 0xf7, - 0xb8, 0x7f, 0x0d, 0xfa, 0x22, 0xb7, 0xbb, 0xbe, 0x88, 0x7f, 0xcb, 0x01, 0x6c, 0x49, 0x0f, 0xf3, - 0x5b, 0x7e, 0x3b, 0x0b, 0x44, 0x54, 0xe1, 0x2f, 0xb7, 0xfd, 0x95, 0xb2, 0x8f, 0x7d, 0x77, 0x34, - 0x55, 0x3c, 0x3a, 0x2b, 0xcf, 0xef, 0xe2, 0xac, 0xfc, 0xa3, 0x30, 0x2a, 0x82, 0xf0, 0xb1, 0x7a, - 0xbd, 0x49, 0x5d, 0x9c, 0xff, 0xa5, 0x3c, 0x82, 0x42, 0x3e, 0x0a, 0x55, 0xe7, 0xc4, 0x6a, 0x31, - 0x32, 0xed, 0x2f, 0x32, 0x70, 0x2e, 0x9d, 0xc3, 0x3d, 0x32, 0x0c, 0x52, 0x04, 0x90, 0xfb, 0xcb, - 0x11, 0xc0, 0x01, 0x9c, 0x7f, 0x1d, 0x6d, 0x01, 0xdc, 0x23, 0x93, 0xc7, 0x5b, 0x2d, 0x80, 0x6f, - 0xcd, 0x40, 0xa9, 0x9b, 0x00, 0xca, 0xfb, 0x0c, 0x30, 0x38, 0x00, 0x19, 0x68, 0xff, 0x3a, 0x03, - 0x27, 0xe6, 0x57, 0x7d, 0x23, 0x00, 0x78, 0x07, 0xa0, 0x9c, 0xd1, 0xc5, 0x4c, 0x76, 0x0f, 0x17, - 0x33, 0x37, 0xa0, 0x8f, 0xbf, 0x83, 0x29, 0x7a, 0xe3, 0x71, 0xb9, 0xba, 0xb6, 0xf2, 0x9e, 0x9d, - 0xae, 0x3d, 0x02, 0x16, 0xda, 0x4f, 0x64, 0x60, 0x94, 0xb5, 0xab, 0x6e, 0xd8, 0xf5, 0x69, 0xc3, - 0x6a, 0xb4, 0xdd, 0xc3, 0xfc, 0xb6, 0x3d, 0x5d, 0x6b, 0x6a, 0xbf, 0x93, 0x85, 0x93, 0x58, 0x36, - 0x3d, 0x4c, 0xc7, 0x23, 0xaa, 0x30, 0x1f, 0xde, 0x87, 0x1c, 0xc4, 0x39, 0xfa, 0xde, 0xd6, 0xad, - 0x05, 0x18, 0x89, 0xcb, 0x42, 0x2c, 0x5b, 0x0f, 0xc7, 0xca, 0xce, 0x87, 0xd8, 0x0e, 0x6f, 0xa1, - 0xf0, 0xd0, 0x3e, 0x9b, 0x85, 0xb1, 0xa8, 0x20, 0xdf, 0x3d, 0xb2, 0x42, 0xed, 0xed, 0x12, 0x7c, - 0x16, 0x46, 0xa3, 0x2f, 0x59, 0xb0, 0xfc, 0x46, 0x70, 0x11, 0x8e, 0x57, 0xb3, 0x22, 0xd1, 0x5e, - 0xc3, 0xf2, 0xfc, 0x9a, 0xcf, 0x90, 0xb1, 0x94, 0x6f, 0xf1, 0x76, 0x8a, 0x54, 0xee, 0x91, 0x65, - 0xeb, 0x1e, 0x93, 0xca, 0x3d, 0xb2, 0x96, 0x1d, 0xba, 0x54, 0xbe, 0x33, 0x0b, 0xa7, 0x23, 0xd8, - 0x1c, 0x6d, 0x2e, 0xde, 0x33, 0x96, 0xde, 0x87, 0xe4, 0xfe, 0xe2, 0x6f, 0x26, 0xa4, 0xf4, 0x48, - 0x3c, 0x47, 0x4f, 0x84, 0xef, 0xc0, 0x33, 0xc1, 0x67, 0x6f, 0x93, 0x58, 0xaa, 0xb0, 0xee, 0x91, - 0xe1, 0x75, 0x34, 0x84, 0x75, 0x8f, 0x8c, 0xba, 0x7b, 0x4f, 0x58, 0x3f, 0x94, 0x85, 0xf3, 0xe9, - 0xc2, 0x2a, 0x37, 0x1a, 0xd3, 0x8e, 0x1b, 0xe1, 0x8e, 0x85, 0x97, 0x10, 0xde, 0xa7, 0x63, 0x33, - 0xbb, 0x4e, 0xd7, 0x2c, 0xba, 0x7e, 0xe8, 0x62, 0x9a, 0x86, 0x02, 0x7f, 0x93, 0xce, 0xc2, 0xe1, - 0xf8, 0x4e, 0x26, 0x9b, 0xf8, 0x8e, 0xe7, 0x40, 0x5c, 0x51, 0xef, 0x4c, 0x24, 0x9c, 0x56, 0xfb, - 0xd3, 0x0c, 0x0c, 0x97, 0xdb, 0xa6, 0xe5, 0xe3, 0xa9, 0xe6, 0x7e, 0xaf, 0xfd, 0xf6, 0x7a, 0x46, - 0xb2, 0xb7, 0xd5, 0xed, 0x7d, 0xc1, 0x69, 0x6d, 0x5e, 0xa9, 0x84, 0x1b, 0x7e, 0xcf, 0x14, 0xf5, - 0x0d, 0xab, 0xa1, 0xb6, 0x17, 0x59, 0xb6, 0x7e, 0x34, 0x0b, 0xe3, 0x09, 0x52, 0x72, 0x3e, 0x96, - 0xd7, 0x12, 0xcf, 0x5d, 0x15, 0x8f, 0x27, 0x9e, 0xe1, 0x72, 0x17, 0x47, 0xc5, 0xe7, 0x21, 0x3f, - 0x65, 0x6c, 0xf0, 0x6f, 0xeb, 0xe5, 0x2c, 0x4d, 0x63, 0x43, 0x3e, 0x52, 0x44, 0x3c, 0x59, 0x84, - 0x53, 0xe1, 0x15, 0xe2, 0x82, 0xd5, 0xa4, 0xb3, 0xf6, 0x9c, 0xd5, 0x68, 0x58, 0x9e, 0x70, 0x57, - 0x78, 0x72, 0x6b, 0xb3, 0x74, 0xc1, 0x77, 0x7c, 0xa3, 0x51, 0x8b, 0xae, 0x1e, 0x7d, 0xab, 0x49, - 0x6b, 0x96, 0x5d, 0x6b, 0x22, 0xa5, 0xc4, 0x32, 0x9d, 0x15, 0x99, 0x05, 0x82, 0x49, 0x94, 0xea, - 0x86, 0x6d, 0x53, 0x73, 0xd6, 0x9e, 0xd8, 0xf0, 0x29, 0x77, 0x73, 0xc8, 0xf1, 0x33, 0x4f, 0x9e, - 0x6d, 0x89, 0xa3, 0x19, 0xe3, 0x45, 0x46, 0xa0, 0xa7, 0x34, 0xd2, 0x7e, 0x38, 0x0f, 0xe3, 0x55, - 0x5a, 0x6f, 0xbb, 0x96, 0xbf, 0xa1, 0x63, 0x08, 0xd1, 0xd1, 0x52, 0x9f, 0xa0, 0xa7, 0xf3, 0xdb, - 0xf4, 0xf4, 0x65, 0xe8, 0x0b, 0x3c, 0x10, 0xf8, 0x0d, 0x0a, 0x86, 0x87, 0x0a, 0xc7, 0x03, 0xf9, - 0x0a, 0x4a, 0x50, 0x91, 0x06, 0x9c, 0x5d, 0x60, 0xdd, 0x94, 0xde, 0x99, 0x85, 0x3d, 0x74, 0x66, - 0x17, 0x7e, 0xe4, 0x83, 0x70, 0x06, 0xb1, 0x29, 0xdd, 0xda, 0x87, 0x8f, 0x42, 0x5b, 0x8f, 0x3f, - 0x2a, 0xbd, 0x73, 0x3b, 0xb5, 0x27, 0x1f, 0x82, 0xa1, 0x70, 0x80, 0x58, 0xd4, 0x13, 0x57, 0x33, - 0x5d, 0xc6, 0x19, 0xcf, 0x16, 0xce, 0xc0, 0x18, 0x94, 0x11, 0xcf, 0x38, 0x1d, 0xe3, 0xa5, 0xfd, - 0x66, 0x06, 0xce, 0x5e, 0xbd, 0xcb, 0x93, 0xfd, 0x22, 0xa2, 0xea, 0x3b, 0xae, 0xb1, 0x4c, 0xaf, - 0xda, 0xc6, 0x62, 0xe3, 0xf0, 0x57, 0xfe, 0xf7, 0x41, 0x9f, 0xc9, 0x3f, 0x4a, 0xe8, 0xd4, 0x63, - 0xa1, 0xf3, 0x7a, 0xca, 0x4b, 0x8b, 0xef, 0xd7, 0x83, 0x36, 0xda, 0x6f, 0x65, 0xe0, 0xc1, 0x54, - 0x42, 0xcb, 0x7b, 0x3b, 0x7c, 0xde, 0xa7, 0x73, 0x1d, 0x3e, 0x4f, 0x4c, 0xa2, 0x17, 0x61, 0xcc, - 0xb2, 0x7d, 0xba, 0xec, 0x72, 0x67, 0xa2, 0x28, 0xa1, 0xab, 0x3e, 0x2a, 0xc1, 0x71, 0x74, 0x3d, - 0x07, 0xa1, 0xf3, 0xb6, 0x1b, 0xb8, 0xf3, 0x7b, 0xb5, 0xb6, 0x6b, 0xf1, 0x71, 0xa9, 0x9f, 0xf4, - 0x14, 0x5f, 0x7f, 0xef, 0x96, 0x6b, 0xb1, 0x07, 0x18, 0xfe, 0x0a, 0xb5, 0x8d, 0xda, 0xba, 0xe3, - 0xae, 0xa2, 0xdf, 0x0d, 0x1f, 0x9c, 0xfa, 0x28, 0x87, 0xdf, 0x09, 0xc0, 0xe4, 0x31, 0x18, 0x5e, - 0x6e, 0xb4, 0x69, 0x58, 0x04, 0x80, 0x5f, 0x66, 0xea, 0x43, 0x0c, 0x18, 0x5e, 0x01, 0x3d, 0x04, - 0x80, 0x44, 0x58, 0x0f, 0x9f, 0xdf, 0x5c, 0xea, 0x03, 0x0c, 0xb2, 0x20, 0xba, 0xeb, 0x2c, 0xd7, - 0x6a, 0x2e, 0xa4, 0x5a, 0xc3, 0xb1, 0x97, 0x6b, 0x3e, 0x75, 0x9b, 0xf8, 0xa2, 0xe8, 0x76, 0xa1, - 0x9f, 0x46, 0x0a, 0xbc, 0x1b, 0xf2, 0x6e, 0x38, 0xf6, 0xf2, 0x02, 0x75, 0x9b, 0xec, 0x55, 0x9f, - 0x04, 0x22, 0x5e, 0xd5, 0xc5, 0x53, 0x1d, 0xfe, 0x71, 0xe8, 0x77, 0xa1, 0x8b, 0x8f, 0xe0, 0xc7, - 0x3d, 0xf8, 0x61, 0x25, 0x18, 0xe4, 0x99, 0xd0, 0xb9, 0xd0, 0xd0, 0x39, 0x4b, 0x07, 0x0e, 0x42, - 0x79, 0x9d, 0x06, 0xe1, 0x0b, 0xc4, 0xfd, 0xaf, 0x74, 0xf1, 0x4b, 0xfb, 0x44, 0x0e, 0x08, 0x1e, - 0x7a, 0x84, 0x56, 0x03, 0x96, 0x7b, 0xdf, 0x8f, 0xa2, 0x45, 0xd3, 0x6a, 0x76, 0x57, 0xd3, 0xea, - 0xa8, 0xdd, 0x6e, 0xd6, 0x8c, 0x56, 0xab, 0xb6, 0x64, 0x35, 0x30, 0x51, 0x01, 0x2e, 0x7c, 0xfa, - 0xb0, 0xdd, 0x6e, 0x96, 0x5b, 0xad, 0x69, 0x0e, 0x24, 0xef, 0x84, 0x71, 0x46, 0x87, 0x9d, 0x14, - 0x52, 0xa2, 0x3f, 0x8e, 0xce, 0x18, 0xa0, 0xf3, 0x54, 0x40, 0xfb, 0x00, 0xf4, 0x0b, 0x9e, 0x7c, - 0xad, 0xea, 0xd5, 0xfb, 0x38, 0x33, 0x8f, 0xf5, 0x5c, 0xc8, 0x86, 0x4f, 0xae, 0xbd, 0xfa, 0x40, - 0xd0, 0x1e, 0x0b, 0xe6, 0xd8, 0x6d, 0x9e, 0x5c, 0x1b, 0xbb, 0xb5, 0x57, 0x0f, 0x7f, 0x93, 0xf3, - 0x30, 0xc2, 0xb8, 0x84, 0x02, 0xe3, 0x35, 0x46, 0x7a, 0x75, 0x05, 0x4a, 0xae, 0xc0, 0xc9, 0x18, - 0x84, 0xdb, 0xa0, 0xdc, 0x77, 0xa6, 0x57, 0x4f, 0xc5, 0x69, 0x3f, 0x98, 0x83, 0x21, 0xd6, 0x1d, - 0x6c, 0x7d, 0x3a, 0xa4, 0x8e, 0x38, 0x03, 0x7d, 0x8e, 0xbb, 0x5c, 0x6b, 0xbb, 0x0d, 0x31, 0xf6, - 0x0a, 0x8e, 0xbb, 0x7c, 0xcb, 0x6d, 0x90, 0x53, 0x50, 0x60, 0xbd, 0x63, 0x99, 0x62, 0x88, 0xf5, - 0x1a, 0xad, 0xd6, 0xac, 0x49, 0xca, 0xbc, 0x43, 0xb0, 0x3e, 0x45, 0xad, 0x8e, 0x5b, 0x7b, 0xee, - 0x75, 0xd1, 0xcb, 0x57, 0xbc, 0x04, 0x12, 0xfb, 0x09, 0xab, 0x56, 0xf0, 0x83, 0x00, 0x85, 0x85, - 0x89, 0xdb, 0x12, 0x93, 0xf7, 0x89, 0xca, 0x42, 0x20, 0x23, 0x16, 0x7c, 0x13, 0x63, 0x92, 0x29, - 0x20, 0x11, 0x55, 0xd3, 0x31, 0xad, 0x25, 0x8b, 0xf2, 0x38, 0xe9, 0x5e, 0x7e, 0xb3, 0x9d, 0xc4, - 0xea, 0x63, 0x01, 0x93, 0x39, 0x01, 0x21, 0x2f, 0x71, 0x25, 0xe4, 0x74, 0xb8, 0xf6, 0xf1, 0xbe, - 0xe5, 0x76, 0x9a, 0x82, 0x42, 0xcd, 0xc4, 0xf6, 0xb8, 0x10, 0x6a, 0x37, 0x61, 0x18, 0x1d, 0xc8, - 0xab, 0xb4, 0x41, 0xeb, 0xbe, 0xe3, 0x92, 0x07, 0x20, 0x77, 0x3d, 0x70, 0x21, 0xe7, 0x51, 0x41, - 0xab, 0x74, 0x43, 0x67, 0x30, 0xa2, 0x85, 0x6e, 0x00, 0x59, 0xbc, 0x37, 0x46, 0x4f, 0x2d, 0x7e, - 0xbb, 0x1f, 0xdc, 0xe9, 0x6b, 0xff, 0xa8, 0x1f, 0xc6, 0xaa, 0x95, 0xd9, 0xe9, 0xe9, 0xab, 0xd5, - 0xdb, 0xb3, 0x53, 0x58, 0x57, 0xdf, 0x3c, 0x14, 0x3b, 0x69, 0x06, 0x60, 0xd2, 0xb1, 0x6d, 0x8a, - 0x75, 0x94, 0xc3, 0xaa, 0x4f, 0x81, 0xdf, 0x42, 0x88, 0xe9, 0xc0, 0x43, 0x6a, 0x4b, 0x2e, 0x42, - 0x3f, 0xff, 0xa2, 0xd9, 0x29, 0x61, 0x3f, 0xa1, 0x0f, 0x9d, 0xd7, 0xb2, 0x96, 0x96, 0xd0, 0xa1, - 0x31, 0x44, 0x93, 0xf3, 0xd0, 0x37, 0x75, 0xb3, 0x5a, 0x2d, 0xdf, 0x0c, 0xae, 0xd6, 0x31, 0x02, - 0xdc, 0xb4, 0xbd, 0x9a, 0x67, 0xd8, 0x9e, 0x1e, 0x20, 0xc9, 0x63, 0x50, 0x98, 0xad, 0x20, 0x19, - 0xcf, 0x6b, 0x82, 0x0e, 0x8a, 0x56, 0x8b, 0x53, 0x09, 0x14, 0x3e, 0xf7, 0xf6, 0xec, 0x94, 0xe4, - 0x5f, 0xc2, 0x9f, 0xbb, 0x66, 0x99, 0xdc, 0x89, 0x34, 0x44, 0x93, 0xe7, 0x61, 0xa8, 0x4a, 0x5d, - 0xcb, 0x68, 0xdc, 0x6c, 0xe3, 0xd6, 0x53, 0xca, 0x93, 0xee, 0x21, 0xbc, 0x66, 0x23, 0x42, 0x8f, - 0x91, 0x91, 0x73, 0x90, 0x9f, 0xb1, 0xec, 0x20, 0xc8, 0x18, 0xa3, 0x50, 0x57, 0x2c, 0xdb, 0xd7, - 0x11, 0x4a, 0x1e, 0x83, 0xdc, 0xb5, 0x85, 0x59, 0xe1, 0x33, 0x8b, 0xbc, 0xde, 0xf0, 0x63, 0x39, - 0xd7, 0xaf, 0x2d, 0xcc, 0x92, 0xe7, 0x61, 0x80, 0x2d, 0x8a, 0xd4, 0xae, 0x53, 0xaf, 0x38, 0x88, - 0x1f, 0xc3, 0x7d, 0xf2, 0x02, 0xa0, 0xec, 0x04, 0x13, 0x52, 0x92, 0xeb, 0x30, 0x16, 0x94, 0xef, - 0x0d, 0xf3, 0xcc, 0x0f, 0x45, 0xa7, 0x75, 0xeb, 0x02, 0x97, 0x96, 0x6a, 0x3e, 0xd1, 0x90, 0x98, - 0x50, 0x54, 0x61, 0x6c, 0x9f, 0x28, 0xf9, 0xd1, 0x5e, 0xd8, 0xda, 0x2c, 0x3d, 0x9e, 0x60, 0x5a, - 0x73, 0x05, 0x95, 0xc4, 0xbd, 0x23, 0x27, 0xf2, 0x61, 0x80, 0xb2, 0xef, 0xbb, 0xd6, 0x62, 0x9b, - 0x99, 0x9b, 0x23, 0xdd, 0xe3, 0x64, 0xb5, 0xad, 0xcd, 0xd2, 0x49, 0x23, 0x24, 0x4f, 0x8d, 0x96, - 0x95, 0xd8, 0x91, 0x57, 0x60, 0x88, 0xad, 0x78, 0xc1, 0x28, 0x14, 0x71, 0x28, 0x68, 0x62, 0xb2, - 0xb5, 0xb1, 0xe6, 0x09, 0x84, 0x6c, 0x62, 0xca, 0x0d, 0xc8, 0x17, 0xc1, 0x48, 0x6c, 0x1c, 0xf3, - 0x7a, 0x3c, 0x92, 0x17, 0x6a, 0x0c, 0xcb, 0x73, 0x62, 0x63, 0x74, 0x49, 0xc8, 0x3a, 0x96, 0x13, - 0x3b, 0xce, 0x8b, 0x7c, 0x38, 0x2a, 0xd6, 0x1c, 0xc8, 0x85, 0x17, 0x95, 0xe0, 0x05, 0x7b, 0x9e, - 0xd8, 0xda, 0x2c, 0x3d, 0x9a, 0x14, 0x2f, 0x96, 0x99, 0x90, 0xb7, 0x69, 0xa9, 0x3c, 0xb4, 0x3f, - 0xc8, 0xc2, 0xc9, 0x72, 0xdb, 0x5f, 0xa9, 0xb8, 0x54, 0x44, 0x67, 0x1d, 0xc0, 0xf1, 0xe1, 0xde, - 0x96, 0x91, 0x3d, 0xba, 0x60, 0x28, 0x93, 0x4d, 0x7e, 0x1f, 0x93, 0xcd, 0x12, 0x8c, 0x96, 0xcd, - 0xa6, 0x65, 0x97, 0x79, 0xfd, 0xf7, 0xb9, 0xe9, 0x32, 0x2e, 0x4b, 0x52, 0x32, 0x15, 0x05, 0x2d, - 0xbe, 0x87, 0x57, 0x1e, 0x62, 0xa8, 0x9a, 0xc1, 0x71, 0xb5, 0xe6, 0x92, 0x51, 0xab, 0xaf, 0x18, - 0xf6, 0x32, 0x5b, 0x7a, 0x94, 0x56, 0xda, 0x67, 0xb3, 0xf0, 0x50, 0x50, 0x31, 0x82, 0xfa, 0xac, - 0xef, 0x2c, 0x7b, 0x39, 0x56, 0x4c, 0xf4, 0x3e, 0x94, 0xbe, 0xf6, 0x99, 0x2c, 0x9c, 0x53, 0xe3, - 0x6d, 0xef, 0x7b, 0xa1, 0xfc, 0x7e, 0x16, 0xc6, 0xb9, 0xd1, 0x57, 0x31, 0xfc, 0x95, 0x49, 0xae, - 0x51, 0xfb, 0x92, 0xc4, 0x45, 0xe8, 0xe7, 0x6c, 0xc2, 0xe0, 0x17, 0x5c, 0xd9, 0xb8, 0xb2, 0xe2, - 0x8a, 0x1a, 0xa0, 0xc9, 0x4d, 0x38, 0x59, 0x5e, 0x5a, 0xa2, 0x75, 0x3f, 0xaa, 0xb5, 0x73, 0x33, - 0x2a, 0x99, 0xc1, 0x6b, 0x9a, 0x08, 0x7c, 0x54, 0xc3, 0x0a, 0xfd, 0xfc, 0x53, 0xdb, 0x91, 0x05, - 0x38, 0xad, 0xc2, 0xab, 0x7c, 0x7b, 0x99, 0x97, 0xca, 0x9c, 0x24, 0x38, 0xf2, 0xff, 0xf4, 0x0e, - 0x6d, 0xd3, 0xde, 0x12, 0x97, 0xed, 0xde, 0x6e, 0x6f, 0x89, 0x6b, 0x78, 0x6a, 0x3b, 0xed, 0x47, - 0x73, 0x00, 0xd5, 0x96, 0x61, 0xdb, 0x98, 0x3f, 0xee, 0xfe, 0xf5, 0x56, 0x7c, 0x2e, 0x16, 0x9c, - 0xb4, 0xd3, 0x21, 0xf3, 0xbc, 0xc8, 0xf7, 0x68, 0xb6, 0xdd, 0xc0, 0x9d, 0x37, 0xcc, 0x37, 0x87, - 0x40, 0xd9, 0x26, 0x09, 0x29, 0xc9, 0x2c, 0xe4, 0xcb, 0xee, 0xb2, 0x27, 0x82, 0x8e, 0xbb, 0xa7, - 0xc0, 0x30, 0xdc, 0xe5, 0xf4, 0x45, 0x1d, 0x59, 0x68, 0x5f, 0x9f, 0x85, 0x07, 0xf8, 0x90, 0x79, - 0xcd, 0x35, 0x5a, 0x2b, 0x55, 0xea, 0xfb, 0xb8, 0x9b, 0xbf, 0x7f, 0x27, 0x91, 0x6f, 0xca, 0xc2, - 0x69, 0x6e, 0x26, 0x4f, 0x53, 0x93, 0xf2, 0x33, 0x91, 0xa3, 0xed, 0x43, 0xfa, 0x16, 0x0b, 0xe7, - 0x68, 0x3b, 0xa5, 0x1e, 0xa0, 0x70, 0x7e, 0x29, 0x0b, 0xa7, 0xa3, 0xaa, 0xc1, 0xb1, 0xda, 0xe9, - 0xf7, 0xbd, 0x70, 0xf6, 0x36, 0x41, 0xa6, 0x8a, 0xf4, 0x3e, 0xb6, 0xb9, 0x65, 0x55, 0xe8, 0xdd, - 0xa3, 0xef, 0x61, 0x9a, 0x48, 0x8f, 0x87, 0xf0, 0xbe, 0xb4, 0xf4, 0x97, 0xb3, 0x70, 0x26, 0x12, - 0xa9, 0xb8, 0x70, 0x3a, 0x1e, 0xf9, 0x07, 0x2f, 0xd3, 0xe3, 0xa1, 0xbf, 0xaf, 0xa1, 0x9f, 0x2a, - 0xd3, 0xe3, 0xb1, 0xbf, 0x2f, 0x3d, 0xfd, 0xae, 0x2c, 0x3c, 0x14, 0xc9, 0xb4, 0xbc, 0x4c, 0x6d, - 0x5f, 0x77, 0x1a, 0x0d, 0xa7, 0xed, 0x2f, 0xb8, 0xd6, 0xf2, 0xf2, 0x21, 0x45, 0x70, 0x1d, 0xdc, - 0x99, 0xf2, 0x69, 0x28, 0x88, 0x5b, 0x1c, 0x2c, 0x6e, 0xaf, 0x8b, 0x5f, 0x7b, 0x94, 0xd6, 0x3f, - 0xcc, 0x42, 0x29, 0x5d, 0x5a, 0xd3, 0x8e, 0x5b, 0xa7, 0x53, 0x8e, 0x4d, 0x8f, 0xe5, 0x25, 0xc9, - 0xeb, 0xbb, 0xb3, 0xf0, 0x70, 0xba, 0xbc, 0xd8, 0x7f, 0x8b, 0x46, 0x7d, 0xf5, 0x58, 0x5c, 0x92, - 0xb8, 0x7e, 0x21, 0x0b, 0x27, 0xd9, 0x9f, 0x56, 0x7d, 0xc6, 0xf1, 0x7c, 0xf6, 0xaa, 0x07, 0xb0, - 0x0a, 0xef, 0x6d, 0xc5, 0x38, 0x08, 0x5f, 0xfd, 0xa0, 0x7b, 0xf2, 0xfb, 0xea, 0x9e, 0xde, 0x7d, - 0x6c, 0x69, 0x92, 0x02, 0x3d, 0xb4, 0x25, 0xf8, 0xed, 0x2a, 0xd0, 0x03, 0x58, 0x7f, 0xef, 0x67, - 0x81, 0x7e, 0x5d, 0x0e, 0xc6, 0x26, 0x5d, 0x67, 0xdd, 0xbe, 0x46, 0xd7, 0x69, 0xe3, 0xd0, 0x86, - 0xfb, 0x3d, 0x61, 0x20, 0xba, 0x7b, 0x34, 0x10, 0x83, 0x76, 0xe4, 0x15, 0x18, 0x8d, 0x64, 0x29, - 0x27, 0x4d, 0x47, 0x3f, 0x81, 0x3a, 0x43, 0xd5, 0xde, 0x60, 0x38, 0x91, 0xdd, 0x57, 0xa5, 0xd6, - 0xfe, 0x34, 0xd6, 0x1b, 0xf7, 0xb7, 0xb9, 0xbe, 0xef, 0xde, 0xb8, 0x05, 0xa7, 0x27, 0xdb, 0xae, - 0x4b, 0x6d, 0x3f, 0xbd, 0x53, 0xf0, 0x26, 0xad, 0xce, 0x29, 0x6a, 0xc9, 0xce, 0xe9, 0xd0, 0x98, - 0xb1, 0x15, 0x71, 0x7a, 0x2a, 0xdb, 0xbe, 0x88, 0x6d, 0x9b, 0x53, 0xa4, 0xb1, 0x4d, 0x6f, 0xac, - 0xfd, 0x4c, 0x56, 0xee, 0xfa, 0x43, 0x9b, 0xd5, 0xde, 0x16, 0x5d, 0xaf, 0x7d, 0x22, 0x07, 0x23, - 0xec, 0xb5, 0x16, 0x0c, 0x6f, 0xf5, 0xd8, 0x84, 0xd9, 0xcf, 0x02, 0xc1, 0xbe, 0x22, 0x90, 0xa4, - 0x48, 0x0f, 0x5b, 0x94, 0xdf, 0x82, 0xc1, 0x3b, 0x7d, 0x45, 0x80, 0xd7, 0xbe, 0x27, 0x1f, 0x75, - 0xc7, 0xb1, 0x01, 0x74, 0xd8, 0xdd, 0x41, 0xe6, 0xe1, 0xa4, 0x98, 0xdb, 0x02, 0x10, 0x4f, 0x03, - 0xd6, 0x27, 0x15, 0xe1, 0x17, 0xd3, 0x62, 0xdb, 0xa3, 0x6e, 0xcd, 0x37, 0xbc, 0x55, 0x91, 0x0e, - 0x2c, 0xb5, 0x21, 0x63, 0x28, 0x66, 0xb5, 0x38, 0xc3, 0xfe, 0x88, 0x61, 0x30, 0x21, 0x26, 0x18, - 0xa6, 0x35, 0xd4, 0xbe, 0x3f, 0x03, 0x63, 0xea, 0xe7, 0x90, 0x4b, 0xd0, 0xcf, 0x7e, 0x87, 0x09, - 0x64, 0x84, 0x7b, 0x7b, 0xc4, 0x91, 0xfb, 0x4a, 0x05, 0x34, 0xe4, 0x05, 0x18, 0x40, 0xb7, 0x34, - 0x6c, 0x90, 0x8d, 0xf2, 0xf6, 0x44, 0x0d, 0x2c, 0x86, 0xe6, 0xcd, 0x22, 0x52, 0xf2, 0x12, 0x0c, - 0xce, 0x46, 0xfe, 0xbc, 0xe2, 0x02, 0x1a, 0xc3, 0x08, 0xa4, 0x96, 0x11, 0x81, 0x2e, 0x53, 0x6b, - 0x5f, 0xc8, 0x46, 0xaa, 0x7e, 0x6c, 0x9a, 0xee, 0xcb, 0x34, 0xfd, 0xbe, 0x1c, 0x0c, 0x4f, 0x3a, - 0xb6, 0x6f, 0xd4, 0xfd, 0xe3, 0xc3, 0xe0, 0xfd, 0x1c, 0xb2, 0x91, 0x12, 0xf4, 0x5e, 0x6d, 0x1a, - 0x56, 0x43, 0x18, 0x3e, 0x98, 0x46, 0x91, 0x32, 0x80, 0xce, 0xe1, 0xe4, 0x35, 0xcc, 0xa9, 0xc5, - 0x24, 0x1d, 0x3a, 0x21, 0x8e, 0x44, 0xd5, 0x38, 0x24, 0x14, 0x4f, 0x31, 0x55, 0xe7, 0x00, 0x3e, - 0x72, 0xe4, 0x96, 0x72, 0x9f, 0x1d, 0x1f, 0x8c, 0x1e, 0x91, 0x3e, 0xfb, 0x5c, 0x0e, 0x4e, 0xab, - 0x0e, 0x7b, 0xc7, 0x03, 0x4e, 0x74, 0xde, 0x5f, 0x81, 0x93, 0xaa, 0x6c, 0xa6, 0x98, 0x34, 0x7a, - 0xbb, 0xfb, 0x8e, 0x5c, 0xda, 0xda, 0x2c, 0x3d, 0x92, 0x74, 0x94, 0x64, 0x0f, 0x4b, 0xf5, 0x26, - 0x49, 0x7d, 0x48, 0x6a, 0xcf, 0xdc, 0x23, 0x11, 0xd7, 0xf7, 0x79, 0xcf, 0x7c, 0x53, 0x36, 0xd9, - 0x33, 0xc7, 0x13, 0x9e, 0x58, 0xb8, 0x7f, 0x2e, 0x0b, 0x8f, 0xab, 0xc2, 0x79, 0xfd, 0xf9, 0xa7, - 0x5f, 0xd4, 0x69, 0x90, 0x79, 0xf9, 0x78, 0x7a, 0x11, 0x4a, 0x8c, 0x69, 0x91, 0x0d, 0x2f, 0x8c, - 0xc2, 0x14, 0x69, 0x91, 0x19, 0x44, 0x17, 0x98, 0x1d, 0x88, 0xf3, 0x78, 0x4e, 0xd8, 0x85, 0x38, - 0xbf, 0x63, 0x5b, 0x71, 0x1e, 0x0f, 0xe4, 0xc0, 0x5d, 0x2d, 0x0f, 0xf0, 0x9a, 0xe5, 0x8b, 0xfc, - 0xf4, 0x47, 0xfc, 0xaa, 0x4c, 0xf2, 0x73, 0xcd, 0xef, 0xc9, 0xcf, 0x35, 0x4a, 0x3e, 0xd5, 0xbb, - 0x87, 0xe4, 0x53, 0xaf, 0x40, 0x9f, 0x90, 0xa3, 0xd8, 0xb7, 0x9f, 0x89, 0xbe, 0x02, 0xc1, 0x9d, - 0x1e, 0x1f, 0x48, 0xff, 0x09, 0xe8, 0xf3, 0x78, 0x42, 0x36, 0xb1, 0xaf, 0xc6, 0x58, 0x22, 0x01, - 0xd2, 0x83, 0x3f, 0xc8, 0x39, 0xc0, 0x02, 0x73, 0x72, 0xa8, 0x0f, 0x2f, 0x38, 0xc7, 0xfe, 0x25, - 0xb3, 0xd0, 0x27, 0xa2, 0x06, 0x8a, 0x80, 0x61, 0x23, 0xa1, 0x5a, 0x46, 0xfd, 0xcc, 0x83, 0x07, - 0xf8, 0x99, 0xb5, 0x20, 0x96, 0x03, 0xc2, 0x05, 0x48, 0xfb, 0x89, 0x0c, 0x8c, 0xa9, 0x8d, 0xc8, - 0x93, 0x50, 0xe0, 0x7f, 0x89, 0x1d, 0x3a, 0xe6, 0x85, 0xe5, 0x2d, 0xe4, 0xbc, 0xb0, 0x82, 0xfa, - 0x79, 0x18, 0xd0, 0x83, 0x50, 0x10, 0xb1, 0x43, 0x47, 0xff, 0xdd, 0x30, 0x3e, 0x24, 0x56, 0x43, - 0x27, 0x00, 0x92, 0xc7, 0x20, 0x37, 0xdf, 0x30, 0xc5, 0xc6, 0x1c, 0xe3, 0x95, 0x9c, 0x86, 0x9c, - 0xf4, 0x96, 0x61, 0x19, 0xd1, 0x4d, 0xba, 0x2e, 0x9c, 0xbd, 0x91, 0xc8, 0xa6, 0xeb, 0x32, 0xd1, - 0x4d, 0xba, 0xae, 0xfd, 0x7c, 0x26, 0x70, 0xdf, 0xbd, 0x61, 0x79, 0xfe, 0xac, 0xbd, 0x66, 0x34, - 0xac, 0xb0, 0x23, 0xc8, 0x6b, 0x30, 0x12, 0x21, 0xa5, 0xfc, 0x09, 0x89, 0x3c, 0x43, 0x18, 0x61, - 0xff, 0x88, 0x14, 0x55, 0x13, 0x6f, 0x46, 0xce, 0x4b, 0xca, 0x2f, 0x9d, 0x5a, 0xc8, 0x41, 0xf9, - 0xc2, 0x15, 0x7b, 0x68, 0xce, 0xf2, 0x3c, 0xcb, 0x5e, 0xe6, 0xb1, 0x9d, 0x39, 0x0c, 0xb3, 0xc2, - 0xf3, 0x93, 0x26, 0x87, 0xd7, 0x5c, 0x86, 0x90, 0x83, 0x83, 0xe4, 0x06, 0xda, 0x9f, 0x67, 0xe0, - 0x2c, 0xe3, 0x84, 0x19, 0x4f, 0x13, 0x1f, 0xb6, 0xaf, 0x01, 0xdc, 0xec, 0x22, 0x29, 0x31, 0xaa, - 0x1f, 0x4d, 0x26, 0xf9, 0x50, 0x08, 0x15, 0xee, 0x5d, 0x64, 0xbf, 0xb7, 0xa4, 0x73, 0x3f, 0x97, - 0xc1, 0xeb, 0xc1, 0xc5, 0x06, 0xbd, 0x75, 0x73, 0xf6, 0xf5, 0x03, 0xba, 0xc0, 0xde, 0xeb, 0xd4, - 0xf5, 0x2a, 0x8c, 0x79, 0xf8, 0x2e, 0xb5, 0xb6, 0x6d, 0xdd, 0xc5, 0x93, 0x2f, 0xf1, 0x31, 0xa7, - 0xa5, 0x8f, 0x91, 0xde, 0x55, 0x1f, 0xe1, 0xf4, 0xb7, 0x6c, 0xeb, 0x2e, 0x26, 0x7c, 0x7d, 0x3f, - 0x26, 0xdb, 0x97, 0x28, 0xc8, 0x59, 0xe8, 0x0f, 0xca, 0x15, 0x89, 0xe2, 0x3f, 0xe1, 0x6f, 0x32, - 0x06, 0xb9, 0xb6, 0x65, 0xe2, 0x6b, 0xf6, 0xea, 0xec, 0x4f, 0xed, 0x57, 0x73, 0x30, 0x5e, 0xbe, - 0x53, 0x9d, 0x9d, 0x0c, 0xa3, 0x18, 0x44, 0xd0, 0x6e, 0x73, 0x97, 0xb2, 0x08, 0xe8, 0xc9, 0x24, - 0x8c, 0xf0, 0xa4, 0x0b, 0xa2, 0x22, 0x08, 0x3f, 0x97, 0xea, 0xe5, 0xd1, 0x14, 0x71, 0x8c, 0xa4, - 0xa4, 0xc3, 0x88, 0x11, 0x85, 0x43, 0x3c, 0x52, 0x87, 0x07, 0x62, 0xa4, 0x35, 0x23, 0xcc, 0x69, - 0x17, 0xe4, 0x13, 0x79, 0xc7, 0xd6, 0x66, 0xe9, 0xb1, 0x8e, 0x44, 0x12, 0xeb, 0x33, 0x32, 0xeb, - 0x28, 0x37, 0x9e, 0x47, 0xae, 0xc3, 0x38, 0x6f, 0x8f, 0x87, 0x76, 0xa1, 0x93, 0x04, 0x63, 0x2e, - 0xe5, 0x8e, 0x90, 0x90, 0x72, 0x9e, 0x30, 0x44, 0x32, 0x81, 0x8b, 0x80, 0xeb, 0x3b, 0x70, 0x8a, - 0xd3, 0xb7, 0xa8, 0x8b, 0x23, 0xd1, 0xb1, 0x6b, 0x1e, 0xf5, 0x45, 0xdc, 0xf6, 0xc4, 0x63, 0x5b, - 0x9b, 0xa5, 0x52, 0x2a, 0x81, 0xc4, 0xf4, 0x04, 0x12, 0x54, 0x42, 0x7c, 0x95, 0xfa, 0xb2, 0x9f, - 0x46, 0x61, 0x17, 0x6a, 0xfe, 0xcd, 0x59, 0x38, 0x33, 0x43, 0x8d, 0x86, 0xbf, 0x32, 0xb9, 0x42, - 0xeb, 0xab, 0xc7, 0xae, 0xd2, 0x31, 0xab, 0x25, 0x55, 0x3a, 0xc7, 0x26, 0x72, 0x37, 0xe9, 0x1c, - 0x5b, 0xbc, 0x42, 0x3a, 0xdf, 0x9d, 0x85, 0x0b, 0x69, 0x9b, 0x03, 0xbc, 0x1d, 0x70, 0xe7, 0xd7, - 0xa8, 0xeb, 0x5a, 0x26, 0x3d, 0xc4, 0x45, 0xe5, 0xe0, 0xec, 0x61, 0xb9, 0xc3, 0xf2, 0x7b, 0xf4, - 0x88, 0xdd, 0x99, 0xb8, 0x0e, 0xb1, 0x12, 0xd8, 0xbd, 0x25, 0xae, 0x6f, 0xcc, 0xc2, 0xc9, 0xaa, - 0xb5, 0xec, 0xf9, 0x8e, 0x4b, 0x2b, 0x98, 0xfd, 0xe4, 0x58, 0x93, 0x3a, 0x8a, 0xe6, 0x10, 0x0b, - 0xee, 0xdd, 0xeb, 0xa2, 0x39, 0x1e, 0x50, 0x42, 0x34, 0xff, 0x3e, 0x0f, 0xa3, 0x73, 0x93, 0x95, - 0xa0, 0x0e, 0xb0, 0x6f, 0xb8, 0x47, 0xb4, 0x1c, 0x42, 0x74, 0xb6, 0x90, 0xdf, 0xc3, 0xd9, 0xc2, - 0xc1, 0xb9, 0x17, 0x88, 0x72, 0xfc, 0x85, 0x5d, 0x95, 0xe3, 0x7f, 0x1c, 0x46, 0x9a, 0xf5, 0x56, - 0x2d, 0xc8, 0x78, 0x65, 0x99, 0x22, 0xd7, 0xd4, 0x50, 0xb3, 0xde, 0x0a, 0x8a, 0x4c, 0x9b, 0xa4, - 0x04, 0x83, 0xf5, 0x86, 0x45, 0x6d, 0xbf, 0x66, 0xd9, 0x4b, 0x8e, 0xc8, 0x2f, 0x05, 0x1c, 0x34, - 0x6b, 0x2f, 0x39, 0x8c, 0xc0, 0xc3, 0xef, 0xe1, 0x04, 0x3c, 0x99, 0x14, 0x70, 0x10, 0x12, 0x5c, - 0x84, 0x31, 0x3c, 0xcd, 0xaf, 0x3b, 0x8d, 0xa0, 0x44, 0x9e, 0xc8, 0x25, 0x35, 0x1a, 0xc0, 0x83, - 0x6c, 0x75, 0xef, 0x84, 0x71, 0xcb, 0x5e, 0x76, 0xd9, 0x0e, 0xdd, 0x68, 0xfb, 0xbc, 0x4a, 0x8f, - 0xc8, 0x2d, 0x35, 0x2a, 0x10, 0xe5, 0xb6, 0xcf, 0xeb, 0xf4, 0x5c, 0x80, 0x31, 0xaa, 0x92, 0x62, - 0x8a, 0x12, 0x7d, 0x84, 0xc6, 0x28, 0xb5, 0xcf, 0xe7, 0x61, 0x38, 0x52, 0xb7, 0xab, 0x87, 0x74, - 0x24, 0x76, 0x7f, 0x2b, 0x5b, 0xb4, 0x63, 0xea, 0xdb, 0xc5, 0x6d, 0xea, 0xc7, 0x33, 0xd0, 0xb7, - 0x42, 0x0d, 0x93, 0xba, 0x3c, 0x1f, 0xd6, 0xe0, 0x95, 0x53, 0x97, 0xd6, 0x5d, 0xa3, 0xd5, 0xa2, - 0xae, 0xc8, 0x98, 0xc2, 0xd3, 0x1a, 0x4d, 0xbc, 0xce, 0x9a, 0xff, 0xc6, 0x66, 0xe9, 0xa5, 0x65, - 0xcb, 0x5f, 0x69, 0x2f, 0x5e, 0xaa, 0x3b, 0xcd, 0xcb, 0xcb, 0xae, 0xb1, 0x66, 0xf9, 0x78, 0x34, - 0x6c, 0x34, 0x2e, 0xfb, 0xb4, 0x81, 0x79, 0x21, 0x2f, 0x1b, 0x2d, 0xeb, 0x32, 0x96, 0xd1, 0xb9, - 0x1c, 0x72, 0x5a, 0x70, 0x0d, 0xcb, 0xf7, 0xb6, 0x36, 0x4b, 0xe3, 0xe2, 0x61, 0xf2, 0xc1, 0x99, - 0x00, 0x69, 0xbf, 0x9c, 0x81, 0xf1, 0xb9, 0xc9, 0xca, 0xb5, 0xea, 0xfc, 0x4d, 0xbd, 0x32, 0x39, - 0x47, 0x3d, 0xcf, 0x58, 0xa6, 0xe4, 0x09, 0xe8, 0x13, 0x10, 0x71, 0xca, 0x84, 0xc7, 0x7b, 0x6f, - 0x78, 0x8e, 0xed, 0xb6, 0xea, 0x7a, 0x80, 0x13, 0xe5, 0x9e, 0xb2, 0x5d, 0xca, 0x3d, 0x69, 0x50, - 0x68, 0x52, 0x7f, 0xc5, 0x09, 0x0e, 0xc8, 0xf0, 0x0c, 0x9c, 0x43, 0x74, 0xf1, 0x3f, 0x99, 0x87, - 0x42, 0xcb, 0x70, 0x8d, 0xa6, 0xb7, 0xdd, 0x6d, 0xd9, 0xc3, 0x58, 0x97, 0x12, 0x49, 0x53, 0x6f, - 0xc7, 0x04, 0x1b, 0xed, 0xeb, 0xf3, 0xf8, 0x4d, 0x61, 0x4a, 0x8d, 0x8f, 0xb5, 0x29, 0x4f, 0xd6, - 0xbb, 0xe7, 0x93, 0x86, 0x17, 0x20, 0xdf, 0xde, 0xe5, 0xe8, 0x68, 0x8b, 0xd1, 0xe1, 0xed, 0x69, - 0x74, 0x88, 0x56, 0x81, 0x56, 0xe6, 0x77, 0xab, 0x95, 0xde, 0x2e, 0xee, 0xf8, 0x39, 0x2d, 0xb9, - 0x09, 0x7d, 0x4d, 0xde, 0xfd, 0x62, 0x10, 0x84, 0x59, 0x28, 0x13, 0xfa, 0x31, 0xf1, 0x80, 0xa8, - 0x17, 0x3d, 0x2e, 0x5a, 0xc8, 0x9a, 0x25, 0x40, 0x31, 0x2d, 0xef, 0x3b, 0x64, 0x2d, 0xff, 0xeb, - 0x79, 0x38, 0x1d, 0x69, 0xc4, 0x4d, 0xc7, 0xb7, 0x96, 0x2c, 0x7e, 0xbd, 0x72, 0x1f, 0xa9, 0x85, - 0xd4, 0xc1, 0xbd, 0x07, 0xd1, 0xc1, 0x91, 0x9a, 0x15, 0x76, 0xa1, 0x66, 0xf7, 0x92, 0x5a, 0xfc, - 0x54, 0x0e, 0x1e, 0x88, 0xd4, 0xe2, 0x06, 0xd6, 0xe5, 0xab, 0x56, 0xaf, 0x56, 0x7d, 0x97, 0x1a, - 0xcd, 0x23, 0xab, 0x19, 0xc6, 0xae, 0x34, 0xc3, 0xd8, 0xf3, 0x84, 0x21, 0xf7, 0x64, 0xe1, 0x90, - 0x7b, 0xf2, 0x5b, 0xf2, 0x70, 0x2e, 0xea, 0x49, 0x71, 0x7e, 0x3f, 0xb3, 0xb0, 0x50, 0x39, 0xea, - 0xb3, 0xff, 0xee, 0x3b, 0x33, 0xb8, 0x73, 0xeb, 0x4d, 0xbd, 0x73, 0x8b, 0x16, 0xe4, 0x42, 0xc7, - 0x05, 0xf9, 0x9d, 0x30, 0xe0, 0x1a, 0xeb, 0x3c, 0x44, 0x40, 0xce, 0x01, 0x18, 0x02, 0xf5, 0x7e, - 0xd7, 0x58, 0xe7, 0x01, 0x08, 0xe7, 0x20, 0xbf, 0xe8, 0x98, 0x1b, 0x68, 0xc7, 0x0c, 0xf1, 0xa7, - 0xb1, 0xdf, 0x3a, 0xfe, 0x1b, 0x53, 0x91, 0x81, 0x43, 0x56, 0x91, 0xef, 0xcc, 0xc3, 0xc9, 0x09, - 0xa7, 0x6d, 0x9b, 0xd7, 0xe9, 0x46, 0xcb, 0xb0, 0x5c, 0x9d, 0xd6, 0x9d, 0x35, 0xf6, 0x09, 0x7f, - 0xf9, 0x2e, 0xb1, 0x07, 0xb7, 0x6f, 0x7d, 0x12, 0x06, 0x16, 0x9c, 0x55, 0x6a, 0x4b, 0x69, 0xb6, - 0xb1, 0x46, 0xba, 0xcf, 0x80, 0x3c, 0x23, 0x55, 0x44, 0x40, 0x9e, 0x86, 0xbe, 0x09, 0x87, 0x5f, - 0x1e, 0x4a, 0xb5, 0x5e, 0x17, 0x1d, 0x71, 0x69, 0x28, 0x09, 0x4d, 0x90, 0x91, 0x17, 0x60, 0xa0, - 0xd2, 0x5e, 0x6c, 0x58, 0xf5, 0xeb, 0x34, 0x88, 0x54, 0x41, 0xb7, 0xe5, 0x16, 0x02, 0x6b, 0xab, - 0x74, 0x23, 0x96, 0xd5, 0x28, 0x20, 0xe5, 0xc5, 0xbc, 0xb9, 0x7c, 0x27, 0x9d, 0xb6, 0xed, 0xa3, - 0x1a, 0x0d, 0x07, 0xc5, 0xbc, 0x39, 0xa2, 0x86, 0xd7, 0x24, 0x7a, 0x9c, 0x90, 0x3c, 0x0f, 0x43, - 0x01, 0x60, 0xce, 0x31, 0xa9, 0x9c, 0x54, 0x32, 0x6c, 0xd8, 0x74, 0x4c, 0xaa, 0xc7, 0xc8, 0xc8, - 0x1c, 0x8c, 0x4d, 0x38, 0x3e, 0xa6, 0xf6, 0x9b, 0x5f, 0x9a, 0x77, 0xad, 0x65, 0xcb, 0x16, 0xb7, - 0xce, 0x8f, 0x6e, 0x6d, 0x96, 0x1e, 0x62, 0xdf, 0x88, 0x89, 0x01, 0x6b, 0xce, 0x52, 0xcd, 0x41, - 0xb4, 0x9c, 0xdc, 0x51, 0x6d, 0xca, 0x0c, 0x86, 0xb8, 0xb2, 0x38, 0x7e, 0x68, 0x2e, 0x1c, 0x2b, - 0x4b, 0x07, 0x65, 0x99, 0x87, 0xf1, 0x8a, 0x4b, 0xd7, 0x2c, 0xa7, 0xed, 0xa9, 0x4a, 0x83, 0x9d, - 0xd0, 0x12, 0xc8, 0x5a, 0xaa, 0xf6, 0x24, 0xdb, 0x92, 0x57, 0x61, 0xe8, 0x26, 0x5d, 0x8f, 0x78, - 0xf5, 0x45, 0xc9, 0xd2, 0x6c, 0xba, 0x9e, 0xce, 0x26, 0xd6, 0x22, 0x55, 0x2d, 0xfa, 0xf7, 0xa1, - 0x16, 0x39, 0x78, 0x87, 0xac, 0x16, 0xd7, 0x1c, 0x8b, 0x97, 0x22, 0xbf, 0x4d, 0xdd, 0xd0, 0xa4, - 0x9c, 0x36, 0xac, 0xc6, 0x3e, 0x53, 0xd5, 0xbd, 0xdd, 0x35, 0x25, 0xad, 0x5b, 0x0a, 0x7b, 0xef, - 0x96, 0xdf, 0xcd, 0xc2, 0x60, 0x75, 0x72, 0x76, 0x2e, 0x58, 0xec, 0xb7, 0x2f, 0x43, 0x3c, 0x01, - 0xc3, 0x3c, 0x89, 0x5e, 0xd9, 0x34, 0x5d, 0xea, 0x79, 0x62, 0x13, 0x8b, 0xaa, 0x25, 0x12, 0xe5, - 0x19, 0x1c, 0x23, 0xdf, 0x1c, 0xc7, 0x9a, 0xb0, 0xb9, 0x91, 0x99, 0x00, 0x18, 0xc8, 0x2f, 0xb6, - 0xb7, 0x51, 0x48, 0x87, 0xc1, 0xa0, 0xf2, 0xdc, 0x18, 0x92, 0x92, 0x27, 0xa1, 0x30, 0xc7, 0x97, - 0xe0, 0x7c, 0xe4, 0x96, 0xc2, 0x97, 0x5e, 0xd9, 0x2d, 0x85, 0xd3, 0x90, 0xf3, 0x90, 0xaf, 0x44, - 0xcb, 0x39, 0x4e, 0xa0, 0x6c, 0x21, 0x97, 0xdd, 0x35, 0x2a, 0xdc, 0x99, 0x26, 0x3f, 0xc1, 0x16, - 0xe2, 0xc2, 0x0e, 0xf2, 0xc8, 0xb1, 0xb5, 0x39, 0x3d, 0x8f, 0x1c, 0x63, 0x41, 0x4e, 0x06, 0x85, - 0x37, 0xf8, 0xc9, 0x99, 0x28, 0xe6, 0xb1, 0x01, 0x43, 0x5c, 0xc6, 0x5e, 0xcb, 0xb1, 0x3d, 0x4a, - 0x1e, 0x06, 0xe0, 0x7a, 0x87, 0x65, 0xfe, 0xb1, 0x88, 0xb2, 0x2e, 0x41, 0xc2, 0x17, 0xca, 0xee, - 0xfb, 0x85, 0xb4, 0xbf, 0x93, 0x85, 0x11, 0xf6, 0xec, 0x49, 0xa7, 0xd9, 0x74, 0xec, 0x29, 0x36, - 0x42, 0xa6, 0xa0, 0x4f, 0xf4, 0xb6, 0x50, 0xf4, 0xd0, 0x3c, 0x92, 0x14, 0x81, 0xbb, 0x0d, 0xb9, - 0xfc, 0x87, 0xac, 0x87, 0x81, 0xa2, 0x28, 0xd1, 0x35, 0xf9, 0x28, 0xba, 0x46, 0x8a, 0xa9, 0x91, - 0xcb, 0x77, 0x4b, 0xd4, 0xe4, 0x15, 0xb6, 0x52, 0x25, 0xd2, 0x2e, 0xa2, 0x83, 0x4c, 0x2c, 0xdb, - 0xa2, 0x3c, 0x39, 0xc9, 0x0d, 0xc8, 0x0c, 0x1e, 0x3a, 0xa3, 0x34, 0x45, 0xb7, 0x9d, 0x8c, 0x7f, - 0x04, 0xc7, 0xf1, 0xe1, 0xe4, 0x8a, 0x5f, 0x72, 0xb9, 0xe9, 0x80, 0x42, 0xfb, 0xa6, 0x1c, 0x8c, - 0xb1, 0x26, 0x6c, 0x0b, 0x63, 0xd9, 0xcb, 0x98, 0x22, 0xff, 0x10, 0x26, 0xa0, 0x97, 0xa1, 0xc0, - 0xbb, 0x28, 0xe1, 0x5a, 0x12, 0xeb, 0x3c, 0xb5, 0x35, 0xc7, 0xb0, 0x71, 0xc1, 0xb3, 0xd1, 0xcb, - 0xe3, 0x82, 0xa7, 0xad, 0x97, 0xc7, 0x05, 0xa7, 0x21, 0x17, 0xa1, 0x97, 0x5b, 0x16, 0xbd, 0x51, - 0xd6, 0x71, 0x34, 0x28, 0xe4, 0xea, 0x30, 0xdc, 0xa4, 0x78, 0x11, 0x35, 0xd5, 0xf5, 0x67, 0x6d, - 0x93, 0xde, 0x15, 0xc9, 0xd2, 0xb1, 0x93, 0x3d, 0x06, 0xad, 0x59, 0x0c, 0x2c, 0x57, 0x9f, 0x8e, - 0x88, 0xd9, 0x3c, 0x11, 0x74, 0x99, 0x6c, 0xc7, 0xe0, 0x3c, 0x11, 0x76, 0xb2, 0xfa, 0xd8, 0x78, - 0x13, 0x6d, 0x2b, 0x0b, 0xe3, 0xa2, 0x3f, 0x11, 0x7a, 0x34, 0x7b, 0xe7, 0x45, 0x80, 0x05, 0x61, - 0x82, 0x87, 0x19, 0xc9, 0x51, 0x88, 0x81, 0x61, 0x5e, 0x8b, 0x4d, 0xb2, 0x12, 0x31, 0x56, 0xff, - 0x16, 0xa5, 0x2c, 0x66, 0xa7, 0xe4, 0x1a, 0xf9, 0x54, 0x40, 0x95, 0xa6, 0x11, 0x31, 0xb9, 0x0c, - 0x7d, 0x53, 0x96, 0xd7, 0x6a, 0x18, 0xb1, 0xe0, 0x75, 0x93, 0x83, 0xe4, 0x11, 0x2d, 0xa8, 0xb4, - 0x6f, 0xcf, 0xc1, 0xd9, 0x49, 0x7e, 0x8c, 0x5f, 0xd1, 0xa9, 0xe7, 0xbb, 0x16, 0xf7, 0x10, 0x3c, - 0xbe, 0xc0, 0x4b, 0x94, 0x39, 0xdc, 0x4d, 0x00, 0xd0, 0x4d, 0x38, 0x99, 0x26, 0x55, 0x91, 0x48, - 0x1e, 0x53, 0xcd, 0x06, 0xf7, 0x29, 0xad, 0x9a, 0x2b, 0x51, 0xe8, 0xa9, 0xed, 0xb4, 0xcf, 0x65, - 0xe1, 0x44, 0xb9, 0xd5, 0x2a, 0xb7, 0xfd, 0x15, 0xd5, 0x61, 0x68, 0xcf, 0xdb, 0x74, 0x39, 0x56, - 0x3b, 0xbb, 0xc7, 0x30, 0xfd, 0x60, 0xab, 0x9f, 0xdb, 0xe5, 0x56, 0x7f, 0x06, 0xa0, 0xbe, 0x0f, - 0xa7, 0x8f, 0xa8, 0x6d, 0x52, 0x32, 0x91, 0xe6, 0x1e, 0x4b, 0x26, 0x2e, 0x99, 0xe8, 0xe6, 0xf9, - 0x3e, 0x97, 0xcc, 0x1f, 0xa8, 0x92, 0xc1, 0xed, 0xc8, 0xc6, 0xbe, 0x24, 0xf3, 0x12, 0x8c, 0x1a, - 0xad, 0x16, 0xbf, 0x85, 0xac, 0x23, 0x53, 0xb9, 0x0e, 0x9b, 0x82, 0xd2, 0x87, 0x0d, 0xf9, 0xf1, - 0x7b, 0x16, 0x89, 0x74, 0x62, 0x96, 0xdf, 0xcf, 0x89, 0x59, 0xef, 0x1e, 0x8f, 0x3f, 0x77, 0x71, - 0x90, 0xad, 0x7d, 0x55, 0x16, 0xc6, 0x6e, 0xdb, 0xd4, 0x3f, 0xb0, 0xf9, 0x2b, 0x7a, 0x8d, 0xec, - 0x2e, 0x4e, 0x61, 0x0f, 0x5f, 0xfb, 0xe2, 0x82, 0x38, 0x80, 0xe9, 0xea, 0xed, 0x21, 0x88, 0x03, - 0x98, 0x9d, 0x8e, 0xaa, 0x20, 0xfe, 0x6e, 0x2e, 0xaa, 0xdc, 0x21, 0xca, 0x3d, 0x1c, 0x3b, 0x04, - 0xef, 0x2b, 0x00, 0x5b, 0x87, 0xbe, 0x8a, 0xb1, 0xc1, 0xe4, 0xb9, 0xdd, 0x06, 0xbf, 0xc4, 0x0c, - 0xe4, 0x16, 0xa7, 0x4d, 0xdd, 0x52, 0x07, 0x8c, 0xd2, 0x7a, 0xe9, 0x38, 0x27, 0xeb, 0x7e, 0x72, - 0xb2, 0xbe, 0x25, 0xbd, 0xf4, 0x8b, 0xd9, 0x44, 0x2f, 0x1d, 0x3b, 0x48, 0xef, 0x2b, 0xcb, 0xeb, - 0xb7, 0xe5, 0xe0, 0xe4, 0xac, 0x2d, 0xe2, 0xb7, 0xe6, 0x1c, 0xf3, 0x40, 0x32, 0xcd, 0x1d, 0x4f, - 0x4e, 0x07, 0xab, 0xf6, 0xc9, 0x4e, 0x3a, 0x0e, 0x9a, 0xb8, 0xe7, 0x3a, 0xe9, 0x17, 0xb2, 0x6a, - 0x27, 0x1d, 0x4f, 0x4d, 0xfb, 0x9a, 0x9a, 0xd8, 0x64, 0x1f, 0x0a, 0xb4, 0x4a, 0xeb, 0x2e, 0x3d, - 0xce, 0x36, 0x74, 0xe0, 0x12, 0x3d, 0x9e, 0x48, 0x0e, 0x5a, 0xa2, 0xc7, 0xa3, 0x7e, 0x5f, 0x12, - 0x65, 0x86, 0x78, 0x28, 0xd1, 0x03, 0x0b, 0xc5, 0x39, 0x5e, 0xec, 0x0e, 0x7e, 0xbb, 0xa4, 0xf4, - 0xd2, 0xf1, 0x4c, 0x72, 0xcf, 0xf5, 0x52, 0x6c, 0x76, 0x3a, 0xb0, 0x00, 0xa5, 0xfb, 0x7d, 0xbb, - 0xa4, 0x53, 0xdf, 0xb5, 0xe8, 0x9a, 0xd1, 0x38, 0xde, 0x2e, 0xdd, 0xbb, 0xdb, 0xa5, 0x78, 0x27, - 0x1d, 0xcf, 0x4d, 0xf7, 0xe4, 0x76, 0x29, 0xde, 0x49, 0xc7, 0x53, 0xd3, 0xbe, 0xa6, 0xa6, 0xff, - 0x90, 0x85, 0xb3, 0x93, 0xd4, 0xf5, 0xcb, 0x6d, 0x7f, 0xc5, 0x71, 0x2d, 0x7f, 0x23, 0x08, 0xf2, - 0x0e, 0x5d, 0x2c, 0xfe, 0xd2, 0x7d, 0xbe, 0xe5, 0x0b, 0xc5, 0xdc, 0x1e, 0x2f, 0x14, 0xa3, 0x63, - 0xff, 0xfc, 0x2e, 0x8e, 0xfd, 0x0d, 0x18, 0xac, 0x1b, 0x35, 0x47, 0x48, 0x40, 0xc8, 0xf1, 0x89, - 0xb0, 0x37, 0xd2, 0xc4, 0x14, 0xbe, 0x09, 0x7a, 0x63, 0x48, 0xad, 0x65, 0x6f, 0x8c, 0xba, 0x11, - 0x90, 0x6b, 0x9f, 0xcc, 0xc2, 0x43, 0x5d, 0x19, 0x91, 0x4b, 0xd0, 0x57, 0x37, 0x78, 0xd4, 0x62, - 0x46, 0x2a, 0x36, 0x60, 0xa8, 0x8e, 0x50, 0x85, 0xba, 0x81, 0x2e, 0x50, 0xef, 0x83, 0xa1, 0x3a, - 0x3f, 0xd4, 0x44, 0x5f, 0x41, 0x71, 0xc5, 0x28, 0xfc, 0x09, 0x22, 0xb8, 0xec, 0x82, 0x25, 0xe0, - 0xe8, 0x47, 0xf8, 0x57, 0xe1, 0x54, 0x9d, 0xba, 0x22, 0x9a, 0x87, 0x86, 0xaf, 0xcf, 0x93, 0x15, - 0x0d, 0x5e, 0x79, 0x4c, 0xfe, 0x7a, 0x41, 0x94, 0xf8, 0x76, 0xcc, 0x97, 0x92, 0xca, 0x45, 0x7a, - 0xea, 0xc9, 0x7a, 0x92, 0x83, 0xa7, 0xfd, 0xf1, 0xff, 0xcf, 0xde, 0xd5, 0xec, 0xb6, 0x6d, 0x3c, - 0xf1, 0x50, 0xb2, 0x14, 0x65, 0x64, 0xc7, 0xeb, 0x75, 0xfc, 0x8f, 0x9d, 0xc4, 0x4a, 0xa2, 0x7f, - 0xd2, 0x0f, 0xa1, 0x40, 0x8a, 0x14, 0x2d, 0x50, 0xa0, 0x87, 0xae, 0xc8, 0xa5, 0xb5, 0x35, 0x3f, - 0x54, 0x2e, 0x65, 0x47, 0xbd, 0x10, 0xb4, 0xcc, 0x44, 0x6c, 0x15, 0x49, 0x20, 0x95, 0xa4, 0x3d, - 0x16, 0x28, 0xd0, 0x57, 0x28, 0x7a, 0xe8, 0xa5, 0xef, 0xd0, 0x73, 0x8f, 0x39, 0xf6, 0x09, 0x8a, - 0x36, 0xc7, 0xbe, 0x41, 0x2f, 0x45, 0xb1, 0x4b, 0x4a, 0x22, 0x05, 0xcb, 0x80, 0xdb, 0x4b, 0x0a, - 0xfb, 0xb6, 0xfb, 0x9b, 0xdf, 0x2c, 0x67, 0x77, 0x87, 0x14, 0x29, 0xce, 0x0c, 0x15, 0xb8, 0x79, - 0xca, 0xd0, 0xb8, 0x0b, 0xd5, 0x8c, 0x5e, 0xea, 0x83, 0xb7, 0xa6, 0x46, 0x3d, 0x7a, 0xff, 0xdd, - 0x0f, 0x97, 0xec, 0xc4, 0x5c, 0x29, 0x37, 0xf3, 0x39, 0x8c, 0xf7, 0xa0, 0xd4, 0xeb, 0xfb, 0xe1, - 0x70, 0xbb, 0x20, 0x67, 0x7a, 0xfa, 0xa0, 0x49, 0x70, 0x94, 0xa0, 0x67, 0x83, 0xa3, 0x24, 0x80, - 0x1f, 0x42, 0xe5, 0x38, 0x8c, 0xfd, 0xa3, 0x41, 0x90, 0xe4, 0xe8, 0x55, 0x92, 0x70, 0xb3, 0x29, - 0x96, 0x0d, 0x37, 0x9b, 0x62, 0xf5, 0xbf, 0x14, 0xb8, 0x76, 0xd2, 0x81, 0xf0, 0x3b, 0x50, 0x96, - 0x65, 0xac, 0xa3, 0x6c, 0xc5, 0xad, 0x04, 0xc9, 0x6e, 0x7e, 0x82, 0xe0, 0x07, 0x70, 0x39, 0x7e, - 0x76, 0xf4, 0x79, 0xd0, 0x9b, 0xa4, 0xfb, 0x2e, 0x9d, 0x25, 0x85, 0xb2, 0xc1, 0x3d, 0x29, 0x84, - 0x3f, 0x86, 0xb5, 0x38, 0x88, 0x42, 0x7f, 0xe0, 0x0d, 0x9f, 0x3d, 0x3d, 0x4a, 0x2f, 0x58, 0x69, - 0xc8, 0x5d, 0x4e, 0x90, 0x0d, 0xb9, 0x4b, 0x04, 0x96, 0xc4, 0x31, 0x85, 0xf5, 0x79, 0xd4, 0xb0, - 0xd7, 0xf7, 0xe3, 0x7e, 0x1a, 0xca, 0x24, 0x3f, 0x94, 0xb0, 0x20, 0xca, 0x86, 0x74, 0x8d, 0xa7, - 0x21, 0xc5, 0x2d, 0x3f, 0xee, 0xd7, 0xbf, 0x2d, 0xc0, 0x46, 0x33, 0xf0, 0x9f, 0xc6, 0xe7, 0xfe, - 0xe5, 0xef, 0xc2, 0x4a, 0x9c, 0xe3, 0xb7, 0xbf, 0x0b, 0x2b, 0x71, 0x8e, 0x5f, 0xff, 0xfe, 0x50, - 0x04, 0xa4, 0x0e, 0xfc, 0x38, 0x0e, 0x1f, 0x87, 0x17, 0x6f, 0x7e, 0x5f, 0xc3, 0x1b, 0xd1, 0xfc, - 0x06, 0x5d, 0x3c, 0x29, 0xbc, 0x76, 0x1b, 0x24, 0xbf, 0xbf, 0x33, 0xdb, 0xa0, 0x8b, 0xa7, 0x84, - 0x7f, 0xf5, 0x94, 0xf0, 0x73, 0x01, 0x56, 0x65, 0xe6, 0x85, 0x3e, 0x8a, 0x5e, 0xf8, 0xd1, 0x7f, - 0xbd, 0x74, 0xec, 0x7c, 0x11, 0x56, 0xce, 0x16, 0xfc, 0xfd, 0xcf, 0xeb, 0xc5, 0xd6, 0xbf, 0x29, - 0xc1, 0x0d, 0x32, 0x9e, 0xd6, 0x4f, 0x71, 0xfd, 0xe8, 0x49, 0x30, 0xd1, 0x42, 0x7f, 0xa0, 0x05, - 0xc3, 0x30, 0x38, 0x97, 0x85, 0x47, 0xd2, 0x0c, 0xfc, 0xd2, 0x99, 0x32, 0xf0, 0x1f, 0x00, 0x24, - 0x6b, 0xd7, 0x1a, 0xc5, 0x93, 0x34, 0xa0, 0x7d, 0xfd, 0x8f, 0x5f, 0x6f, 0x57, 0x27, 0x12, 0xf5, - 0xfa, 0xa3, 0x78, 0xe2, 0x64, 0x28, 0x73, 0x85, 0xf6, 0x28, 0x9a, 0xa4, 0xe9, 0x6f, 0x59, 0x85, - 0xf1, 0x28, 0x9a, 0x29, 0x08, 0x0a, 0xfe, 0x08, 0xaa, 0xe2, 0x7c, 0x1d, 0x3c, 0x0f, 0x8e, 0x59, - 0x3b, 0xde, 0xae, 0xcc, 0xc3, 0xb3, 0xa3, 0x14, 0xf6, 0xc2, 0x5c, 0x75, 0xc9, 0x2c, 0x1d, 0xd7, - 0xa1, 0x9c, 0xfc, 0x65, 0x9a, 0xa6, 0x4e, 0xca, 0xe4, 0xe0, 0xb1, 0x44, 0x9c, 0x54, 0x82, 0x3f, - 0x80, 0x2b, 0xcd, 0xc1, 0xa8, 0xf7, 0x85, 0xd0, 0x48, 0x0a, 0xde, 0x24, 0x59, 0x4f, 0x47, 0x09, - 0xe8, 0x85, 0xe3, 0x6c, 0xd6, 0xd3, 0x8c, 0x8a, 0x9b, 0xb0, 0x96, 0x76, 0xda, 0x51, 0xf0, 0x38, - 0xfc, 0x32, 0x29, 0x80, 0x93, 0x64, 0x52, 0x4c, 0x75, 0xc7, 0x52, 0x92, 0xbd, 0xed, 0xce, 0xa9, - 0x34, 0x7a, 0xc9, 0xf7, 0x86, 0xf6, 0xc3, 0xe1, 0x31, 0xde, 0x81, 0xad, 0x0e, 0xa7, 0x8e, 0xb7, - 0xcf, 0x2c, 0xcd, 0xeb, 0x58, 0xbc, 0x4d, 0x55, 0xa6, 0x33, 0xaa, 0xa1, 0x4b, 0x78, 0x13, 0xd6, - 0xe7, 0xa2, 0x56, 0xc7, 0x24, 0x16, 0x52, 0xf0, 0x06, 0xac, 0xcd, 0xc1, 0xa6, 0xed, 0xa2, 0x02, - 0xbe, 0x06, 0x68, 0x0e, 0xf1, 0x2e, 0x77, 0xa9, 0x89, 0x8a, 0x8d, 0xef, 0x15, 0x00, 0x71, 0x94, - 0x24, 0xb7, 0x0c, 0xdf, 0x84, 0xeb, 0x92, 0x64, 0x3b, 0x6c, 0x8f, 0x59, 0x0b, 0x47, 0xda, 0x82, - 0x8d, 0xac, 0xd0, 0xb0, 0x55, 0x62, 0x20, 0x65, 0x66, 0x40, 0x0a, 0x73, 0x6e, 0x67, 0x8e, 0x96, - 0x82, 0xf6, 0xbe, 0x4b, 0x50, 0x71, 0x11, 0xe5, 0x2a, 0x33, 0xd1, 0x0a, 0xbe, 0x0e, 0x9b, 0x59, - 0x94, 0x5a, 0xae, 0x43, 0x98, 0x86, 0x4a, 0x8d, 0x37, 0xa1, 0x2a, 0xff, 0xdb, 0x48, 0x6b, 0x16, - 0xaf, 0x42, 0xc5, 0x6e, 0x72, 0xea, 0x1c, 0x48, 0x6b, 0x00, 0xca, 0x1a, 0xb5, 0x84, 0x65, 0x4a, - 0x23, 0x82, 0xaa, 0xa9, 0x13, 0x7d, 0x30, 0x7a, 0x21, 0x9f, 0xb3, 0x77, 0x61, 0xc7, 0xd4, 0x89, - 0xa7, 0x1b, 0xf6, 0xa1, 0xe7, 0x76, 0xdb, 0x74, 0x61, 0x1e, 0x6f, 0xc3, 0xfd, 0xbc, 0xb8, 0x4d, - 0x1d, 0x8f, 0x53, 0xce, 0x99, 0x6d, 0x79, 0x2a, 0x75, 0x5c, 0xa6, 0x33, 0x95, 0xb8, 0x14, 0x29, - 0x62, 0xdd, 0xf3, 0x54, 0x66, 0x79, 0x4d, 0x62, 0x69, 0xa8, 0xd0, 0xf8, 0x53, 0x01, 0xe0, 0xba, - 0xdb, 0x4e, 0x8d, 0xab, 0xc2, 0x65, 0x66, 0x1d, 0x10, 0x83, 0x89, 0x23, 0x54, 0x60, 0xc5, 0x6e, - 0x53, 0xb1, 0x11, 0x57, 0xa0, 0xa4, 0x1a, 0x36, 0xa7, 0xa8, 0x20, 0x40, 0x87, 0x12, 0x0d, 0x15, - 0x05, 0x78, 0xe8, 0x30, 0x97, 0xa2, 0x15, 0xd1, 0x34, 0xb8, 0x4b, 0x5c, 0x54, 0x12, 0x4d, 0x5d, - 0x36, 0xcb, 0x62, 0x30, 0x4e, 0x5d, 0xd9, 0xb9, 0x2c, 0xa6, 0xad, 0x4f, 0x7b, 0x15, 0x21, 0x12, - 0x43, 0x6b, 0xcc, 0x41, 0x57, 0x44, 0x47, 0x0c, 0x29, 0x3a, 0x20, 0x16, 0xc4, 0xa1, 0xa6, 0x7d, - 0x40, 0x51, 0x55, 0x8c, 0x65, 0xee, 0x0b, 0x78, 0x55, 0x34, 0x1d, 0x53, 0x34, 0xd7, 0xc4, 0x48, - 0x0e, 0x25, 0x46, 0x9b, 0xb8, 0x2d, 0x74, 0x55, 0xd8, 0x23, 0xc7, 0x5c, 0x4f, 0x34, 0x2d, 0x62, - 0x52, 0x84, 0x52, 0x8e, 0x66, 0x30, 0x6b, 0x1f, 0x6d, 0x48, 0x43, 0xba, 0xa6, 0xec, 0x60, 0xa1, - 0x20, 0x5b, 0x9b, 0x8d, 0xaf, 0x15, 0x28, 0xdb, 0x5c, 0xae, 0xf5, 0x75, 0xd8, 0xb4, 0xf9, 0x49, - 0xab, 0xbc, 0x01, 0x6b, 0x53, 0x81, 0xc1, 0xac, 0xce, 0xa3, 0xc4, 0x2b, 0xa7, 0x90, 0x49, 0x54, - 0x9b, 0xa3, 0x82, 0x70, 0x9e, 0x29, 0x74, 0xc8, 0x2c, 0xcd, 0x3e, 0xe4, 0xa8, 0x88, 0xd7, 0xa1, - 0x3a, 0x05, 0x99, 0xcd, 0xd1, 0x0a, 0xc6, 0x70, 0x75, 0x06, 0xb4, 0x89, 0x66, 0x73, 0x54, 0x6a, - 0x3c, 0x87, 0x55, 0x2d, 0x78, 0x1e, 0xf6, 0x82, 0xd4, 0x75, 0x77, 0x61, 0x47, 0xa3, 0x07, 0x4c, - 0xa5, 0x4b, 0x9d, 0x37, 0x2f, 0x26, 0x6d, 0x86, 0x14, 0xfc, 0x3f, 0xc0, 0x79, 0xf8, 0x13, 0x62, - 0xea, 0xa8, 0x80, 0xb7, 0xe1, 0x5a, 0x1e, 0x67, 0x96, 0xdb, 0xb1, 0x28, 0x2a, 0x36, 0x7e, 0x54, - 0x60, 0x8b, 0x0e, 0xfc, 0x78, 0x12, 0xf6, 0xe2, 0xc0, 0x8f, 0x7a, 0x7d, 0xd5, 0x9f, 0x04, 0x4f, - 0x46, 0xd1, 0x57, 0xb8, 0x0e, 0x35, 0x6a, 0x10, 0xee, 0x32, 0x95, 0x53, 0xe2, 0xa8, 0x2d, 0x4f, - 0x38, 0xd1, 0x9e, 0xed, 0x74, 0xbd, 0x3d, 0x6a, 0x51, 0x87, 0x18, 0xe8, 0x12, 0xfe, 0x3f, 0xdc, - 0x5e, 0xc2, 0xe1, 0x54, 0xed, 0x38, 0xcc, 0xed, 0x22, 0x05, 0xdf, 0x85, 0xdd, 0xa5, 0x24, 0xd1, - 0x47, 0x05, 0x5c, 0x83, 0x1b, 0xcb, 0x28, 0x9f, 0x1a, 0xa8, 0xd8, 0xf8, 0x4e, 0x01, 0x6c, 0x8f, - 0x83, 0x21, 0xcf, 0x9b, 0x78, 0x07, 0x6e, 0x09, 0xef, 0xf1, 0x96, 0x1b, 0x78, 0x17, 0x76, 0x4f, - 0x64, 0x64, 0xcc, 0xbb, 0x0d, 0x37, 0x97, 0x50, 0x52, 0xe3, 0x6e, 0xc1, 0xf6, 0xc9, 0x04, 0x69, - 0xda, 0x4f, 0x0a, 0x6c, 0x91, 0xe3, 0xa7, 0xe1, 0x30, 0x39, 0x73, 0x62, 0x53, 0x27, 0xe9, 0xcf, - 0xf6, 0x5b, 0x70, 0x8f, 0x68, 0xa6, 0xd8, 0x1b, 0xd5, 0x65, 0xb6, 0xc5, 0x3d, 0x71, 0xee, 0x09, - 0x1f, 0xed, 0xf0, 0x85, 0xdd, 0x7c, 0x03, 0xea, 0xa7, 0x30, 0xd5, 0x16, 0xb1, 0xf6, 0xc4, 0x85, - 0x01, 0xdf, 0x83, 0x3b, 0x4b, 0x79, 0xd4, 0x22, 0x4d, 0x83, 0x6a, 0xa8, 0x80, 0xef, 0xc3, 0xdd, - 0xa5, 0x2c, 0x8d, 0xf1, 0x84, 0x56, 0x6c, 0xf8, 0xb9, 0xcf, 0xfe, 0x88, 0x59, 0xaa, 0xb6, 0xe5, - 0x12, 0xd5, 0x3d, 0xc9, 0xfd, 0x77, 0x60, 0x2b, 0x27, 0x6d, 0x76, 0x38, 0xb3, 0x28, 0xe7, 0xc9, - 0x45, 0x25, 0x27, 0x9a, 0x2d, 0x6d, 0xa1, 0xa9, 0xbd, 0xfc, 0xbd, 0x76, 0xe9, 0xe5, 0xab, 0x9a, - 0xf2, 0xcb, 0xab, 0x9a, 0xf2, 0xdb, 0xab, 0x9a, 0xf2, 0xd9, 0xc3, 0xb3, 0xd4, 0x88, 0x48, 0x7e, - 0x98, 0x8f, 0xca, 0xf2, 0x5e, 0xfc, 0xbd, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x50, 0x91, 0xe1, - 0x27, 0x50, 0x41, 0x02, 0x00, + // 25450 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xfd, 0x79, 0x7c, 0x24, 0xc9, + 0x75, 0x18, 0x08, 0xa3, 0x0e, 0x14, 0x80, 0x87, 0x3b, 0xfa, 0xaa, 0xe9, 0xe9, 0x99, 0x9a, 0xa9, + 0x99, 0x69, 0x76, 0x93, 0x33, 0xdd, 0x33, 0x3d, 0x07, 0x39, 0x9c, 0x21, 0x67, 0x0a, 0xd7, 0xa0, + 0xba, 0x81, 0x46, 0x31, 0x0b, 0xdd, 0x3d, 0x24, 0x45, 0x96, 0x12, 0x95, 0x01, 0x20, 0x07, 0x55, + 0x99, 0xc5, 0xcc, 0x2c, 0xa0, 0x31, 0xfe, 0xbe, 0xb5, 0x6e, 0x89, 0x12, 0x49, 0x71, 0x49, 0xcb, + 0x94, 0x25, 0xdb, 0x2b, 0xc9, 0xf2, 0x5a, 0x96, 0x64, 0xc9, 0xf2, 0x7a, 0xad, 0x5b, 0x96, 0x44, + 0x7b, 0x45, 0xdd, 0x87, 0x2d, 0xad, 0x56, 0x92, 0x41, 0xad, 0x6c, 0xad, 0x77, 0xb1, 0xb6, 0x57, + 0x96, 0x64, 0x4b, 0x96, 0x2d, 0xed, 0xfe, 0xe2, 0x45, 0x64, 0x66, 0x64, 0x64, 0x56, 0xe1, 0x1c, + 0xa1, 0x31, 0x8d, 0x7f, 0xba, 0x51, 0xef, 0xbd, 0x78, 0x99, 0xf9, 0xe2, 0x45, 0xc4, 0x8b, 0x88, + 0x77, 0xc0, 0x65, 0x8f, 0x36, 0x68, 0xcb, 0x76, 0xbc, 0xab, 0x0d, 0xba, 0xa2, 0xd7, 0x37, 0xaf, + 0x7a, 0x9b, 0x2d, 0xea, 0x5e, 0xa5, 0xeb, 0xd4, 0xf2, 0xfc, 0xff, 0xae, 0xb4, 0x1c, 0xdb, 0xb3, + 0x49, 0x8e, 0xff, 0x3a, 0x7f, 0x7a, 0xc5, 0x5e, 0xb1, 0x11, 0x74, 0x95, 0xfd, 0xc5, 0xb1, 0xe7, + 0x2f, 0xac, 0xd8, 0xf6, 0x4a, 0x83, 0x5e, 0xc5, 0x5f, 0x4b, 0xed, 0xe5, 0xab, 0xae, 0xe7, 0xb4, + 0xeb, 0x9e, 0xc0, 0x16, 0x54, 0xac, 0x67, 0x36, 0xa9, 0xeb, 0xe9, 0xcd, 0x96, 0x20, 0x78, 0x58, + 0x25, 0xd8, 0x70, 0xf4, 0x56, 0x8b, 0x3a, 0xe2, 0xe1, 0xe7, 0xdf, 0x11, 0xbc, 0xa7, 0x5e, 0xaf, + 0x53, 0xd7, 0x6d, 0x98, 0xae, 0x77, 0x75, 0xfd, 0x19, 0xe9, 0x97, 0x20, 0x7c, 0x34, 0xf9, 0x83, + 0xf0, 0x5f, 0x41, 0xf2, 0x54, 0x32, 0x89, 0xff, 0x44, 0xe5, 0xd1, 0xc5, 0xcf, 0xa4, 0xa1, 0x7f, + 0x9e, 0x7a, 0xba, 0xa1, 0x7b, 0x3a, 0xb9, 0x00, 0xbd, 0x65, 0xcb, 0xa0, 0x77, 0xf3, 0xa9, 0x47, + 0x52, 0x97, 0x32, 0x13, 0xb9, 0xed, 0xad, 0x42, 0x9a, 0x9a, 0x1a, 0x07, 0x92, 0x87, 0x20, 0xbb, + 0xb8, 0xd9, 0xa2, 0xf9, 0xf4, 0x23, 0xa9, 0x4b, 0x03, 0x13, 0x03, 0xdb, 0x5b, 0x85, 0x5e, 0x14, + 0x9a, 0x86, 0x60, 0xf2, 0x28, 0xa4, 0xcb, 0x53, 0xf9, 0x0c, 0x22, 0xc7, 0xb7, 0xb7, 0x0a, 0xc3, + 0x6d, 0xd3, 0x78, 0xd2, 0x6e, 0x9a, 0x1e, 0x6d, 0xb6, 0xbc, 0x4d, 0x2d, 0x5d, 0x9e, 0x22, 0x17, + 0x21, 0x3b, 0x69, 0x1b, 0x34, 0x9f, 0x45, 0x22, 0xb2, 0xbd, 0x55, 0x18, 0xa9, 0xdb, 0x06, 0x95, + 0xa8, 0x10, 0x4f, 0x5e, 0x85, 0xec, 0xa2, 0xd9, 0xa4, 0xf9, 0xde, 0x47, 0x52, 0x97, 0x06, 0xaf, + 0x9d, 0xbf, 0xc2, 0xc5, 0x77, 0xc5, 0x17, 0xdf, 0x95, 0x45, 0x5f, 0xbe, 0x13, 0x63, 0x5f, 0xd8, + 0x2a, 0xf4, 0x6c, 0x6f, 0x15, 0xb2, 0x4c, 0xe4, 0x9f, 0xfe, 0x62, 0x21, 0xa5, 0x61, 0x4b, 0xf2, + 0x32, 0x0c, 0x4e, 0x36, 0xda, 0xae, 0x47, 0x9d, 0x9b, 0x7a, 0x93, 0xe6, 0x73, 0xf8, 0xc0, 0xf3, + 0xdb, 0x5b, 0x85, 0xb3, 0x75, 0x0e, 0xae, 0x59, 0x7a, 0x53, 0x7e, 0xb0, 0x4c, 0x5e, 0xfc, 0xa1, + 0x14, 0x8c, 0x56, 0xa9, 0xeb, 0x9a, 0xb6, 0x15, 0xc8, 0xe6, 0x09, 0x18, 0x10, 0xa0, 0xf2, 0x14, + 0xca, 0x67, 0x60, 0xa2, 0x6f, 0x7b, 0xab, 0x90, 0x71, 0x4d, 0x43, 0x0b, 0x31, 0xe4, 0x69, 0xe8, + 0xbb, 0x63, 0x7a, 0xab, 0xf3, 0x33, 0x25, 0x21, 0xa7, 0xb3, 0xdb, 0x5b, 0x05, 0xb2, 0x61, 0x7a, + 0xab, 0xb5, 0xe6, 0xb2, 0x2e, 0x3d, 0xd0, 0x27, 0x23, 0x73, 0x30, 0x56, 0x71, 0xcc, 0x75, 0xdd, + 0xa3, 0x37, 0xe8, 0x66, 0xc5, 0x6e, 0x98, 0xf5, 0x4d, 0x21, 0xc5, 0x47, 0xb6, 0xb7, 0x0a, 0x17, + 0x5a, 0x1c, 0x57, 0x5b, 0xa3, 0x9b, 0xb5, 0x16, 0x62, 0x25, 0x26, 0xb1, 0x96, 0xc5, 0x5f, 0x06, + 0x18, 0xba, 0xe5, 0x52, 0x27, 0x78, 0xef, 0x8b, 0x90, 0x65, 0xbf, 0xc5, 0x2b, 0xa3, 0xcc, 0xdb, + 0x2e, 0x75, 0x64, 0x99, 0x33, 0x3c, 0xb9, 0x0c, 0xbd, 0x73, 0xf6, 0x8a, 0x69, 0x89, 0xd7, 0x3e, + 0xb5, 0xbd, 0x55, 0x18, 0x6d, 0x30, 0x80, 0x44, 0xc9, 0x29, 0xc8, 0xfb, 0x61, 0xa8, 0xdc, 0x64, + 0x3a, 0x64, 0x5b, 0xba, 0x67, 0x3b, 0xe2, 0x6d, 0x51, 0xba, 0xa6, 0x04, 0x97, 0x1a, 0x46, 0xe8, + 0xc9, 0x7b, 0x01, 0x4a, 0x77, 0xaa, 0x9a, 0xdd, 0xa0, 0x25, 0xed, 0xa6, 0x50, 0x06, 0x6c, 0xad, + 0x6f, 0xb8, 0x35, 0xc7, 0x6e, 0xd0, 0x9a, 0xee, 0xc8, 0x8f, 0x95, 0xa8, 0xc9, 0x34, 0x8c, 0x94, + 0x70, 0x54, 0x68, 0xf4, 0x63, 0x6d, 0xea, 0x7a, 0x6e, 0xbe, 0xf7, 0x91, 0xcc, 0xa5, 0x81, 0x89, + 0x87, 0xb6, 0xb7, 0x0a, 0x0f, 0xf0, 0xf1, 0x52, 0x73, 0x04, 0x4a, 0x62, 0xa1, 0x34, 0x22, 0x13, + 0x30, 0x5c, 0x7a, 0xb3, 0xed, 0xd0, 0xb2, 0x41, 0x2d, 0xcf, 0xf4, 0x36, 0x85, 0x86, 0x5c, 0xd8, + 0xde, 0x2a, 0xe4, 0x75, 0x86, 0xa8, 0x99, 0x02, 0x23, 0x31, 0x89, 0x36, 0x21, 0x0b, 0x30, 0xfe, + 0xda, 0x64, 0xa5, 0x4a, 0x9d, 0x75, 0xb3, 0x4e, 0x4b, 0xf5, 0xba, 0xdd, 0xb6, 0xbc, 0x7c, 0x1f, + 0xf2, 0x79, 0x74, 0x7b, 0xab, 0xf0, 0xd0, 0x4a, 0xbd, 0x55, 0x73, 0x39, 0xb6, 0xa6, 0x73, 0xb4, + 0xc4, 0x2c, 0xde, 0x96, 0x7c, 0x08, 0x86, 0x17, 0x1d, 0xa6, 0x85, 0xc6, 0x14, 0x65, 0xf0, 0x7c, + 0x3f, 0xea, 0xff, 0xd9, 0x2b, 0x62, 0xa6, 0xe2, 0x50, 0xbf, 0x67, 0xf9, 0xcb, 0x7a, 0xbc, 0x41, + 0xcd, 0x40, 0x9c, 0xfc, 0xb2, 0x11, 0x56, 0x84, 0x42, 0x9e, 0x7d, 0xbc, 0xe9, 0x50, 0x23, 0xa6, + 0x6d, 0x03, 0xf8, 0xce, 0x97, 0xb7, 0xb7, 0x0a, 0x4f, 0x38, 0x82, 0xa6, 0xd6, 0x55, 0xed, 0x3a, + 0xb2, 0x22, 0xd3, 0xd0, 0xcf, 0xb4, 0xe9, 0x86, 0x69, 0x19, 0x79, 0x78, 0x24, 0x75, 0x69, 0xe4, + 0xda, 0x98, 0xff, 0xf6, 0x3e, 0x7c, 0xe2, 0xdc, 0xf6, 0x56, 0xe1, 0x14, 0xd3, 0xc1, 0xda, 0x9a, + 0x69, 0xc9, 0x53, 0x44, 0xd0, 0x94, 0x8d, 0xa2, 0x09, 0xdb, 0xc3, 0xa1, 0x3b, 0x18, 0x8e, 0xa2, + 0x25, 0xdb, 0x53, 0x87, 0xad, 0x4f, 0x46, 0x26, 0x61, 0x78, 0xc2, 0xf6, 0xca, 0x96, 0xeb, 0xe9, + 0x56, 0x9d, 0x96, 0xa7, 0xf2, 0x43, 0xd8, 0x0e, 0xd5, 0x82, 0xb5, 0x33, 0x05, 0xa6, 0x16, 0x99, + 0x94, 0xa2, 0x6d, 0xc8, 0x3c, 0x00, 0x7b, 0x85, 0x05, 0xc7, 0x64, 0x03, 0x61, 0x18, 0xdf, 0x9f, + 0xc8, 0xef, 0xcf, 0x31, 0x13, 0x0f, 0x6c, 0x6f, 0x15, 0xce, 0xe0, 0x17, 0xd8, 0x08, 0x90, 0x75, + 0x35, 0x24, 0x23, 0x2f, 0xc0, 0x00, 0xfb, 0xc5, 0x54, 0xd7, 0xcd, 0x8f, 0xa0, 0x9a, 0xe6, 0xb7, + 0xb7, 0x0a, 0xa7, 0xb1, 0x25, 0xd3, 0x73, 0x59, 0x43, 0x43, 0x52, 0xf2, 0xd9, 0x14, 0x7f, 0x8f, + 0x45, 0x47, 0x37, 0x3d, 0x37, 0x3f, 0x8a, 0x5a, 0x70, 0xe6, 0x4a, 0x30, 0x73, 0xcf, 0xe9, 0x4b, + 0xb4, 0x71, 0x5b, 0x6f, 0xb4, 0xa9, 0x3b, 0xf1, 0x25, 0x6c, 0x02, 0xfc, 0xad, 0xad, 0xc2, 0x4b, + 0x2b, 0xa6, 0xb7, 0xda, 0x5e, 0xba, 0x52, 0xb7, 0x9b, 0x57, 0x57, 0x1c, 0x7d, 0xdd, 0xf4, 0x74, + 0xcf, 0xb4, 0x2d, 0xbd, 0x71, 0x35, 0x5c, 0x5b, 0x5a, 0xa6, 0xb2, 0x18, 0x5c, 0xe1, 0x4f, 0x08, + 0xbe, 0xc6, 0xc3, 0x9f, 0xea, 0xd7, 0x70, 0x22, 0x52, 0x86, 0x51, 0xf6, 0x4b, 0x9e, 0x56, 0xc7, + 0x50, 0xc6, 0x85, 0xed, 0xad, 0xc2, 0x83, 0xd8, 0xbe, 0xc3, 0xdc, 0xaa, 0xb6, 0x23, 0x4f, 0xc1, + 0x80, 0x5b, 0xb7, 0x5b, 0xb4, 0xd6, 0x32, 0xad, 0xfc, 0x38, 0x7e, 0x5e, 0xa0, 0x26, 0x55, 0x86, + 0xa8, 0x98, 0x96, 0xd6, 0xef, 0x8a, 0xbf, 0xc8, 0x53, 0x90, 0x9b, 0xa0, 0x7a, 0xb3, 0x3c, 0x95, + 0x27, 0xf8, 0xc0, 0x33, 0xdb, 0x5b, 0x85, 0xf1, 0x25, 0xaa, 0x37, 0xa3, 0x9d, 0x29, 0x88, 0xc8, + 0x3c, 0x8c, 0x4d, 0xd8, 0x1e, 0xf2, 0x59, 0x58, 0x16, 0x7d, 0x79, 0x2a, 0x1c, 0x96, 0x4c, 0x1b, + 0xf8, 0xd3, 0xed, 0xe5, 0x78, 0xff, 0xc5, 0x9a, 0x16, 0xff, 0x24, 0x0b, 0x23, 0x6c, 0xa0, 0x4a, + 0x73, 0x6a, 0x89, 0x2d, 0x0f, 0x0c, 0xc2, 0xbe, 0xc6, 0x6d, 0xe9, 0x75, 0x2a, 0xa6, 0x57, 0x54, + 0x6d, 0xcb, 0x07, 0xca, 0x22, 0x50, 0xe8, 0xc9, 0x65, 0xe8, 0xe7, 0xa0, 0xf2, 0x94, 0x98, 0x71, + 0x87, 0xb7, 0xb7, 0x0a, 0x03, 0x2e, 0xc2, 0x6a, 0xa6, 0xa1, 0x05, 0x68, 0x36, 0xe5, 0xf1, 0xbf, + 0x67, 0x6d, 0xd7, 0x63, 0xcc, 0xc5, 0x84, 0x8b, 0xba, 0x2d, 0x1a, 0xac, 0x0a, 0x94, 0x3c, 0xe5, + 0x45, 0x1b, 0x91, 0x17, 0x01, 0x38, 0xa4, 0x64, 0x18, 0x8e, 0x98, 0x75, 0x51, 0x91, 0x05, 0x0b, + 0xdd, 0x30, 0xe4, 0x29, 0x5b, 0x22, 0x26, 0x4d, 0x18, 0xe2, 0xbf, 0x50, 0xf3, 0xf8, 0x94, 0x3b, + 0x78, 0xed, 0x52, 0xd0, 0x65, 0x11, 0xe9, 0x5c, 0x91, 0x49, 0xa7, 0x2d, 0xcf, 0xd9, 0x9c, 0x28, + 0x88, 0x55, 0xfa, 0x9c, 0x78, 0x54, 0x03, 0x71, 0xf2, 0xfa, 0x20, 0xb7, 0x61, 0x8b, 0xf7, 0x8c, + 0xed, 0x6c, 0xe8, 0x8e, 0x41, 0x8d, 0x89, 0x4d, 0x79, 0xf1, 0x5e, 0xf6, 0xc1, 0xb5, 0x25, 0x79, + 0x3e, 0x92, 0xc9, 0xd9, 0x4c, 0xc0, 0xb9, 0x55, 0xdb, 0x4b, 0x38, 0x0f, 0xf5, 0xc5, 0xa4, 0xe5, + 0xb6, 0x97, 0xd4, 0xb9, 0x27, 0xda, 0x86, 0xad, 0x0f, 0x1c, 0x70, 0x9b, 0x3a, 0x6c, 0x65, 0xc7, + 0xa9, 0x58, 0xac, 0x0f, 0x82, 0xc9, 0x3a, 0xc7, 0xc4, 0x79, 0x88, 0x26, 0xe7, 0x5f, 0x81, 0xf1, + 0x98, 0x28, 0xc8, 0x18, 0x64, 0xd6, 0xe8, 0x26, 0x57, 0x17, 0x8d, 0xfd, 0x49, 0x4e, 0x43, 0xef, + 0x3a, 0x1b, 0xcb, 0x5c, 0x0d, 0x34, 0xfe, 0xe3, 0xbd, 0xe9, 0xf7, 0xa4, 0x98, 0x19, 0x42, 0x26, + 0x6d, 0xcb, 0xa2, 0x75, 0x4f, 0xb6, 0x44, 0x5e, 0x80, 0x81, 0x39, 0xbb, 0xae, 0x37, 0xb0, 0x1f, + 0xb9, 0xde, 0xe1, 0xb4, 0xc2, 0x3a, 0xf0, 0x4a, 0x83, 0x61, 0xe4, 0x69, 0x25, 0x20, 0x65, 0x0a, + 0xa0, 0xd1, 0xa6, 0xed, 0x51, 0x6c, 0x98, 0x0e, 0x15, 0x00, 0x1b, 0x3a, 0x88, 0x92, 0x15, 0x20, + 0x24, 0x26, 0x57, 0xa1, 0xbf, 0xc2, 0x8c, 0xaf, 0xba, 0xdd, 0x10, 0xca, 0x87, 0xf6, 0x01, 0x1a, + 0x64, 0xf2, 0x04, 0xee, 0x13, 0x15, 0x67, 0x61, 0x64, 0xb2, 0x61, 0x52, 0xcb, 0x93, 0xdf, 0x9a, + 0x4d, 0x03, 0xa5, 0x15, 0x6a, 0x79, 0xf2, 0x5b, 0xe3, 0xc4, 0xa1, 0x33, 0xa8, 0x3a, 0x19, 0x22, + 0x69, 0xf1, 0x97, 0x32, 0xf0, 0xc0, 0x8d, 0xf6, 0x12, 0x75, 0x2c, 0xea, 0x51, 0x57, 0xcc, 0x22, + 0x01, 0xd7, 0x9b, 0x30, 0x1e, 0x43, 0x0a, 0xee, 0x68, 0x3d, 0xad, 0x05, 0x48, 0x7f, 0x72, 0x92, + 0x97, 0xe0, 0x58, 0x53, 0x32, 0x0b, 0xa3, 0x21, 0x90, 0xbd, 0x84, 0x9b, 0x4f, 0xe3, 0xc4, 0xfd, + 0xf0, 0xf6, 0x56, 0xe1, 0xbc, 0xc4, 0x8d, 0xbd, 0xb6, 0xac, 0xc1, 0x6a, 0x33, 0x72, 0x03, 0xc6, + 0x42, 0xd0, 0x6b, 0x8e, 0xdd, 0x6e, 0xb9, 0xf9, 0x0c, 0xb2, 0xc2, 0xf9, 0x52, 0x62, 0xb5, 0x82, + 0x48, 0x79, 0x0e, 0x52, 0x1b, 0x92, 0xaf, 0x4a, 0xc9, 0xdc, 0xc4, 0x28, 0xcc, 0xe2, 0x28, 0x7c, + 0xb7, 0x3f, 0x0a, 0x3b, 0x0a, 0xe9, 0x8a, 0xda, 0x52, 0x0c, 0x4a, 0xe5, 0x35, 0x62, 0x83, 0x32, + 0xf6, 0xc4, 0xf3, 0x93, 0x70, 0x26, 0x91, 0xd7, 0x9e, 0xb4, 0xfa, 0xf7, 0x33, 0x32, 0x97, 0x8a, + 0x6d, 0x04, 0x9d, 0xb9, 0x20, 0x77, 0x66, 0xc5, 0x36, 0x70, 0x8d, 0x49, 0x85, 0x33, 0xb7, 0xf4, + 0xb2, 0x2d, 0xdb, 0x50, 0x57, 0x99, 0x78, 0x5b, 0xf2, 0x51, 0x38, 0x1b, 0x03, 0xf2, 0xe9, 0x9a, + 0x6b, 0xff, 0xc5, 0xed, 0xad, 0x42, 0x31, 0x81, 0xab, 0x3a, 0x7b, 0x77, 0xe0, 0x42, 0x74, 0x38, + 0x27, 0x49, 0xdd, 0xb6, 0x3c, 0xdd, 0xb4, 0xc4, 0xd2, 0xc8, 0x47, 0xc9, 0x3b, 0xb6, 0xb7, 0x0a, + 0x8f, 0xc9, 0x3a, 0xe8, 0xd3, 0xa8, 0x2f, 0xdf, 0x89, 0x0f, 0x31, 0x20, 0x9f, 0x80, 0x2a, 0x37, + 0xf5, 0x15, 0x7f, 0x1b, 0x75, 0x69, 0x7b, 0xab, 0xf0, 0x78, 0xe2, 0x33, 0x4c, 0x46, 0x25, 0x9b, + 0x6d, 0x9d, 0x38, 0x11, 0x0d, 0x48, 0x88, 0xbb, 0x69, 0x1b, 0x14, 0xbf, 0xa1, 0x17, 0xf9, 0x17, + 0xb7, 0xb7, 0x0a, 0x0f, 0x4b, 0xfc, 0x2d, 0xdb, 0xa0, 0xea, 0xeb, 0x27, 0xb4, 0x2e, 0xfe, 0x6e, + 0x06, 0xce, 0xde, 0x31, 0x2d, 0xc3, 0xde, 0x70, 0xa7, 0xa8, 0xbb, 0xe6, 0xd9, 0xad, 0xa0, 0xa3, + 0x3f, 0x0c, 0x67, 0xa2, 0x18, 0x61, 0x08, 0x8b, 0xce, 0x7e, 0x62, 0x7b, 0xab, 0xf0, 0xe8, 0x06, + 0x27, 0xa8, 0x19, 0x9c, 0xc2, 0xb7, 0xa4, 0xa5, 0x87, 0x26, 0xf3, 0x60, 0xab, 0x87, 0x80, 0x48, + 0xf3, 0x1c, 0xae, 0x1e, 0x3e, 0x2b, 0x65, 0xa5, 0x93, 0xc9, 0xc9, 0x73, 0x90, 0x9b, 0xb2, 0x9b, + 0xba, 0x69, 0x89, 0x1e, 0xc4, 0x19, 0x3f, 0x78, 0x17, 0xc4, 0xc8, 0x26, 0x07, 0xa7, 0x65, 0xcf, + 0x14, 0x2f, 0x83, 0x7b, 0x2d, 0x69, 0x4b, 0xe3, 0x37, 0x55, 0xf6, 0x5c, 0x32, 0x39, 0x69, 0xc3, + 0xb0, 0x78, 0x85, 0xc8, 0xfa, 0xfa, 0x8c, 0x3f, 0xb2, 0x93, 0xa5, 0x78, 0x25, 0xd2, 0x86, 0x8f, + 0x69, 0x7c, 0x5b, 0xff, 0x33, 0x63, 0x03, 0x3a, 0xfa, 0x94, 0xf3, 0xaf, 0x02, 0x89, 0xb3, 0xd8, + 0xdb, 0x02, 0x95, 0x81, 0x87, 0xab, 0xa5, 0xf9, 0xb9, 0xb2, 0xe1, 0x6f, 0x65, 0x2a, 0x8e, 0xbd, + 0x6e, 0x1a, 0xd2, 0x04, 0xbd, 0x04, 0xe7, 0x14, 0xd4, 0x34, 0xee, 0x9e, 0x82, 0x4d, 0x34, 0xaa, + 0xaf, 0xbf, 0x4d, 0x6a, 0x09, 0x9a, 0x1a, 0xdf, 0x62, 0x45, 0xed, 0xbb, 0x4e, 0x8c, 0xd8, 0x30, + 0x54, 0x50, 0xd5, 0x55, 0xdb, 0xf1, 0xea, 0x6d, 0x4f, 0xf4, 0x3e, 0x0e, 0xc3, 0xd8, 0x33, 0x5c, + 0x41, 0xd4, 0xe5, 0x11, 0x3e, 0x1f, 0xf2, 0xf1, 0x14, 0x8c, 0x95, 0x3c, 0xcf, 0x31, 0x97, 0xda, + 0x1e, 0x9d, 0xd7, 0x5b, 0x2d, 0xd3, 0x5a, 0xc1, 0xe9, 0x7c, 0xf0, 0xda, 0xcb, 0x81, 0x19, 0xd4, + 0x55, 0x12, 0x57, 0xd4, 0xe6, 0xd2, 0x2c, 0xac, 0xfb, 0xa8, 0x5a, 0x93, 0xe3, 0xe4, 0x59, 0x58, + 0x6d, 0xc7, 0x66, 0xe1, 0x44, 0x5e, 0x7b, 0xea, 0xba, 0xcf, 0x64, 0xe0, 0xc2, 0xc2, 0x9a, 0xa7, + 0x6b, 0xd4, 0xb5, 0xdb, 0x4e, 0x9d, 0xba, 0xb7, 0x5a, 0x86, 0xee, 0xd1, 0x70, 0x32, 0x2e, 0x40, + 0x6f, 0xc9, 0x30, 0xa8, 0x81, 0xec, 0x7a, 0xf9, 0x71, 0x8f, 0xce, 0x00, 0x1a, 0x87, 0x93, 0x27, + 0xa0, 0x4f, 0xb4, 0x41, 0xee, 0xbd, 0x13, 0x83, 0xdb, 0x5b, 0x85, 0xbe, 0x36, 0x07, 0x69, 0x3e, + 0x8e, 0x91, 0x4d, 0xd1, 0x06, 0x65, 0x64, 0x99, 0x90, 0xcc, 0xe0, 0x20, 0xcd, 0xc7, 0x91, 0x0f, + 0xc0, 0x08, 0xb2, 0x0d, 0xde, 0x47, 0x2c, 0x6f, 0xa7, 0x7d, 0xe9, 0xca, 0x2f, 0xcb, 0xad, 0x0f, + 0x7c, 0x9b, 0x9a, 0xe3, 0x37, 0xd0, 0x14, 0x06, 0xe4, 0x0e, 0x8c, 0x89, 0x97, 0x08, 0x99, 0xf6, + 0x76, 0x61, 0x8a, 0xdb, 0x0a, 0xf1, 0xfe, 0x12, 0xdb, 0x18, 0x13, 0xc6, 0x58, 0xbc, 0x76, 0xc8, + 0x38, 0xb7, 0x13, 0x63, 0xf1, 0xc5, 0x32, 0x63, 0x95, 0x49, 0xf1, 0x83, 0x30, 0x24, 0x37, 0x24, + 0x67, 0xf1, 0x48, 0x8d, 0x8f, 0x13, 0x3c, 0x8c, 0x33, 0x0d, 0x3c, 0x47, 0x7b, 0x06, 0xa7, 0xb8, + 0xba, 0x63, 0xb6, 0x98, 0x61, 0x28, 0x94, 0x7c, 0x74, 0x7b, 0xab, 0x30, 0x68, 0x84, 0x60, 0x4d, + 0xa6, 0x29, 0xfe, 0xe7, 0x14, 0x9c, 0x65, 0xbc, 0x4b, 0xae, 0x6b, 0xae, 0x58, 0x4d, 0xd9, 0x32, + 0x7b, 0x12, 0x72, 0x55, 0x7c, 0x9e, 0x78, 0xd2, 0xe9, 0xed, 0xad, 0xc2, 0x18, 0x7f, 0x03, 0x79, + 0xaa, 0xe3, 0x34, 0xc1, 0x79, 0x52, 0x7a, 0x87, 0xf3, 0x24, 0xb6, 0x6b, 0xf1, 0x74, 0xc7, 0x33, + 0xad, 0x95, 0xaa, 0xa7, 0x7b, 0x6d, 0x37, 0xb2, 0x6b, 0x11, 0x98, 0x9a, 0x8b, 0xa8, 0xc8, 0xae, + 0x25, 0xd2, 0x88, 0xbc, 0x02, 0x43, 0xd3, 0x96, 0x11, 0x32, 0xe1, 0x53, 0xeb, 0x83, 0x6c, 0x33, + 0x41, 0x11, 0x1e, 0x67, 0x11, 0x69, 0x50, 0xfc, 0xce, 0x34, 0xe4, 0xf9, 0xe1, 0xcf, 0x9c, 0xe9, + 0x7a, 0xf3, 0xb4, 0xb9, 0x24, 0xcd, 0x4e, 0x33, 0xfe, 0x69, 0x12, 0xc3, 0x49, 0xe6, 0x06, 0x5a, + 0x7b, 0xe2, 0x34, 0xa9, 0x61, 0xba, 0xb1, 0x63, 0x07, 0xa5, 0x15, 0x29, 0x43, 0x1f, 0xe7, 0xcc, + 0xcd, 0xc5, 0xc1, 0x6b, 0x79, 0x5f, 0x11, 0xd4, 0x47, 0x73, 0x65, 0x68, 0x72, 0x62, 0xf9, 0x20, + 0x43, 0xb4, 0x67, 0xdb, 0xec, 0xb0, 0xcd, 0xa2, 0xe9, 0x35, 0xfc, 0x75, 0x9e, 0xcf, 0x14, 0xd2, + 0x3b, 0x79, 0x0c, 0x29, 0x9b, 0xa0, 0x4a, 0x3b, 0xf2, 0x4e, 0x18, 0x97, 0xe9, 0x71, 0xd3, 0xcb, + 0x17, 0x75, 0x6d, 0x54, 0x0f, 0x68, 0x71, 0xb7, 0x5b, 0xfc, 0x7c, 0x06, 0xc6, 0xd4, 0x77, 0x25, + 0x77, 0xa0, 0xff, 0xba, 0x6d, 0x5a, 0xd4, 0x58, 0xb0, 0x50, 0x30, 0xdd, 0xcf, 0x62, 0xfd, 0x5d, + 0xde, 0xa9, 0x37, 0xb0, 0x4d, 0x4d, 0xde, 0x1b, 0xe1, 0xd1, 0x6c, 0xc0, 0x8c, 0x7c, 0x08, 0x06, + 0xd8, 0xee, 0x62, 0x1d, 0x39, 0xa7, 0x77, 0xe4, 0xfc, 0x88, 0xe0, 0x7c, 0xda, 0xe1, 0x8d, 0xe2, + 0xac, 0x43, 0x76, 0x4c, 0x9d, 0x35, 0xaa, 0xbb, 0xb6, 0xbf, 0x82, 0xa3, 0x3a, 0x3b, 0x08, 0x91, + 0xd5, 0x99, 0xd3, 0xb0, 0x4d, 0x11, 0xff, 0x58, 0xec, 0x7d, 0x69, 0x57, 0xcc, 0xbb, 0x48, 0xed, + 0x78, 0x89, 0x98, 0x58, 0x30, 0x2a, 0xfa, 0x71, 0xd5, 0x6c, 0xe1, 0x7e, 0x12, 0x85, 0x3b, 0x72, + 0xed, 0xe2, 0x15, 0xff, 0xcc, 0xe5, 0x8a, 0x74, 0x82, 0xbf, 0xfe, 0xcc, 0x95, 0xf9, 0x80, 0x1c, + 0x0f, 0xc2, 0x70, 0x28, 0x28, 0x2c, 0x64, 0x25, 0x6b, 0x46, 0xc8, 0xc9, 0xa3, 0x30, 0x24, 0x5e, + 0x8a, 0xf7, 0x24, 0xee, 0x8b, 0xb5, 0x41, 0x0e, 0xe3, 0xbd, 0xf8, 0x35, 0x69, 0x78, 0x2a, 0xec, + 0x45, 0x8d, 0xae, 0x9b, 0x74, 0x23, 0x7c, 0xa8, 0x38, 0xb5, 0x63, 0x83, 0xdf, 0x9d, 0x5c, 0xd5, + 0xad, 0x15, 0x6a, 0x90, 0xcb, 0xd0, 0xcb, 0xcf, 0xa7, 0x52, 0xb8, 0x37, 0xc1, 0x89, 0x55, 0x3d, + 0x9a, 0xe2, 0x14, 0xc4, 0x86, 0x9c, 0x38, 0x91, 0xe2, 0x3a, 0x5e, 0x8a, 0xeb, 0xf8, 0x2e, 0x9e, + 0x28, 0xce, 0x9c, 0xf8, 0xea, 0x87, 0x7d, 0x13, 0x3b, 0x75, 0x12, 0x8f, 0x39, 0xff, 0x22, 0x0c, + 0x4a, 0xc4, 0x7b, 0x5a, 0xde, 0x7e, 0xa4, 0x57, 0x1e, 0xf5, 0xfe, 0x6b, 0x89, 0x51, 0x7f, 0x95, + 0x8d, 0x56, 0xd7, 0x65, 0x26, 0x74, 0x2a, 0x3c, 0x50, 0x6a, 0x72, 0x50, 0x74, 0x4c, 0x22, 0x88, + 0x3c, 0x0b, 0xfd, 0x9c, 0x45, 0x70, 0x58, 0x83, 0x07, 0x3d, 0x0e, 0xc2, 0xa2, 0x46, 0x4a, 0x40, + 0x48, 0xbe, 0x3b, 0x05, 0x0f, 0x75, 0x95, 0x04, 0xea, 0xe7, 0xe0, 0xb5, 0xe7, 0xf7, 0x25, 0xc6, + 0x89, 0xa7, 0xb6, 0xb7, 0x0a, 0x97, 0x25, 0xe5, 0x71, 0x24, 0x9a, 0x5a, 0x9d, 0x13, 0x49, 0xef, + 0xd5, 0xfd, 0x55, 0xd8, 0x4e, 0x89, 0x3f, 0x74, 0x06, 0x0f, 0xcf, 0xad, 0xfa, 0xa6, 0xff, 0x92, + 0xd9, 0x70, 0xa7, 0x24, 0xbe, 0x77, 0xd9, 0x27, 0x49, 0x78, 0x4c, 0x07, 0x2e, 0xa4, 0x0e, 0xe7, + 0x38, 0x66, 0x4a, 0xdf, 0x5c, 0x58, 0x9e, 0xb7, 0x2d, 0x6f, 0xd5, 0x7f, 0x40, 0xaf, 0x7c, 0xfa, + 0x8c, 0x0f, 0x30, 0xf4, 0xcd, 0x9a, 0xbd, 0x5c, 0x6b, 0x32, 0xaa, 0x84, 0x67, 0x74, 0xe2, 0xc4, + 0x96, 0x1c, 0x31, 0x0d, 0xf8, 0x93, 0x71, 0x2e, 0xbc, 0x1b, 0xf0, 0xa7, 0x8e, 0xf8, 0xd4, 0xab, + 0x34, 0x4a, 0x9a, 0x81, 0xfb, 0xf6, 0x39, 0x03, 0x3f, 0x07, 0x67, 0x71, 0xac, 0xb2, 0x65, 0x3f, + 0xf2, 0xf8, 0x7c, 0x3f, 0x7b, 0x33, 0xed, 0x34, 0xc7, 0x46, 0x5f, 0xa0, 0x58, 0x86, 0xa1, 0x39, + 0xbb, 0xbe, 0x16, 0xe8, 0xeb, 0x8b, 0x90, 0x5b, 0xd4, 0x9d, 0x15, 0xea, 0x61, 0x67, 0x0c, 0x5e, + 0x1b, 0xbf, 0xc2, 0x2f, 0xfc, 0x18, 0x11, 0x47, 0x4c, 0x8c, 0x88, 0x19, 0x32, 0xe7, 0xe1, 0x6f, + 0x4d, 0x34, 0x28, 0xfe, 0x44, 0x0e, 0x86, 0xc4, 0xe5, 0x14, 0x2e, 0xac, 0xe4, 0xbd, 0xe1, 0x75, + 0x9f, 0x98, 0xd2, 0x83, 0x93, 0xd7, 0xe0, 0x62, 0x61, 0x88, 0x31, 0xfb, 0x95, 0xad, 0x42, 0x6a, + 0x7b, 0xab, 0xd0, 0xa3, 0xf5, 0x4b, 0x47, 0x38, 0xe1, 0xd2, 0x2f, 0xd9, 0x3a, 0xf2, 0x75, 0x93, + 0xd2, 0x96, 0x9b, 0x02, 0xaf, 0x40, 0x9f, 0x78, 0x07, 0xa1, 0xf2, 0xe7, 0xc2, 0x93, 0xc3, 0xc8, + 0x25, 0x9b, 0xd2, 0xda, 0x6f, 0x45, 0x5e, 0x86, 0x1c, 0x3f, 0x49, 0x13, 0x02, 0x38, 0x9b, 0x7c, + 0xf2, 0xa8, 0x34, 0x17, 0x6d, 0xc8, 0x2c, 0x40, 0x78, 0x8a, 0x16, 0xdc, 0x29, 0x0a, 0x0e, 0xf1, + 0xf3, 0x35, 0x85, 0x8b, 0xd4, 0x96, 0xbc, 0x00, 0x43, 0x8b, 0xd4, 0x69, 0x9a, 0x96, 0xde, 0xa8, + 0x9a, 0x6f, 0xfa, 0xd7, 0x8a, 0x68, 0x03, 0xb9, 0xe6, 0x9b, 0xb2, 0x26, 0x44, 0xe8, 0xc8, 0x47, + 0x92, 0x4e, 0xa9, 0xfa, 0xf0, 0x45, 0x1e, 0xdd, 0xf1, 0xf8, 0x46, 0x79, 0x9f, 0x84, 0x43, 0xab, + 0x0f, 0xc0, 0x70, 0xe4, 0x80, 0x42, 0xdc, 0x1b, 0x3d, 0x14, 0x67, 0x2d, 0x9d, 0xb6, 0x28, 0x6c, + 0xa3, 0x1c, 0xd8, 0x50, 0x2a, 0x5b, 0xa6, 0x67, 0xea, 0x8d, 0x49, 0xbb, 0xd9, 0xd4, 0x2d, 0x23, + 0x3f, 0x10, 0x0e, 0x25, 0x93, 0x63, 0x6a, 0x75, 0x8e, 0x92, 0x87, 0x52, 0xb4, 0x11, 0xb9, 0x01, + 0x63, 0xa2, 0x0f, 0x35, 0x5a, 0xb7, 0x1d, 0x66, 0x96, 0xe1, 0xb5, 0x90, 0x18, 0x4b, 0x2e, 0xc7, + 0xd5, 0x1c, 0x1f, 0x29, 0xef, 0x7b, 0xd4, 0x86, 0x6c, 0xda, 0x2e, 0x5b, 0xeb, 0x26, 0xdb, 0x49, + 0x0c, 0xe1, 0xcb, 0xe0, 0xb4, 0x6d, 0x72, 0x90, 0x3c, 0x6d, 0x0b, 0x2a, 0xc9, 0x12, 0x18, 0xde, + 0xd9, 0x12, 0xb8, 0x9e, 0xed, 0x1f, 0x1c, 0x1b, 0x52, 0x2f, 0x0a, 0x8b, 0x7f, 0x3f, 0x03, 0x83, + 0xe2, 0x4d, 0x98, 0xf5, 0x72, 0x32, 0x7e, 0x0e, 0x32, 0x7e, 0x12, 0xc7, 0x41, 0xee, 0xb0, 0xc6, + 0x41, 0xf1, 0x13, 0xe9, 0x60, 0xb2, 0xab, 0x38, 0xa6, 0x75, 0xb0, 0xc9, 0xee, 0x22, 0xc0, 0xe4, + 0x6a, 0xdb, 0x5a, 0xe3, 0x0e, 0x11, 0xe9, 0xd0, 0x21, 0xa2, 0x6e, 0x6a, 0x12, 0x86, 0x3c, 0x04, + 0xd9, 0x29, 0xc6, 0x9f, 0xf5, 0xcc, 0xd0, 0xc4, 0xc0, 0x17, 0x38, 0xa7, 0xd4, 0x53, 0x1a, 0x82, + 0xd9, 0x36, 0x7a, 0x62, 0xd3, 0xa3, 0x7c, 0xe3, 0x92, 0xe1, 0xdb, 0xe8, 0x25, 0x06, 0xd0, 0x38, + 0x9c, 0x3c, 0x07, 0xe3, 0x53, 0xb4, 0xa1, 0x6f, 0xce, 0x9b, 0x8d, 0x86, 0xe9, 0xd2, 0xba, 0x6d, + 0x19, 0x2e, 0x0a, 0x59, 0x3c, 0xae, 0xe9, 0x6a, 0x71, 0x02, 0x52, 0x84, 0xdc, 0xc2, 0xf2, 0xb2, + 0x4b, 0x3d, 0x14, 0x5f, 0x66, 0x02, 0xd8, 0xdc, 0x6f, 0x23, 0x44, 0x13, 0x98, 0xe2, 0xbf, 0x4d, + 0xb1, 0x7d, 0x2a, 0x1e, 0xf0, 0x84, 0x83, 0xe8, 0x20, 0x22, 0xb9, 0x1c, 0xda, 0x4d, 0x69, 0xfc, + 0xda, 0x51, 0xf1, 0xb5, 0x7d, 0xc2, 0x76, 0x0a, 0x2d, 0xa6, 0xc4, 0xaf, 0xca, 0xec, 0xf4, 0x55, + 0x2f, 0xc0, 0xe0, 0xe2, 0x54, 0x65, 0xc2, 0x7f, 0x48, 0x16, 0x1f, 0x72, 0x5a, 0x3c, 0x64, 0xc8, + 0x33, 0x5a, 0x4b, 0x35, 0xff, 0x49, 0x32, 0x61, 0xf1, 0x3b, 0x33, 0x70, 0x4e, 0x7c, 0xe9, 0x64, + 0xc3, 0x6c, 0x2d, 0xd9, 0xba, 0x63, 0x68, 0xb4, 0x4e, 0xcd, 0x75, 0x7a, 0x3c, 0x07, 0x6c, 0x74, + 0xc8, 0x65, 0x0f, 0x30, 0xe4, 0xae, 0x45, 0x4f, 0x43, 0xb9, 0xb1, 0x35, 0xc6, 0xc4, 0x29, 0x9f, + 0x86, 0x46, 0xcf, 0x40, 0x8b, 0x90, 0x9b, 0xa3, 0xd6, 0x8a, 0xb7, 0x8a, 0xca, 0xd5, 0xcb, 0x95, + 0xab, 0x81, 0x10, 0x4d, 0x60, 0x24, 0xbe, 0xb8, 0x71, 0xea, 0x8b, 0xf3, 0x65, 0x3b, 0x27, 0x4d, + 0x26, 0x2a, 0x7e, 0x7b, 0x06, 0x4e, 0xab, 0xdd, 0x54, 0xa5, 0x96, 0x71, 0xd2, 0x47, 0xf7, 0x4e, + 0x1f, 0x7d, 0x57, 0x16, 0x1e, 0xf4, 0x0f, 0xd4, 0x57, 0x75, 0x87, 0x1a, 0x53, 0xa6, 0x43, 0xeb, + 0x9e, 0xed, 0x6c, 0x1e, 0x63, 0xfb, 0xf1, 0xf0, 0xba, 0xea, 0x39, 0xc8, 0x89, 0x83, 0x28, 0xbe, + 0x0e, 0x8e, 0x04, 0x6f, 0x82, 0xd0, 0xd8, 0x0a, 0xca, 0x0f, 0xb1, 0x94, 0x0e, 0xce, 0xed, 0xa6, + 0x83, 0xdf, 0x03, 0xc3, 0x81, 0xe8, 0xa5, 0xee, 0x43, 0x63, 0xd3, 0xf0, 0x11, 0xbc, 0x03, 0xa3, + 0x84, 0xf8, 0x34, 0x1f, 0x50, 0x9e, 0x42, 0x63, 0x70, 0x58, 0x3c, 0x2d, 0x68, 0x67, 0x1a, 0x9a, + 0x4c, 0xa4, 0xaa, 0xca, 0xc0, 0x6e, 0x54, 0xe5, 0x33, 0xbd, 0x70, 0x3e, 0x59, 0x55, 0x34, 0xaa, + 0x1b, 0x27, 0x9a, 0xf2, 0xf6, 0xd4, 0x94, 0x47, 0x21, 0x5b, 0xd1, 0xbd, 0x55, 0xa1, 0x22, 0xe8, + 0xb4, 0xb2, 0x6c, 0x36, 0x68, 0xad, 0xa5, 0x7b, 0xab, 0x1a, 0xa2, 0xa4, 0xb9, 0x09, 0x90, 0x63, + 0xd2, 0xdc, 0x14, 0x1a, 0x30, 0x83, 0x8f, 0xa4, 0x2e, 0x65, 0x93, 0x0c, 0x18, 0x55, 0x29, 0x87, + 0x76, 0xa3, 0x94, 0x9f, 0xed, 0xed, 0x34, 0x7f, 0xdd, 0x71, 0x4c, 0x8f, 0x9e, 0x68, 0xe5, 0x89, + 0x56, 0x1e, 0x81, 0x56, 0xfe, 0x5a, 0x1a, 0x86, 0x83, 0xfd, 0xec, 0x1b, 0xb4, 0x7e, 0x34, 0xeb, + 0x68, 0xb8, 0x0d, 0xcc, 0x1c, 0x78, 0x1b, 0x78, 0x10, 0x25, 0x2c, 0x06, 0xfb, 0x72, 0x6e, 0xea, + 0xa0, 0x94, 0xf9, 0xbe, 0x3c, 0x38, 0x97, 0x7f, 0x14, 0xfa, 0xe6, 0xf5, 0xbb, 0x66, 0xb3, 0xdd, + 0x14, 0x3b, 0x1c, 0x74, 0xb6, 0x6e, 0xea, 0x77, 0x35, 0x1f, 0x5e, 0xfc, 0x97, 0x29, 0x18, 0x11, + 0x42, 0x15, 0xcc, 0x0f, 0x24, 0xd5, 0x50, 0x3a, 0xe9, 0x03, 0x4b, 0x27, 0xb3, 0x7f, 0xe9, 0x14, + 0xff, 0x66, 0x06, 0xf2, 0x33, 0x66, 0x83, 0x2e, 0x3a, 0xba, 0xe5, 0x2e, 0x53, 0x47, 0x1c, 0x45, + 0x4c, 0x33, 0x56, 0x07, 0xfa, 0x40, 0x69, 0x1a, 0x4a, 0xef, 0x6b, 0x1a, 0x7a, 0x17, 0x0c, 0x88, + 0x97, 0x09, 0x1c, 0xfd, 0x71, 0xa4, 0x39, 0x3e, 0x50, 0x0b, 0xf1, 0x8c, 0xb8, 0xd4, 0x6a, 0x39, + 0xf6, 0x3a, 0x75, 0xf8, 0x5d, 0xae, 0x20, 0xd6, 0x7d, 0xa0, 0x16, 0xe2, 0x25, 0xce, 0xd4, 0xb7, + 0x7f, 0x65, 0xce, 0xd4, 0xd1, 0x42, 0x3c, 0xb9, 0x04, 0xfd, 0x73, 0x76, 0x1d, 0xdd, 0x5b, 0xc5, + 0x54, 0x34, 0xb4, 0xbd, 0x55, 0xe8, 0x6f, 0x08, 0x98, 0x16, 0x60, 0x19, 0xe5, 0x94, 0xbd, 0x61, + 0x35, 0x6c, 0x9d, 0x7b, 0x01, 0xf6, 0x73, 0x4a, 0x43, 0xc0, 0xb4, 0x00, 0xcb, 0x28, 0x99, 0xcc, + 0xd1, 0xbb, 0xb2, 0x3f, 0xe4, 0xb9, 0x2c, 0x60, 0x5a, 0x80, 0x2d, 0x7e, 0x7f, 0x96, 0x69, 0xaf, + 0x6b, 0xbe, 0x79, 0xdf, 0xaf, 0x25, 0xe1, 0x80, 0xe9, 0xdd, 0xc7, 0x80, 0xb9, 0x6f, 0xce, 0x52, + 0x8b, 0x7f, 0xd2, 0x07, 0x20, 0xa4, 0x3f, 0x7d, 0xb2, 0xd9, 0x3d, 0x98, 0xd6, 0x4c, 0xc1, 0xf8, + 0xb4, 0xb5, 0xaa, 0x5b, 0x75, 0x6a, 0x84, 0x27, 0xca, 0x39, 0x1c, 0xda, 0x18, 0x22, 0x40, 0x05, + 0x32, 0x3c, 0x52, 0xd6, 0xe2, 0x0d, 0xc8, 0x33, 0x30, 0x58, 0xb6, 0x3c, 0xea, 0xe8, 0x75, 0xcf, + 0x5c, 0xa7, 0x62, 0x6a, 0x40, 0xff, 0x09, 0x33, 0x04, 0x6b, 0x32, 0x0d, 0x79, 0x0e, 0x86, 0x2a, + 0xba, 0xe3, 0x99, 0x75, 0xb3, 0xa5, 0x5b, 0x9e, 0xb8, 0xbf, 0xe1, 0xcb, 0x7e, 0x4b, 0x82, 0x6b, + 0x11, 0x2a, 0xf2, 0x11, 0x18, 0xc0, 0x6d, 0x33, 0x46, 0x33, 0x0d, 0xec, 0x78, 0xcf, 0xfd, 0x58, + 0xe8, 0x27, 0xcd, 0x0f, 0xc6, 0xd1, 0x4f, 0x42, 0xbd, 0xea, 0x0e, 0x38, 0x92, 0xd7, 0xa1, 0x6f, + 0xda, 0x32, 0x90, 0x39, 0xec, 0xc8, 0xbc, 0x28, 0x98, 0x9f, 0x0d, 0x99, 0xdb, 0x2d, 0x85, 0xb7, + 0xcf, 0x2e, 0x79, 0x94, 0x0d, 0xbe, 0x75, 0xa3, 0x6c, 0xe8, 0x2d, 0xb8, 0xb1, 0x18, 0x3e, 0xac, + 0x1b, 0x8b, 0x91, 0x7d, 0xde, 0x58, 0x14, 0x7f, 0x28, 0x05, 0x83, 0x13, 0x95, 0x99, 0x60, 0xf8, + 0x3e, 0x00, 0x99, 0x8a, 0x70, 0xe8, 0xc9, 0x72, 0x83, 0xa6, 0x65, 0x1a, 0x1a, 0x83, 0x91, 0xcb, + 0xd0, 0x3f, 0x89, 0x8e, 0xc0, 0xe2, 0x8a, 0x39, 0xcb, 0x17, 0xc0, 0x3a, 0xc2, 0x30, 0x1e, 0xc0, + 0x47, 0x93, 0x27, 0xa0, 0xaf, 0xe2, 0xd8, 0x2b, 0x8e, 0xde, 0x14, 0x8b, 0x30, 0x7a, 0x54, 0xb5, + 0x38, 0x48, 0xf3, 0x71, 0xe4, 0x65, 0x18, 0x29, 0xb5, 0x0d, 0xd3, 0x0b, 0xa3, 0xd6, 0xb2, 0x68, + 0xf7, 0xe2, 0x2d, 0x88, 0xce, 0x30, 0x35, 0xff, 0x6b, 0x4c, 0x43, 0x53, 0x68, 0x8b, 0x9f, 0x4d, + 0xf9, 0x3b, 0x05, 0xf6, 0xbc, 0x6a, 0x1b, 0x6f, 0x45, 0xf0, 0xcd, 0xfb, 0xf9, 0xf3, 0x5c, 0x0e, + 0xd2, 0x7c, 0x1c, 0xb9, 0x0c, 0xbd, 0xd3, 0x8e, 0x63, 0x3b, 0x72, 0x00, 0x19, 0x65, 0x00, 0xd9, + 0x93, 0x00, 0x29, 0xc8, 0xbb, 0x61, 0x90, 0x4f, 0x59, 0xfc, 0x9c, 0x37, 0xd3, 0xed, 0x12, 0x5e, + 0xa6, 0x2c, 0x7e, 0x3e, 0x23, 0x99, 0x7c, 0xbc, 0xc3, 0xee, 0xc3, 0x0b, 0x99, 0x67, 0x21, 0x33, + 0x51, 0x99, 0x11, 0xf3, 0xe7, 0x29, 0xbf, 0xa9, 0xa4, 0x68, 0x4a, 0x3b, 0x46, 0x4d, 0x2e, 0x40, + 0xb6, 0xc2, 0x94, 0x2f, 0x87, 0xca, 0xd5, 0xbf, 0xbd, 0x55, 0xc8, 0xb6, 0x98, 0xf6, 0x21, 0x14, + 0xb1, 0x6c, 0xff, 0xc4, 0x37, 0x69, 0x1c, 0x1b, 0x6e, 0x9d, 0x2e, 0x40, 0xb6, 0xe4, 0xac, 0xac, + 0x8b, 0x49, 0x0f, 0xb1, 0xba, 0xb3, 0xb2, 0xae, 0x21, 0x94, 0x5c, 0x05, 0xd0, 0xa8, 0xd7, 0x76, + 0x2c, 0x8c, 0xed, 0x1c, 0xc0, 0xe3, 0x48, 0x9c, 0x4c, 0x1d, 0x84, 0xd6, 0xea, 0xb6, 0x41, 0x35, + 0x89, 0xa4, 0xf8, 0x77, 0xc3, 0x3b, 0xb5, 0x29, 0xd3, 0x5d, 0x3b, 0xe9, 0xc2, 0x3d, 0x74, 0xa1, + 0x2e, 0x4e, 0x7c, 0xe3, 0x9d, 0x54, 0x80, 0xde, 0x99, 0x86, 0xbe, 0xe2, 0x62, 0x1f, 0x0a, 0x87, + 0xcd, 0x65, 0x06, 0xd0, 0x38, 0x5c, 0xe9, 0xa7, 0xfe, 0x9d, 0xfb, 0xe9, 0x73, 0xbd, 0xc1, 0x68, + 0xbb, 0x49, 0xbd, 0x0d, 0xdb, 0x39, 0xe9, 0xaa, 0xdd, 0x76, 0xd5, 0x45, 0xe8, 0xab, 0x3a, 0x75, + 0xe9, 0xb4, 0x04, 0xb7, 0x13, 0xae, 0x53, 0xe7, 0x27, 0x25, 0x3e, 0x92, 0xd1, 0x4d, 0xb9, 0x1e, + 0xd2, 0xf5, 0x85, 0x74, 0x86, 0xeb, 0x09, 0x3a, 0x81, 0x14, 0x74, 0x15, 0xdb, 0xf1, 0x44, 0xc7, + 0x05, 0x74, 0x2d, 0xdb, 0xf1, 0x34, 0x1f, 0x49, 0xde, 0x05, 0xb0, 0x38, 0x59, 0xf1, 0x83, 0x96, + 0x06, 0x42, 0x87, 0x5b, 0x11, 0xad, 0xa4, 0x49, 0x68, 0xb2, 0x08, 0x03, 0x0b, 0x2d, 0xea, 0xf0, + 0x9d, 0x14, 0x8f, 0xd6, 0x7c, 0x87, 0x22, 0x5a, 0xd1, 0xef, 0x57, 0xc4, 0xff, 0x01, 0x39, 0x5f, + 0x9d, 0x6c, 0xff, 0xa7, 0x16, 0x32, 0x22, 0xef, 0x86, 0x5c, 0x89, 0x9b, 0x89, 0x83, 0xc8, 0x32, + 0x10, 0x19, 0xee, 0x60, 0x39, 0x8a, 0x6f, 0xf9, 0x75, 0xfc, 0x5b, 0x13, 0xe4, 0xc5, 0xcb, 0x30, + 0xa6, 0x3e, 0x86, 0x0c, 0x42, 0xdf, 0xe4, 0xc2, 0xcd, 0x9b, 0xd3, 0x93, 0x8b, 0x63, 0x3d, 0xa4, + 0x1f, 0xb2, 0xd5, 0xe9, 0x9b, 0x53, 0x63, 0xa9, 0xe2, 0xf7, 0x4a, 0x33, 0x08, 0x53, 0xad, 0x93, + 0x5b, 0xf9, 0x03, 0x5d, 0x3f, 0x8d, 0xe1, 0x55, 0x34, 0x1e, 0x38, 0x34, 0x4d, 0xcf, 0xa3, 0x86, + 0x58, 0x25, 0xf0, 0xaa, 0xd6, 0xbb, 0xab, 0xc5, 0xf0, 0xe4, 0x49, 0x18, 0x46, 0x98, 0xb8, 0x65, + 0xe5, 0xdb, 0x6b, 0xd1, 0xc0, 0xb9, 0xab, 0x45, 0x91, 0xc5, 0x9f, 0x0f, 0x2f, 0xe6, 0xe7, 0xa8, + 0x7e, 0x5c, 0x2f, 0x65, 0xef, 0x91, 0xfe, 0x2a, 0xfe, 0x48, 0x2f, 0x0f, 0xa5, 0xe3, 0xc1, 0xf8, + 0x47, 0x21, 0xca, 0xf0, 0x14, 0x39, 0xb3, 0x87, 0x53, 0xe4, 0x27, 0x21, 0x37, 0x4f, 0xbd, 0x55, + 0xdb, 0xf7, 0x29, 0x44, 0x43, 0xb4, 0x89, 0x10, 0xd9, 0x1d, 0x87, 0xd3, 0x90, 0x35, 0x20, 0x7e, + 0xa4, 0x7d, 0x10, 0xed, 0xe0, 0x9f, 0x5a, 0x9f, 0x8b, 0x6d, 0x73, 0xaa, 0x98, 0x8f, 0x03, 0x63, + 0x95, 0x4e, 0x07, 0xd1, 0x14, 0x92, 0x93, 0xdf, 0x9f, 0x6d, 0x15, 0x72, 0x9c, 0x46, 0x4b, 0x60, + 0x4b, 0x3e, 0x00, 0x03, 0xf3, 0x33, 0x25, 0x11, 0x75, 0xcf, 0x1d, 0x52, 0x1e, 0x08, 0xa4, 0xe8, + 0x23, 0x02, 0x91, 0x60, 0xdc, 0x62, 0x73, 0x59, 0x8f, 0x07, 0xdd, 0x87, 0x5c, 0x98, 0xb6, 0xf0, + 0x08, 0x48, 0x71, 0x38, 0x11, 0x68, 0x4b, 0x34, 0x2e, 0x52, 0x95, 0x15, 0xc7, 0x2a, 0xda, 0xd2, + 0x7f, 0x80, 0xd1, 0xbd, 0x00, 0xe3, 0xa5, 0x56, 0xab, 0x61, 0x52, 0x03, 0xf5, 0x45, 0x6b, 0x37, + 0xa8, 0x2b, 0x9c, 0xb9, 0x30, 0xa8, 0x4e, 0xe7, 0xc8, 0x1a, 0xe6, 0x7a, 0xa8, 0x39, 0xed, 0xa8, + 0xeb, 0x6f, 0xbc, 0x2d, 0xa6, 0xd6, 0xe0, 0xec, 0x6d, 0xa7, 0x3c, 0x25, 0xdc, 0xb9, 0x78, 0x6a, + 0x0d, 0x1f, 0x1c, 0x75, 0x89, 0x95, 0xc9, 0x8b, 0xbf, 0x9b, 0x86, 0xb3, 0x93, 0x0e, 0xd5, 0x3d, + 0x3a, 0x3f, 0x53, 0x2a, 0xb5, 0xd1, 0x79, 0xb3, 0xd1, 0xa0, 0xd6, 0xca, 0xd1, 0x4c, 0x0a, 0x2f, + 0xc1, 0x48, 0xf0, 0x02, 0xe8, 0x42, 0x2d, 0x87, 0xb7, 0xd6, 0x7d, 0x0c, 0x77, 0xb8, 0xd6, 0x14, + 0x52, 0x72, 0x03, 0x4e, 0x05, 0x90, 0x52, 0xa3, 0x61, 0x6f, 0x68, 0xb4, 0xed, 0x72, 0xb7, 0x95, + 0x7e, 0xee, 0x44, 0x1e, 0x72, 0xd0, 0x19, 0xbe, 0xe6, 0x30, 0x02, 0x2d, 0xa9, 0x15, 0x99, 0x85, + 0xfe, 0x99, 0x86, 0xbd, 0x81, 0x19, 0x56, 0x7a, 0xa3, 0x0b, 0xe7, 0xfc, 0x4c, 0xc9, 0x47, 0x71, + 0xc7, 0xe3, 0x65, 0xc6, 0xcb, 0xdb, 0x6c, 0xc9, 0xaa, 0x17, 0xb4, 0x2e, 0x6e, 0x67, 0xe0, 0xdc, + 0x6d, 0xbd, 0x61, 0x1a, 0xa1, 0x90, 0x35, 0xea, 0xb6, 0x6c, 0xcb, 0xa5, 0xc7, 0x68, 0xb6, 0x88, + 0x0c, 0xc9, 0xec, 0xa1, 0x0c, 0xc9, 0x78, 0x67, 0xf7, 0x1e, 0xb8, 0xb3, 0x73, 0x07, 0xee, 0xec, + 0xbe, 0x03, 0x75, 0xf6, 0xe7, 0xd2, 0x30, 0xe6, 0xc7, 0x0b, 0xc9, 0x39, 0x5f, 0xa4, 0x60, 0x16, + 0x3c, 0x53, 0x55, 0xe2, 0x18, 0x10, 0x4f, 0xaa, 0xd0, 0x37, 0x7d, 0xb7, 0x65, 0x3a, 0xd4, 0xdd, + 0x45, 0x10, 0xc6, 0x43, 0xe2, 0xfc, 0x68, 0x9c, 0xf2, 0x26, 0xb1, 0xa3, 0x23, 0x0e, 0xc6, 0x40, + 0x6f, 0x1e, 0x31, 0x35, 0xe1, 0x27, 0xb2, 0xe1, 0x81, 0xde, 0x22, 0xb2, 0x2a, 0x12, 0xb9, 0x1f, + 0x92, 0x92, 0xc7, 0x20, 0xb3, 0xb8, 0x38, 0x27, 0xd6, 0x06, 0x4c, 0x20, 0xe4, 0x79, 0x72, 0x24, + 0x3b, 0xc3, 0x92, 0xcb, 0xd0, 0xeb, 0xaa, 0x3d, 0x87, 0x00, 0xf9, 0x90, 0x01, 0x01, 0xc5, 0xdf, + 0x49, 0xf3, 0x2c, 0x1a, 0x7c, 0xb6, 0x39, 0x12, 0xcd, 0x9f, 0x80, 0x7e, 0xbf, 0x6f, 0x84, 0xee, + 0x07, 0x71, 0x41, 0x6a, 0x9f, 0xa9, 0xcf, 0x0e, 0x62, 0xc0, 0x0a, 0x7e, 0x80, 0x06, 0xbf, 0x43, + 0xc1, 0x6d, 0x1d, 0x06, 0x68, 0xf8, 0x61, 0x19, 0xef, 0x82, 0x81, 0x60, 0x82, 0x95, 0xef, 0x4e, + 0x82, 0xd9, 0x58, 0x0b, 0xf1, 0xca, 0xba, 0x92, 0x3b, 0x80, 0x15, 0xe2, 0x8b, 0x97, 0x77, 0xe0, + 0x89, 0x78, 0x0f, 0x59, 0xbc, 0x9f, 0x12, 0xe2, 0xe5, 0x31, 0x82, 0xc7, 0x56, 0xbc, 0x87, 0x76, + 0x6f, 0x50, 0xfc, 0xb5, 0x14, 0x10, 0xf6, 0x5a, 0x15, 0xdd, 0x75, 0x37, 0x6c, 0xc7, 0xe0, 0x41, + 0x1f, 0x47, 0x22, 0x98, 0xc3, 0xbb, 0xeb, 0xfd, 0x7a, 0x80, 0x53, 0x11, 0x87, 0xf3, 0x63, 0x3e, + 0x59, 0x5d, 0x8e, 0x8e, 0xa6, 0x6e, 0xd1, 0x64, 0x8f, 0xcb, 0x97, 0xc9, 0xbd, 0x91, 0x10, 0x57, + 0xe9, 0x16, 0xf9, 0x29, 0x18, 0x12, 0x3f, 0x98, 0x59, 0xe0, 0xdf, 0x12, 0xe2, 0x28, 0x75, 0x19, + 0x40, 0x8b, 0xa0, 0xc9, 0xf3, 0x30, 0xc0, 0x06, 0xcc, 0x0a, 0xa6, 0x25, 0xeb, 0x0b, 0x23, 0xb5, + 0x0c, 0x1f, 0x28, 0x2f, 0x3d, 0x01, 0xa5, 0x14, 0x28, 0xd0, 0xbf, 0x8b, 0x90, 0xc1, 0x8f, 0xc2, + 0x60, 0xc9, 0xb2, 0x6c, 0x9e, 0x60, 0xc9, 0x15, 0xd7, 0x3a, 0x1d, 0xb7, 0x24, 0x8f, 0x61, 0x86, + 0x95, 0x90, 0x3e, 0x71, 0x4f, 0x22, 0x33, 0x24, 0xd7, 0xfc, 0x68, 0x33, 0xea, 0x08, 0xeb, 0x1a, + 0xaf, 0xb6, 0x1c, 0x01, 0x8b, 0x07, 0x9b, 0x61, 0xe7, 0x0d, 0x57, 0x1c, 0xbb, 0x65, 0xbb, 0xd4, + 0xe0, 0x82, 0x1a, 0x0c, 0xb3, 0x17, 0xb4, 0x04, 0x02, 0x23, 0x65, 0x23, 0x29, 0xc2, 0x22, 0x4d, + 0xc8, 0x32, 0x9c, 0xf6, 0x2f, 0xd9, 0x83, 0x98, 0xe4, 0xf2, 0x94, 0x8b, 0xc1, 0x16, 0x83, 0x61, + 0x1e, 0xac, 0x10, 0x35, 0xf1, 0xb0, 0x7f, 0xa5, 0xe4, 0x07, 0x35, 0xd7, 0x4c, 0x43, 0xee, 0xea, + 0x44, 0x7e, 0xe4, 0x4b, 0x61, 0x70, 0x5e, 0xbf, 0x3b, 0xd5, 0x16, 0x07, 0x4f, 0xc3, 0xbb, 0xbf, + 0xb9, 0x6a, 0xea, 0x77, 0x6b, 0x86, 0x68, 0xa7, 0x98, 0x1f, 0x32, 0x4b, 0x52, 0x83, 0xb3, 0x15, + 0xc7, 0x6e, 0xda, 0x1e, 0x35, 0x94, 0xf0, 0xde, 0xd1, 0x30, 0x1f, 0x40, 0x4b, 0x50, 0xd4, 0xba, + 0xc4, 0xf9, 0x76, 0x60, 0x43, 0x9a, 0x30, 0x5a, 0x72, 0xdd, 0x76, 0x93, 0x86, 0xb7, 0x7b, 0x63, + 0x3b, 0x7e, 0xc6, 0x3b, 0x84, 0x23, 0xfb, 0x83, 0x3a, 0x36, 0xe5, 0x97, 0x7b, 0x35, 0xcf, 0x94, + 0x9f, 0x88, 0xdf, 0xa2, 0xf2, 0x66, 0xbd, 0xeb, 0x0b, 0x10, 0x93, 0x8f, 0xe4, 0xc7, 0x71, 0x78, + 0x61, 0xef, 0x06, 0xa2, 0xc7, 0xc4, 0x25, 0x72, 0xef, 0x46, 0x9a, 0x90, 0x8f, 0xc2, 0xf9, 0x58, + 0x6f, 0xf0, 0xaf, 0x62, 0x7d, 0x4c, 0xa2, 0x51, 0xcb, 0x2a, 0xc1, 0x44, 0x96, 0x75, 0x81, 0xd6, + 0x85, 0x03, 0x79, 0x14, 0x86, 0xdc, 0xf6, 0x12, 0x3f, 0x1e, 0xaa, 0x2d, 0x6d, 0xf2, 0x8c, 0x5b, + 0xda, 0x60, 0x00, 0x9b, 0xd8, 0xbc, 0x9e, 0xed, 0x1f, 0x19, 0x1b, 0xd5, 0xce, 0xc5, 0x65, 0x8a, + 0xc1, 0x72, 0xc5, 0xbf, 0x95, 0x56, 0x26, 0x43, 0x6e, 0x51, 0x1e, 0x68, 0x32, 0x94, 0x27, 0xb5, + 0xf4, 0x3e, 0x27, 0xb5, 0xc7, 0xe3, 0x6e, 0x2f, 0x09, 0x33, 0xd5, 0x97, 0xc2, 0x88, 0xdf, 0x02, + 0xdf, 0x7b, 0x33, 0x58, 0xed, 0x3a, 0x6b, 0xc4, 0x05, 0xa1, 0x11, 0x63, 0x68, 0x52, 0x6f, 0x2a, + 0x6a, 0xa0, 0xf0, 0x2b, 0xfe, 0x78, 0x0a, 0x20, 0x1c, 0x47, 0xe4, 0xa9, 0x68, 0x8a, 0xcb, 0x54, + 0x78, 0x15, 0x28, 0x32, 0x1d, 0x45, 0x72, 0x5a, 0x92, 0x0b, 0x90, 0xc5, 0x6c, 0x58, 0xe9, 0xf0, + 0xf2, 0x60, 0xcd, 0xb4, 0x0c, 0x0d, 0xa1, 0x0c, 0x2b, 0xa5, 0xad, 0x41, 0x2c, 0xfa, 0xbd, 0xf0, + 0x7d, 0xc2, 0x14, 0x8c, 0x56, 0xdb, 0x4b, 0xb2, 0x3e, 0xc9, 0x29, 0x4e, 0xdc, 0xf6, 0x52, 0x90, + 0xd5, 0x20, 0x92, 0xf2, 0x2c, 0xda, 0xa4, 0xf8, 0x75, 0xd2, 0x56, 0xc5, 0xd7, 0x1b, 0x72, 0x09, + 0xd2, 0xa6, 0x21, 0xba, 0x35, 0x69, 0x8a, 0xe1, 0x8a, 0x97, 0x36, 0x0d, 0x32, 0x0f, 0xa7, 0xda, + 0xd6, 0x9a, 0x65, 0x6f, 0x58, 0xb5, 0xba, 0x6d, 0xb9, 0x9e, 0xa3, 0x9b, 0x96, 0xe7, 0x06, 0x17, + 0xdf, 0xfe, 0x32, 0xc7, 0x49, 0x26, 0x43, 0x8a, 0xd9, 0x1e, 0x8d, 0xb4, 0x63, 0x50, 0xf2, 0x2a, + 0x0c, 0xea, 0x1b, 0x98, 0x2d, 0xc7, 0xb5, 0x1b, 0x54, 0xdc, 0x6d, 0x07, 0x17, 0xd0, 0xa5, 0x3b, + 0xd5, 0x49, 0x8e, 0x89, 0x72, 0x02, 0x7d, 0xc3, 0x15, 0x08, 0xf2, 0x4e, 0xc8, 0xb8, 0xee, 0xaa, + 0xb8, 0xba, 0x0e, 0x0f, 0x03, 0xab, 0xb3, 0xd1, 0x26, 0x8c, 0x68, 0x62, 0x18, 0x06, 0xa5, 0x97, + 0x2e, 0x9e, 0x06, 0x12, 0x7f, 0xd1, 0xe2, 0x1a, 0x9c, 0x49, 0x7c, 0x2e, 0xb9, 0x08, 0xa3, 0x7e, + 0x5a, 0x4c, 0xf6, 0xc6, 0x6d, 0x91, 0x41, 0x6b, 0x58, 0x1b, 0x66, 0xe0, 0x92, 0x63, 0xb9, 0x93, + 0x98, 0x40, 0xf2, 0x9d, 0x30, 0x1e, 0xd2, 0xb5, 0xf8, 0x82, 0xc1, 0xf3, 0x57, 0x69, 0xa3, 0x3e, + 0x65, 0x85, 0x83, 0x8b, 0x1f, 0x82, 0x91, 0xe8, 0xab, 0xb2, 0x11, 0x8c, 0x87, 0x40, 0xd1, 0x47, + 0x0c, 0x72, 0x18, 0x7f, 0xc0, 0x13, 0x30, 0x22, 0x48, 0xa2, 0xdc, 0x87, 0x39, 0xd4, 0xe7, 0xfd, + 0xfd, 0x29, 0x65, 0x24, 0x1f, 0xa1, 0x15, 0xbb, 0xab, 0xd1, 0x5b, 0xfc, 0xd5, 0x0c, 0x0c, 0x56, + 0x6c, 0xc7, 0x13, 0x89, 0xe4, 0x8e, 0xb7, 0x59, 0x29, 0x9d, 0xb8, 0x64, 0xf7, 0x70, 0xe2, 0x72, + 0x01, 0xb2, 0x52, 0xfc, 0x09, 0xbf, 0xe5, 0x35, 0x0c, 0x47, 0x43, 0xe8, 0x5b, 0x1c, 0xbb, 0x17, + 0xf7, 0x08, 0xe9, 0x3b, 0xb0, 0xdf, 0xd5, 0x4f, 0xa4, 0x01, 0x5e, 0x7f, 0xe6, 0x99, 0xfb, 0xb9, + 0x4b, 0x0f, 0xe4, 0x6c, 0x55, 0xfc, 0xd6, 0x14, 0x8c, 0x0a, 0x37, 0x0b, 0x29, 0x0d, 0x72, 0x9f, + 0xef, 0x5f, 0x23, 0xaf, 0x38, 0x1c, 0xa4, 0xf9, 0x38, 0x66, 0xc3, 0x4e, 0xdf, 0x35, 0x3d, 0xbc, + 0x69, 0x96, 0xf2, 0x20, 0x53, 0x01, 0x93, 0x6d, 0x58, 0x9f, 0x8e, 0x3c, 0xe5, 0x3b, 0x90, 0x64, + 0x42, 0xc3, 0x9d, 0x35, 0x98, 0x4e, 0x74, 0x22, 0x29, 0xfe, 0x93, 0x2c, 0x64, 0xa7, 0xef, 0xd2, + 0xfa, 0x31, 0xef, 0x58, 0xe9, 0x5a, 0x2a, 0x7b, 0xc0, 0x6b, 0xa9, 0xfd, 0x38, 0xd4, 0xbd, 0x12, + 0xf6, 0x67, 0x2e, 0xfa, 0x78, 0xa5, 0xe7, 0xd5, 0xc7, 0xfb, 0x3d, 0x7d, 0xfc, 0xfc, 0x31, 0xff, + 0x79, 0x06, 0x32, 0xd5, 0xc9, 0xca, 0x89, 0xde, 0x1c, 0xa9, 0xde, 0x74, 0xf7, 0x38, 0x2a, 0x06, + 0x4e, 0x04, 0xfd, 0x61, 0x88, 0x80, 0xe2, 0x2f, 0xf0, 0xc7, 0x19, 0x18, 0xa9, 0xce, 0x2c, 0x56, + 0xa4, 0x7b, 0xbc, 0x1b, 0xdc, 0x8d, 0x1b, 0x1d, 0x8a, 0x79, 0x97, 0x5e, 0x88, 0x99, 0xdf, 0xb7, + 0xca, 0x96, 0xf7, 0xc2, 0x73, 0x98, 0x3e, 0x19, 0x8f, 0x99, 0x79, 0xa0, 0x88, 0x6b, 0xbe, 0x49, + 0xbf, 0x0d, 0x93, 0x14, 0xf9, 0x0c, 0xc8, 0x4b, 0x90, 0xb9, 0x25, 0xbc, 0xf1, 0x3a, 0xf1, 0x79, + 0xf6, 0x1a, 0xe7, 0xc3, 0x26, 0xc1, 0x4c, 0xdb, 0x34, 0x90, 0x03, 0x6b, 0xc5, 0x1a, 0xbf, 0x26, + 0x0c, 0x8e, 0x5d, 0x35, 0x5e, 0xf1, 0x1b, 0xbf, 0x56, 0x9e, 0x22, 0x55, 0x18, 0xac, 0x50, 0xa7, + 0x69, 0x62, 0x47, 0xf9, 0x33, 0x7e, 0x77, 0x26, 0xa7, 0xb6, 0xb7, 0x0a, 0x83, 0xad, 0xb0, 0x11, + 0x32, 0x93, 0xb9, 0x90, 0x0f, 0x02, 0x70, 0x9b, 0x6c, 0x97, 0xa9, 0xf5, 0x1f, 0xc2, 0x83, 0x0b, + 0xbe, 0x37, 0x4e, 0xd8, 0xa4, 0x4a, 0xcc, 0xc8, 0x1a, 0x8c, 0xcd, 0xdb, 0x86, 0xb9, 0x6c, 0x72, + 0xbf, 0x7b, 0x7c, 0x40, 0x6e, 0x67, 0x6f, 0x57, 0xb6, 0x17, 0x6e, 0x4a, 0xed, 0x92, 0x1e, 0x13, + 0x63, 0x5c, 0xfc, 0xa7, 0xbd, 0x90, 0x65, 0xdd, 0x7e, 0x32, 0x7e, 0x0f, 0x32, 0x7e, 0x4b, 0x30, + 0x76, 0xc7, 0x76, 0xd6, 0x4c, 0x6b, 0x25, 0x08, 0xa3, 0x12, 0x87, 0x6b, 0xe8, 0x87, 0xb9, 0xc1, + 0x71, 0xb5, 0x20, 0xe2, 0x4a, 0x8b, 0x91, 0xef, 0x30, 0x82, 0x5f, 0x04, 0xe0, 0x39, 0x68, 0x90, + 0xa6, 0x3f, 0x4c, 0xac, 0xc5, 0x33, 0xd4, 0x60, 0x64, 0x96, 0x9c, 0x58, 0x2b, 0x24, 0x26, 0x97, + 0x7d, 0x4f, 0xb6, 0x01, 0x74, 0x58, 0xc5, 0x53, 0x44, 0xf4, 0x64, 0x93, 0x8d, 0x00, 0xee, 0xd3, + 0x56, 0x01, 0x90, 0xbc, 0x03, 0x40, 0x11, 0x44, 0x64, 0x72, 0x10, 0x49, 0x92, 0x13, 0x9c, 0x03, + 0x34, 0x89, 0x07, 0x79, 0x41, 0x71, 0x5f, 0x22, 0x11, 0x6e, 0x1d, 0xbd, 0x97, 0x42, 0xf7, 0xd7, + 0xa1, 0x9d, 0xdc, 0x5f, 0x8b, 0xdf, 0x93, 0x81, 0x41, 0xc6, 0xad, 0xda, 0x6e, 0x36, 0x75, 0x67, + 0xf3, 0x44, 0x91, 0x0f, 0xa2, 0xc8, 0x35, 0x18, 0x97, 0xa3, 0xa5, 0x98, 0xe1, 0xeb, 0x67, 0x63, + 0x0c, 0x8e, 0x7a, 0x54, 0x02, 0x71, 0xb1, 0xca, 0xe6, 0x7d, 0x4f, 0x80, 0xf1, 0x70, 0xd4, 0xd5, + 0xe2, 0xbc, 0x8a, 0xdf, 0x94, 0x82, 0x31, 0x15, 0x1a, 0xe8, 0x7e, 0x2a, 0x51, 0xf7, 0x9f, 0x84, + 0x01, 0xe1, 0x00, 0xa5, 0x1b, 0xc2, 0x9b, 0x7b, 0x64, 0x7b, 0xab, 0x00, 0x98, 0xf8, 0xa3, 0xe6, + 0x50, 0xdd, 0xd0, 0x42, 0x02, 0xf2, 0x3c, 0x0c, 0xe1, 0x8f, 0x3b, 0x8e, 0xe9, 0x79, 0x94, 0x77, + 0x46, 0x96, 0xdf, 0x80, 0xf2, 0x06, 0x1b, 0x1c, 0xa1, 0x45, 0xc8, 0x8a, 0x3f, 0x97, 0x86, 0x81, + 0x6a, 0x7b, 0xc9, 0xdd, 0x74, 0x3d, 0xda, 0x3c, 0xe6, 0x3a, 0xe4, 0x1f, 0x3f, 0x65, 0x13, 0x8f, + 0x9f, 0x1e, 0xf3, 0x87, 0x96, 0x74, 0xfd, 0x16, 0x6c, 0x0c, 0x7c, 0x9f, 0xf2, 0x50, 0x8b, 0x72, + 0xfb, 0xd8, 0xea, 0xfc, 0xa3, 0x34, 0x8c, 0x71, 0xd7, 0x9b, 0x29, 0xd3, 0xad, 0x1f, 0x42, 0x34, + 0xe1, 0xd1, 0xcb, 0xf4, 0x60, 0xee, 0x6a, 0xbb, 0x88, 0xd1, 0x2c, 0x7e, 0x75, 0x06, 0x06, 0x4b, + 0x6d, 0x6f, 0xb5, 0xe4, 0xe1, 0xfc, 0x76, 0xb2, 0xc3, 0xde, 0x47, 0x38, 0xd3, 0x90, 0xde, 0x6a, + 0xd5, 0x9a, 0xbe, 0xd4, 0x72, 0x51, 0x27, 0xe3, 0x52, 0xab, 0xd5, 0x81, 0xc1, 0xa0, 0x1e, 0xa2, + 0x8a, 0x3f, 0x9b, 0xe2, 0xa5, 0x39, 0x16, 0xed, 0x35, 0x6a, 0x1d, 0xc2, 0x0d, 0xe2, 0x61, 0x1c, + 0x9a, 0xfb, 0xfd, 0x99, 0xd9, 0x5b, 0x7f, 0xe2, 0xbd, 0xb7, 0x66, 0x37, 0xe8, 0xf1, 0xfe, 0x8c, + 0x43, 0xbc, 0xf7, 0xf6, 0x05, 0x72, 0x08, 0x7e, 0x16, 0x6f, 0x2f, 0x81, 0x1c, 0xc2, 0x99, 0xf2, + 0xdb, 0x43, 0x20, 0x9f, 0x4f, 0xc1, 0xc0, 0x84, 0xed, 0x1d, 0xf3, 0x81, 0x2f, 0xbe, 0xe2, 0x78, + 0xab, 0xb9, 0xff, 0x15, 0xc7, 0x5b, 0x37, 0x8b, 0xdf, 0x9c, 0x86, 0xd3, 0xa2, 0x7c, 0x98, 0x38, + 0x87, 0x3b, 0x99, 0x8e, 0xc5, 0x60, 0x8b, 0x8b, 0xe6, 0x64, 0x1e, 0x12, 0xa2, 0xf9, 0x4c, 0x0a, + 0x4e, 0xa1, 0xaf, 0xac, 0x81, 0x86, 0x48, 0xf0, 0x75, 0xa7, 0x81, 0x7b, 0xd4, 0xf1, 0x14, 0xd3, + 0x1a, 0xff, 0x41, 0x0a, 0x30, 0xf8, 0x86, 0x6d, 0x5a, 0x35, 0x1e, 0x00, 0x20, 0x32, 0x38, 0x03, + 0x03, 0x89, 0x00, 0x80, 0x87, 0x00, 0xda, 0xae, 0xbe, 0x42, 0x6b, 0x4d, 0xdb, 0x10, 0x17, 0xd3, + 0xda, 0x00, 0x42, 0xe6, 0x6d, 0x83, 0x92, 0x27, 0x60, 0x44, 0xc7, 0x5c, 0xf6, 0xd4, 0x10, 0xf9, + 0xb0, 0x71, 0xf3, 0xa0, 0x0d, 0xfb, 0x50, 0x9e, 0x11, 0xfb, 0x97, 0xd3, 0x30, 0x2e, 0xbd, 0x54, + 0xa8, 0xc7, 0xcd, 0x3d, 0x76, 0x56, 0x53, 0xea, 0x2c, 0x67, 0x9f, 0x9d, 0xe5, 0x48, 0x9d, 0xd5, + 0xde, 0x63, 0x67, 0xb5, 0xa3, 0xae, 0xd4, 0x7b, 0xb1, 0x51, 0xe7, 0x60, 0x48, 0x64, 0x21, 0xf6, + 0x98, 0x0c, 0x84, 0xa5, 0xfa, 0x60, 0xa4, 0xe2, 0x5a, 0xb4, 0xcf, 0x54, 0x6b, 0xd3, 0x0d, 0x49, + 0x54, 0x89, 0x86, 0x13, 0xf6, 0x89, 0x44, 0xf7, 0x29, 0xd1, 0x2f, 0x8f, 0x4a, 0x34, 0x9c, 0x50, + 0xee, 0x1f, 0x89, 0x16, 0xbf, 0x2b, 0x03, 0xa7, 0xb1, 0x64, 0x8a, 0x6b, 0xda, 0xd6, 0xdb, 0x60, + 0x23, 0x43, 0xea, 0x51, 0x57, 0xc8, 0xf9, 0x04, 0x57, 0xc8, 0x3f, 0xdb, 0x2a, 0xbc, 0xb0, 0x87, + 0xb2, 0x8d, 0x57, 0xaa, 0x78, 0xd4, 0xc3, 0xb8, 0xfa, 0x4e, 0x94, 0x2e, 0xc0, 0xf5, 0x60, 0xc6, + 0x14, 0x3b, 0xf5, 0xea, 0xf6, 0x56, 0xe1, 0x8c, 0x34, 0xb5, 0xee, 0xff, 0x79, 0x21, 0x6b, 0x4d, + 0x7a, 0x4c, 0xf1, 0x5f, 0xa4, 0xe0, 0x81, 0xe8, 0x12, 0xf8, 0x76, 0xd8, 0x78, 0xfe, 0x8d, 0x34, + 0x9c, 0x79, 0x0d, 0x85, 0x13, 0xb8, 0x73, 0x9f, 0x18, 0x3d, 0x62, 0x65, 0x4f, 0x90, 0xcd, 0xc9, + 0x76, 0xb4, 0xb3, 0x6c, 0x4e, 0x2c, 0x42, 0x21, 0x9b, 0x5f, 0x4c, 0xc1, 0xa9, 0x85, 0xf2, 0xd4, + 0xe4, 0xdb, 0x64, 0x44, 0xc5, 0xbf, 0xe7, 0x98, 0xef, 0x56, 0x63, 0xdf, 0x73, 0xcc, 0xf7, 0xad, + 0x9f, 0x49, 0xc3, 0xa9, 0x6a, 0x69, 0x7e, 0xee, 0xed, 0x32, 0x83, 0x4f, 0xca, 0xb1, 0x47, 0xfe, + 0x29, 0xbe, 0xb0, 0x05, 0xe4, 0xcf, 0xbc, 0x7d, 0xad, 0x73, 0x4c, 0x52, 0x5c, 0x28, 0xc7, 0x7c, + 0xea, 0x3e, 0x14, 0xa1, 0x30, 0xcd, 0x8f, 0x50, 0x1f, 0x73, 0xcd, 0xff, 0x67, 0x39, 0x18, 0xbc, + 0xd1, 0x5e, 0xa2, 0xc2, 0xa3, 0xf5, 0xbe, 0xbe, 0xba, 0xba, 0x06, 0x83, 0x42, 0x0c, 0x78, 0x45, + 0x2b, 0xa5, 0x53, 0x17, 0xe9, 0x24, 0x79, 0x56, 0x58, 0x99, 0x88, 0x5c, 0x80, 0xec, 0x6d, 0xea, + 0x2c, 0xc9, 0xa9, 0x75, 0xd6, 0xa9, 0xb3, 0xa4, 0x21, 0x94, 0xcc, 0x49, 0x8e, 0xeb, 0x95, 0x32, + 0xd6, 0xe4, 0x15, 0x5e, 0x0f, 0x58, 0x64, 0x38, 0x08, 0xc1, 0xd0, 0x5b, 0x26, 0xaf, 0xe6, 0x2b, + 0x67, 0x05, 0x53, 0x5b, 0x92, 0x9b, 0x30, 0x1e, 0x09, 0xcd, 0xc0, 0x82, 0xb4, 0xfd, 0x09, 0xec, + 0x92, 0x4a, 0xd1, 0xc6, 0x9b, 0x92, 0x57, 0x60, 0xc8, 0x07, 0xa2, 0x87, 0xff, 0x40, 0x58, 0x22, + 0x2f, 0x60, 0xa5, 0x94, 0x16, 0x8b, 0x34, 0x90, 0x19, 0xe0, 0x2d, 0x2c, 0x24, 0x30, 0x50, 0x02, + 0x63, 0x22, 0x0d, 0xc8, 0xf3, 0xc8, 0x00, 0x23, 0xcc, 0xd1, 0xe3, 0x73, 0x10, 0xf3, 0xce, 0xe0, + 0x0d, 0xb6, 0x23, 0xe0, 0x3c, 0xbb, 0x50, 0x84, 0x8c, 0x2c, 0x00, 0x84, 0x9e, 0x79, 0xc2, 0x8f, + 0x7e, 0xcf, 0x3e, 0x83, 0x12, 0x0b, 0xd9, 0x15, 0x61, 0x78, 0x3f, 0xae, 0x08, 0xc5, 0x2f, 0x64, + 0x60, 0x50, 0xba, 0x74, 0x23, 0x4f, 0x41, 0xae, 0xd4, 0x6a, 0xdd, 0xd2, 0xca, 0x72, 0xa1, 0x30, + 0xbd, 0xd5, 0xaa, 0xb5, 0x1d, 0x53, 0x8e, 0x0c, 0xe3, 0x44, 0x64, 0x12, 0x86, 0x4b, 0xad, 0x56, + 0xa5, 0xbd, 0xd4, 0x30, 0xeb, 0x52, 0xf1, 0x59, 0x4c, 0x41, 0xc7, 0x5a, 0xb5, 0x10, 0xa3, 0xd6, + 0x9f, 0x8d, 0xb6, 0x21, 0x1f, 0xc5, 0xc4, 0xa9, 0xa2, 0x12, 0x2c, 0x2f, 0x31, 0x5a, 0x4c, 0xb8, + 0x10, 0xbc, 0x12, 0x10, 0x89, 0xd2, 0xaf, 0x7e, 0x8d, 0x3c, 0xf6, 0xa0, 0x58, 0xe9, 0xd7, 0x90, + 0x25, 0x79, 0x1a, 0xfa, 0x4a, 0xad, 0x96, 0x74, 0xdd, 0x8e, 0x9e, 0xb9, 0xac, 0x95, 0xd2, 0xc7, + 0x3e, 0x99, 0xf8, 0x2c, 0xe1, 0xa0, 0x63, 0x3b, 0x1e, 0x0e, 0xa9, 0xe1, 0xf0, 0xb3, 0x7c, 0x8f, + 0x1e, 0x5b, 0xce, 0x55, 0xa8, 0x45, 0xdb, 0xb0, 0xc7, 0xfa, 0x05, 0xd9, 0x73, 0xe1, 0x63, 0x13, + 0x2a, 0xb1, 0xfb, 0x64, 0xe7, 0x5f, 0x86, 0x91, 0xe8, 0x37, 0xee, 0xa9, 0x02, 0xdc, 0x9f, 0xa6, + 0x50, 0x8e, 0xc7, 0x3c, 0xa0, 0xf2, 0x59, 0xc8, 0x94, 0x5a, 0x2d, 0x31, 0x0d, 0xee, 0xe2, 0x5e, + 0x98, 0x51, 0xfb, 0x9f, 0x7e, 0xcc, 0x23, 0xb3, 0xf7, 0xf5, 0xe9, 0x9f, 0xe7, 0x9f, 0x7e, 0xbc, + 0xa3, 0xa6, 0x8b, 0xdf, 0x92, 0x85, 0xd1, 0x52, 0xab, 0x75, 0x52, 0xb8, 0xed, 0xb0, 0x52, 0x5c, + 0x3d, 0x03, 0x20, 0xcd, 0xca, 0x7d, 0x41, 0x8a, 0x89, 0x41, 0x69, 0x46, 0xce, 0xa7, 0x34, 0x89, + 0xc8, 0x57, 0xbf, 0xfe, 0xbd, 0xa8, 0x5f, 0xe8, 0x04, 0x38, 0xb0, 0xd7, 0x1c, 0x98, 0xb0, 0xeb, + 0x1c, 0x98, 0x5f, 0x9e, 0xc1, 0xe9, 0xf8, 0xb8, 0x67, 0x14, 0xbe, 0x57, 0x54, 0x43, 0xf4, 0x73, + 0x6e, 0x4f, 0xd3, 0xcc, 0x4f, 0x65, 0xe4, 0x01, 0x8a, 0xd5, 0xc0, 0x4e, 0x7a, 0xa1, 0xf7, 0x40, + 0x16, 0xff, 0x88, 0x2c, 0x4c, 0x91, 0xa7, 0xd4, 0xaf, 0x45, 0x2d, 0xd2, 0xd4, 0xd6, 0x19, 0x0a, + 0x93, 0xd5, 0x46, 0x69, 0xfd, 0x3e, 0xec, 0xdb, 0x53, 0x1f, 0x6e, 0xa5, 0x31, 0x33, 0x56, 0x90, + 0xb4, 0xf7, 0xe0, 0x1b, 0xa7, 0xab, 0x00, 0xfc, 0x36, 0x23, 0x08, 0x7b, 0x1a, 0xe6, 0x09, 0x36, + 0x79, 0x89, 0x6a, 0x91, 0x60, 0x33, 0x24, 0x09, 0xbc, 0x48, 0x33, 0x89, 0x5e, 0xa4, 0x97, 0xa1, + 0x5f, 0xd3, 0x37, 0x3e, 0xd0, 0xa6, 0x22, 0x96, 0xd8, 0xcf, 0x89, 0xaf, 0x6f, 0xd4, 0x3e, 0xc6, + 0x80, 0x5a, 0x80, 0x26, 0xc5, 0x20, 0xb5, 0x9a, 0xe4, 0xad, 0xc7, 0x8f, 0xff, 0x83, 0x84, 0x6a, + 0xfb, 0x51, 0x74, 0xf2, 0x5e, 0xc8, 0x94, 0xee, 0x54, 0x85, 0x64, 0xcf, 0x4b, 0xd1, 0xb1, 0x42, + 0x5e, 0x1d, 0xdb, 0xde, 0xa9, 0x16, 0xbf, 0x3f, 0x0b, 0xa7, 0x43, 0x01, 0xcf, 0xcd, 0xcd, 0x4b, + 0x32, 0xde, 0xf7, 0xcd, 0xd6, 0xb3, 0x90, 0xd1, 0x5b, 0x2d, 0x31, 0x50, 0x76, 0xf3, 0x15, 0x7a, + 0xab, 0x45, 0x9e, 0x83, 0x9c, 0xbb, 0x87, 0x0c, 0x54, 0x6e, 0x10, 0x0f, 0xc9, 0x7a, 0x43, 0x76, + 0x4a, 0xe5, 0xbd, 0xd3, 0x12, 0x11, 0x2a, 0xcd, 0x8e, 0x22, 0xe7, 0xff, 0x93, 0x4b, 0xd0, 0xdf, + 0x72, 0xec, 0x75, 0xd3, 0xa0, 0x91, 0x34, 0xa0, 0x3e, 0x4c, 0x0b, 0xfe, 0x62, 0x0b, 0x47, 0xd3, + 0x36, 0x68, 0x43, 0xac, 0x4d, 0xb8, 0x70, 0x20, 0x40, 0x5e, 0x38, 0x10, 0x40, 0x5e, 0x86, 0x51, + 0xbf, 0x2c, 0x82, 0x51, 0xe3, 0x8d, 0xfa, 0xc3, 0x46, 0x0a, 0x4a, 0x1b, 0x09, 0x00, 0xf3, 0xd8, + 0xfa, 0x06, 0x8c, 0x9b, 0x56, 0xab, 0xed, 0xf1, 0x8b, 0x4b, 0x11, 0xe9, 0x3b, 0x80, 0x15, 0x36, + 0x30, 0xc1, 0x75, 0x0c, 0x29, 0x47, 0x74, 0x23, 0x92, 0x5f, 0xf6, 0x60, 0x38, 0xf0, 0x4d, 0x20, + 0x76, 0xdb, 0x53, 0xb9, 0x01, 0x72, 0xc3, 0xad, 0x6c, 0x1c, 0x2b, 0xef, 0x8c, 0x39, 0x36, 0xe4, + 0x57, 0xbc, 0x01, 0x30, 0xbb, 0xb8, 0x58, 0x99, 0xa5, 0x3a, 0x93, 0xc9, 0x05, 0x40, 0x27, 0x60, + 0xd9, 0xc7, 0x9a, 0x3b, 0x05, 0xb3, 0x7f, 0x49, 0x21, 0x62, 0xf9, 0xf3, 0x94, 0x20, 0x08, 0x10, + 0x9b, 0x80, 0xe2, 0x37, 0x65, 0xe1, 0x4c, 0xa8, 0x7e, 0x8c, 0xef, 0x61, 0xe8, 0x9f, 0x7f, 0x2b, + 0x9a, 0xde, 0xe3, 0xad, 0xe8, 0x2b, 0xd0, 0xe7, 0xee, 0x6b, 0xa6, 0x16, 0xad, 0x7c, 0xc5, 0xcf, + 0xee, 0x49, 0xf1, 0x9f, 0x02, 0xf0, 0xcf, 0x3c, 0x4c, 0x5f, 0x51, 0xd1, 0x13, 0x3d, 0x84, 0x6a, + 0x41, 0xf9, 0x0e, 0x43, 0xd2, 0xe9, 0x5c, 0x47, 0x9d, 0x7e, 0x00, 0x32, 0x6d, 0xc7, 0xd7, 0x53, + 0xcc, 0x61, 0xde, 0x76, 0x1a, 0x1a, 0xfb, 0x87, 0x3c, 0x0b, 0x43, 0xab, 0x9e, 0xd7, 0xaa, 0x89, + 0x5c, 0xb3, 0x42, 0x2d, 0xf1, 0xe4, 0x45, 0x86, 0x6b, 0x83, 0xec, 0x97, 0x9f, 0x84, 0xf6, 0x45, + 0xe8, 0x5b, 0xc5, 0xfe, 0xe6, 0x59, 0x09, 0xa5, 0x2c, 0x00, 0xa1, 0x2a, 0xf0, 0x20, 0x53, 0x41, + 0xa6, 0xf9, 0x7f, 0x90, 0xe7, 0x20, 0x9c, 0x0c, 0x85, 0x01, 0xc5, 0xeb, 0x72, 0xfb, 0x40, 0x39, + 0xcc, 0xd4, 0x11, 0x73, 0x65, 0xf1, 0xa7, 0xd3, 0xf0, 0x70, 0xa2, 0x5e, 0x4c, 0xd8, 0xc6, 0x66, + 0xb0, 0x94, 0xef, 0x5b, 0x41, 0xa4, 0x8e, 0x4e, 0xef, 0xab, 0xa3, 0xa3, 0x7d, 0x96, 0xd9, 0xa9, + 0xcf, 0x9e, 0x86, 0x41, 0xb1, 0x2e, 0x62, 0xa1, 0x52, 0x5e, 0x66, 0x14, 0x57, 0x1d, 0x09, 0xac, + 0x41, 0x3d, 0xac, 0x58, 0xfa, 0x38, 0xf4, 0x99, 0x6e, 0xad, 0xa1, 0xbb, 0x7c, 0x23, 0x2f, 0xf2, + 0xb9, 0x0b, 0x90, 0x96, 0x33, 0xdd, 0x39, 0xdd, 0xc5, 0x08, 0x87, 0xc0, 0x25, 0x79, 0x88, 0x8f, + 0x3e, 0xf6, 0x5b, 0xc3, 0x7f, 0x8b, 0x7f, 0x98, 0x85, 0xb3, 0xaa, 0x10, 0xc3, 0x14, 0x83, 0x47, + 0x27, 0x3c, 0x31, 0x4a, 0x32, 0x07, 0x18, 0x25, 0xd9, 0x5d, 0x48, 0x5c, 0x5a, 0xd0, 0xc5, 0x61, + 0x48, 0x7c, 0x9d, 0x77, 0xc3, 0x75, 0x3e, 0x6c, 0xe1, 0xd1, 0xbb, 0x9e, 0x18, 0x5c, 0x72, 0x0b, + 0x06, 0xf6, 0x5b, 0x2c, 0xd2, 0xbb, 0x5e, 0x6c, 0x28, 0xf5, 0xed, 0x71, 0x28, 0xf5, 0xef, 0x71, + 0x28, 0x5d, 0x83, 0x21, 0x97, 0x5a, 0x06, 0x46, 0xe0, 0xd5, 0x9a, 0xae, 0x58, 0x11, 0xf0, 0x79, + 0x32, 0x5c, 0x03, 0xf6, 0x6b, 0xd1, 0x6c, 0xd2, 0x79, 0x6c, 0xb3, 0xa1, 0x9b, 0x5e, 0xd0, 0x06, + 0xc2, 0x36, 0x32, 0x5c, 0x03, 0xf6, 0x4b, 0xb4, 0x79, 0x89, 0x2d, 0x5e, 0x98, 0x15, 0x38, 0x68, + 0x36, 0x88, 0xcd, 0xc4, 0xe2, 0x15, 0x41, 0x69, 0xc3, 0x02, 0xc0, 0x1b, 0x17, 0x3f, 0x9f, 0x86, + 0x42, 0xb2, 0xd2, 0x9d, 0x0c, 0xdd, 0xdd, 0x0f, 0xdd, 0x2f, 0x4f, 0x03, 0x89, 0x1b, 0x70, 0xe4, + 0x05, 0x18, 0x40, 0xe8, 0x0a, 0xfb, 0xfc, 0x54, 0x98, 0xb6, 0x51, 0xdf, 0x70, 0x6b, 0x0e, 0x42, + 0x23, 0xc7, 0x89, 0x3e, 0x29, 0x79, 0x11, 0xa0, 0x74, 0xa7, 0xca, 0x36, 0x04, 0x66, 0xdd, 0x5f, + 0x8c, 0x31, 0xce, 0x8f, 0x35, 0x74, 0x39, 0x38, 0x12, 0x6a, 0x17, 0x10, 0xe3, 0x49, 0xe4, 0x9d, + 0xea, 0xac, 0xed, 0x7a, 0x42, 0x56, 0xfc, 0x24, 0x72, 0xc3, 0xad, 0xad, 0xda, 0xae, 0x17, 0x39, + 0x89, 0xe4, 0x64, 0x64, 0x06, 0x46, 0x4a, 0x77, 0xaa, 0x3c, 0x3d, 0x92, 0xa1, 0xd9, 0x0d, 0xff, + 0x08, 0xf3, 0xe1, 0xed, 0xad, 0xc2, 0x79, 0xd6, 0x90, 0xa7, 0x56, 0x32, 0x6a, 0x8e, 0x1d, 0x29, + 0xcc, 0xaf, 0xb4, 0x2a, 0x7e, 0xb2, 0x1f, 0xc6, 0xa6, 0x74, 0x4f, 0x5f, 0xd2, 0x5d, 0x2a, 0xdd, + 0xdf, 0x8c, 0xfa, 0x30, 0xff, 0x73, 0x24, 0x39, 0x18, 0x4b, 0x09, 0x5f, 0xa3, 0x36, 0x20, 0x2f, + 0x85, 0x7c, 0x2b, 0x8e, 0xed, 0xd9, 0x75, 0xbb, 0x21, 0x64, 0x82, 0xfd, 0x6a, 0x2c, 0xd5, 0x5a, + 0x02, 0xac, 0xc5, 0x08, 0xc9, 0x93, 0x30, 0xe8, 0xc3, 0x6e, 0x69, 0x65, 0x21, 0x13, 0x5c, 0x83, + 0x8d, 0xa5, 0x5a, 0xdb, 0x31, 0x35, 0x19, 0x4d, 0x5e, 0x84, 0x21, 0xff, 0xa7, 0x74, 0x98, 0x8b, + 0x27, 0x08, 0xc6, 0x52, 0xec, 0xbc, 0x5e, 0x26, 0x95, 0x9b, 0xe2, 0xb6, 0xb3, 0x37, 0xd2, 0x94, + 0xd9, 0x2b, 0x49, 0x4d, 0x71, 0xc7, 0xf9, 0x31, 0x18, 0xf1, 0x7f, 0x8b, 0x23, 0x6a, 0x1e, 0x6b, + 0xf7, 0xa4, 0x3f, 0x54, 0x54, 0xb1, 0x5e, 0x89, 0x92, 0xf3, 0xc3, 0xea, 0x07, 0xc5, 0x61, 0xf5, + 0x29, 0x63, 0x29, 0x7e, 0x56, 0xad, 0x3c, 0x80, 0x94, 0x61, 0xdc, 0x87, 0x84, 0x1a, 0xda, 0x17, + 0xde, 0x51, 0x18, 0x4b, 0xb5, 0x44, 0x25, 0x8d, 0xb7, 0x22, 0x0d, 0xb8, 0x10, 0x01, 0x1a, 0xee, + 0xaa, 0xb9, 0xec, 0x89, 0x0b, 0x06, 0x51, 0x6b, 0x70, 0x60, 0xe2, 0xd2, 0xf6, 0x56, 0xe1, 0xf1, + 0x80, 0x2b, 0xa7, 0xa9, 0x89, 0xa4, 0x4a, 0xd1, 0xd4, 0xc6, 0x5d, 0xb9, 0x91, 0x2a, 0x9c, 0xf6, + 0xf1, 0xaf, 0x4d, 0x56, 0x2a, 0x8e, 0xfd, 0x06, 0xad, 0x7b, 0xe5, 0x29, 0x71, 0x38, 0x84, 0xe6, + 0xb6, 0xb1, 0x54, 0x5b, 0xa9, 0xb7, 0x98, 0x52, 0x30, 0x5c, 0x94, 0x79, 0x62, 0x63, 0x72, 0x1b, + 0xce, 0x48, 0xf0, 0xb2, 0xe5, 0x7a, 0xba, 0x55, 0xa7, 0x41, 0x22, 0x66, 0x34, 0xbb, 0x05, 0x57, + 0x53, 0x20, 0xa3, 0x6c, 0x93, 0x9b, 0x93, 0x97, 0x61, 0xd8, 0x47, 0x70, 0xbf, 0xb5, 0x41, 0xf4, + 0x5b, 0xc3, 0x21, 0x69, 0x2c, 0xd5, 0xd4, 0x2c, 0x7e, 0x51, 0x62, 0x59, 0xa3, 0x30, 0xa9, 0xed, + 0x50, 0x44, 0xa3, 0x94, 0xec, 0xb5, 0x11, 0x52, 0xf2, 0x4a, 0xa8, 0x51, 0x0b, 0x8e, 0xb9, 0x62, + 0xfa, 0xd5, 0xfa, 0xcf, 0x09, 0xfd, 0xb0, 0x11, 0x98, 0xa4, 0x1f, 0x9c, 0xfc, 0x7c, 0x09, 0x4e, + 0x25, 0xe8, 0xd8, 0x9e, 0x2e, 0x0b, 0x3e, 0x91, 0x0e, 0x5f, 0xe2, 0x98, 0xdf, 0x18, 0x4c, 0x40, + 0xbf, 0xff, 0x25, 0x62, 0xb3, 0x90, 0xef, 0x34, 0x34, 0x55, 0x1e, 0x3e, 0x3e, 0x22, 0x8e, 0x63, + 0x7e, 0x8b, 0x70, 0x18, 0xe2, 0xf8, 0x42, 0x2a, 0x14, 0xc7, 0x31, 0xbf, 0x59, 0xf8, 0xee, 0xde, + 0x70, 0x4e, 0x3a, 0xb9, 0x5e, 0x38, 0xac, 0xd3, 0xcb, 0xd0, 0xe5, 0x39, 0xb7, 0x07, 0x27, 0x72, + 0x59, 0x35, 0xfb, 0xf6, 0xa7, 0x9a, 0xe4, 0x65, 0x18, 0xac, 0xd8, 0xae, 0xb7, 0xe2, 0x50, 0xb7, + 0x12, 0xd4, 0xca, 0xc5, 0xac, 0x7c, 0x2d, 0x01, 0xae, 0xb5, 0xa2, 0xc9, 0xf8, 0x25, 0x72, 0xa9, + 0x46, 0xc1, 0xc0, 0x3e, 0x6a, 0x14, 0xd4, 0x00, 0x26, 0x4b, 0x0b, 0xeb, 0xd4, 0x71, 0x4c, 0xc3, + 0x2f, 0x40, 0xf7, 0x78, 0xc0, 0x21, 0xc0, 0x4c, 0x52, 0xc7, 0xe3, 0x19, 0x3c, 0xe8, 0x14, 0xf5, + 0x74, 0xb3, 0xe1, 0x8a, 0x74, 0xe9, 0x7a, 0xcd, 0x16, 0x24, 0xb2, 0x7d, 0x18, 0x36, 0x2c, 0xfe, + 0x46, 0x26, 0xa6, 0xac, 0xfc, 0xb8, 0xf3, 0x58, 0x2a, 0xeb, 0x21, 0xcc, 0x44, 0xe4, 0x5a, 0xb8, + 0x48, 0xf3, 0x73, 0xe1, 0x5e, 0xa9, 0x68, 0xd0, 0x92, 0x38, 0x16, 0x8e, 0x92, 0x90, 0x0f, 0xc3, + 0xb9, 0x08, 0xa0, 0xa2, 0x3b, 0x7a, 0x93, 0x7a, 0x6c, 0x97, 0x98, 0x0b, 0xcb, 0x40, 0xf8, 0xad, + 0x6b, 0xad, 0x00, 0x2d, 0xf5, 0x4a, 0x27, 0x0e, 0x92, 0xe6, 0xf7, 0xed, 0xc1, 0xd9, 0xff, 0x73, + 0x99, 0xd0, 0x0e, 0x8b, 0x96, 0x73, 0xd3, 0xa8, 0xdb, 0x6e, 0x78, 0xf7, 0x6f, 0x07, 0xef, 0xaf, + 0x3e, 0xf7, 0x2c, 0x8c, 0x96, 0x96, 0x97, 0x69, 0xdd, 0xf3, 0x8b, 0x5c, 0xba, 0xa2, 0x80, 0x0f, + 0xdf, 0x17, 0x09, 0x94, 0x28, 0x5a, 0x28, 0xf7, 0xab, 0xda, 0xac, 0xf8, 0x9b, 0x59, 0xc8, 0x07, + 0xfb, 0x92, 0x20, 0x79, 0xd0, 0x11, 0xda, 0x00, 0xf7, 0x44, 0xaf, 0x98, 0x30, 0x1e, 0x0a, 0x43, + 0x64, 0x6d, 0xc9, 0xf7, 0xe2, 0xbe, 0xa7, 0xa0, 0x32, 0x0b, 0x09, 0xf9, 0x56, 0xe7, 0xbc, 0xd8, + 0xea, 0x90, 0x30, 0x39, 0x53, 0xcd, 0xe5, 0x2c, 0xb4, 0x38, 0x57, 0xf2, 0xa9, 0x14, 0x9c, 0xf6, + 0x3b, 0x65, 0x61, 0x89, 0xd9, 0xfc, 0x78, 0x34, 0xee, 0x6f, 0xb3, 0xde, 0xdb, 0xf9, 0x71, 0xbc, + 0x93, 0xae, 0x24, 0x35, 0xe6, 0x6f, 0x12, 0x64, 0x6b, 0x0e, 0x14, 0xc2, 0x46, 0x1a, 0x7e, 0x30, + 0xef, 0x6a, 0x89, 0xcf, 0x3d, 0xff, 0x1a, 0x3c, 0xd0, 0x91, 0xe5, 0x4e, 0x36, 0x76, 0xaf, 0x6c, + 0x63, 0xff, 0x7a, 0x2a, 0x9c, 0x88, 0x14, 0x21, 0x91, 0x2b, 0x00, 0x21, 0x48, 0xec, 0xba, 0xf1, + 0xe0, 0x24, 0x14, 0x9a, 0x26, 0x51, 0x90, 0x05, 0xc8, 0x09, 0xb1, 0xa4, 0x51, 0x2c, 0xef, 0xda, + 0xa1, 0x17, 0xae, 0xc8, 0x72, 0xc0, 0x1d, 0xb5, 0xf8, 0x66, 0xc1, 0xe6, 0xfc, 0x8b, 0x30, 0xb8, + 0xdf, 0xef, 0xfa, 0x54, 0x06, 0x88, 0xbc, 0x45, 0x3e, 0xc2, 0xfd, 0xc3, 0x31, 0x9e, 0xc2, 0x2e, + 0x41, 0x3f, 0xfb, 0x04, 0xbc, 0xf0, 0x91, 0xae, 0xca, 0xda, 0x02, 0xa6, 0x05, 0xd8, 0xb0, 0x62, + 0x43, 0x5f, 0x72, 0xc5, 0x86, 0xe2, 0x37, 0x65, 0xe0, 0xac, 0xdc, 0x21, 0x53, 0x14, 0x6b, 0xf8, + 0x9e, 0x74, 0xca, 0x5b, 0xd8, 0x29, 0x45, 0xc8, 0xf1, 0x9d, 0x91, 0x28, 0xa6, 0xcc, 0x4f, 0xad, + 0x10, 0xa2, 0x09, 0x4c, 0xf1, 0x5f, 0xa7, 0x61, 0x38, 0xb0, 0x3e, 0x75, 0xc7, 0xbd, 0x8f, 0xbb, + 0xe3, 0x3d, 0x30, 0x8c, 0x39, 0xf7, 0x9b, 0xd4, 0xe2, 0x79, 0xe9, 0x7b, 0xa5, 0xea, 0xe7, 0x3e, + 0x02, 0xcf, 0xf0, 0xb4, 0x28, 0x21, 0xd3, 0x7e, 0x6e, 0xf9, 0x49, 0x95, 0x10, 0xb8, 0xd9, 0xc7, + 0xe1, 0xc5, 0xef, 0xc8, 0xc0, 0x90, 0x2f, 0xe5, 0x09, 0xf3, 0xb8, 0x7a, 0x07, 0x1d, 0xad, 0x90, + 0xaf, 0x02, 0x54, 0x6c, 0xc7, 0xd3, 0x1b, 0x37, 0x43, 0xcd, 0xc7, 0xf3, 0xdb, 0x16, 0x42, 0x79, + 0x1b, 0x89, 0x04, 0xd7, 0xaf, 0xd0, 0xac, 0xe6, 0x13, 0x13, 0x5f, 0xbf, 0x02, 0xa8, 0x26, 0x51, + 0x14, 0x7f, 0x34, 0x0d, 0xa3, 0x7e, 0x27, 0x4d, 0xdf, 0xa5, 0xf5, 0xf6, 0xfd, 0x3c, 0x37, 0x45, + 0xa5, 0xdd, 0xbb, 0xa3, 0xb4, 0x8b, 0x7f, 0x24, 0x4d, 0x24, 0x93, 0x0d, 0xfb, 0x64, 0x22, 0xf9, + 0xcb, 0xd0, 0xf1, 0xe2, 0x57, 0x65, 0xe0, 0xb4, 0x2f, 0xf5, 0x99, 0xb6, 0x85, 0x27, 0x1f, 0x93, + 0x7a, 0xa3, 0x71, 0x3f, 0xef, 0xc6, 0x07, 0x7d, 0x41, 0x2c, 0x88, 0x22, 0x36, 0xc3, 0xfc, 0xfe, + 0x73, 0x59, 0x80, 0x6b, 0xb6, 0x69, 0x68, 0x32, 0x11, 0x79, 0x05, 0x86, 0xfc, 0x9f, 0x25, 0x67, + 0xc5, 0xdf, 0x82, 0xe3, 0x3d, 0x46, 0xd0, 0x48, 0x77, 0x22, 0xa9, 0x2e, 0x23, 0x0d, 0x8a, 0xdf, + 0x9a, 0x81, 0xf3, 0x77, 0x4c, 0xcb, 0xb0, 0x37, 0x5c, 0xe9, 0x64, 0x45, 0xce, 0xcf, 0x5d, 0x6d, + 0xa3, 0x9d, 0x2e, 0xe7, 0xe7, 0x76, 0x39, 0x48, 0xf3, 0x71, 0xe4, 0x79, 0x18, 0xaa, 0x52, 0xc7, + 0xd4, 0x1b, 0x37, 0xdb, 0xcd, 0x25, 0xea, 0x07, 0x2a, 0x60, 0xc4, 0x86, 0x8b, 0xf0, 0x9a, 0x85, + 0x08, 0x2d, 0x42, 0x46, 0x1e, 0x80, 0xcc, 0xad, 0xca, 0x4d, 0x71, 0x33, 0xc5, 0x9d, 0x3f, 0x5a, + 0x96, 0xc6, 0x60, 0xe4, 0x03, 0x70, 0x66, 0x52, 0x9b, 0x9b, 0x32, 0x5d, 0x9e, 0x49, 0xd3, 0xb4, + 0xad, 0x8a, 0x8d, 0x05, 0x1a, 0xb2, 0xe1, 0x17, 0xd6, 0x9d, 0x46, 0xcd, 0x90, 0x28, 0x6a, 0x2d, + 0x24, 0xd1, 0x92, 0x5b, 0x92, 0xcb, 0xd0, 0x7f, 0x83, 0x6e, 0xde, 0x42, 0xff, 0xd8, 0x5e, 0x0c, + 0x29, 0x41, 0x07, 0xb8, 0x35, 0xba, 0x59, 0xc3, 0x3c, 0x20, 0x5a, 0x80, 0x26, 0x13, 0x30, 0x36, + 0x7d, 0xd7, 0xa3, 0x96, 0x41, 0x8d, 0xa0, 0x09, 0x13, 0x6d, 0xaf, 0x9f, 0x77, 0x9c, 0xe3, 0x6a, + 0x61, 0xdb, 0x18, 0x3d, 0xf2, 0xb0, 0x56, 0x75, 0xab, 0x2e, 0xf1, 0xe8, 0x0b, 0x2f, 0x41, 0xa8, + 0xc0, 0x45, 0x78, 0x28, 0xf4, 0xc5, 0x1f, 0xed, 0x0f, 0x7a, 0x67, 0x8a, 0xba, 0x6b, 0x9e, 0xfd, + 0x36, 0x70, 0xe2, 0x3e, 0xb4, 0x78, 0xe0, 0x7d, 0x5a, 0xa1, 0x1f, 0x80, 0x33, 0xaa, 0x48, 0x9d, + 0xa0, 0x50, 0xab, 0xd0, 0xac, 0x0d, 0x4e, 0x50, 0x33, 0x38, 0x85, 0x7f, 0x55, 0xab, 0x25, 0xb7, + 0x64, 0x23, 0x57, 0x40, 0x24, 0x87, 0x70, 0x1c, 0xb9, 0x3e, 0x03, 0xac, 0x7c, 0x20, 0x13, 0x91, + 0x77, 0x42, 0x6e, 0xca, 0x6e, 0xea, 0xa6, 0xef, 0xd7, 0x84, 0x73, 0x6c, 0xf0, 0x5c, 0xc4, 0x68, + 0x82, 0x82, 0xf1, 0x17, 0x0f, 0xc6, 0x2e, 0x1b, 0x08, 0xf9, 0xfb, 0x0d, 0x98, 0x0d, 0xad, 0xc9, + 0x44, 0xc4, 0x86, 0x61, 0xf1, 0x38, 0x71, 0xb1, 0x0a, 0xb8, 0xb5, 0x7d, 0xde, 0x97, 0x51, 0x67, + 0xb5, 0xba, 0x12, 0x69, 0xc7, 0x37, 0xb9, 0xf8, 0x76, 0xfe, 0xc7, 0xf0, 0x2b, 0x56, 0x2d, 0xca, + 0x5f, 0x12, 0x02, 0x2e, 0x01, 0x83, 0x71, 0x21, 0xe0, 0x1a, 0x20, 0x13, 0x91, 0x69, 0x18, 0xc7, + 0x32, 0x96, 0xc1, 0x46, 0x97, 0xa9, 0xc4, 0x10, 0x9a, 0xfc, 0x78, 0x5f, 0xc7, 0x2b, 0x5f, 0xb2, + 0x8f, 0xab, 0xd5, 0x05, 0x5a, 0x8b, 0xb7, 0x60, 0xf3, 0xc8, 0xcd, 0xb9, 0x12, 0x5e, 0xf4, 0xf5, + 0xf3, 0x79, 0xc4, 0x6a, 0xe8, 0x1a, 0x83, 0x91, 0x3b, 0x30, 0xc4, 0xe6, 0xb5, 0x60, 0x98, 0x8c, + 0xa0, 0xa6, 0x14, 0x15, 0x29, 0x24, 0x4c, 0x7d, 0xc2, 0x73, 0x22, 0x44, 0x68, 0x11, 0x46, 0xca, + 0x71, 0xf5, 0xe8, 0xa1, 0x1f, 0x57, 0x9f, 0x7f, 0x15, 0x48, 0xbc, 0x23, 0xf6, 0x74, 0x0d, 0xf9, + 0x57, 0xe1, 0x42, 0xb7, 0x17, 0x61, 0xbb, 0x2c, 0xdc, 0xca, 0x72, 0xc7, 0x84, 0xfe, 0x30, 0x0f, + 0xf1, 0x3a, 0xd5, 0xc4, 0xff, 0xe4, 0x25, 0x18, 0x15, 0x41, 0x0d, 0x6c, 0x9e, 0x5a, 0xd5, 0xdd, + 0x55, 0x31, 0xb9, 0xa3, 0x87, 0x8d, 0x82, 0xd2, 0x86, 0x39, 0xe0, 0x06, 0xdd, 0x9c, 0xd5, 0xdd, + 0xd5, 0xe2, 0x5f, 0xef, 0x85, 0xfc, 0x9c, 0x69, 0xb5, 0xef, 0x9e, 0x4c, 0x5e, 0x87, 0x34, 0x79, + 0x29, 0x33, 0x4d, 0x6e, 0x37, 0x33, 0xcd, 0x93, 0x30, 0x80, 0x9d, 0x80, 0x12, 0xeb, 0x0b, 0x0f, + 0xc3, 0x1a, 0x0c, 0xc8, 0x67, 0x8e, 0x90, 0x80, 0x34, 0xd5, 0x79, 0x83, 0xfb, 0x7e, 0x3d, 0xeb, + 0xbf, 0x5e, 0xa7, 0xfe, 0x3c, 0x9c, 0x59, 0x63, 0x60, 0x17, 0xb3, 0xc6, 0x21, 0x8c, 0x8c, 0x5f, + 0xc9, 0x86, 0x87, 0x6c, 0xc7, 0x3d, 0xf2, 0xe5, 0x30, 0x4c, 0xcf, 0x8f, 0xc0, 0x40, 0x58, 0x7a, + 0x6e, 0xe7, 0x5c, 0xfe, 0x8f, 0x89, 0xa3, 0xdf, 0x73, 0x7e, 0x28, 0x05, 0xd6, 0x9e, 0x53, 0x52, + 0xed, 0x87, 0x1c, 0xc9, 0xeb, 0xd0, 0x37, 0xcd, 0xfd, 0xfc, 0x76, 0x91, 0xc7, 0x3f, 0x28, 0xcf, + 0x17, 0x32, 0xb7, 0x5b, 0xb1, 0xea, 0xc0, 0x9c, 0x9d, 0x32, 0x1e, 0xfb, 0x0e, 0x30, 0x1e, 0xdf, + 0x0f, 0x43, 0x15, 0xdd, 0xf1, 0xcc, 0xba, 0xd9, 0xd2, 0x99, 0x9d, 0xd9, 0x8f, 0xa6, 0x1a, 0xbf, + 0xfb, 0x94, 0xe0, 0xb2, 0x21, 0x2d, 0xd3, 0x17, 0xff, 0x28, 0x05, 0xe3, 0xb1, 0x42, 0xd2, 0xe4, + 0x59, 0x00, 0x0e, 0x91, 0x8a, 0xaa, 0x71, 0x6f, 0xfc, 0xa0, 0xb8, 0xb4, 0xd8, 0x1a, 0x85, 0x64, + 0xe4, 0x2a, 0xf4, 0xf3, 0x5f, 0xa2, 0x5c, 0x44, 0xbc, 0x49, 0xbb, 0x6d, 0x1a, 0x5a, 0x40, 0x14, + 0x3e, 0x05, 0x9d, 0x65, 0x32, 0x89, 0x4d, 0xbc, 0xcd, 0x56, 0xf0, 0x14, 0x74, 0x94, 0x99, 0x84, + 0xe1, 0xf9, 0x99, 0xd2, 0x6d, 0x53, 0x9f, 0x70, 0xec, 0x0d, 0x57, 0x5c, 0x7a, 0xf7, 0xf3, 0x30, + 0x5c, 0xd6, 0x6e, 0xdd, 0xd4, 0x6b, 0x4b, 0x1c, 0x25, 0x3b, 0xea, 0x44, 0xda, 0x14, 0x3f, 0x99, + 0x86, 0xa1, 0xe0, 0xab, 0x4b, 0xc6, 0x51, 0x0d, 0xa1, 0x9c, 0x28, 0xec, 0x9d, 0xd9, 0xa9, 0xb0, + 0xb7, 0x32, 0xab, 0x8a, 0x4a, 0xde, 0x87, 0x97, 0xa2, 0xe6, 0xd3, 0x69, 0x18, 0x0d, 0x9e, 0x7a, + 0x84, 0xce, 0x1d, 0xf7, 0x90, 0x48, 0x3e, 0x95, 0x82, 0xfc, 0x84, 0xd9, 0x68, 0x98, 0xd6, 0x4a, + 0xd9, 0x5a, 0xb6, 0x9d, 0x26, 0xda, 0x6c, 0x47, 0x77, 0x07, 0x58, 0xfc, 0xda, 0x14, 0x8c, 0x8b, + 0x17, 0x9a, 0xd4, 0x1d, 0xe3, 0xe8, 0x2e, 0x58, 0xd4, 0x37, 0x39, 0x3a, 0x7d, 0x29, 0xfe, 0x70, + 0x1a, 0x60, 0xce, 0xae, 0xaf, 0x1d, 0xf3, 0x0c, 0x37, 0x2f, 0x41, 0x8e, 0xe7, 0x08, 0x10, 0x1a, + 0x3b, 0x2e, 0x32, 0xb9, 0xb0, 0x4f, 0xe3, 0x88, 0x89, 0x31, 0xb1, 0xbc, 0xe4, 0x78, 0x9a, 0x81, + 0x7c, 0x4a, 0x13, 0x4d, 0xd8, 0x43, 0x19, 0x9d, 0x58, 0xff, 0x82, 0x87, 0x32, 0x58, 0xf4, 0xa1, + 0xdb, 0x5b, 0x85, 0x6c, 0xc3, 0xae, 0xaf, 0x69, 0x48, 0x5f, 0xfc, 0x6f, 0x29, 0x2e, 0xbb, 0x63, + 0x9e, 0xa7, 0xcb, 0xff, 0xfc, 0xec, 0x1e, 0x3f, 0xff, 0xeb, 0x53, 0x70, 0x5a, 0xa3, 0x75, 0xb6, + 0x9b, 0xd9, 0x9c, 0xb4, 0x0d, 0xfa, 0x1a, 0xb5, 0xa8, 0x73, 0x54, 0x23, 0xea, 0xc7, 0x52, 0x30, + 0x26, 0xbf, 0xcc, 0x2d, 0x97, 0x1e, 0xcd, 0x9a, 0x14, 0x9a, 0xf7, 0x99, 0x3d, 0x78, 0xb2, 0xfc, + 0x45, 0x1f, 0xe4, 0x13, 0x37, 0xe6, 0xc7, 0xd6, 0x3a, 0xed, 0x78, 0xda, 0x92, 0x3d, 0xac, 0xd3, + 0x96, 0xde, 0xbd, 0x9d, 0xb6, 0xe4, 0xf6, 0x7a, 0xda, 0xd2, 0xb7, 0x9b, 0xd3, 0x96, 0x9d, 0x76, + 0x4d, 0x9d, 0x3a, 0x75, 0x9f, 0xbb, 0xa6, 0x88, 0xbd, 0x3e, 0xf0, 0x56, 0xda, 0xeb, 0x70, 0xb8, + 0xf6, 0xfa, 0x7e, 0x0e, 0x89, 0x2e, 0xb1, 0x49, 0xb1, 0x6e, 0x3b, 0x06, 0x35, 0xc4, 0xd9, 0x10, + 0x5e, 0x1b, 0x3b, 0x02, 0xa6, 0x05, 0x58, 0xf2, 0x9c, 0x62, 0xc3, 0x0f, 0xa3, 0x0d, 0x8f, 0xec, + 0x65, 0x1b, 0x3e, 0x6a, 0xb9, 0x2b, 0xa6, 0xce, 0xc8, 0xfe, 0x4d, 0x9d, 0x43, 0xd8, 0x98, 0xfe, + 0xa7, 0x1c, 0x9c, 0x4d, 0xd8, 0x61, 0x1f, 0xdb, 0xe1, 0x7f, 0x78, 0xe7, 0x25, 0xfb, 0x19, 0xf5, + 0x91, 0x93, 0x8f, 0xdc, 0x4e, 0x27, 0x1f, 0x6b, 0xea, 0x18, 0xee, 0xc3, 0x31, 0xfc, 0x4c, 0x97, + 0x93, 0x8f, 0x43, 0x1a, 0xc1, 0xfd, 0x6f, 0xe5, 0x08, 0x1e, 0x78, 0x4b, 0x47, 0x30, 0xec, 0x75, + 0x04, 0x0f, 0xee, 0x69, 0x04, 0x0f, 0xed, 0x66, 0x04, 0x1f, 0xc2, 0xb8, 0xfb, 0x2f, 0x69, 0x18, + 0x97, 0x4e, 0x48, 0x0f, 0xc1, 0x0c, 0x2e, 0xc1, 0xa8, 0xc4, 0x10, 0x37, 0xe6, 0xe9, 0x30, 0x10, + 0xa5, 0x4e, 0x1d, 0x4f, 0x8d, 0x63, 0x51, 0xe9, 0xd9, 0xe3, 0xcb, 0x06, 0xb5, 0x3c, 0xd3, 0xdb, + 0x14, 0xc3, 0x2f, 0x78, 0xbc, 0x0f, 0xe7, 0x82, 0x34, 0xc5, 0x2f, 0x2d, 0xa0, 0x97, 0x7c, 0xb1, + 0xb3, 0xfb, 0xf0, 0xc5, 0x5e, 0x83, 0xd3, 0xd2, 0xcb, 0x94, 0xda, 0xde, 0xaa, 0xed, 0xb0, 0xb7, + 0xe8, 0x15, 0xa5, 0x23, 0x7d, 0x5e, 0x09, 0x34, 0xe2, 0x78, 0x3b, 0xc4, 0xd4, 0x74, 0x1f, 0xa5, + 0x25, 0x32, 0x2d, 0x7e, 0x5f, 0x2a, 0xf9, 0x69, 0xe4, 0x22, 0x64, 0x51, 0x6e, 0xa9, 0x70, 0xc9, + 0x56, 0x44, 0x86, 0x78, 0xf2, 0x64, 0xb0, 0xb8, 0xa7, 0xc3, 0x2c, 0x1f, 0x7c, 0x51, 0x97, 0xb3, + 0xaa, 0x89, 0xe5, 0xfd, 0x55, 0x18, 0x12, 0xd7, 0x96, 0x37, 0xe8, 0x66, 0x50, 0xc4, 0xfb, 0xc2, + 0xf6, 0x56, 0x21, 0x2f, 0xee, 0x35, 0xf1, 0x3c, 0x3b, 0xe2, 0xe6, 0x1e, 0x69, 0x51, 0xfc, 0xde, + 0x14, 0x5c, 0xd4, 0xa8, 0x45, 0x37, 0xf4, 0xa5, 0x86, 0x7c, 0xae, 0x2e, 0x2c, 0x5f, 0x36, 0x9f, + 0x99, 0x6e, 0x53, 0xf7, 0xea, 0xab, 0x07, 0xd2, 0xa0, 0x19, 0x18, 0x92, 0x27, 0xe8, 0x3d, 0x4c, + 0xde, 0x91, 0x76, 0xc5, 0x2f, 0x66, 0xa1, 0x6f, 0xc2, 0xf6, 0xae, 0xdb, 0xa6, 0x75, 0xa0, 0xf7, + 0x09, 0x4d, 0xda, 0xf4, 0x1e, 0x4e, 0xac, 0x9f, 0xc6, 0x87, 0xe3, 0x5c, 0x21, 0x45, 0x65, 0x2e, + 0xd9, 0x5e, 0x2c, 0x3f, 0x9c, 0x20, 0x63, 0xdd, 0x29, 0xb2, 0x8f, 0x64, 0xc3, 0xee, 0x54, 0x93, + 0x8f, 0x07, 0x79, 0x48, 0x5e, 0x80, 0x01, 0x4c, 0xf9, 0x20, 0xb9, 0x2b, 0x60, 0x80, 0x25, 0x4f, + 0x12, 0xa1, 0x3c, 0x23, 0x24, 0x25, 0x1f, 0x8e, 0x94, 0x7a, 0xf4, 0x8b, 0xce, 0xaa, 0x93, 0x64, + 0xd5, 0x73, 0xda, 0x75, 0x6f, 0xa2, 0xd8, 0xa9, 0xd6, 0xe3, 0x9f, 0x6d, 0x15, 0x72, 0x9c, 0x26, + 0x52, 0xf5, 0xf1, 0x59, 0xee, 0xe9, 0x86, 0xef, 0xd4, 0x17, 0x8e, 0x7a, 0xbc, 0xc8, 0x52, 0x5e, + 0x29, 0x20, 0x54, 0xd6, 0xca, 0xfe, 0x03, 0xac, 0x95, 0x93, 0x30, 0x3c, 0x61, 0x7b, 0x52, 0x30, + 0xdf, 0x40, 0x98, 0x38, 0x90, 0x49, 0x3e, 0x39, 0x92, 0x2f, 0xda, 0x86, 0x5c, 0x86, 0x5e, 0xac, + 0x22, 0x20, 0xa6, 0x78, 0x3c, 0x4f, 0xc4, 0x32, 0x03, 0x72, 0x0e, 0x11, 0x5e, 0x15, 0xe3, 0xdb, + 0x7a, 0x61, 0xc8, 0x6f, 0x79, 0x44, 0x6a, 0xf6, 0x14, 0xe4, 0x66, 0x6d, 0xa9, 0x28, 0x3f, 0xc6, + 0x0a, 0xae, 0xda, 0xae, 0x12, 0x04, 0x29, 0x88, 0x58, 0x07, 0xdd, 0xb4, 0x0d, 0x39, 0xd2, 0x15, + 0x3b, 0xc8, 0xb2, 0x8d, 0x58, 0x6e, 0xca, 0x80, 0x90, 0xcd, 0x47, 0x18, 0x24, 0x2c, 0x39, 0xc5, + 0x28, 0x81, 0xc1, 0x88, 0x97, 0x14, 0x38, 0xb7, 0x57, 0x05, 0xee, 0xdb, 0xaf, 0x02, 0xf7, 0x1f, + 0xae, 0x02, 0x7f, 0x10, 0x86, 0xf0, 0x49, 0xd3, 0x77, 0x5b, 0xa6, 0x43, 0xdd, 0x5d, 0x18, 0x11, + 0x0f, 0x08, 0x23, 0x62, 0x98, 0xbf, 0x37, 0xe5, 0x0d, 0xd1, 0x76, 0x88, 0xb0, 0x52, 0xd4, 0x1c, + 0x0e, 0xa0, 0xe6, 0xa7, 0xa1, 0x97, 0x97, 0x6a, 0x19, 0xe4, 0xcb, 0x39, 0xfe, 0x08, 0xeb, 0xc3, + 0x0c, 0x49, 0xf5, 0x61, 0x8a, 0xbf, 0x91, 0x82, 0xbe, 0x5b, 0xd6, 0x9a, 0x65, 0x6f, 0x1c, 0x4c, + 0x3b, 0x9f, 0x85, 0x41, 0xc1, 0x46, 0x5a, 0xd2, 0xd1, 0xd1, 0xa5, 0xcd, 0xc1, 0x35, 0xe4, 0xa4, + 0xc9, 0x54, 0xe4, 0xe5, 0xa0, 0xd1, 0x64, 0x50, 0x7c, 0x86, 0x5f, 0x2d, 0xf8, 0x8d, 0xea, 0xd1, + 0x2a, 0xf6, 0x32, 0x39, 0xb9, 0x00, 0xd9, 0x29, 0xf6, 0xaa, 0x52, 0xe2, 0x20, 0x1e, 0x7f, 0xcf, + 0xa0, 0xc5, 0x4f, 0x64, 0x61, 0x44, 0xb9, 0x74, 0x78, 0x27, 0x0c, 0x88, 0x43, 0x7f, 0xd3, 0x2f, + 0xab, 0x8f, 0x9e, 0x2e, 0x01, 0x50, 0xeb, 0xe7, 0x7f, 0x96, 0x0d, 0xf2, 0x7e, 0xe8, 0xb3, 0x5d, + 0xb4, 0x44, 0xf0, 0x5b, 0x46, 0xc2, 0xe1, 0xb6, 0x50, 0x65, 0xef, 0xce, 0x07, 0x92, 0x20, 0x91, + 0xb5, 0xd7, 0x76, 0xf1, 0xd3, 0x9e, 0x83, 0x01, 0xdd, 0x75, 0xa9, 0x57, 0xf3, 0xf4, 0x15, 0xb9, + 0xd2, 0x7e, 0x00, 0x94, 0x47, 0x12, 0x02, 0x17, 0xf5, 0x15, 0xf2, 0x2a, 0x0c, 0xd7, 0x1d, 0x8a, + 0xb6, 0x8a, 0xde, 0x08, 0xb3, 0x64, 0x08, 0xaf, 0x1e, 0x09, 0x21, 0xaf, 0xc1, 0x21, 0xa2, 0x6c, + 0x90, 0xdb, 0x30, 0x2c, 0x3e, 0x87, 0x07, 0xf4, 0xe2, 0xa0, 0x1c, 0x09, 0x57, 0x47, 0x2e, 0x12, + 0x1e, 0xd2, 0x2b, 0xe2, 0xba, 0x65, 0x72, 0x99, 0xaf, 0x21, 0x91, 0x92, 0x05, 0x20, 0x1b, 0x74, + 0x09, 0x6d, 0x16, 0xf6, 0x2c, 0x5e, 0x28, 0x1a, 0xc7, 0x71, 0x3f, 0x0f, 0x86, 0x8e, 0x63, 0xe5, + 0x18, 0xf1, 0x0d, 0xba, 0x54, 0x8a, 0x20, 0xc9, 0x1d, 0x38, 0x13, 0x6f, 0xc2, 0x3e, 0x99, 0xbb, + 0x7d, 0x3c, 0xb6, 0xbd, 0x55, 0x28, 0x24, 0x12, 0x48, 0x6c, 0x4f, 0xc5, 0xd8, 0x96, 0x8d, 0xeb, + 0xd9, 0xfe, 0xbe, 0xb1, 0x7e, 0x6d, 0x84, 0xb5, 0xf5, 0xed, 0x76, 0xd3, 0x28, 0xfe, 0x7c, 0x8a, + 0xd9, 0xe7, 0xec, 0x83, 0xa6, 0x99, 0x20, 0x0e, 0x94, 0xc0, 0xe2, 0x62, 0x90, 0xe7, 0x2a, 0x71, + 0x26, 0x0e, 0x32, 0x5b, 0x5d, 0x81, 0x9c, 0x21, 0x5f, 0x36, 0x9c, 0x8d, 0x76, 0x82, 0xff, 0x1c, + 0x4d, 0x50, 0x91, 0x4b, 0x22, 0xe9, 0x51, 0xb6, 0xb3, 0x41, 0xc3, 0xd3, 0x1c, 0x15, 0xbf, 0x2c, + 0x0d, 0x43, 0xd2, 0xd7, 0x5c, 0x3b, 0xd0, 0xe7, 0xbc, 0x77, 0x77, 0xaf, 0xe9, 0x3b, 0x9b, 0xe3, + 0x11, 0x94, 0xff, 0xca, 0x61, 0xca, 0xaf, 0xde, 0x3d, 0xa4, 0xfc, 0xf2, 0xb3, 0x3b, 0xe5, 0xf6, + 0x96, 0xdd, 0xe9, 0x7a, 0xb6, 0x3f, 0x3d, 0x96, 0xb9, 0x9e, 0xed, 0xcf, 0x8e, 0xf5, 0x62, 0x41, + 0x08, 0x2c, 0x22, 0xcb, 0x8f, 0x34, 0xad, 0x65, 0x73, 0xe5, 0x98, 0x87, 0x94, 0x1f, 0x6e, 0xb1, + 0x0c, 0x45, 0x36, 0xc7, 0x3c, 0xbe, 0xfc, 0x2d, 0x95, 0xcd, 0xf1, 0x0e, 0x36, 0x3f, 0x44, 0xd9, + 0xfc, 0x66, 0x0a, 0xf2, 0x89, 0xb2, 0x29, 0x1d, 0x91, 0xff, 0xf1, 0xa1, 0xe5, 0xba, 0x2f, 0xfe, + 0x5e, 0x1a, 0xc6, 0xcb, 0x96, 0x47, 0x57, 0xf8, 0x46, 0xf4, 0x98, 0x4f, 0x15, 0x37, 0x60, 0x50, + 0xfa, 0x18, 0xd1, 0xe7, 0x41, 0x5d, 0x34, 0x09, 0xd5, 0xa9, 0x2e, 0x9a, 0x44, 0x72, 0x78, 0x01, + 0xfa, 0xaa, 0x90, 0x8f, 0xf9, 0x9c, 0x73, 0x3c, 0x84, 0x7c, 0xcc, 0x27, 0xaf, 0x7b, 0x54, 0xc8, + 0x9f, 0xce, 0xc0, 0xa9, 0x84, 0x87, 0x93, 0x8b, 0x61, 0x7a, 0xfd, 0x54, 0x18, 0xa9, 0xe7, 0xa7, + 0xd7, 0x0f, 0x92, 0xea, 0x93, 0x45, 0xcc, 0xb9, 0xb5, 0x50, 0x9e, 0x9a, 0x14, 0x52, 0x2d, 0x4a, + 0x49, 0x5d, 0x19, 0x38, 0xe9, 0xcb, 0x82, 0xbc, 0x5c, 0xb6, 0x69, 0xd4, 0x95, 0xbc, 0x5c, 0xac, + 0x0d, 0xf9, 0x12, 0x18, 0x28, 0xbd, 0xd9, 0x76, 0x28, 0xf2, 0xcd, 0x44, 0x5d, 0x6b, 0x03, 0x44, + 0x12, 0x67, 0x9e, 0x62, 0x8c, 0x51, 0xa8, 0xbc, 0x43, 0x86, 0x64, 0x01, 0x72, 0xaf, 0x99, 0xde, + 0x6c, 0x7b, 0x49, 0xf4, 0x42, 0x50, 0x23, 0x82, 0x43, 0x93, 0xf8, 0xe2, 0x0e, 0x9e, 0xd7, 0xba, + 0x93, 0xf7, 0x40, 0xbc, 0x01, 0x99, 0x83, 0xde, 0xd2, 0x9d, 0xaa, 0x56, 0x12, 0x3d, 0xf1, 0x88, + 0x9c, 0xd7, 0xb6, 0xd4, 0x91, 0x1d, 0xa6, 0x83, 0xd2, 0xe5, 0xc3, 0x14, 0xa4, 0x2f, 0x7e, 0x32, + 0x05, 0xe7, 0x3b, 0x0b, 0x8f, 0x3c, 0x0d, 0x7d, 0x9a, 0xdd, 0xa0, 0x25, 0xed, 0xa6, 0xe8, 0x19, + 0x94, 0x26, 0xdb, 0xe4, 0xd6, 0x74, 0x47, 0xde, 0x8b, 0xf8, 0x64, 0xe4, 0x7d, 0x30, 0x58, 0x76, + 0xdd, 0x36, 0x75, 0xaa, 0xcf, 0xde, 0xd2, 0xca, 0x62, 0xcb, 0x8a, 0x5b, 0x22, 0x13, 0xc1, 0x35, + 0xf7, 0x59, 0xa5, 0x00, 0x85, 0x4c, 0x5f, 0xfc, 0xba, 0x14, 0x5c, 0xe8, 0x26, 0x74, 0xf2, 0x2c, + 0xf4, 0x2f, 0x52, 0x4b, 0xb7, 0xbc, 0xf2, 0x94, 0x78, 0x25, 0xdc, 0x01, 0x7a, 0x08, 0x8b, 0x6e, + 0x64, 0x02, 0x42, 0xd6, 0x88, 0x9f, 0x35, 0x07, 0x3e, 0x6e, 0xfc, 0x5c, 0x1c, 0x61, 0x4a, 0x23, + 0x9f, 0xb0, 0xf8, 0x61, 0x78, 0xa0, 0x63, 0x1f, 0x91, 0xf7, 0xc3, 0xd0, 0x82, 0xb3, 0xa2, 0x5b, + 0xe6, 0x9b, 0x7c, 0x88, 0xa5, 0xc2, 0x5d, 0xb6, 0x2d, 0xc1, 0xe5, 0x9d, 0x9f, 0x4c, 0x5f, 0x5c, + 0x82, 0x7c, 0xa7, 0x0e, 0x23, 0x33, 0x30, 0x82, 0x25, 0x0a, 0x4b, 0x56, 0x7d, 0xd5, 0x76, 0x42, + 0xd9, 0x63, 0x42, 0x04, 0x8f, 0x61, 0x6a, 0x3a, 0xa2, 0x94, 0x3e, 0x50, 0x5a, 0x15, 0x7f, 0x29, + 0x0d, 0x43, 0x95, 0x46, 0x7b, 0xc5, 0x3c, 0x8c, 0xca, 0xb1, 0xfb, 0xcd, 0x1d, 0x2b, 0x57, 0xf3, + 0xcc, 0xec, 0xb3, 0x9a, 0xe7, 0xcb, 0x90, 0x6b, 0xe1, 0x77, 0xa8, 0xd7, 0x0b, 0xfc, 0xeb, 0x3a, + 0x5d, 0x2f, 0xf0, 0x36, 0x6c, 0xfe, 0xaa, 0x1f, 0x60, 0xfe, 0x0a, 0xdb, 0x4a, 0x02, 0x3d, 0x84, + 0xc2, 0xb1, 0x27, 0x02, 0x95, 0x05, 0x7a, 0x08, 0x75, 0x63, 0x4f, 0x04, 0x6a, 0x15, 0xff, 0x71, + 0x1a, 0x46, 0xa2, 0x8f, 0x24, 0x4f, 0xc3, 0x20, 0x7f, 0x0c, 0x3f, 0x77, 0x4b, 0x49, 0xc1, 0x92, + 0x21, 0x58, 0x03, 0xfe, 0x43, 0x1c, 0x20, 0x8e, 0xae, 0xea, 0x6e, 0x2d, 0x3c, 0x01, 0xe3, 0x6e, + 0x45, 0xfd, 0xfc, 0x54, 0x5e, 0x41, 0x69, 0x23, 0xab, 0xba, 0x3b, 0x19, 0xfe, 0x26, 0xd3, 0x40, + 0x1c, 0xda, 0x76, 0x69, 0x94, 0x01, 0x77, 0xf7, 0xe5, 0xab, 0x47, 0x0c, 0xab, 0x8d, 0x73, 0x98, + 0xcc, 0xe6, 0x23, 0xc1, 0x6b, 0xa3, 0x32, 0xf4, 0x76, 0x3f, 0x71, 0x7e, 0x6c, 0x7b, 0xab, 0x70, + 0x46, 0xa2, 0x4f, 0x3e, 0x72, 0xe6, 0x04, 0x53, 0xba, 0xa7, 0xf3, 0x43, 0x0f, 0xbf, 0x03, 0x8a, + 0xff, 0xf1, 0xef, 0xa5, 0xa0, 0x77, 0xc1, 0xa2, 0x0b, 0xcb, 0xe4, 0x19, 0x18, 0x60, 0x1a, 0x33, + 0x67, 0xb3, 0xce, 0x4c, 0x09, 0xbf, 0x3e, 0x49, 0x95, 0x10, 0x31, 0xdb, 0xa3, 0x85, 0x54, 0xe4, + 0x39, 0x80, 0x30, 0x77, 0x86, 0x50, 0x3f, 0x22, 0xb7, 0xe1, 0x98, 0xd9, 0x1e, 0x4d, 0xa2, 0xf3, + 0x5b, 0x89, 0xcc, 0x03, 0x99, 0x78, 0x2b, 0x8e, 0xf1, 0x5b, 0x89, 0x01, 0x32, 0x07, 0x84, 0xfd, + 0xaa, 0xe8, 0xae, 0xbb, 0x61, 0x3b, 0xc6, 0xe4, 0xaa, 0x6e, 0xad, 0x50, 0x75, 0x7b, 0x1a, 0xa7, + 0x98, 0xed, 0xd1, 0x12, 0xda, 0x91, 0xf7, 0xc2, 0x90, 0x1c, 0x55, 0xa1, 0x3a, 0x23, 0xca, 0xb8, + 0xd9, 0x1e, 0x2d, 0x42, 0x4b, 0xde, 0x0d, 0x83, 0xe2, 0xf7, 0x75, 0x5b, 0x78, 0x3a, 0x49, 0x59, + 0x8b, 0x25, 0xd4, 0x6c, 0x8f, 0x26, 0x53, 0x4a, 0x0f, 0xad, 0x38, 0xa6, 0xe5, 0x09, 0x3f, 0x7a, + 0xf5, 0xa1, 0x88, 0x93, 0x1e, 0x8a, 0xbf, 0xc9, 0xfb, 0x60, 0x38, 0xa8, 0x79, 0x80, 0x71, 0xa2, + 0xfc, 0x26, 0xe2, 0x8c, 0xd2, 0x98, 0x23, 0x67, 0x7b, 0xb4, 0x28, 0x35, 0xb9, 0x04, 0x39, 0x8d, + 0xba, 0xe6, 0x9b, 0xbe, 0x9f, 0xc2, 0x88, 0x34, 0xd0, 0xcd, 0x37, 0x99, 0x94, 0x04, 0x9e, 0xf5, + 0x4e, 0xe8, 0x75, 0x21, 0xee, 0x0d, 0x88, 0xf2, 0x94, 0x69, 0xcb, 0x60, 0xbd, 0x23, 0x79, 0xcd, + 0xbc, 0x1a, 0x56, 0x82, 0xe0, 0x29, 0xa1, 0xf0, 0xb2, 0x20, 0x92, 0xdb, 0x4d, 0xc6, 0xce, 0xf6, + 0x68, 0x0a, 0xbd, 0x24, 0xd5, 0x29, 0xd3, 0x5d, 0x13, 0x75, 0xc5, 0x54, 0xa9, 0x32, 0x94, 0x24, + 0x55, 0xf6, 0x53, 0x7a, 0xf4, 0x4d, 0xea, 0x6d, 0xd8, 0xce, 0x9a, 0xa8, 0x22, 0xa6, 0x3e, 0x5a, + 0x60, 0xa5, 0x47, 0x0b, 0x88, 0xfc, 0xe8, 0x30, 0x76, 0x2d, 0xf6, 0x68, 0xdd, 0xd3, 0xe5, 0x47, + 0xf3, 0xa3, 0x4e, 0xbf, 0x93, 0xe6, 0xa8, 0xbe, 0xee, 0x87, 0xa7, 0xa9, 0x1d, 0x8a, 0x38, 0xa9, + 0x43, 0xf1, 0x37, 0x7b, 0x68, 0xc5, 0x76, 0xbc, 0x19, 0xdb, 0xd9, 0xd0, 0x1d, 0x23, 0x3f, 0x16, + 0x7d, 0xa8, 0x84, 0x62, 0x0f, 0x95, 0x7e, 0xb2, 0x0e, 0x7a, 0xfd, 0x99, 0x67, 0xfc, 0x76, 0xe3, + 0xd1, 0x0e, 0x0a, 0x31, 0xac, 0x83, 0xc2, 0x5f, 0xa4, 0x00, 0x99, 0xea, 0x64, 0x25, 0x4f, 0x90, + 0x7c, 0x30, 0x78, 0xc3, 0xc9, 0xca, 0x6c, 0x8f, 0xc6, 0x30, 0xa4, 0x08, 0xd9, 0xe9, 0xbb, 0xb4, + 0x9e, 0x3f, 0x85, 0x14, 0x43, 0x3e, 0x05, 0x83, 0xcd, 0xf6, 0x68, 0x88, 0x63, 0x53, 0x44, 0xb5, + 0xbd, 0xe4, 0x62, 0x41, 0xeb, 0xfc, 0xe9, 0xe8, 0x14, 0x11, 0x20, 0xd8, 0x14, 0x11, 0xfc, 0x20, + 0x33, 0x30, 0xc6, 0xed, 0x4a, 0x3c, 0x08, 0xc2, 0xe9, 0x3a, 0x7f, 0x26, 0xba, 0xd6, 0xa8, 0xf8, + 0xd9, 0x1e, 0x2d, 0xd6, 0x86, 0x89, 0xab, 0xd4, 0xf6, 0x56, 0x4b, 0x1e, 0x4e, 0x6e, 0xf9, 0xb3, + 0x4a, 0xaa, 0xf0, 0x10, 0xc5, 0xc4, 0x25, 0xfd, 0x24, 0x0b, 0x70, 0xaa, 0x54, 0xaf, 0x53, 0xd7, + 0x15, 0xa9, 0x93, 0xc5, 0x64, 0x75, 0x2e, 0xba, 0x33, 0x4c, 0x20, 0x99, 0xed, 0xd1, 0x92, 0x5a, + 0x92, 0x49, 0x18, 0x65, 0x13, 0x8a, 0x54, 0x3d, 0x3b, 0x9f, 0x8f, 0x3a, 0x6d, 0x29, 0xe8, 0xd9, + 0x1e, 0x4d, 0x6d, 0xc1, 0x3a, 0x91, 0xed, 0x1b, 0x44, 0xfb, 0x07, 0xa2, 0x9d, 0x18, 0x62, 0x58, + 0x27, 0x86, 0xbf, 0xfc, 0x56, 0x62, 0xe6, 0x3c, 0x1f, 0x6f, 0x15, 0xce, 0x9c, 0xe1, 0x2f, 0xa2, + 0xc1, 0xe9, 0x68, 0xe1, 0x6f, 0xf1, 0xd4, 0x07, 0xa3, 0x5e, 0x26, 0x49, 0x34, 0xb3, 0x3d, 0x5a, + 0x62, 0xdb, 0x38, 0x4f, 0xf1, 0x4e, 0x17, 0xba, 0xf1, 0x0c, 0xde, 0x2e, 0xb1, 0x2d, 0xd1, 0xbb, + 0x14, 0x28, 0xcf, 0x3f, 0x14, 0xdd, 0x43, 0x76, 0x24, 0x9c, 0xed, 0xd1, 0xba, 0x94, 0x39, 0xbf, + 0xd5, 0xa1, 0x5a, 0x78, 0xfe, 0x61, 0x64, 0xff, 0x90, 0xb4, 0x45, 0x8d, 0x13, 0xcd, 0xf6, 0x68, + 0x1d, 0x6a, 0x8d, 0xdf, 0xea, 0x50, 0x4c, 0x3a, 0x5f, 0xe8, 0xca, 0x36, 0x90, 0x47, 0x87, 0x52, + 0xd4, 0x0b, 0x89, 0x75, 0x98, 0xf3, 0x8f, 0x44, 0x55, 0x37, 0x81, 0x84, 0xa9, 0x6e, 0x52, 0x05, + 0xe7, 0x85, 0xc4, 0xc2, 0xc1, 0xf9, 0x47, 0xbb, 0x30, 0x0c, 0xde, 0x31, 0xb1, 0xe4, 0xf0, 0x42, + 0x62, 0xe5, 0xde, 0x7c, 0x31, 0xca, 0x30, 0x81, 0x84, 0x31, 0x4c, 0xaa, 0xf9, 0xbb, 0x90, 0x58, + 0xe0, 0x35, 0xff, 0x58, 0x17, 0x86, 0xe1, 0x1b, 0x26, 0x95, 0x86, 0x7d, 0x77, 0xa4, 0xc2, 0x6a, + 0xfe, 0xf1, 0xe8, 0xbc, 0x21, 0xa1, 0xd8, 0xbc, 0x21, 0xd7, 0x62, 0x9d, 0x8c, 0x15, 0x73, 0xcb, + 0x3f, 0x11, 0x1d, 0xe6, 0x0a, 0x9a, 0x0d, 0x73, 0xb5, 0xfc, 0xdb, 0x64, 0xac, 0xe0, 0x54, 0xfe, + 0x62, 0x27, 0x26, 0x88, 0x8e, 0x32, 0xe1, 0xc9, 0xf1, 0xcb, 0x09, 0x15, 0x8f, 0xf2, 0xef, 0x88, + 0xc6, 0x1b, 0xc5, 0x08, 0x66, 0x7b, 0xb4, 0x84, 0x3a, 0x49, 0x5a, 0x72, 0x1e, 0xd9, 0xfc, 0xa5, + 0xe8, 0xb0, 0x4d, 0xa2, 0x61, 0xc3, 0x36, 0x31, 0x07, 0xed, 0x5c, 0x52, 0x8c, 0x67, 0xfe, 0x72, + 0xd4, 0x30, 0x8b, 0x53, 0x30, 0xc3, 0x2c, 0x21, 0x36, 0x54, 0x4b, 0x4e, 0x1e, 0x9a, 0x7f, 0x67, + 0xd7, 0x37, 0x44, 0x9a, 0x84, 0x37, 0xe4, 0xb9, 0x34, 0x43, 0xdb, 0xe9, 0x56, 0xab, 0x61, 0xeb, + 0x46, 0xfe, 0x5d, 0x89, 0xb6, 0x13, 0x47, 0x4a, 0xb6, 0x13, 0x07, 0xb0, 0x55, 0x5e, 0x8e, 0xbd, + 0xcb, 0x3f, 0x19, 0x5d, 0xe5, 0x65, 0x1c, 0x5b, 0xe5, 0x23, 0x71, 0x7a, 0x93, 0xb1, 0x38, 0xb5, + 0xfc, 0x53, 0x51, 0x05, 0x50, 0xd0, 0x4c, 0x01, 0xd4, 0xc8, 0xb6, 0x8f, 0x76, 0x8e, 0xec, 0xca, + 0x5f, 0x89, 0x9e, 0x85, 0x75, 0xa2, 0x9b, 0xed, 0xd1, 0x3a, 0x47, 0x87, 0x95, 0x13, 0x02, 0xb5, + 0xf2, 0x57, 0xa3, 0x0a, 0x16, 0x23, 0x60, 0x0a, 0x16, 0x0f, 0xef, 0x2a, 0x27, 0x44, 0x5a, 0xe5, + 0x9f, 0xee, 0xc8, 0x2a, 0xf8, 0xe6, 0x84, 0xf8, 0xac, 0xe7, 0xe4, 0x50, 0xa9, 0xfc, 0x33, 0xd1, + 0xc5, 0x2e, 0xc4, 0xb0, 0xc5, 0x4e, 0x0a, 0xa9, 0x7a, 0x4e, 0x0e, 0x12, 0xca, 0x5f, 0x8b, 0xb7, + 0x0a, 0x97, 0x48, 0x29, 0x98, 0x48, 0x4b, 0x8e, 0xad, 0xc9, 0x3f, 0x1b, 0xd5, 0xba, 0x24, 0x1a, + 0xa6, 0x75, 0x89, 0x71, 0x39, 0x33, 0xf1, 0x10, 0x99, 0xfc, 0x73, 0xea, 0x2e, 0x3b, 0x8a, 0x67, + 0x96, 0x4f, 0x2c, 0xac, 0xe6, 0x55, 0x35, 0xc9, 0x79, 0xfe, 0x79, 0xe5, 0x5e, 0x3d, 0x82, 0x65, + 0xf6, 0xad, 0x92, 0x14, 0xfd, 0x55, 0x35, 0x2f, 0x78, 0xfe, 0x85, 0x64, 0x0e, 0x81, 0xae, 0xa8, + 0x79, 0xc4, 0x5f, 0x55, 0x53, 0x69, 0xe7, 0xdf, 0x9d, 0xcc, 0x21, 0x90, 0xae, 0x9a, 0x7a, 0xfb, + 0x19, 0xa9, 0xae, 0x6b, 0xfe, 0x3d, 0x51, 0xd3, 0x31, 0x40, 0x30, 0xd3, 0x31, 0xac, 0xfe, 0xfa, + 0x8c, 0x54, 0x0f, 0x35, 0xff, 0x62, 0xac, 0x49, 0xf0, 0xb2, 0x52, 0xd5, 0xd4, 0x67, 0xa4, 0x3a, + 0xa2, 0xf9, 0xf7, 0xc6, 0x9a, 0x04, 0x6f, 0x27, 0x55, 0x1b, 0x35, 0xba, 0xa5, 0x7e, 0xc9, 0xbf, + 0x94, 0x98, 0xc7, 0x22, 0x81, 0x72, 0xb6, 0x47, 0xeb, 0x96, 0x42, 0xe6, 0xa3, 0x9d, 0x03, 0x8e, + 0xf2, 0x2f, 0x47, 0x87, 0x70, 0x27, 0x3a, 0x36, 0x84, 0x3b, 0x06, 0x2d, 0xbd, 0x4f, 0x49, 0xd2, + 0x97, 0x7f, 0x5f, 0x74, 0x8a, 0x8b, 0x20, 0xd9, 0x14, 0xa7, 0xa6, 0xf4, 0x8b, 0x64, 0x9f, 0xcb, + 0xbf, 0x3f, 0x3a, 0xc5, 0xc9, 0x38, 0x36, 0xc5, 0x45, 0x32, 0xd5, 0x4d, 0xc6, 0x92, 0xa2, 0xe5, + 0x5f, 0x89, 0x4e, 0x71, 0x0a, 0x9a, 0x4d, 0x71, 0x6a, 0x1a, 0xb5, 0xf7, 0x29, 0xb9, 0xc1, 0xf2, + 0xaf, 0x26, 0xbf, 0x3f, 0x22, 0xe5, 0xf7, 0xe7, 0x99, 0xc4, 0xb4, 0xe4, 0x24, 0x57, 0xf9, 0x52, + 0x74, 0xfc, 0x26, 0xd1, 0xb0, 0xf1, 0x9b, 0x98, 0x20, 0x4b, 0xdd, 0x38, 0x08, 0xad, 0x9a, 0xe8, + 0xb2, 0x71, 0x08, 0x4d, 0x91, 0x04, 0x70, 0x64, 0x8f, 0xcc, 0x37, 0x42, 0x93, 0x1d, 0xf6, 0xc8, + 0xfe, 0x36, 0x48, 0xa1, 0x67, 0xb3, 0x6b, 0xcc, 0x7b, 0x3e, 0x3f, 0x15, 0x9d, 0x5d, 0x63, 0x04, + 0x6c, 0x76, 0x8d, 0xfb, 0xdc, 0xcf, 0xc0, 0x98, 0xd0, 0x22, 0x1e, 0x14, 0x60, 0x5a, 0x2b, 0xf9, + 0x69, 0x25, 0xad, 0x81, 0x82, 0x67, 0xb3, 0x93, 0x0a, 0xc3, 0xf5, 0x9a, 0xc3, 0x26, 0x1b, 0x66, + 0x6b, 0xc9, 0xd6, 0x1d, 0xa3, 0x4a, 0x2d, 0x23, 0x3f, 0xa3, 0xac, 0xd7, 0x09, 0x34, 0xb8, 0x5e, + 0x27, 0xc0, 0x31, 0xf7, 0xb5, 0x02, 0xd7, 0x78, 0x49, 0xa1, 0xfc, 0x6b, 0xc8, 0xb6, 0xd0, 0x89, + 0xad, 0x20, 0x9b, 0xed, 0xd1, 0x3a, 0x71, 0x60, 0xb6, 0xfa, 0xfc, 0x66, 0xf5, 0x03, 0x73, 0x41, + 0x5e, 0xb5, 0x8a, 0x43, 0x5b, 0xba, 0x43, 0xf3, 0xb3, 0x51, 0x5b, 0x3d, 0x91, 0x88, 0xd9, 0xea, + 0x89, 0x88, 0x38, 0x5b, 0x7f, 0x2c, 0x94, 0xbb, 0xb1, 0x0d, 0x47, 0x44, 0x72, 0x6b, 0x36, 0x3b, + 0x45, 0x11, 0x4c, 0x40, 0x73, 0xb6, 0xb5, 0x82, 0x27, 0x15, 0xd7, 0xa3, 0xb3, 0x53, 0x67, 0x4a, + 0x36, 0x3b, 0x75, 0xc6, 0x32, 0x55, 0x8f, 0x62, 0xf9, 0x18, 0xbc, 0x11, 0x55, 0xf5, 0x04, 0x12, + 0xa6, 0xea, 0x09, 0xe0, 0x38, 0x43, 0x8d, 0xba, 0xd4, 0xcb, 0xcf, 0x75, 0x63, 0x88, 0x24, 0x71, + 0x86, 0x08, 0x8e, 0x33, 0x9c, 0xa1, 0x5e, 0x7d, 0x35, 0x3f, 0xdf, 0x8d, 0x21, 0x92, 0xc4, 0x19, + 0x22, 0x98, 0x6d, 0x36, 0xa3, 0xe0, 0x89, 0x76, 0x63, 0xcd, 0xef, 0xb3, 0x9b, 0xd1, 0xcd, 0x66, + 0x47, 0x42, 0xb6, 0xd9, 0xec, 0x88, 0x24, 0x5f, 0xb7, 0xeb, 0xf8, 0x85, 0xfc, 0x02, 0x3e, 0xf0, + 0x4a, 0x68, 0x17, 0xec, 0xa6, 0xd5, 0x6c, 0x8f, 0xb6, 0xdb, 0xf8, 0x88, 0x77, 0x05, 0x5e, 0xb9, + 0xf9, 0x0a, 0x3e, 0x6a, 0x34, 0x38, 0xab, 0xe0, 0xe0, 0xd9, 0x1e, 0x2d, 0xf0, 0xdb, 0x7d, 0x37, + 0x0c, 0xe2, 0x47, 0x95, 0x2d, 0xd3, 0x9b, 0x9a, 0xc8, 0x7f, 0x20, 0xba, 0x65, 0x92, 0x50, 0x6c, + 0xcb, 0x24, 0xfd, 0x64, 0x93, 0x38, 0xfe, 0xe4, 0x53, 0xcc, 0xd4, 0x44, 0x5e, 0x8b, 0x4e, 0xe2, + 0x11, 0x24, 0x9b, 0xc4, 0x23, 0x80, 0xe0, 0xb9, 0x53, 0x8e, 0xdd, 0x9a, 0x9a, 0xc8, 0x57, 0x13, + 0x9e, 0xcb, 0x51, 0xc1, 0x73, 0xf9, 0xcf, 0xe0, 0xb9, 0xd5, 0xd5, 0xb6, 0x37, 0xc5, 0xbe, 0x71, + 0x31, 0xe1, 0xb9, 0x3e, 0x32, 0x78, 0xae, 0x0f, 0x60, 0x53, 0x21, 0x02, 0x2a, 0x8e, 0xcd, 0x26, + 0xed, 0x1b, 0x66, 0xa3, 0x91, 0xbf, 0x15, 0x9d, 0x0a, 0x55, 0x3c, 0x9b, 0x0a, 0x55, 0x18, 0x33, + 0x3d, 0xf9, 0x5b, 0xd1, 0xa5, 0xf6, 0x4a, 0xfe, 0x76, 0xd4, 0xf4, 0x0c, 0x31, 0xcc, 0xf4, 0x0c, + 0x7f, 0xe1, 0xee, 0x82, 0xfd, 0xd2, 0xe8, 0xb2, 0x43, 0xdd, 0xd5, 0xfc, 0x1d, 0x65, 0x77, 0x21, + 0xe1, 0x70, 0x77, 0x21, 0xfd, 0x26, 0x2b, 0xf0, 0x60, 0x64, 0xa1, 0xf1, 0x6f, 0x6d, 0xaa, 0x54, + 0x77, 0xea, 0xab, 0xf9, 0xd7, 0x91, 0xd5, 0x63, 0x89, 0x4b, 0x55, 0x94, 0x74, 0xb6, 0x47, 0xeb, + 0xc6, 0x09, 0xb7, 0xe5, 0x1f, 0x98, 0xe3, 0x15, 0x38, 0xb4, 0xca, 0xa4, 0xbf, 0x09, 0xfd, 0xa0, + 0xb2, 0x2d, 0x8f, 0x93, 0xe0, 0xb6, 0x3c, 0x0e, 0x26, 0x2d, 0x78, 0x58, 0xd9, 0xaa, 0xcd, 0xeb, + 0x0d, 0xb6, 0x2f, 0xa1, 0x46, 0x45, 0xaf, 0xaf, 0x51, 0x2f, 0xff, 0x21, 0xe4, 0x7d, 0xb1, 0xc3, + 0x86, 0x4f, 0xa1, 0x9e, 0xed, 0xd1, 0x76, 0xe0, 0x47, 0x8a, 0x90, 0xad, 0xce, 0x2c, 0x56, 0xf2, + 0x1f, 0x8e, 0x9e, 0x6f, 0x32, 0xd8, 0x6c, 0x8f, 0x86, 0x38, 0x66, 0xa5, 0xdd, 0x6a, 0xad, 0x38, + 0xba, 0x41, 0xb9, 0xa1, 0x85, 0xb6, 0x9b, 0x30, 0x40, 0xbf, 0x24, 0x6a, 0xa5, 0x75, 0xa2, 0x63, + 0x56, 0x5a, 0x27, 0x1c, 0x53, 0xd4, 0x48, 0x0d, 0xf0, 0xfc, 0x47, 0xa2, 0x8a, 0x1a, 0x41, 0x32, + 0x45, 0x8d, 0x56, 0x0c, 0x7f, 0x1d, 0xce, 0x06, 0xfb, 0x79, 0xb1, 0xfe, 0xf2, 0x4e, 0xcb, 0x7f, + 0x14, 0xf9, 0x3c, 0x1c, 0xbb, 0x0c, 0x88, 0x50, 0xcd, 0xf6, 0x68, 0x1d, 0xda, 0xb3, 0x15, 0xf7, + 0x46, 0x7b, 0x89, 0x3a, 0x16, 0xf5, 0x98, 0x49, 0x25, 0x9f, 0x12, 0x7e, 0x69, 0x74, 0xc5, 0xed, + 0x40, 0xc6, 0x56, 0xdc, 0x0e, 0xa8, 0x44, 0xe6, 0x42, 0xa8, 0xfa, 0x0e, 0xcc, 0x03, 0x99, 0x76, + 0xe2, 0x90, 0xc8, 0x5c, 0x58, 0x6a, 0x4b, 0x3b, 0x30, 0x0f, 0xac, 0xb5, 0x4e, 0x1c, 0xc8, 0x25, + 0xc8, 0x55, 0xab, 0xf3, 0x5a, 0xdb, 0xca, 0xd7, 0x15, 0x77, 0x64, 0x84, 0xce, 0xf6, 0x68, 0x02, + 0xcf, 0xcc, 0xa0, 0xe9, 0x86, 0xee, 0x7a, 0x66, 0xdd, 0xc5, 0x11, 0xe3, 0x8f, 0x10, 0x23, 0x6a, + 0x06, 0x25, 0xd1, 0x30, 0x33, 0x28, 0x09, 0xce, 0xec, 0xc5, 0x49, 0xdd, 0x75, 0x75, 0xcb, 0x70, + 0xf4, 0x09, 0x5c, 0x26, 0xa8, 0x12, 0x63, 0x18, 0xc1, 0x32, 0x7b, 0x31, 0x0a, 0xc1, 0xc3, 0x77, + 0x1f, 0xe2, 0x9b, 0x39, 0xcb, 0xca, 0xe1, 0xbb, 0x82, 0xc7, 0xc3, 0x77, 0x05, 0x86, 0x76, 0xa7, + 0x0f, 0xd3, 0xe8, 0x8a, 0xc9, 0x44, 0x94, 0x5f, 0x51, 0xec, 0x4e, 0x95, 0x00, 0xed, 0x4e, 0x15, + 0x18, 0x79, 0x25, 0x7f, 0xb9, 0x5d, 0xed, 0xf0, 0x4a, 0xe1, 0x2a, 0x1b, 0x6b, 0xc3, 0xd6, 0xef, + 0x70, 0x70, 0x4c, 0x6d, 0x5a, 0x7a, 0xd3, 0x9e, 0x9a, 0xf0, 0xa5, 0x6e, 0x46, 0xd7, 0xef, 0x8e, + 0x84, 0x6c, 0xfd, 0xee, 0x88, 0x64, 0xb3, 0xab, 0xbf, 0xd1, 0x5a, 0xd5, 0x1d, 0x6a, 0x4c, 0x99, + 0x0e, 0x9e, 0x2c, 0x6e, 0xf2, 0xad, 0xe1, 0x1b, 0xd1, 0xd9, 0xb5, 0x0b, 0x29, 0x9b, 0x5d, 0xbb, + 0xa0, 0x99, 0x91, 0x97, 0x8c, 0xd6, 0xa8, 0x6e, 0xe4, 0xd7, 0xa2, 0x46, 0x5e, 0x67, 0x4a, 0x66, + 0xe4, 0x75, 0xc6, 0x76, 0xfe, 0x9c, 0x3b, 0x8e, 0xe9, 0xd1, 0x7c, 0x63, 0x37, 0x9f, 0x83, 0xa4, + 0x9d, 0x3f, 0x07, 0xd1, 0x6c, 0x43, 0xa8, 0x76, 0x48, 0x33, 0xba, 0x21, 0x8c, 0x77, 0x83, 0xda, + 0x82, 0x59, 0x2c, 0x22, 0x98, 0x32, 0x6f, 0x45, 0x2d, 0x16, 0x01, 0x66, 0x16, 0x4b, 0x18, 0x6e, + 0x19, 0x89, 0x8b, 0xcb, 0xdb, 0xd1, 0x35, 0x54, 0xc6, 0xb1, 0x35, 0x34, 0x12, 0x43, 0xf7, 0xee, + 0x48, 0x20, 0x47, 0xbe, 0x15, 0xb5, 0x3a, 0x24, 0x14, 0xb3, 0x3a, 0xe4, 0x90, 0x8f, 0x49, 0x18, + 0xc5, 0x5b, 0x70, 0xad, 0x1d, 0xdc, 0xe3, 0x7c, 0x2c, 0xfa, 0x99, 0x0a, 0x9a, 0x7d, 0xa6, 0x02, + 0x8a, 0x30, 0x11, 0xd3, 0x96, 0xd3, 0x81, 0x49, 0x78, 0x3e, 0xa8, 0x80, 0xc8, 0x1c, 0x90, 0x6a, + 0x69, 0x7e, 0xae, 0x6c, 0x54, 0xe4, 0x2b, 0x32, 0x37, 0x7a, 0x02, 0x1b, 0xa7, 0x98, 0xed, 0xd1, + 0x12, 0xda, 0x91, 0x37, 0xe0, 0x82, 0x80, 0x8a, 0x4c, 0x20, 0x15, 0x51, 0xee, 0x5c, 0x7c, 0xa4, + 0x17, 0x75, 0x14, 0xec, 0x46, 0x3b, 0xdb, 0xa3, 0x75, 0xe5, 0xd5, 0xf9, 0x59, 0x62, 0x7d, 0x68, + 0xef, 0xe6, 0x59, 0xc1, 0x22, 0xd1, 0x95, 0x57, 0xe7, 0x67, 0x09, 0xb9, 0xaf, 0xef, 0xe6, 0x59, + 0x41, 0x27, 0x74, 0xe5, 0x45, 0x5c, 0x28, 0x74, 0xc3, 0x97, 0x1a, 0x8d, 0xfc, 0x06, 0x3e, 0xee, + 0x1d, 0xbb, 0x79, 0x5c, 0x09, 0x0d, 0xce, 0x9d, 0x38, 0xb2, 0x59, 0x7a, 0xa1, 0x45, 0xad, 0x6a, + 0x64, 0x01, 0xba, 0x1b, 0x9d, 0xa5, 0x63, 0x04, 0x6c, 0x96, 0x8e, 0x01, 0xd9, 0x80, 0x92, 0xe3, + 0x81, 0xf2, 0x9b, 0xd1, 0x01, 0x25, 0xe3, 0xd8, 0x80, 0x8a, 0xc4, 0x0e, 0x2d, 0xc0, 0xa9, 0x85, + 0x35, 0x4f, 0xf7, 0x2d, 0x48, 0x57, 0x74, 0xe5, 0x9b, 0xca, 0x25, 0x53, 0x9c, 0x04, 0x2f, 0x99, + 0xe2, 0x60, 0x36, 0x46, 0x18, 0xb8, 0xba, 0x69, 0xd5, 0x67, 0x74, 0xb3, 0xd1, 0x76, 0x68, 0xfe, + 0xaf, 0x44, 0xc7, 0x88, 0x82, 0x66, 0x63, 0x44, 0x01, 0xb1, 0x05, 0x9a, 0x81, 0x4a, 0xae, 0x6b, + 0xae, 0x58, 0x62, 0x5f, 0xd9, 0x6e, 0x78, 0xf9, 0xff, 0x5f, 0x74, 0x81, 0x4e, 0xa2, 0x61, 0x0b, + 0x74, 0x12, 0x1c, 0x4f, 0x9d, 0x58, 0x2f, 0xb0, 0xc5, 0x43, 0xbe, 0xab, 0xfc, 0xff, 0x2b, 0xa7, + 0x4e, 0x09, 0x34, 0x78, 0xea, 0x94, 0x00, 0x67, 0xeb, 0x23, 0xb7, 0xc9, 0xe6, 0xcc, 0xe0, 0xae, + 0xfa, 0xbf, 0x8b, 0xae, 0x8f, 0x2a, 0x9e, 0xad, 0x8f, 0x2a, 0x2c, 0xca, 0x47, 0x74, 0xc1, 0x5f, + 0xed, 0xc4, 0x27, 0x90, 0x7f, 0xac, 0x0d, 0x79, 0x4d, 0xe6, 0x23, 0x46, 0xca, 0x97, 0xa5, 0x3a, + 0x31, 0x0a, 0x86, 0x47, 0xac, 0x51, 0x94, 0x91, 0x46, 0xd7, 0x4d, 0xba, 0x91, 0xff, 0xf2, 0x8e, + 0x8c, 0x38, 0x41, 0x94, 0x11, 0x87, 0x91, 0x0f, 0xc2, 0xd9, 0x10, 0x36, 0x4f, 0x9b, 0x4b, 0xc1, + 0xcc, 0xf4, 0x15, 0xa9, 0xa8, 0x19, 0x9c, 0x4c, 0xc6, 0xcc, 0xe0, 0x64, 0x4c, 0x12, 0x6b, 0x21, + 0xba, 0xaf, 0xdc, 0x81, 0x75, 0x20, 0xc1, 0x0e, 0x0c, 0x92, 0x58, 0x0b, 0x69, 0x7e, 0xd5, 0x0e, + 0xac, 0x03, 0x99, 0x76, 0x60, 0x40, 0x3e, 0x9e, 0x82, 0x8b, 0xc9, 0xa8, 0x52, 0xa3, 0x31, 0x63, + 0x3b, 0x21, 0x2e, 0xff, 0xd5, 0xa9, 0xe8, 0x41, 0xc3, 0xee, 0x9a, 0xcd, 0xf6, 0x68, 0xbb, 0x7c, + 0x00, 0x79, 0x3f, 0x0c, 0x97, 0xda, 0x86, 0xe9, 0xe1, 0xc5, 0x1b, 0x33, 0x9c, 0xbf, 0x26, 0xa5, + 0x6c, 0x71, 0x64, 0x2c, 0x6e, 0x71, 0x64, 0x00, 0xb9, 0x0e, 0xe3, 0x55, 0x5a, 0x6f, 0x63, 0x1a, + 0x0b, 0xda, 0xb2, 0x1d, 0x8f, 0xf1, 0xf8, 0xda, 0x54, 0x74, 0x12, 0x8b, 0x51, 0xb0, 0x49, 0x2c, + 0x06, 0x24, 0xb7, 0x63, 0xb7, 0xf2, 0xa2, 0x33, 0xbf, 0x2e, 0xd5, 0xf5, 0x5a, 0x3e, 0xe8, 0xcb, + 0xe4, 0xe6, 0xa4, 0xa2, 0xdc, 0xa2, 0x0b, 0xae, 0x1f, 0x4f, 0x75, 0xb9, 0x46, 0x97, 0x66, 0xb8, + 0x38, 0x98, 0x71, 0x8c, 0xdc, 0x5d, 0x0b, 0x8e, 0x5f, 0x9f, 0xea, 0x72, 0xed, 0x1d, 0x72, 0x4c, + 0x00, 0x93, 0xe7, 0xb9, 0xa7, 0x88, 0x60, 0xf4, 0x0d, 0xa9, 0xb8, 0xab, 0x48, 0xd0, 0x5e, 0x22, + 0x64, 0xcd, 0x6e, 0xb9, 0x81, 0xd2, 0x7f, 0x22, 0x15, 0xf7, 0xcd, 0x0b, 0x9b, 0x85, 0xbf, 0x08, + 0x85, 0xf3, 0xd3, 0x77, 0x3d, 0xea, 0x58, 0x7a, 0x03, 0xbb, 0xb3, 0xea, 0xd9, 0x8e, 0xbe, 0x42, + 0xa7, 0x2d, 0x7d, 0xa9, 0x41, 0xf3, 0x9f, 0x4c, 0x45, 0x2d, 0xd8, 0xce, 0xa4, 0xcc, 0x82, 0xed, + 0x8c, 0x25, 0xab, 0xf0, 0x60, 0x12, 0x76, 0xca, 0x74, 0xf1, 0x39, 0x9f, 0x4a, 0x45, 0x4d, 0xd8, + 0x2e, 0xb4, 0xcc, 0x84, 0xed, 0x82, 0x26, 0xd7, 0x60, 0x60, 0xc2, 0xf6, 0xa7, 0xdf, 0x6f, 0x54, + 0x9c, 0x21, 0x03, 0xcc, 0x6c, 0x8f, 0x16, 0x92, 0x89, 0x36, 0x62, 0x50, 0x7f, 0x3a, 0xde, 0x26, + 0xbc, 0x7c, 0x0a, 0x7e, 0x88, 0x36, 0x42, 0xdc, 0xff, 0x7d, 0xbc, 0x4d, 0x78, 0xc7, 0x15, 0xfc, + 0x60, 0x33, 0x09, 0x7f, 0xe2, 0xfc, 0x4c, 0x89, 0xd9, 0x6d, 0x93, 0xab, 0x7a, 0xa3, 0x41, 0xad, + 0x15, 0x9a, 0xff, 0x8c, 0x32, 0x93, 0x24, 0x93, 0xb1, 0x99, 0x24, 0x19, 0x43, 0xbe, 0x04, 0xce, + 0xdd, 0xd6, 0x1b, 0xa6, 0x11, 0xe2, 0xfc, 0x52, 0xfd, 0xf9, 0xcf, 0xa6, 0xa2, 0xbb, 0xe9, 0x0e, + 0x74, 0x6c, 0x37, 0xdd, 0x01, 0x45, 0xe6, 0x81, 0xe0, 0x32, 0x1a, 0xcc, 0x16, 0x6c, 0x7d, 0xce, + 0xff, 0xb5, 0x54, 0xd4, 0x4e, 0x8d, 0x93, 0x30, 0x3b, 0x35, 0x0e, 0x25, 0xb5, 0xce, 0x35, 0x0f, + 0xf3, 0xdf, 0x94, 0x8a, 0x9e, 0xd6, 0x74, 0x22, 0x9c, 0xed, 0xd1, 0x3a, 0x17, 0x4e, 0x7c, 0x0d, + 0xc6, 0xaa, 0x95, 0xf2, 0xcc, 0xcc, 0x74, 0xf5, 0x76, 0x79, 0x0a, 0x43, 0x35, 0x8c, 0xfc, 0x5f, + 0x57, 0x56, 0x2c, 0x95, 0x80, 0xad, 0x58, 0x2a, 0x8c, 0xbc, 0x04, 0x43, 0xec, 0xfd, 0xd9, 0x80, + 0xc1, 0x4f, 0xfe, 0x5c, 0x2a, 0x6a, 0x4e, 0xc9, 0x48, 0x66, 0x4e, 0xc9, 0xbf, 0x49, 0x15, 0x4e, + 0x33, 0x29, 0x56, 0x1c, 0xba, 0x4c, 0x1d, 0x6a, 0xd5, 0xfd, 0x31, 0xfd, 0xcd, 0xa9, 0xa8, 0x95, + 0x91, 0x44, 0xc4, 0xac, 0x8c, 0x24, 0x38, 0x59, 0x83, 0x0b, 0xea, 0x49, 0x90, 0x1c, 0xd7, 0x9b, + 0xff, 0x1b, 0x29, 0xc5, 0x18, 0xee, 0x42, 0x8c, 0xc6, 0x70, 0x17, 0x3c, 0xb1, 0xe0, 0x21, 0x71, + 0xac, 0x22, 0x1c, 0x2e, 0xd5, 0xa7, 0x7d, 0x0b, 0x7f, 0xda, 0x13, 0xa1, 0x43, 0x60, 0x17, 0xea, + 0xd9, 0x1e, 0xad, 0x3b, 0x3b, 0xa6, 0x67, 0xf1, 0xca, 0x7e, 0xf9, 0x6f, 0x4d, 0x25, 0x7b, 0xa4, + 0x44, 0xdc, 0x94, 0x93, 0x4a, 0x02, 0x7e, 0xb0, 0x53, 0x5d, 0xba, 0xfc, 0xdf, 0x54, 0xc6, 0x5b, + 0x32, 0x19, 0x1b, 0x6f, 0x1d, 0x0a, 0xdb, 0x5d, 0x87, 0x71, 0xae, 0xd4, 0x15, 0x1d, 0x87, 0xa1, + 0xb5, 0x42, 0x8d, 0xfc, 0xdf, 0x52, 0x56, 0xbb, 0x18, 0x05, 0xba, 0xf6, 0xa8, 0x40, 0x36, 0x75, + 0x57, 0x5b, 0xba, 0x65, 0xe1, 0x31, 0x6b, 0xfe, 0x6f, 0x2b, 0x53, 0x77, 0x88, 0x42, 0xc7, 0xdd, + 0xe0, 0x17, 0xd3, 0x84, 0x6e, 0x35, 0x5d, 0xf3, 0xff, 0x83, 0xa2, 0x09, 0xdd, 0x88, 0x99, 0x26, + 0x74, 0x2d, 0x10, 0x7b, 0xbb, 0x43, 0x8c, 0x7d, 0xfe, 0xdb, 0x94, 0x15, 0x39, 0x91, 0x8a, 0xad, + 0xc8, 0xc9, 0x21, 0xfa, 0xb7, 0x3b, 0xc4, 0xa7, 0xe7, 0xbf, 0xbd, 0x3b, 0xdf, 0x70, 0xa5, 0x4f, + 0x0e, 0x6f, 0xbf, 0xdd, 0x21, 0xb6, 0x3b, 0xff, 0x1d, 0xdd, 0xf9, 0x86, 0x8e, 0x7d, 0xc9, 0xa1, + 0xe1, 0xb5, 0xce, 0x71, 0xd1, 0xf9, 0xbf, 0xa3, 0x4e, 0x5d, 0x1d, 0x08, 0x71, 0xea, 0xea, 0x14, + 0x5c, 0xbd, 0x04, 0x0f, 0x70, 0x0d, 0x79, 0xcd, 0xd1, 0x5b, 0xab, 0x55, 0xea, 0x79, 0xa6, 0xb5, + 0xe2, 0xef, 0xc4, 0xbe, 0x33, 0xa5, 0x1c, 0x8f, 0x75, 0xa2, 0xc4, 0xe3, 0xb1, 0x4e, 0x48, 0xa6, + 0xbc, 0xb1, 0x08, 0xe8, 0xfc, 0xdf, 0x55, 0x94, 0x37, 0x46, 0xc1, 0x94, 0x37, 0x1e, 0x38, 0x7d, + 0x3d, 0x21, 0xd0, 0x37, 0xff, 0x3f, 0x76, 0xe6, 0x15, 0xbc, 0x5f, 0x42, 0x7c, 0xf0, 0xf5, 0x84, + 0x78, 0xd6, 0xfc, 0xdf, 0xeb, 0xcc, 0x2b, 0xf4, 0x41, 0x8a, 0x87, 0xc1, 0x7e, 0x10, 0xce, 0xf2, + 0xd9, 0x7c, 0x86, 0x1a, 0x34, 0xf2, 0xa1, 0xdf, 0xa5, 0x8c, 0xfd, 0x64, 0x32, 0x3c, 0x72, 0x4f, + 0xc4, 0x24, 0xb1, 0x16, 0xef, 0xfa, 0xf7, 0x77, 0x60, 0x1d, 0x6e, 0x08, 0x92, 0x31, 0x6c, 0xbd, + 0x91, 0xa3, 0xdf, 0xf2, 0xdf, 0xad, 0xac, 0x37, 0x32, 0x12, 0xdd, 0x39, 0xe4, 0x50, 0xb9, 0x97, + 0xa2, 0x91, 0x5e, 0xf9, 0xef, 0x49, 0x6c, 0x1c, 0x74, 0x40, 0x34, 0x2c, 0xec, 0xa5, 0x68, 0x54, + 0x53, 0xfe, 0x7b, 0x13, 0x1b, 0x07, 0x1f, 0x10, 0x0d, 0x81, 0x62, 0x5b, 0xa4, 0xb6, 0x67, 0x73, + 0x56, 0x91, 0xe9, 0xe1, 0x1f, 0xa8, 0x5b, 0xa4, 0x44, 0x32, 0xdc, 0x22, 0x25, 0x62, 0x92, 0x58, + 0x8b, 0xcf, 0xfb, 0xbe, 0x1d, 0x58, 0x4b, 0x1b, 0xbb, 0x44, 0x4c, 0x12, 0x6b, 0xf1, 0xf1, 0xdf, + 0xbf, 0x03, 0x6b, 0x69, 0x63, 0x97, 0x88, 0x61, 0xe6, 0x58, 0x88, 0xb9, 0x4d, 0x1d, 0x37, 0x54, + 0xbf, 0x7f, 0xa8, 0x98, 0x63, 0x1d, 0xe8, 0x98, 0x39, 0xd6, 0x01, 0x95, 0xc8, 0x5d, 0x08, 0xe5, + 0x07, 0x76, 0xe2, 0x1e, 0xde, 0xcb, 0x74, 0x40, 0x25, 0x72, 0x17, 0x72, 0xf9, 0x47, 0x3b, 0x71, + 0x0f, 0x2f, 0x66, 0x3a, 0xa0, 0x98, 0x51, 0x54, 0xf5, 0x74, 0xcf, 0xac, 0xcf, 0xda, 0xae, 0x27, + 0x2d, 0xf2, 0xff, 0x93, 0x62, 0x14, 0x25, 0x11, 0x31, 0xa3, 0x28, 0x09, 0x1e, 0x67, 0x2a, 0xa4, + 0xf1, 0x8f, 0xbb, 0x32, 0x0d, 0x2d, 0xad, 0x24, 0x78, 0x9c, 0xa9, 0x10, 0xc2, 0xff, 0xdc, 0x95, + 0x69, 0xe8, 0x29, 0x9f, 0x04, 0x67, 0x96, 0xe9, 0xa4, 0x63, 0x6f, 0x58, 0xd7, 0xe9, 0x06, 0x6d, + 0x88, 0x4f, 0xff, 0x27, 0x8a, 0x65, 0xaa, 0x12, 0xe0, 0x2d, 0x8a, 0x02, 0x8b, 0x32, 0x12, 0x9f, + 0xfb, 0x83, 0x1d, 0x19, 0x85, 0xc7, 0x44, 0x2a, 0x2c, 0xca, 0x48, 0x7c, 0xe2, 0x0f, 0x75, 0x64, + 0x14, 0x1e, 0x13, 0xa9, 0x30, 0x52, 0x82, 0x11, 0x8c, 0x95, 0xd0, 0x5d, 0xdf, 0xf3, 0xf3, 0xc7, + 0x52, 0xd1, 0x5b, 0xaf, 0x28, 0x7a, 0xb6, 0x47, 0x53, 0x1a, 0xc8, 0x2c, 0xc4, 0x27, 0xfd, 0x78, + 0x07, 0x16, 0xa1, 0xbf, 0x63, 0x14, 0x22, 0xb3, 0x10, 0x1f, 0xf3, 0x13, 0x1d, 0x58, 0x84, 0x0e, + 0x8f, 0x51, 0x08, 0x79, 0x0f, 0x0c, 0x56, 0x67, 0x16, 0x2b, 0x7e, 0xdd, 0xf1, 0x7f, 0x9a, 0x52, + 0xa2, 0x8a, 0x42, 0x1c, 0x46, 0x15, 0x85, 0x3f, 0xc9, 0xfb, 0x61, 0x78, 0xd2, 0xb6, 0x3c, 0xbd, + 0xee, 0x6f, 0x40, 0x7f, 0x52, 0x39, 0x43, 0x89, 0x60, 0x67, 0x7b, 0xb4, 0x28, 0xb9, 0xd4, 0x5e, + 0xbc, 0xfb, 0x4f, 0x25, 0xb7, 0x0f, 0x5e, 0x3d, 0x4a, 0xce, 0x66, 0xb4, 0x3b, 0xb6, 0xb3, 0xd6, + 0xb0, 0x75, 0xc3, 0xcf, 0x73, 0x2a, 0x5e, 0xe4, 0xa7, 0x95, 0x19, 0x2d, 0x99, 0x8c, 0xcd, 0x68, + 0xc9, 0x98, 0x24, 0xd6, 0xa2, 0x8b, 0x3e, 0xbf, 0x03, 0xeb, 0x70, 0x1e, 0x4e, 0xc6, 0x24, 0xb1, + 0x16, 0x9f, 0xff, 0xcf, 0x76, 0x60, 0x1d, 0xce, 0xc3, 0xc9, 0x18, 0x66, 0x5a, 0xbf, 0x66, 0x7a, + 0x7e, 0x60, 0xdb, 0x3f, 0x57, 0x4c, 0xeb, 0x10, 0xc5, 0x4c, 0xeb, 0xf0, 0x17, 0xa1, 0x70, 0x3e, + 0x08, 0x95, 0x0c, 0xf7, 0xae, 0x65, 0x6b, 0x9d, 0xed, 0x8f, 0xf3, 0xff, 0x8b, 0x72, 0x2a, 0xd2, + 0x99, 0x74, 0xb6, 0x47, 0xeb, 0xc2, 0x88, 0x54, 0x14, 0x3f, 0x45, 0x9e, 0x00, 0x30, 0xff, 0x33, + 0xa9, 0x2e, 0x8e, 0x8a, 0x9c, 0x26, 0xe6, 0xa8, 0xc8, 0xc1, 0x62, 0xce, 0x5a, 0x6a, 0xd0, 0x5b, + 0x37, 0xcb, 0xaf, 0x4b, 0xb3, 0xeb, 0x17, 0xe2, 0x73, 0x56, 0x8c, 0x48, 0xcc, 0x59, 0x31, 0x38, + 0xf9, 0xca, 0x14, 0x3c, 0xae, 0xca, 0xf7, 0xf5, 0xe7, 0x9f, 0x7e, 0x51, 0xa3, 0xeb, 0x76, 0x5d, + 0xb6, 0xac, 0x7e, 0x96, 0x3f, 0xe5, 0xc9, 0x4e, 0xdd, 0x95, 0xd4, 0x68, 0xb6, 0x47, 0xdb, 0x15, + 0xf3, 0x5d, 0xbc, 0x85, 0x50, 0x9a, 0x9f, 0xdb, 0xd3, 0x5b, 0x04, 0x2a, 0xb4, 0x2b, 0xe6, 0xbb, + 0x78, 0x0b, 0x31, 0x2a, 0x7e, 0x7e, 0x4f, 0x6f, 0x11, 0x8c, 0x91, 0x5d, 0x31, 0xc7, 0xdd, 0xe7, + 0x9d, 0x6a, 0x79, 0x32, 0xf0, 0xf5, 0xd9, 0xb4, 0xea, 0xf9, 0x5f, 0x50, 0x77, 0x9f, 0x2a, 0x05, + 0xee, 0x3e, 0x55, 0x20, 0x5b, 0xee, 0x67, 0xa9, 0xde, 0x60, 0xdb, 0x51, 0x5a, 0x5f, 0x8b, 0x18, + 0x6f, 0xbf, 0xa8, 0x2c, 0xf7, 0x1d, 0xe8, 0xd8, 0x72, 0xdf, 0x01, 0x95, 0xc8, 0x5d, 0x48, 0xe8, + 0x97, 0x76, 0xe2, 0x1e, 0x9a, 0x2a, 0x1d, 0x50, 0x89, 0xdc, 0x85, 0x16, 0xfc, 0xf2, 0x4e, 0xdc, + 0x43, 0x53, 0xa5, 0x03, 0x8a, 0x7c, 0x63, 0x0a, 0x2e, 0x25, 0x75, 0x07, 0xcf, 0xfd, 0x11, 0x94, + 0x0d, 0xe4, 0xb2, 0xfa, 0x15, 0xfe, 0xbc, 0xa7, 0xbb, 0xf5, 0x77, 0x52, 0xc3, 0xd9, 0x1e, 0x6d, + 0xd7, 0x0f, 0xd9, 0xe5, 0x1b, 0x09, 0x09, 0xfc, 0xea, 0x9e, 0xdf, 0x28, 0x10, 0xc9, 0xae, 0x1f, + 0x82, 0x13, 0x8e, 0xb9, 0xe2, 0x7a, 0xb6, 0x43, 0x2b, 0x76, 0xc3, 0xac, 0xfb, 0xeb, 0xcd, 0xaf, + 0xa9, 0x13, 0x4e, 0x02, 0x11, 0x4e, 0x38, 0x09, 0xf0, 0x38, 0x53, 0xa1, 0x31, 0xff, 0xa2, 0x2b, + 0x53, 0xc9, 0x9c, 0x4b, 0x80, 0xc7, 0x99, 0x0a, 0x31, 0xfd, 0xcb, 0xae, 0x4c, 0x25, 0x73, 0x2e, + 0x01, 0x4e, 0x2c, 0x78, 0x28, 0x34, 0x74, 0x4b, 0x2b, 0xd4, 0xf2, 0x34, 0xbb, 0xd1, 0xb0, 0xdb, + 0xde, 0xa2, 0x63, 0xae, 0xac, 0x50, 0x27, 0xff, 0xeb, 0xca, 0x01, 0x59, 0x57, 0xea, 0xd9, 0x1e, + 0xad, 0x3b, 0x3b, 0xe2, 0x41, 0x21, 0x99, 0x60, 0xc6, 0x76, 0xea, 0x74, 0xca, 0xb6, 0x68, 0xfe, + 0x37, 0x52, 0xd1, 0xeb, 0xe9, 0x1d, 0xe8, 0x67, 0x7b, 0xb4, 0x9d, 0x58, 0x92, 0x8f, 0xc1, 0xc3, + 0xc9, 0x24, 0xec, 0xbf, 0x25, 0xbd, 0xbe, 0x96, 0xff, 0x5f, 0x53, 0x51, 0x9f, 0xbf, 0xee, 0xe4, + 0xb3, 0x3d, 0xda, 0x0e, 0x0c, 0xc9, 0x14, 0x8c, 0xce, 0x4f, 0x56, 0x22, 0x01, 0x1d, 0xbf, 0x99, + 0x52, 0xc2, 0xaf, 0xa2, 0x78, 0x0c, 0xbf, 0x8a, 0x82, 0x98, 0x3d, 0x15, 0x82, 0xa6, 0x2d, 0x23, + 0xff, 0xbf, 0x29, 0xf6, 0x54, 0x04, 0x8b, 0xfe, 0xa5, 0x32, 0x80, 0xcd, 0xb3, 0x21, 0xc0, 0xbf, + 0x97, 0xff, 0x2d, 0x65, 0x9e, 0x8d, 0x51, 0xb0, 0x79, 0x36, 0x06, 0x64, 0x56, 0x4e, 0x08, 0xbc, + 0x69, 0x0b, 0x97, 0x5f, 0xd3, 0xb6, 0xf2, 0xbf, 0xad, 0x58, 0x39, 0xc9, 0x64, 0xcc, 0xca, 0x49, + 0xc6, 0x30, 0xd5, 0x9e, 0xb0, 0xdb, 0x96, 0x71, 0x83, 0x6e, 0xb6, 0x74, 0xd3, 0xf1, 0xe3, 0x90, + 0xf2, 0xbf, 0xa3, 0xa8, 0x76, 0x12, 0x11, 0x53, 0xed, 0x24, 0x78, 0x8c, 0xa9, 0xed, 0xf1, 0xb7, + 0xfd, 0x57, 0xdd, 0x98, 0x0a, 0xa2, 0x18, 0x53, 0x01, 0x27, 0x9f, 0x48, 0xc1, 0x3b, 0x64, 0xc4, + 0x75, 0xdb, 0xb4, 0xd0, 0x03, 0xfb, 0x36, 0x75, 0x82, 0xef, 0x99, 0xd1, 0xcd, 0x06, 0x35, 0xf2, + 0x5b, 0xfc, 0x41, 0x57, 0x93, 0x1e, 0xd4, 0xa5, 0xdd, 0x6c, 0x8f, 0xb6, 0xdb, 0x47, 0x90, 0x25, + 0x78, 0x20, 0x14, 0x29, 0xb3, 0xcc, 0xa8, 0x55, 0xad, 0x4e, 0x57, 0x3d, 0x87, 0xea, 0xcd, 0xfc, + 0x17, 0x95, 0xc3, 0xb6, 0x8e, 0x94, 0xe8, 0x4b, 0xde, 0x09, 0x49, 0xd6, 0xe0, 0x42, 0x88, 0xf4, + 0xed, 0xc2, 0xc5, 0xc5, 0x8a, 0xaf, 0x4e, 0xbf, 0xab, 0x1c, 0xd3, 0x76, 0x23, 0x9e, 0xed, 0xd1, + 0xba, 0x32, 0xc3, 0x8b, 0x8f, 0xc9, 0xf2, 0x3c, 0x7b, 0x07, 0xd3, 0x5a, 0xe1, 0x7e, 0x51, 0xff, + 0xbb, 0x7a, 0xf1, 0xa1, 0x10, 0xe0, 0xc5, 0x87, 0x02, 0xc3, 0xdb, 0xdc, 0xc9, 0xf2, 0xbc, 0x6f, + 0x29, 0x70, 0x4e, 0xbf, 0xa7, 0xde, 0xe6, 0xaa, 0x14, 0x78, 0x9b, 0xab, 0x02, 0x99, 0x35, 0x2d, + 0x12, 0x54, 0x55, 0x34, 0xea, 0x7a, 0x8e, 0x89, 0x91, 0x3a, 0xfe, 0x99, 0xe6, 0xbf, 0x56, 0xac, + 0xe9, 0xce, 0xa4, 0xcc, 0x9a, 0xee, 0x8c, 0x45, 0x6b, 0xba, 0xd5, 0xc2, 0x6b, 0x31, 0xd9, 0x88, + 0xf9, 0x37, 0xaa, 0x35, 0x1d, 0xa7, 0x41, 0x6b, 0x3a, 0x0e, 0x8e, 0x71, 0x14, 0x6f, 0xfc, 0xfb, + 0xdd, 0x38, 0x86, 0x97, 0xbb, 0x09, 0xe0, 0x18, 0x47, 0xb1, 0x06, 0xfd, 0x1f, 0xdd, 0x38, 0x4a, + 0xa1, 0x49, 0x71, 0x70, 0x8c, 0x23, 0x6a, 0xf9, 0x66, 0xfe, 0xdf, 0x76, 0xe3, 0xc8, 0x69, 0x62, + 0x1c, 0x39, 0x98, 0xe9, 0xd0, 0x6d, 0x8b, 0x7a, 0x11, 0x21, 0xfe, 0x9f, 0x8a, 0x0e, 0xa9, 0x04, + 0x4c, 0x87, 0x54, 0x58, 0x94, 0x91, 0x90, 0xdd, 0xff, 0xd5, 0x91, 0x51, 0x78, 0x44, 0xa1, 0xc2, + 0xa2, 0x8c, 0x84, 0xc8, 0xb6, 0x3b, 0x32, 0x0a, 0x8f, 0x28, 0x54, 0x18, 0xb9, 0x0d, 0x67, 0x7c, + 0xcb, 0x26, 0xea, 0x2a, 0xfd, 0x7f, 0x2b, 0xb7, 0x02, 0x89, 0x54, 0xb3, 0x3d, 0x5a, 0x72, 0xf3, + 0x04, 0xbe, 0xe2, 0x73, 0xff, 0x5d, 0x77, 0xbe, 0xe1, 0x2d, 0x46, 0x22, 0x22, 0x81, 0xaf, 0xf8, + 0xfa, 0x7f, 0xdf, 0x9d, 0x6f, 0x78, 0x8b, 0x91, 0x88, 0x60, 0x73, 0x7b, 0xd9, 0x12, 0xf7, 0x8a, + 0xf3, 0xb6, 0x11, 0x9c, 0x44, 0xfd, 0x07, 0x65, 0x6e, 0x4f, 0x22, 0x62, 0x73, 0x7b, 0x12, 0x3c, + 0xce, 0x54, 0xc8, 0xe0, 0xff, 0xe9, 0xca, 0x34, 0xb4, 0xda, 0x92, 0xe0, 0x71, 0xa6, 0x42, 0x00, + 0x7f, 0xd0, 0x95, 0x69, 0x68, 0xb5, 0x25, 0xc1, 0x99, 0x58, 0x03, 0x78, 0x95, 0xd6, 0x1d, 0xea, + 0x1f, 0xd7, 0xfc, 0x47, 0x45, 0xac, 0x89, 0x54, 0x4c, 0xac, 0x89, 0x88, 0x04, 0xbe, 0x42, 0x04, + 0x7f, 0xd8, 0x9d, 0x6f, 0xa8, 0x06, 0x89, 0x88, 0x04, 0xbe, 0x42, 0x0a, 0x7f, 0xd4, 0x9d, 0x6f, + 0xa8, 0x06, 0x89, 0x88, 0x08, 0xdf, 0x88, 0xf5, 0xfe, 0xc7, 0x9d, 0xf8, 0x2a, 0xe6, 0x7b, 0x72, + 0xf3, 0x04, 0xbe, 0x42, 0x0e, 0xff, 0xa9, 0x3b, 0xdf, 0x04, 0x39, 0x44, 0x4c, 0xf8, 0x38, 0x5f, + 0x21, 0x87, 0xff, 0xdc, 0x9d, 0x6f, 0x82, 0x1c, 0x22, 0x56, 0x3c, 0xba, 0x2e, 0x71, 0xb3, 0x11, + 0x0f, 0xf2, 0xcc, 0x37, 0xa9, 0x91, 0xff, 0x93, 0x98, 0xeb, 0x92, 0x42, 0xc1, 0x5d, 0x97, 0x14, + 0x20, 0x2e, 0x76, 0xd4, 0xf1, 0x82, 0x1a, 0x3d, 0xfe, 0x7e, 0x89, 0xaf, 0xa0, 0x7f, 0xaa, 0x2e, + 0x76, 0x1d, 0x49, 0x71, 0xb1, 0xeb, 0x88, 0x45, 0xdf, 0x82, 0xc0, 0xfd, 0x7d, 0x6e, 0x6e, 0xde, + 0xb7, 0x26, 0xfe, 0x8b, 0xea, 0x5b, 0x90, 0x40, 0x84, 0xbe, 0x05, 0x09, 0x70, 0xc6, 0x54, 0xa3, + 0x9e, 0x63, 0xd2, 0x75, 0xbd, 0x21, 0x4f, 0x0b, 0x7f, 0x96, 0x52, 0x83, 0xe9, 0xe3, 0x44, 0x3c, + 0x98, 0x3e, 0x0e, 0x8f, 0x33, 0x15, 0xba, 0xf0, 0x5f, 0xbb, 0x32, 0x0d, 0xa7, 0x85, 0x24, 0x78, + 0x9c, 0xa9, 0x50, 0x84, 0xff, 0xd6, 0x95, 0x69, 0x38, 0x2d, 0x24, 0xc1, 0xc9, 0x55, 0xe8, 0xc7, + 0x34, 0x9e, 0x5a, 0xdb, 0xca, 0xff, 0xb9, 0x92, 0xc9, 0xcf, 0x47, 0xcc, 0xf6, 0x68, 0x01, 0x11, + 0xa9, 0x75, 0x2e, 0xde, 0x9f, 0xff, 0x0b, 0xe5, 0x32, 0xb8, 0x13, 0xe1, 0x6c, 0x8f, 0xd6, 0x91, + 0x09, 0xdb, 0x33, 0x24, 0xd7, 0x54, 0xcb, 0xff, 0xbf, 0xca, 0x9e, 0x21, 0x99, 0x8c, 0xed, 0x19, + 0x3a, 0x14, 0xcb, 0xab, 0x41, 0x7e, 0xda, 0x5a, 0x37, 0x1d, 0xdb, 0xe2, 0xb1, 0xb0, 0xf6, 0xb2, + 0x19, 0x38, 0x9d, 0x7d, 0x59, 0x3a, 0xfa, 0xee, 0x9d, 0x08, 0xd9, 0xbb, 0x77, 0xc2, 0xb1, 0x41, + 0x35, 0x41, 0xf5, 0xa6, 0x1b, 0xb1, 0x23, 0xbe, 0x3c, 0xad, 0x24, 0x94, 0x50, 0x29, 0x30, 0xa1, + 0x84, 0x0a, 0x54, 0x78, 0x89, 0xb7, 0xfc, 0x8a, 0xce, 0xbc, 0xc2, 0x4b, 0xe6, 0x18, 0x50, 0xe1, + 0x25, 0xf4, 0xe6, 0x2b, 0x3b, 0xf3, 0x92, 0x12, 0x5d, 0xa8, 0x40, 0xbc, 0x3b, 0x69, 0xe8, 0xae, + 0x6b, 0x2e, 0x9b, 0x81, 0x29, 0xf1, 0x55, 0x69, 0x35, 0x47, 0x56, 0x94, 0x80, 0xe7, 0xc8, 0x8a, + 0xc2, 0xa2, 0x8c, 0xc4, 0xf7, 0x7d, 0x75, 0x47, 0x46, 0xd2, 0x6d, 0x8e, 0x02, 0x8b, 0x32, 0x12, + 0x1f, 0xf7, 0x35, 0x1d, 0x19, 0x49, 0xb7, 0x39, 0x0a, 0x0c, 0xe7, 0xc4, 0xba, 0xdd, 0xa2, 0x86, + 0xec, 0x1e, 0xfd, 0xb5, 0x8a, 0x98, 0x62, 0x14, 0x38, 0x27, 0xaa, 0x40, 0x85, 0x97, 0xef, 0xca, + 0xd9, 0x99, 0x57, 0xd8, 0x7d, 0x31, 0xa0, 0xc2, 0x4b, 0x7c, 0xe1, 0xc7, 0x3b, 0xf3, 0x0a, 0xbb, + 0x2f, 0x06, 0x24, 0x2f, 0xc1, 0x10, 0x1e, 0x3e, 0xf8, 0x19, 0xd9, 0xbe, 0x5e, 0x49, 0xc3, 0x29, + 0x23, 0x67, 0x7b, 0xb4, 0x08, 0x31, 0x5b, 0x8c, 0xc2, 0x49, 0x54, 0xde, 0xd0, 0x7d, 0x43, 0x3a, + 0xba, 0x18, 0x25, 0x52, 0xb1, 0xc5, 0x28, 0x11, 0x81, 0x87, 0x2d, 0x49, 0x88, 0x09, 0xdb, 0xd8, + 0xe4, 0x79, 0x88, 0x3e, 0x91, 0x56, 0x0e, 0x5b, 0xba, 0x92, 0xe3, 0x61, 0x4b, 0x57, 0x0a, 0xbc, + 0x08, 0x57, 0x28, 0x84, 0xef, 0xe0, 0x27, 0xd3, 0xca, 0x45, 0x78, 0x22, 0x19, 0x5e, 0x84, 0x27, + 0x62, 0xf0, 0xc0, 0x2a, 0x11, 0x13, 0x7e, 0xce, 0xa7, 0xd2, 0xca, 0x81, 0x55, 0x77, 0x7a, 0x3c, + 0xb0, 0xea, 0x4e, 0xc2, 0x16, 0xe1, 0x90, 0x84, 0x17, 0xaf, 0x9e, 0x32, 0xf5, 0xc6, 0x14, 0xb5, + 0x4c, 0x6a, 0xe4, 0xbf, 0x51, 0x4d, 0xc4, 0xdd, 0x91, 0x94, 0x2d, 0xc2, 0x9d, 0xb1, 0x13, 0x7d, + 0xd0, 0x8b, 0xab, 0xf1, 0xf5, 0x5c, 0xff, 0x0f, 0xa7, 0xc6, 0x7e, 0x24, 0x75, 0x3d, 0xd7, 0xff, + 0x23, 0xa9, 0xb1, 0x1f, 0x65, 0xff, 0xff, 0x68, 0x6a, 0xec, 0xc7, 0x52, 0xda, 0x03, 0xca, 0x29, + 0x57, 0xa5, 0xa1, 0x8b, 0x31, 0x91, 0x88, 0xe2, 0x3f, 0x13, 0x51, 0x5c, 0x63, 0x8b, 0xdf, 0x96, + 0x82, 0x21, 0x7e, 0xae, 0x20, 0xaa, 0x7a, 0x9d, 0x87, 0x7e, 0x9e, 0xeb, 0xc8, 0x4f, 0x2d, 0xad, + 0x05, 0xbf, 0xc9, 0x45, 0x18, 0x99, 0xd3, 0x5d, 0x0f, 0x5f, 0xb1, 0x6c, 0x19, 0xf4, 0x2e, 0xe6, + 0xf9, 0xcc, 0x68, 0x0a, 0x94, 0xcc, 0x71, 0x3a, 0xde, 0x0e, 0x2b, 0x62, 0x66, 0x76, 0x2c, 0x66, + 0xd5, 0xff, 0x85, 0xad, 0x42, 0x0f, 0xd6, 0xae, 0x52, 0xda, 0x16, 0x7f, 0x3e, 0x05, 0xb1, 0x2c, + 0x4c, 0xfb, 0xcf, 0x48, 0xbf, 0x00, 0xa3, 0x4a, 0x21, 0x55, 0x91, 0xac, 0x74, 0x97, 0x75, 0x56, + 0xd5, 0xd6, 0xe4, 0x1d, 0x41, 0x92, 0xcc, 0x5b, 0xda, 0x9c, 0x28, 0x54, 0xd6, 0xb7, 0xbd, 0x55, + 0xc8, 0xb4, 0x9d, 0x86, 0x26, 0xa1, 0x44, 0x71, 0x99, 0xdf, 0x38, 0x15, 0x96, 0x98, 0x24, 0x17, + 0x45, 0x7a, 0x7c, 0xa9, 0xdc, 0x62, 0xdb, 0xa5, 0x8e, 0x5c, 0xde, 0x0c, 0xd3, 0xe1, 0xbf, 0x1f, + 0x86, 0xca, 0xcd, 0x16, 0x75, 0x5c, 0xdb, 0xd2, 0x3d, 0xdb, 0x11, 0xe9, 0xbb, 0x31, 0xd1, 0xb6, + 0x29, 0xc1, 0xe5, 0x44, 0xdb, 0x32, 0x3d, 0xb9, 0x0c, 0xbd, 0x1a, 0x16, 0xe8, 0xca, 0x60, 0x71, + 0x4f, 0x4c, 0x61, 0x8b, 0x45, 0xba, 0xe4, 0x5c, 0xe8, 0x48, 0xc1, 0x48, 0x6f, 0xb9, 0x3a, 0xa6, + 0x53, 0x0d, 0x48, 0xdb, 0x0c, 0x20, 0x93, 0x22, 0x05, 0x79, 0x12, 0x72, 0x78, 0x0f, 0xe9, 0xe6, + 0x7b, 0x91, 0x16, 0x73, 0xac, 0x37, 0x10, 0x22, 0xa7, 0x6c, 0xe7, 0x34, 0xe4, 0x06, 0x8c, 0x85, + 0xb1, 0xb5, 0xaf, 0x39, 0x76, 0xbb, 0xe5, 0xe6, 0x73, 0xd8, 0xae, 0xb0, 0xbd, 0x55, 0x78, 0x70, + 0x2d, 0xc0, 0xd5, 0x56, 0x10, 0x29, 0xb1, 0x88, 0x35, 0x24, 0xb3, 0x30, 0x1a, 0xc2, 0x98, 0x88, + 0x78, 0xe9, 0x58, 0x91, 0x1e, 0x5c, 0xe2, 0xc5, 0xc4, 0x29, 0xb3, 0x52, 0x9b, 0x91, 0x32, 0xf4, + 0xf9, 0x15, 0xd7, 0x76, 0xae, 0x09, 0x7b, 0x4a, 0x54, 0x5c, 0xeb, 0x93, 0x6b, 0xad, 0xf9, 0xed, + 0xc9, 0x0c, 0x8c, 0x68, 0x76, 0xdb, 0xa3, 0x8b, 0xb6, 0xd8, 0x1c, 0x8b, 0x22, 0x80, 0xf8, 0x4e, + 0x0e, 0xc3, 0xd4, 0x3c, 0xbb, 0x56, 0xe7, 0x38, 0x39, 0x65, 0x79, 0xb4, 0x15, 0xb9, 0x09, 0xe3, + 0xb1, 0x28, 0x64, 0x51, 0x12, 0x10, 0xeb, 0x61, 0x49, 0x9f, 0x17, 0x67, 0x16, 0x6f, 0x4a, 0xbe, + 0x26, 0x05, 0xb9, 0x45, 0x47, 0x37, 0x3d, 0x57, 0x64, 0x62, 0x3d, 0x73, 0x65, 0xc3, 0xd1, 0x5b, + 0x4c, 0x3f, 0xae, 0x60, 0xf5, 0xd6, 0xdb, 0x7a, 0xa3, 0x4d, 0xdd, 0x89, 0x3b, 0xec, 0xeb, 0x7e, + 0x6b, 0xab, 0xf0, 0x12, 0x4f, 0xbb, 0x7f, 0xa5, 0x6e, 0x37, 0xaf, 0xae, 0x38, 0xfa, 0xba, 0xc9, + 0xcf, 0x5a, 0xf5, 0xc6, 0x55, 0x8f, 0x36, 0x30, 0xa4, 0xe2, 0xaa, 0xde, 0x32, 0xaf, 0x62, 0x9d, + 0xff, 0xab, 0x01, 0x27, 0xfe, 0x04, 0xa6, 0x02, 0x1e, 0xfe, 0x25, 0xab, 0x00, 0xc7, 0x91, 0x9b, + 0x00, 0xe2, 0x53, 0x4b, 0xad, 0x96, 0x48, 0xeb, 0x2a, 0x05, 0x22, 0xf8, 0x18, 0xae, 0xd8, 0x81, + 0xc0, 0xf4, 0x96, 0x54, 0x19, 0x57, 0x93, 0x38, 0x30, 0x2d, 0x58, 0x14, 0x6f, 0xe4, 0x8b, 0x69, + 0x58, 0x4a, 0x12, 0x2f, 0x50, 0x09, 0x42, 0x52, 0x9b, 0x91, 0x25, 0x18, 0x15, 0x7c, 0x7d, 0xa7, + 0x55, 0x91, 0xfa, 0xf5, 0x9c, 0xf2, 0x7a, 0x3e, 0x9a, 0x2b, 0x6d, 0xf0, 0x8e, 0x86, 0x00, 0xcb, + 0xcf, 0x50, 0x5a, 0x90, 0x09, 0x18, 0xf6, 0xff, 0xbe, 0xa9, 0x37, 0xa9, 0x9b, 0x1f, 0x45, 0x8d, + 0xc5, 0x32, 0xa8, 0x7e, 0x7b, 0x2c, 0x3e, 0x28, 0x8b, 0x2e, 0xda, 0x44, 0xe6, 0xc1, 0xb5, 0x7e, + 0x2c, 0x81, 0x87, 0xaa, 0xf3, 0xd1, 0x26, 0x64, 0x12, 0x86, 0x83, 0xac, 0x72, 0xb7, 0x6e, 0x95, + 0xa7, 0x30, 0x6f, 0xac, 0x28, 0x55, 0xd9, 0x5c, 0xd6, 0x6b, 0xa2, 0x6a, 0x5b, 0xbb, 0x1d, 0x2d, + 0x55, 0x19, 0x69, 0x23, 0x15, 0x13, 0xe0, 0x89, 0x64, 0x95, 0x62, 0x02, 0xad, 0x84, 0x62, 0x02, + 0x15, 0xf2, 0x3e, 0x18, 0x2c, 0xdd, 0xa9, 0x8a, 0x22, 0x09, 0x6e, 0xfe, 0x14, 0xbe, 0x3b, 0x96, + 0x45, 0xc0, 0xca, 0x0c, 0xa2, 0xa0, 0x82, 0xfc, 0xea, 0x32, 0x3d, 0x99, 0x86, 0x91, 0x88, 0x8b, + 0x82, 0x9b, 0x3f, 0x8d, 0x1c, 0xf0, 0xcd, 0x75, 0xc4, 0xd4, 0x1c, 0x81, 0x92, 0x87, 0x57, 0xb4, + 0x11, 0xd3, 0x9a, 0x29, 0xd3, 0xd5, 0x1b, 0x0d, 0x7b, 0x43, 0xa3, 0x58, 0x8f, 0x01, 0xb3, 0xd0, + 0xf6, 0x73, 0xad, 0x31, 0x04, 0xaa, 0xe6, 0x70, 0x9c, 0xdc, 0xa3, 0x4a, 0x33, 0xf2, 0x06, 0x90, + 0x12, 0xfb, 0x4d, 0x0d, 0xff, 0x7c, 0xba, 0x3c, 0xe5, 0xe6, 0xcf, 0x62, 0x0d, 0x6b, 0xa2, 0xa6, + 0x4f, 0x2f, 0x4f, 0x4d, 0x5c, 0x14, 0xd3, 0xc7, 0xc3, 0x3a, 0x6f, 0x55, 0xf3, 0x53, 0xa7, 0xd7, + 0x4c, 0x43, 0x7e, 0xe3, 0x04, 0xae, 0x64, 0x03, 0xce, 0x55, 0x1c, 0xba, 0x6e, 0xda, 0x6d, 0xd7, + 0x5f, 0x3e, 0xfc, 0x79, 0xeb, 0xdc, 0x8e, 0xf3, 0xd6, 0xa3, 0xe2, 0xc1, 0x67, 0x5a, 0x0e, 0x5d, + 0xaf, 0xf9, 0xb5, 0x8b, 0x23, 0x15, 0x23, 0x3b, 0x71, 0x67, 0xe2, 0xc2, 0xfd, 0xa9, 0x80, 0x9b, + 0xd4, 0xcd, 0xe7, 0xc3, 0xa9, 0x96, 0x57, 0xfe, 0x30, 0x03, 0x9c, 0x2c, 0x2e, 0xa5, 0x19, 0xd1, + 0x80, 0xbc, 0x36, 0xe9, 0xc7, 0xac, 0x96, 0xea, 0x75, 0xbb, 0x6d, 0x79, 0x6e, 0xfe, 0x01, 0x64, + 0x56, 0x64, 0x62, 0x59, 0xa9, 0xb7, 0x6a, 0x2e, 0x47, 0xd7, 0x74, 0x81, 0x97, 0xc5, 0x12, 0x6f, + 0x4d, 0xe6, 0x60, 0xac, 0xe2, 0xa0, 0x07, 0xfd, 0x0d, 0xba, 0xc9, 0xcf, 0x60, 0x30, 0x19, 0xae, + 0x98, 0x2a, 0x5b, 0x1c, 0x87, 0xe5, 0x85, 0x5b, 0x88, 0x95, 0x97, 0x15, 0xb5, 0xa5, 0x5c, 0x39, + 0xf7, 0xc1, 0xdd, 0x55, 0xce, 0xa5, 0x30, 0x26, 0x22, 0x5e, 0xef, 0x7a, 0xd4, 0x62, 0x4b, 0xbd, + 0x2b, 0x12, 0xdf, 0xe6, 0x95, 0x08, 0xd9, 0x00, 0xcf, 0xa7, 0x0e, 0x31, 0xca, 0x68, 0x00, 0x96, + 0x5f, 0x4c, 0x6d, 0x12, 0x2f, 0x2f, 0xfb, 0xd0, 0x3e, 0xca, 0xcb, 0xbe, 0x00, 0x03, 0xd7, 0x6d, + 0x93, 0x87, 0x98, 0x62, 0x96, 0x5b, 0x51, 0xf6, 0xf4, 0x0d, 0xdb, 0xb4, 0x6a, 0x58, 0x43, 0x54, + 0xae, 0xde, 0x12, 0x90, 0x92, 0x69, 0xe8, 0xc7, 0xad, 0x4e, 0xc5, 0xb4, 0x44, 0x16, 0xdb, 0xb1, + 0xc8, 0xbe, 0xa8, 0x62, 0x5a, 0x7c, 0xf4, 0x63, 0x35, 0xd0, 0x5a, 0x2b, 0x52, 0xeb, 0x31, 0x68, + 0x4a, 0x3e, 0x04, 0x79, 0x45, 0xaf, 0xf9, 0xc0, 0x64, 0x63, 0xe6, 0x11, 0x1c, 0x33, 0xb1, 0x92, + 0x03, 0x3e, 0xc1, 0x44, 0x96, 0x29, 0xb0, 0xd6, 0xb1, 0x3d, 0xb9, 0x06, 0x83, 0xf8, 0xad, 0x3c, + 0x8c, 0x0b, 0xb3, 0xd8, 0xf6, 0xf3, 0x1a, 0xe6, 0x5c, 0x3a, 0x1c, 0xae, 0xc9, 0x44, 0x64, 0x1e, + 0xc6, 0x26, 0x6c, 0x0f, 0x5f, 0x6f, 0x61, 0x99, 0xd7, 0xa2, 0xc4, 0x6c, 0xb5, 0x03, 0x13, 0x8f, + 0x6e, 0x6f, 0x15, 0x1e, 0x62, 0x0d, 0xf9, 0x07, 0xd9, 0xcb, 0xf1, 0x12, 0x96, 0xb1, 0xa6, 0xc5, + 0xaf, 0x4a, 0x87, 0x62, 0x22, 0x05, 0xbf, 0x92, 0x2f, 0xb7, 0xec, 0x06, 0xb6, 0xb7, 0x0a, 0xbc, + 0x56, 0xaa, 0xa8, 0xdf, 0x4b, 0x16, 0x61, 0x30, 0x8c, 0x21, 0x76, 0xf3, 0x69, 0xfc, 0xfe, 0x47, + 0x55, 0xb1, 0x5e, 0x91, 0x68, 0x78, 0x9d, 0x7b, 0xac, 0x59, 0xa0, 0x87, 0x50, 0x4d, 0x66, 0x43, + 0x1e, 0x85, 0x21, 0x9e, 0x6b, 0xbb, 0xe6, 0x84, 0xe6, 0x9e, 0x36, 0xc8, 0x61, 0x68, 0xdf, 0x9d, + 0xff, 0x28, 0x8c, 0xa9, 0x4c, 0x13, 0xca, 0xb6, 0x3f, 0x27, 0x97, 0x6d, 0x97, 0xfd, 0xcf, 0xc5, + 0x8b, 0x59, 0xd4, 0x90, 0xb8, 0xc8, 0x65, 0xdd, 0x5f, 0x84, 0xb3, 0xc9, 0x44, 0x4c, 0x26, 0xdc, + 0x08, 0x4d, 0xe1, 0x88, 0x47, 0x99, 0xe0, 0x6b, 0x0a, 0xd3, 0xb3, 0xf8, 0x1f, 0x32, 0xb2, 0x7d, + 0x40, 0x2e, 0x40, 0x16, 0x47, 0x62, 0x2a, 0x2c, 0xc2, 0x8a, 0x75, 0xee, 0xb3, 0xa2, 0x64, 0xf5, + 0x80, 0x7f, 0x6b, 0xe9, 0x97, 0xb3, 0x19, 0xd9, 0xde, 0x2a, 0x40, 0x58, 0x98, 0x53, 0x0b, 0x09, + 0xc8, 0x55, 0x80, 0x4a, 0x7b, 0xa9, 0x61, 0xd6, 0x4b, 0x86, 0xe1, 0x88, 0xa2, 0xa9, 0xbc, 0xfc, + 0x03, 0x42, 0x6b, 0xba, 0x61, 0x38, 0x9a, 0x44, 0xc2, 0x14, 0xca, 0x8f, 0x2c, 0x0a, 0x0b, 0x16, + 0xa3, 0x42, 0x09, 0x6b, 0x42, 0xd4, 0xdc, 0x97, 0x88, 0xc8, 0x7b, 0x01, 0xc2, 0xe5, 0x4a, 0xec, + 0x04, 0xd0, 0x94, 0x91, 0x57, 0x37, 0xd9, 0x94, 0x09, 0xa9, 0xd9, 0xc2, 0x2e, 0x4f, 0x97, 0x9b, + 0xa2, 0x8e, 0x31, 0x2e, 0xec, 0x91, 0x39, 0x56, 0x9e, 0xc0, 0xa2, 0x4d, 0xc8, 0x02, 0x8c, 0xc7, + 0x66, 0x48, 0x51, 0xde, 0x18, 0x35, 0x3a, 0x61, 0x7a, 0x95, 0x0d, 0xc7, 0x58, 0x5b, 0xf2, 0x18, + 0x64, 0x6e, 0x69, 0x65, 0x51, 0x36, 0x95, 0x57, 0xdc, 0x8d, 0x14, 0x2d, 0x62, 0x58, 0xf2, 0x22, + 0x00, 0xdf, 0xc7, 0x56, 0x6c, 0xc7, 0x43, 0x8b, 0x77, 0x98, 0x17, 0xa4, 0xf7, 0x10, 0x5a, 0x63, + 0x66, 0x96, 0xfc, 0xd1, 0x21, 0x71, 0xf1, 0x2b, 0xd2, 0x31, 0xb3, 0x8b, 0x09, 0x5e, 0xbc, 0x85, + 0xd4, 0xf9, 0x28, 0x78, 0xff, 0xd5, 0xb9, 0xe0, 0x25, 0x22, 0x72, 0x09, 0xfa, 0x2b, 0x6c, 0xd1, + 0xab, 0xdb, 0x0d, 0xa1, 0x0a, 0x58, 0x3b, 0xab, 0x25, 0x60, 0x5a, 0x80, 0x25, 0xd7, 0x78, 0x95, + 0x70, 0x4b, 0xa9, 0x8d, 0xde, 0x16, 0x30, 0xb5, 0x48, 0x38, 0x83, 0xb1, 0x36, 0x81, 0x51, 0x98, + 0x0d, 0xdb, 0x24, 0x98, 0x7c, 0x01, 0x5d, 0xb8, 0xe1, 0xea, 0xdd, 0x69, 0xc3, 0x55, 0xfc, 0xc9, + 0x54, 0x7c, 0x09, 0x21, 0xcf, 0xc5, 0xeb, 0x09, 0xe3, 0x0c, 0x1b, 0x00, 0xe5, 0xa7, 0x06, 0x95, + 0x85, 0x23, 0x95, 0x81, 0xd3, 0xfb, 0xae, 0x0c, 0x9c, 0xd9, 0x63, 0x65, 0xe0, 0xe2, 0x7f, 0xcd, + 0x76, 0x4d, 0x3e, 0x76, 0x24, 0x15, 0xe4, 0x5e, 0x84, 0x61, 0xfe, 0xf4, 0x92, 0x1b, 0xdb, 0xfa, + 0xf2, 0xdc, 0x4a, 0x35, 0x9d, 0x8f, 0x4a, 0x57, 0x8b, 0x52, 0x92, 0x57, 0x60, 0xc8, 0xff, 0x00, + 0xac, 0x38, 0x2d, 0x55, 0x4a, 0x0e, 0x0c, 0x36, 0xa5, 0x36, 0x73, 0xa4, 0x01, 0x79, 0x1e, 0x06, + 0xd0, 0x5c, 0x6f, 0xe9, 0x75, 0xbf, 0x74, 0x39, 0xaf, 0x75, 0xee, 0x03, 0xe5, 0x75, 0x36, 0xa0, + 0x24, 0x1f, 0x81, 0x1c, 0x6e, 0xc7, 0xf8, 0xbe, 0x58, 0x72, 0x54, 0xe9, 0x22, 0x5b, 0xbe, 0x81, + 0x13, 0x8b, 0x03, 0xdf, 0x80, 0x23, 0x20, 0xb2, 0x01, 0x47, 0x08, 0x59, 0x84, 0x53, 0x15, 0x87, + 0x1a, 0x98, 0x17, 0x70, 0xfa, 0x6e, 0xcb, 0xe1, 0x93, 0xa3, 0x98, 0x20, 0xd0, 0xfe, 0x6a, 0xf9, + 0x68, 0x66, 0x19, 0x0a, 0xbc, 0x5c, 0x13, 0x39, 0xa1, 0x39, 0x33, 0xca, 0xf9, 0x9b, 0xdc, 0xa0, + 0x9b, 0x1b, 0xb6, 0x63, 0xb0, 0x6d, 0x74, 0x60, 0x94, 0x0b, 0x41, 0xaf, 0x09, 0x94, 0x6c, 0x94, + 0x47, 0x1b, 0x9d, 0x7f, 0x11, 0x06, 0xa5, 0x2f, 0x49, 0x58, 0x91, 0x4e, 0xcb, 0x2b, 0xd2, 0x80, + 0xbc, 0xe2, 0xfc, 0x40, 0xba, 0x43, 0x1a, 0xcf, 0x23, 0xd1, 0xbb, 0x57, 0xa0, 0x4f, 0x2c, 0x3b, + 0xe2, 0xd0, 0x6b, 0x97, 0xe7, 0x4a, 0x7e, 0x2b, 0x32, 0xa1, 0x4c, 0x37, 0xb2, 0x25, 0x29, 0xe0, + 0x9d, 0x5e, 0x3e, 0x98, 0x7e, 0x0a, 0xd0, 0xcb, 0x53, 0xa5, 0xf7, 0x86, 0xe6, 0xc7, 0xc7, 0x30, + 0xaf, 0x02, 0x87, 0x17, 0xff, 0x38, 0xdd, 0x21, 0x47, 0xe9, 0xfd, 0x2b, 0x33, 0xb6, 0xf0, 0xf8, + 0xc2, 0x28, 0x4f, 0xa1, 0xe4, 0x86, 0xc5, 0xc2, 0xe3, 0x83, 0x99, 0x51, 0x21, 0x13, 0x91, 0x2b, + 0x00, 0x15, 0xdd, 0xd1, 0x9b, 0xd4, 0x63, 0x7b, 0x71, 0x7e, 0x9a, 0x85, 0x56, 0x48, 0x2b, 0x80, + 0x6a, 0x12, 0x45, 0xf1, 0xbb, 0x32, 0xdd, 0x72, 0xb8, 0x9e, 0xc8, 0x7e, 0x2f, 0xb2, 0xbf, 0x06, + 0x83, 0x81, 0x64, 0xcb, 0x53, 0x68, 0x2f, 0x89, 0x36, 0x81, 0xf0, 0xb1, 0x8d, 0x44, 0x44, 0x2e, + 0xf3, 0x77, 0xad, 0x9a, 0x6f, 0x52, 0x9c, 0xf7, 0x86, 0x45, 0x95, 0x7e, 0xdd, 0xd3, 0x6b, 0xae, + 0xf9, 0x26, 0xd5, 0x02, 0x74, 0xf1, 0xa7, 0xd3, 0x89, 0x89, 0x70, 0x4f, 0xfa, 0x68, 0x0f, 0x7d, + 0x94, 0x20, 0x44, 0x9e, 0xc2, 0xf7, 0x44, 0x88, 0x7b, 0x10, 0xe2, 0x1f, 0xa4, 0x13, 0x13, 0x1e, + 0x9f, 0x08, 0x71, 0x2f, 0xb3, 0xc5, 0x93, 0x30, 0xa0, 0xd9, 0x1b, 0xee, 0x24, 0xee, 0x89, 0xf8, + 0x5c, 0x81, 0x13, 0xb5, 0x63, 0x6f, 0xb8, 0x35, 0xdc, 0xed, 0x68, 0x21, 0x41, 0xf1, 0x4f, 0xd3, + 0x5d, 0x52, 0x42, 0x9f, 0x08, 0xfe, 0xad, 0x5c, 0x22, 0x7f, 0x30, 0x1d, 0x49, 0x39, 0x7d, 0xff, + 0x0a, 0xfb, 0x2a, 0x40, 0xb5, 0xbe, 0x4a, 0x9b, 0x3a, 0xee, 0x83, 0x7b, 0xc3, 0x23, 0x0b, 0x17, + 0xa1, 0x7c, 0x1b, 0x2c, 0x91, 0x14, 0x7f, 0x38, 0xad, 0xe4, 0xdc, 0x3e, 0x91, 0xdd, 0xae, 0x65, + 0x17, 0x68, 0x9d, 0x48, 0x23, 0x7e, 0x22, 0xb9, 0xdd, 0x4a, 0xee, 0xeb, 0xd2, 0x4a, 0xc6, 0xf5, + 0xfb, 0x56, 0x76, 0x6c, 0x00, 0xc6, 0x33, 0xc1, 0xdf, 0xb7, 0x9a, 0xf4, 0x24, 0x0c, 0x08, 0x39, + 0x04, 0x4b, 0x05, 0x9f, 0xf7, 0x39, 0x10, 0x0f, 0x68, 0x03, 0x82, 0xe2, 0x57, 0xa7, 0x21, 0x9a, + 0x09, 0xff, 0x3e, 0xd5, 0xa1, 0x1f, 0x4c, 0x47, 0x6b, 0x00, 0xdc, 0xbf, 0xfa, 0x73, 0x05, 0xa0, + 0xda, 0x5e, 0xaa, 0x8b, 0x48, 0xfb, 0x5e, 0xe9, 0x84, 0x3f, 0x80, 0x6a, 0x12, 0x45, 0xf1, 0xcf, + 0xd3, 0x89, 0x85, 0x09, 0xee, 0x5f, 0x01, 0x3e, 0x8b, 0xa7, 0xe2, 0x75, 0x2b, 0x9c, 0xc8, 0xf1, + 0x10, 0x92, 0x8d, 0x3f, 0xf5, 0x3a, 0x33, 0x20, 0x24, 0xef, 0x49, 0x30, 0xd7, 0xf0, 0x92, 0x30, + 0x34, 0xd7, 0xe4, 0xc3, 0x7c, 0xc9, 0x70, 0xfb, 0xb9, 0xf4, 0x4e, 0x75, 0x1c, 0xee, 0xe7, 0x55, + 0xb5, 0xaf, 0xa2, 0x6f, 0x62, 0xbd, 0x41, 0xd6, 0x13, 0x43, 0x13, 0x67, 0xb6, 0xb7, 0x0a, 0xe3, + 0x2d, 0x0e, 0x92, 0xaf, 0x95, 0x05, 0x55, 0xf1, 0xf7, 0x7b, 0x93, 0x8b, 0x08, 0xdc, 0xbf, 0x22, + 0xbc, 0x00, 0xd9, 0x8a, 0xee, 0xad, 0x0a, 0x4d, 0xc6, 0xdb, 0xc0, 0x96, 0xee, 0xad, 0x6a, 0x08, + 0x25, 0x97, 0xa1, 0x5f, 0xd3, 0x37, 0xf8, 0x99, 0x27, 0xbf, 0x39, 0xc3, 0x83, 0x1d, 0x47, 0xdf, + 0xa8, 0xf1, 0x73, 0xcf, 0x00, 0x4d, 0x8a, 0x90, 0x9b, 0xa7, 0xde, 0xaa, 0x6d, 0x88, 0x93, 0x6f, + 0xd8, 0xde, 0x2a, 0xe4, 0x9a, 0x08, 0xd1, 0x04, 0x86, 0x3d, 0x6c, 0xc2, 0x36, 0x36, 0xf1, 0xe6, + 0x6b, 0x88, 0x3f, 0x6c, 0xc9, 0x36, 0x36, 0x35, 0x84, 0x92, 0x8f, 0xa7, 0xa0, 0x6f, 0x96, 0xea, + 0x06, 0x1b, 0x21, 0x03, 0xdd, 0x1c, 0xaa, 0x5e, 0x3f, 0x1c, 0x87, 0xaa, 0xf1, 0x55, 0xfe, 0x30, + 0x59, 0x51, 0xc4, 0xf3, 0xc9, 0x6b, 0xd0, 0x3f, 0xa9, 0x7b, 0x74, 0xc5, 0x76, 0x36, 0xd1, 0x45, + 0x6c, 0x24, 0x74, 0x13, 0x8e, 0xe8, 0x8f, 0x4f, 0xc4, 0x6f, 0xc6, 0xea, 0xe2, 0x97, 0x16, 0x34, + 0x66, 0x62, 0xe1, 0x37, 0x73, 0xe8, 0x23, 0x26, 0xc4, 0xc2, 0xaf, 0xf0, 0x34, 0x81, 0x09, 0x8f, + 0x95, 0x87, 0x92, 0x8f, 0x95, 0xd1, 0x7a, 0x44, 0x37, 0xd2, 0x49, 0xdb, 0xa0, 0xe8, 0x8b, 0x35, + 0x2c, 0xac, 0x47, 0x84, 0xd6, 0xea, 0xb6, 0xc1, 0xac, 0xc7, 0x80, 0xa4, 0xf8, 0xc5, 0x5e, 0x48, + 0x4c, 0x39, 0x7e, 0xa2, 0xe4, 0x27, 0x4a, 0x1e, 0x2a, 0xf9, 0x54, 0x4c, 0xc9, 0xcf, 0xc7, 0x93, + 0xd8, 0xdf, 0xa3, 0x1a, 0xfe, 0xb9, 0x6c, 0xac, 0x04, 0xc6, 0xfd, 0xbd, 0xbb, 0x0c, 0xa5, 0xd7, + 0xbb, 0xa3, 0xf4, 0x82, 0x01, 0x91, 0xdb, 0x71, 0x40, 0xf4, 0xed, 0x76, 0x40, 0xf4, 0x77, 0x1c, + 0x10, 0xa1, 0x82, 0x0c, 0x74, 0x54, 0x90, 0xb2, 0x18, 0x34, 0x20, 0x64, 0xa8, 0xfa, 0xdc, 0x55, + 0x3d, 0xa7, 0x5d, 0xf7, 0xd0, 0x6f, 0x63, 0x84, 0x8d, 0xa6, 0x50, 0x9d, 0xff, 0x6c, 0xab, 0x90, + 0xe3, 0x58, 0x3e, 0xc2, 0x8a, 0x3f, 0x9f, 0xed, 0x52, 0xb7, 0xe6, 0x48, 0x74, 0xe4, 0x59, 0xc8, + 0x94, 0x5a, 0x2d, 0xa1, 0x1f, 0xa7, 0xa4, 0x68, 0x86, 0x0e, 0xad, 0x18, 0x35, 0x79, 0x2f, 0x64, + 0x4a, 0x77, 0xaa, 0x42, 0x25, 0xce, 0x4b, 0xb9, 0x82, 0xc4, 0x97, 0x74, 0x6c, 0x7b, 0xa7, 0x4a, + 0x5e, 0x0e, 0xcb, 0x60, 0xae, 0xb6, 0xad, 0x35, 0xb1, 0x51, 0x14, 0x9e, 0xe4, 0xbe, 0x27, 0x4f, + 0x9d, 0xa1, 0xd8, 0x76, 0x51, 0xa1, 0x55, 0xb4, 0x29, 0xb7, 0x7b, 0x6d, 0xea, 0xdb, 0x51, 0x9b, + 0xfa, 0x77, 0xab, 0x4d, 0x03, 0xbb, 0xd0, 0x26, 0xd8, 0x51, 0x9b, 0x06, 0x0f, 0xae, 0x4d, 0x2d, + 0x38, 0x1f, 0xaf, 0x35, 0x16, 0x68, 0x84, 0x06, 0x24, 0x8e, 0x15, 0x8e, 0x25, 0x78, 0xf5, 0xdf, + 0xe6, 0xd8, 0xda, 0x06, 0xa2, 0x6b, 0x2e, 0xc3, 0xcb, 0xae, 0x97, 0xf1, 0xd6, 0xc5, 0x1f, 0x48, + 0x77, 0x2e, 0x91, 0x76, 0x3c, 0xa7, 0xb8, 0x2f, 0x4d, 0x94, 0x52, 0x56, 0x49, 0xce, 0xd6, 0x51, + 0xca, 0x0a, 0xdb, 0x24, 0x99, 0xd9, 0x70, 0x3e, 0x1e, 0xdf, 0x18, 0x7c, 0xf8, 0x07, 0x80, 0xc4, + 0xb1, 0xa2, 0x97, 0xd0, 0x83, 0x8b, 0x86, 0xd8, 0x5a, 0x8b, 0xa3, 0xe5, 0x4e, 0x8a, 0x37, 0xc6, + 0x4e, 0xea, 0x18, 0x51, 0x79, 0x5c, 0x3b, 0x29, 0x41, 0x48, 0x4a, 0x27, 0x75, 0x16, 0xb2, 0xda, + 0x49, 0x09, 0x32, 0xfb, 0xfe, 0x74, 0xa7, 0xe2, 0x7a, 0x07, 0x92, 0xd8, 0x13, 0x71, 0x8f, 0x45, + 0x8c, 0x13, 0x72, 0xa3, 0xae, 0x8a, 0x33, 0x30, 0x24, 0x0b, 0x51, 0x48, 0x69, 0x37, 0x02, 0x8e, + 0xb4, 0x23, 0x2f, 0xc3, 0xa0, 0x1f, 0x66, 0x16, 0x3a, 0x31, 0xa1, 0x3b, 0xa2, 0x3f, 0xb1, 0x2a, + 0x3e, 0x4c, 0x32, 0x39, 0x79, 0x12, 0x72, 0x33, 0x58, 0x1a, 0x5e, 0x9e, 0x91, 0x79, 0xb1, 0x78, + 0xd9, 0xb5, 0x88, 0xd3, 0x14, 0xff, 0x4d, 0x7f, 0x42, 0xc0, 0xfb, 0x81, 0x84, 0x25, 0xa9, 0x49, + 0x7a, 0x5f, 0x6a, 0xf2, 0x1c, 0xe4, 0xf8, 0xcc, 0x2f, 0x04, 0x38, 0x22, 0x67, 0x57, 0x6d, 0xbb, + 0x4a, 0x33, 0x41, 0x7b, 0x40, 0xa1, 0xbd, 0x00, 0x03, 0x18, 0xfe, 0x2d, 0x9d, 0x9d, 0xe3, 0xd9, + 0x49, 0x93, 0x01, 0xd5, 0x33, 0x97, 0x90, 0x94, 0xac, 0x03, 0x09, 0xf3, 0x29, 0xb0, 0x79, 0x82, + 0x1a, 0x25, 0x7e, 0x4b, 0xd9, 0xdd, 0xad, 0xff, 0x9d, 0x7e, 0x3c, 0x81, 0xe9, 0xb7, 0xe6, 0x13, + 0x37, 0x35, 0x6a, 0x72, 0x37, 0xa1, 0x7f, 0x7f, 0xc2, 0x13, 0xc8, 0x26, 0x9c, 0x0a, 0xa0, 0x33, + 0xa6, 0x65, 0xba, 0xab, 0xf8, 0xe0, 0xbe, 0x1d, 0x1f, 0xfc, 0x2e, 0xf1, 0xe0, 0x42, 0xf8, 0xe0, + 0x65, 0xd1, 0x3e, 0xfe, 0xe4, 0xa4, 0x67, 0x90, 0x1b, 0x30, 0x56, 0x5d, 0xb5, 0x1d, 0x6f, 0x8a, + 0xba, 0x75, 0xc7, 0x6c, 0x61, 0x22, 0x28, 0xbe, 0xe8, 0xa2, 0x77, 0xbc, 0xcb, 0x70, 0x35, 0x23, + 0x44, 0xca, 0xae, 0xd7, 0x6a, 0x43, 0x26, 0x77, 0xcd, 0x74, 0xd7, 0xe6, 0xe8, 0x3a, 0x6d, 0x88, + 0x15, 0x19, 0xe5, 0xee, 0x98, 0xee, 0x5a, 0xad, 0xc1, 0xa0, 0xb2, 0xdc, 0x03, 0x52, 0xf2, 0x32, + 0xe4, 0xf8, 0x71, 0xa1, 0x30, 0xe7, 0xa4, 0xea, 0xdb, 0x0c, 0xda, 0x41, 0xc5, 0x44, 0x1b, 0xf2, + 0x91, 0xa4, 0x30, 0xad, 0xc1, 0x68, 0x8a, 0xa7, 0x18, 0x41, 0x07, 0x9e, 0x09, 0x51, 0x5b, 0xb2, + 0xbd, 0x3d, 0xb4, 0x4f, 0x7b, 0xfb, 0x03, 0x30, 0x1c, 0x32, 0xae, 0xd8, 0x06, 0x6e, 0x58, 0xa4, + 0x48, 0xe2, 0x08, 0xb2, 0x03, 0xb7, 0x28, 0x07, 0x32, 0x03, 0x7d, 0x22, 0xf6, 0x5f, 0x44, 0x48, + 0x3d, 0x9c, 0x5c, 0xac, 0xbe, 0xd3, 0xf8, 0x14, 0xe8, 0x88, 0x27, 0xee, 0xe8, 0xee, 0x3c, 0x71, + 0x8b, 0x3f, 0x99, 0x82, 0x53, 0xec, 0x6d, 0x14, 0x59, 0x92, 0x37, 0x00, 0x18, 0x58, 0x38, 0x4f, + 0xa6, 0xd0, 0x79, 0xf2, 0x5d, 0xf2, 0x37, 0x2a, 0x0d, 0xae, 0x84, 0xd4, 0xdc, 0x71, 0xf2, 0x21, + 0x3f, 0x3e, 0x66, 0xad, 0xbd, 0x44, 0x6b, 0x31, 0x0f, 0x4a, 0x89, 0xfb, 0xf9, 0xf7, 0xf1, 0xc8, + 0xc3, 0xfd, 0x3a, 0x2b, 0x7e, 0x5f, 0xba, 0x63, 0x79, 0xd5, 0x23, 0x59, 0x8e, 0x27, 0xa0, 0xdf, + 0x77, 0x28, 0x15, 0x13, 0x65, 0x2c, 0x08, 0xa3, 0xd3, 0xb3, 0x7d, 0x3c, 0xf9, 0x70, 0x62, 0xaf, + 0x88, 0x25, 0xf9, 0xc1, 0x2e, 0xfd, 0xa0, 0x70, 0x4c, 0xe2, 0x92, 0x2c, 0xb0, 0x23, 0xb4, 0x5f, + 0xee, 0x79, 0x81, 0xfd, 0x6a, 0xaa, 0x63, 0x19, 0xdc, 0xe3, 0x2a, 0xb0, 0xe2, 0x67, 0x7a, 0xfd, + 0xea, 0xbb, 0x07, 0xfa, 0x84, 0x27, 0x61, 0x40, 0x24, 0x64, 0x8e, 0xc6, 0x8c, 0x88, 0x2b, 0x24, + 0xbc, 0x92, 0x0c, 0x08, 0xd8, 0xf6, 0x52, 0x0a, 0xb8, 0x92, 0x62, 0x46, 0xa4, 0x60, 0x2b, 0x4d, + 0x22, 0x61, 0x1b, 0xc8, 0xe9, 0xbb, 0xa6, 0x87, 0xbb, 0x51, 0xd6, 0x97, 0x19, 0xbe, 0x81, 0xa4, + 0x77, 0x4d, 0x8f, 0xef, 0x45, 0x03, 0x34, 0xdb, 0x1c, 0x0a, 0xeb, 0xa4, 0x37, 0xdc, 0x1c, 0xf2, + 0x6d, 0x6b, 0x60, 0x8b, 0x3c, 0x09, 0x03, 0x22, 0x10, 0x43, 0xb8, 0x37, 0x8a, 0xb7, 0x15, 0xa1, + 0x1b, 0xf8, 0xb6, 0x01, 0x01, 0xe3, 0xa8, 0xd1, 0x95, 0xd0, 0xa1, 0x1b, 0x39, 0x3a, 0x08, 0xd1, + 0x04, 0x86, 0x5c, 0x83, 0x91, 0xaa, 0xa7, 0x5b, 0x86, 0xee, 0x18, 0x0b, 0x6d, 0xaf, 0xd5, 0xf6, + 0xe4, 0xc3, 0x10, 0xd7, 0x33, 0xec, 0xb6, 0xa7, 0x29, 0x14, 0xe4, 0x69, 0x18, 0xf6, 0x21, 0xd3, + 0x8e, 0x63, 0x3b, 0xf2, 0x8e, 0xd7, 0xf5, 0x0c, 0xea, 0x38, 0x5a, 0x94, 0x80, 0xbc, 0x07, 0x86, + 0xcb, 0x56, 0x90, 0x5f, 0x58, 0x9b, 0x13, 0xfb, 0x5f, 0x8c, 0x6e, 0x37, 0x03, 0x44, 0xad, 0xed, + 0x34, 0xb4, 0x28, 0x21, 0x79, 0x1e, 0x86, 0x2a, 0x0d, 0xdd, 0x63, 0x66, 0x26, 0x9a, 0x50, 0x83, + 0x61, 0xe0, 0x49, 0x4b, 0xc0, 0xb9, 0x03, 0x42, 0x84, 0x4c, 0x6e, 0x86, 0x56, 0xdb, 0x50, 0x42, + 0x33, 0x66, 0xb6, 0x69, 0x11, 0x32, 0xf2, 0x7e, 0x18, 0xf5, 0x7f, 0x8b, 0x8a, 0x02, 0x22, 0x7a, + 0x18, 0x6d, 0xdd, 0xa0, 0xe5, 0x3a, 0xc7, 0x69, 0x2a, 0x71, 0xf1, 0xb7, 0x53, 0x22, 0x66, 0x27, + 0xd0, 0xaf, 0x97, 0x60, 0xa4, 0xda, 0x5e, 0x0a, 0xec, 0x12, 0x3f, 0x83, 0x82, 0x08, 0x3a, 0x90, + 0x30, 0xfc, 0x34, 0x23, 0x42, 0xca, 0xc4, 0xe6, 0xeb, 0x3b, 0x06, 0xb9, 0x0b, 0x05, 0x45, 0xb1, + 0x05, 0x61, 0x07, 0x18, 0x1a, 0xaf, 0x45, 0x09, 0x99, 0xa2, 0x86, 0x91, 0xa2, 0xb2, 0xa2, 0x4a, + 0xe1, 0xa5, 0x9a, 0x44, 0x22, 0xe9, 0x4a, 0xb6, 0x93, 0xae, 0x14, 0x6f, 0x89, 0xb0, 0xcf, 0xdb, + 0xf3, 0xc1, 0xe7, 0x3d, 0x04, 0xd9, 0xdb, 0xf3, 0xc1, 0x47, 0xe1, 0xd9, 0xe8, 0x7a, 0x93, 0xf1, + 0x46, 0x30, 0x79, 0x0c, 0x72, 0xb7, 0xe7, 0xb1, 0xdf, 0xf8, 0x9b, 0x0f, 0x6e, 0x6f, 0x15, 0xfa, + 0xd6, 0x45, 0x8f, 0x09, 0x54, 0xf1, 0xb3, 0xd9, 0x30, 0x27, 0xd2, 0x81, 0xc6, 0x72, 0x59, 0x11, + 0xbe, 0x98, 0x97, 0xce, 0x44, 0xf2, 0x2b, 0x75, 0x32, 0x69, 0xa2, 0xdd, 0xb6, 0x10, 0xfb, 0x54, + 0x75, 0x6b, 0xaa, 0xa0, 0xd5, 0x94, 0x10, 0xaa, 0xa0, 0xd4, 0x89, 0xa0, 0xb7, 0xf3, 0x44, 0xf0, + 0x12, 0x8c, 0x70, 0xb7, 0x42, 0x9e, 0xa2, 0xc9, 0xf3, 0xf7, 0x0d, 0xa8, 0x32, 0xd4, 0xc7, 0xd4, + 0xd0, 0xbf, 0x4e, 0x53, 0x48, 0x13, 0xc6, 0x73, 0x6e, 0xef, 0xe3, 0xb9, 0x6f, 0xa7, 0xf1, 0xfc, + 0x2c, 0xf4, 0x97, 0x2a, 0x65, 0x4e, 0xdc, 0x2f, 0x05, 0x15, 0xb5, 0xcc, 0x1a, 0x65, 0x40, 0xd9, + 0x54, 0xf3, 0x09, 0xd9, 0xfe, 0x51, 0x4c, 0x70, 0x03, 0xd2, 0x89, 0x1e, 0x42, 0xe4, 0xfd, 0x23, + 0xa7, 0x29, 0x6e, 0xa7, 0xe3, 0xd5, 0xc9, 0xef, 0xdf, 0x53, 0xf2, 0xcb, 0xd1, 0xe8, 0x0d, 0x54, + 0x05, 0x3c, 0x81, 0x94, 0x83, 0xc7, 0xf8, 0x41, 0xe4, 0x35, 0xe8, 0xbf, 0x41, 0x37, 0x79, 0xa0, + 0x51, 0x2e, 0xb4, 0xa2, 0xd7, 0x04, 0x4c, 0xee, 0x1a, 0x9f, 0xae, 0xf8, 0xe3, 0xe9, 0x78, 0xdd, + 0xf5, 0xfb, 0x57, 0xd8, 0x4f, 0x43, 0x1f, 0x8a, 0xb2, 0xec, 0xfb, 0x98, 0xa0, 0x00, 0x51, 0xdc, + 0xd1, 0x90, 0x37, 0x9f, 0xac, 0xf8, 0xed, 0x39, 0xb5, 0x18, 0xff, 0xfd, 0x2b, 0xbd, 0x97, 0x60, + 0x70, 0xd2, 0xb6, 0x5c, 0xcc, 0x50, 0x5c, 0xf7, 0x15, 0x16, 0xa3, 0x3f, 0xeb, 0x21, 0x58, 0x3e, + 0x2e, 0x91, 0xa8, 0xf7, 0xa3, 0xbc, 0x6c, 0xab, 0x8f, 0x22, 0xc7, 0x85, 0xbe, 0x2f, 0xdc, 0xea, + 0x2f, 0x31, 0xa0, 0x7a, 0x38, 0x13, 0x92, 0x92, 0x5b, 0xd0, 0x3f, 0xb9, 0x6a, 0x36, 0x0c, 0x87, + 0x5a, 0x18, 0xa0, 0x26, 0xed, 0xd1, 0xa3, 0x7d, 0x79, 0x05, 0xff, 0x45, 0x5a, 0xfe, 0x3a, 0x75, + 0xd1, 0x2c, 0x92, 0xd1, 0x42, 0xc0, 0xce, 0x7f, 0x53, 0x1a, 0x20, 0x6c, 0x40, 0x1e, 0x81, 0x74, + 0xb0, 0x3e, 0xa2, 0x5f, 0x74, 0x44, 0x83, 0xd2, 0x68, 0x23, 0x8a, 0xb1, 0x9d, 0xde, 0x71, 0x6c, + 0xdf, 0x82, 0x1c, 0xbf, 0x62, 0xc5, 0xd0, 0x45, 0x29, 0x9f, 0x55, 0xc7, 0x17, 0xbe, 0x82, 0xf4, + 0x7c, 0xae, 0xc5, 0x2d, 0x67, 0x64, 0xae, 0xe5, 0xcc, 0xce, 0xd7, 0xa1, 0x17, 0xff, 0x22, 0x17, + 0x21, 0x8b, 0x52, 0x4c, 0xe1, 0xc5, 0x09, 0xda, 0x19, 0x8a, 0xfc, 0x10, 0xcf, 0xba, 0x69, 0xd2, + 0xb6, 0x3c, 0x11, 0xa7, 0x9e, 0xba, 0x34, 0x24, 0xe4, 0x22, 0x60, 0x11, 0xb9, 0x08, 0x58, 0xf1, + 0x67, 0xd2, 0x30, 0x1e, 0xbc, 0x26, 0xb3, 0x28, 0xf0, 0x48, 0xe3, 0xbe, 0x1d, 0x26, 0x2f, 0x02, + 0x60, 0x7a, 0x2c, 0x26, 0x4f, 0x3f, 0x26, 0x18, 0x47, 0x09, 0x32, 0x42, 0xb5, 0x8d, 0x9c, 0x37, + 0x84, 0xc4, 0xc5, 0x9f, 0x4d, 0x41, 0x58, 0xe4, 0xff, 0x10, 0x0e, 0x0a, 0xe4, 0xed, 0x58, 0x7a, + 0x9f, 0xfb, 0x57, 0xbf, 0x2f, 0x32, 0x7b, 0xeb, 0x8b, 0xe8, 0xb7, 0x1c, 0xc2, 0x96, 0xf4, 0x28, + 0xbf, 0xe5, 0x8b, 0x69, 0x20, 0xa2, 0x0a, 0x7f, 0xa9, 0xed, 0xad, 0x96, 0x3c, 0xec, 0xbb, 0xe3, + 0xa9, 0xe2, 0xe1, 0x59, 0x79, 0x76, 0x0f, 0x67, 0xe5, 0x1f, 0x85, 0x51, 0x11, 0x84, 0x8f, 0xd5, + 0xeb, 0x0d, 0xea, 0xe0, 0xfc, 0x2f, 0xe5, 0x11, 0x14, 0xf2, 0x51, 0xa8, 0x3a, 0x27, 0x56, 0x8b, + 0x90, 0x15, 0xff, 0x22, 0x05, 0x17, 0x92, 0x39, 0xdc, 0x23, 0xc3, 0x20, 0x41, 0x00, 0x99, 0xbf, + 0x1c, 0x01, 0x1c, 0xc2, 0xf9, 0xd7, 0xf1, 0x16, 0xc0, 0x3d, 0x32, 0x79, 0xbc, 0xd5, 0x02, 0xf8, + 0xf6, 0x14, 0x14, 0xba, 0x09, 0xa0, 0x74, 0xc0, 0x00, 0x83, 0x43, 0x90, 0x41, 0xf1, 0x5f, 0xa5, + 0xe0, 0xd4, 0xc2, 0x9a, 0xa7, 0xfb, 0x00, 0xf7, 0x10, 0x94, 0x33, 0xbc, 0x98, 0x49, 0xef, 0xe3, + 0x62, 0x66, 0x0e, 0xfa, 0xf8, 0x3b, 0x18, 0xa2, 0x37, 0x1e, 0x97, 0xab, 0x6b, 0x2b, 0xef, 0xd9, + 0xe9, 0xda, 0xc3, 0x67, 0x51, 0xfc, 0xa9, 0x14, 0x8c, 0xb2, 0x76, 0xd5, 0x4d, 0xab, 0x3e, 0xa3, + 0x9b, 0x8d, 0xb6, 0x73, 0x94, 0xdf, 0xb6, 0xaf, 0x6b, 0xcd, 0xe2, 0xef, 0xa6, 0xe1, 0x34, 0x96, + 0x4d, 0x0f, 0xd2, 0xf1, 0x88, 0x2a, 0xcc, 0x47, 0xf7, 0x21, 0x87, 0x71, 0x8e, 0xbe, 0xbf, 0x75, + 0x6b, 0x11, 0x46, 0xa2, 0xb2, 0x10, 0xcb, 0xd6, 0xc3, 0x91, 0xb2, 0xf3, 0x01, 0xb6, 0xc3, 0x5b, + 0x28, 0x3c, 0x8a, 0x9f, 0x4d, 0xc3, 0x58, 0x58, 0x90, 0xef, 0x1e, 0x59, 0xa1, 0xf6, 0x77, 0x09, + 0x5e, 0x86, 0xd1, 0xf0, 0x4b, 0x16, 0x4d, 0xaf, 0xe1, 0x5f, 0x84, 0xe3, 0xd5, 0xac, 0x48, 0xb4, + 0xd7, 0x30, 0x5d, 0xaf, 0xe6, 0x31, 0x64, 0x24, 0xe5, 0x5b, 0xb4, 0x9d, 0x22, 0x95, 0x7b, 0x64, + 0xd9, 0xba, 0xc7, 0xa4, 0x72, 0x8f, 0xac, 0x65, 0x47, 0x2e, 0x95, 0xef, 0x4e, 0xc3, 0xd9, 0x10, + 0x36, 0x4f, 0x9b, 0x4b, 0xf7, 0x8c, 0xa5, 0xf7, 0x21, 0xb9, 0xbf, 0xf8, 0x9b, 0x09, 0x29, 0x3d, + 0x12, 0xcd, 0xd1, 0x13, 0xe2, 0x3b, 0xf0, 0x8c, 0xf1, 0xd9, 0xdf, 0x24, 0x96, 0x28, 0xac, 0x7b, + 0x64, 0x78, 0x1d, 0x0f, 0x61, 0xdd, 0x23, 0xa3, 0xee, 0xde, 0x13, 0xd6, 0x8f, 0xa4, 0xe1, 0x62, + 0xb2, 0xb0, 0x4a, 0x8d, 0xc6, 0x8c, 0xed, 0x84, 0xb8, 0x13, 0xe1, 0xc5, 0x84, 0xf7, 0xe9, 0xc8, + 0xcc, 0xae, 0xd1, 0x75, 0x93, 0x6e, 0x1c, 0xb9, 0x98, 0x66, 0x20, 0xc7, 0xdf, 0xa4, 0xb3, 0x70, + 0x38, 0xbe, 0x93, 0xc9, 0x26, 0xbe, 0xe3, 0x39, 0x10, 0x57, 0xd4, 0xbb, 0x13, 0x09, 0xa7, 0x2d, + 0xfe, 0x69, 0x0a, 0x86, 0x4b, 0x6d, 0xc3, 0xf4, 0xf0, 0x54, 0xf3, 0xa0, 0xd7, 0x7e, 0xfb, 0x3d, + 0x23, 0xd9, 0xdf, 0xea, 0xf6, 0x3e, 0xff, 0xb4, 0x36, 0xab, 0x54, 0xc2, 0x0d, 0xbe, 0x67, 0x8a, + 0x7a, 0xba, 0xd9, 0x50, 0xdb, 0x8b, 0x2c, 0x5b, 0x3f, 0x9e, 0x86, 0xf1, 0x18, 0x29, 0xb9, 0x18, + 0xc9, 0x6b, 0x89, 0xe7, 0xae, 0x8a, 0xc7, 0x13, 0xcf, 0x70, 0xb9, 0x87, 0xa3, 0xe2, 0x8b, 0x90, + 0x9d, 0xd2, 0x37, 0xf9, 0xb7, 0xf5, 0x72, 0x96, 0x86, 0xbe, 0x29, 0x1f, 0x29, 0x22, 0x9e, 0x2c, + 0xc1, 0x99, 0xe0, 0x0a, 0x71, 0xd1, 0x6c, 0xd2, 0xb2, 0x35, 0x6f, 0x36, 0x1a, 0xa6, 0x2b, 0xdc, + 0x15, 0x9e, 0xdc, 0xde, 0x2a, 0x5c, 0xf2, 0x6c, 0x4f, 0x6f, 0xd4, 0xc2, 0xab, 0x47, 0xcf, 0x6c, + 0xd2, 0x9a, 0x69, 0xd5, 0x9a, 0x48, 0x29, 0xb1, 0x4c, 0x66, 0x45, 0xca, 0x40, 0x30, 0x89, 0x52, + 0x5d, 0xb7, 0x2c, 0x6a, 0x94, 0xad, 0x89, 0x4d, 0x8f, 0x72, 0x37, 0x87, 0x0c, 0x3f, 0xf3, 0xe4, + 0xd9, 0x96, 0x38, 0x9a, 0x31, 0x5e, 0x62, 0x04, 0x5a, 0x42, 0xa3, 0xe2, 0x8f, 0x66, 0x61, 0xbc, + 0x4a, 0xeb, 0x6d, 0xc7, 0xf4, 0x36, 0x35, 0x0c, 0x21, 0x3a, 0x5e, 0xea, 0xe3, 0xf7, 0x74, 0x76, + 0x87, 0x9e, 0xbe, 0x0a, 0x7d, 0xbe, 0x07, 0x02, 0xbf, 0x41, 0xc1, 0xf0, 0x50, 0xe1, 0x78, 0x20, + 0x5f, 0x41, 0x09, 0x2a, 0xd2, 0x80, 0xf3, 0x8b, 0xac, 0x9b, 0x92, 0x3b, 0x33, 0xb7, 0x8f, 0xce, + 0xec, 0xc2, 0x8f, 0x7c, 0x10, 0xce, 0x21, 0x36, 0xa1, 0x5b, 0xfb, 0xf0, 0x51, 0x68, 0xeb, 0xf1, + 0x47, 0x25, 0x77, 0x6e, 0xa7, 0xf6, 0xe4, 0x43, 0x30, 0x14, 0x0c, 0x10, 0x93, 0xba, 0xe2, 0x6a, + 0xa6, 0xcb, 0x38, 0xe3, 0xd9, 0xc2, 0x19, 0x18, 0x83, 0x32, 0xa2, 0x19, 0xa7, 0x23, 0xbc, 0x8a, + 0xbf, 0x9d, 0x82, 0xf3, 0xd3, 0x77, 0x79, 0xb2, 0x5f, 0x44, 0x54, 0x3d, 0xdb, 0xd1, 0x57, 0xe8, + 0xb4, 0xa5, 0x2f, 0x35, 0x8e, 0x7e, 0xe5, 0x7f, 0x1f, 0xf4, 0x19, 0xfc, 0xa3, 0x84, 0x4e, 0x3d, + 0x16, 0x38, 0xaf, 0x27, 0xbc, 0xb4, 0xf8, 0x7e, 0xcd, 0x6f, 0x53, 0xfc, 0x9d, 0x14, 0x3c, 0x98, + 0x48, 0x68, 0xba, 0x6f, 0x87, 0xcf, 0xfb, 0x74, 0xa6, 0xc3, 0xe7, 0x89, 0x49, 0xf4, 0x32, 0x8c, + 0x99, 0x96, 0x47, 0x57, 0x1c, 0xee, 0x4c, 0x14, 0x26, 0x74, 0xd5, 0x46, 0x25, 0x38, 0x8e, 0xae, + 0xe7, 0x20, 0x70, 0xde, 0x76, 0x7c, 0x77, 0x7e, 0xb7, 0xd6, 0x76, 0x4c, 0x3e, 0x2e, 0xb5, 0xd3, + 0xae, 0xe2, 0xeb, 0xef, 0xde, 0x72, 0x4c, 0xf6, 0x00, 0xdd, 0x5b, 0xa5, 0x96, 0x5e, 0xdb, 0xb0, + 0x9d, 0x35, 0xf4, 0xbb, 0xe1, 0x83, 0x53, 0x1b, 0xe5, 0xf0, 0x3b, 0x3e, 0x98, 0x3c, 0x06, 0xc3, + 0x2b, 0x8d, 0x36, 0x0d, 0x8a, 0x00, 0xf0, 0xcb, 0x4c, 0x6d, 0x88, 0x01, 0x83, 0x2b, 0xa0, 0x87, + 0x00, 0x90, 0x08, 0xeb, 0xe1, 0xf3, 0x9b, 0x4b, 0x6d, 0x80, 0x41, 0x16, 0x45, 0x77, 0x9d, 0xe7, + 0x5a, 0xcd, 0x85, 0x54, 0x6b, 0xd8, 0xd6, 0x4a, 0xcd, 0xa3, 0x4e, 0x13, 0x5f, 0x14, 0xdd, 0x2e, + 0xb4, 0xb3, 0x48, 0x81, 0x77, 0x43, 0xee, 0x9c, 0x6d, 0xad, 0x2c, 0x52, 0xa7, 0xc9, 0x5e, 0xf5, + 0x49, 0x20, 0xe2, 0x55, 0x1d, 0x3c, 0xd5, 0xe1, 0x1f, 0x87, 0x7e, 0x17, 0x9a, 0xf8, 0x08, 0x7e, + 0xdc, 0x83, 0x1f, 0x56, 0x80, 0x41, 0x9e, 0x09, 0x9d, 0x0b, 0x0d, 0x9d, 0xb3, 0x34, 0xe0, 0x20, + 0x94, 0xd7, 0x59, 0x10, 0xbe, 0x40, 0xdc, 0xff, 0x4a, 0x13, 0xbf, 0x8a, 0x9f, 0xc8, 0x00, 0xc1, + 0x43, 0x8f, 0xc0, 0x6a, 0xc0, 0x72, 0xef, 0x07, 0x51, 0xb4, 0x70, 0x5a, 0x4d, 0xef, 0x69, 0x5a, + 0x1d, 0xb5, 0xda, 0xcd, 0x9a, 0xde, 0x6a, 0xd5, 0x96, 0xcd, 0x06, 0x26, 0x2a, 0xc0, 0x85, 0x4f, + 0x1b, 0xb6, 0xda, 0xcd, 0x52, 0xab, 0x35, 0xc3, 0x81, 0xe4, 0x9d, 0x30, 0xce, 0xe8, 0xb0, 0x93, + 0x02, 0x4a, 0xf4, 0xc7, 0xd1, 0x18, 0x03, 0x74, 0x9e, 0xf2, 0x69, 0x1f, 0x80, 0x7e, 0xc1, 0x93, + 0xaf, 0x55, 0xbd, 0x5a, 0x1f, 0x67, 0xe6, 0xb2, 0x9e, 0x0b, 0xd8, 0xf0, 0xc9, 0xb5, 0x57, 0x1b, + 0xf0, 0xdb, 0x63, 0xc1, 0x1c, 0xab, 0xcd, 0x93, 0x6b, 0x63, 0xb7, 0xf6, 0x6a, 0xc1, 0x6f, 0x72, + 0x11, 0x46, 0x18, 0x97, 0x40, 0x60, 0xbc, 0xc6, 0x48, 0xaf, 0xa6, 0x40, 0xc9, 0x35, 0x38, 0x1d, + 0x81, 0x70, 0x1b, 0x94, 0xfb, 0xce, 0xf4, 0x6a, 0x89, 0xb8, 0xe2, 0x0f, 0x67, 0x60, 0x88, 0x75, + 0x07, 0x5b, 0x9f, 0x8e, 0xa8, 0x23, 0xce, 0x41, 0x9f, 0xed, 0xac, 0xd4, 0xda, 0x4e, 0x43, 0x8c, + 0xbd, 0x9c, 0xed, 0xac, 0xdc, 0x72, 0x1a, 0xe4, 0x0c, 0xe4, 0x58, 0xef, 0x98, 0x86, 0x18, 0x62, + 0xbd, 0x7a, 0xab, 0x55, 0x36, 0x48, 0x89, 0x77, 0x08, 0xd6, 0xa7, 0xa8, 0xd5, 0x71, 0x6b, 0xcf, + 0xbd, 0x2e, 0x7a, 0xf9, 0x8a, 0x17, 0x43, 0x62, 0x3f, 0x61, 0xd5, 0x0a, 0x7e, 0x10, 0xa0, 0xb0, + 0x30, 0x70, 0x5b, 0x62, 0xf0, 0x3e, 0x51, 0x59, 0x08, 0x64, 0xc8, 0x82, 0x6f, 0x62, 0x0c, 0x32, + 0x05, 0x24, 0xa4, 0x6a, 0xda, 0x86, 0xb9, 0x6c, 0x52, 0x1e, 0x27, 0xdd, 0xcb, 0x6f, 0xb6, 0xe3, + 0x58, 0x6d, 0xcc, 0x67, 0x32, 0x2f, 0x20, 0xe4, 0x25, 0xae, 0x84, 0x9c, 0x0e, 0xd7, 0x3e, 0xde, + 0xb7, 0xdc, 0x4e, 0x53, 0x50, 0xa8, 0x99, 0xd8, 0x1e, 0x17, 0xc2, 0xe2, 0x4d, 0x18, 0x46, 0x07, + 0xf2, 0x2a, 0x6d, 0xd0, 0xba, 0x67, 0x3b, 0xe4, 0x01, 0xc8, 0xdc, 0xf0, 0x5d, 0xc8, 0x79, 0x54, + 0xd0, 0x1a, 0xdd, 0xd4, 0x18, 0x8c, 0x14, 0x03, 0x37, 0x80, 0x34, 0xde, 0x1b, 0xa3, 0xa7, 0x16, + 0xbf, 0xdd, 0xf7, 0xef, 0xf4, 0x8b, 0xff, 0xb0, 0x1f, 0xc6, 0xaa, 0x95, 0xf2, 0xcc, 0xcc, 0x74, + 0xf5, 0x76, 0x79, 0x0a, 0xeb, 0xea, 0x1b, 0x47, 0x62, 0x27, 0xcd, 0x02, 0x4c, 0xda, 0x96, 0x45, + 0xb1, 0x8e, 0x72, 0x50, 0xf5, 0xc9, 0xf7, 0x5b, 0x08, 0x30, 0x1d, 0x78, 0x48, 0x6d, 0xc9, 0x65, + 0xe8, 0xe7, 0x5f, 0x54, 0x9e, 0x12, 0xf6, 0x13, 0xfa, 0xd0, 0xb9, 0x2d, 0x73, 0x79, 0x19, 0x1d, + 0x1a, 0x03, 0x34, 0xb9, 0x08, 0x7d, 0x53, 0x37, 0xab, 0xd5, 0xd2, 0x4d, 0xff, 0x6a, 0x1d, 0x23, + 0xc0, 0x0d, 0xcb, 0xad, 0xb9, 0xba, 0xe5, 0x6a, 0x3e, 0x92, 0x3c, 0x06, 0xb9, 0x72, 0x05, 0xc9, + 0x78, 0x5e, 0x13, 0x74, 0x50, 0x34, 0x5b, 0x9c, 0x4a, 0xa0, 0xf0, 0xb9, 0xb7, 0xcb, 0x53, 0x92, + 0x7f, 0x09, 0x7f, 0xee, 0xba, 0x69, 0x70, 0x27, 0xd2, 0x00, 0x4d, 0x9e, 0x87, 0xa1, 0x2a, 0x75, + 0x4c, 0xbd, 0x71, 0xb3, 0x8d, 0x5b, 0x4f, 0x29, 0x4f, 0xba, 0x8b, 0xf0, 0x9a, 0x85, 0x08, 0x2d, + 0x42, 0x46, 0x2e, 0x40, 0x76, 0xd6, 0xb4, 0xfc, 0x20, 0x63, 0x8c, 0x42, 0x5d, 0x35, 0x2d, 0x4f, + 0x43, 0x28, 0x79, 0x0c, 0x32, 0xd7, 0x17, 0xcb, 0xc2, 0x67, 0x16, 0x79, 0xbd, 0xe1, 0x45, 0x72, + 0xae, 0x5f, 0x5f, 0x2c, 0x93, 0xe7, 0x61, 0x80, 0x2d, 0x8a, 0xd4, 0xaa, 0x53, 0x37, 0x3f, 0x88, + 0x1f, 0xc3, 0x7d, 0xf2, 0x7c, 0xa0, 0xec, 0x04, 0x13, 0x50, 0x92, 0x1b, 0x30, 0xe6, 0x97, 0xef, + 0x0d, 0xf2, 0xcc, 0x0f, 0x85, 0xa7, 0x75, 0x1b, 0x02, 0x97, 0x94, 0x6a, 0x3e, 0xd6, 0x90, 0x18, + 0x90, 0x57, 0x61, 0x6c, 0x9f, 0x28, 0xf9, 0xd1, 0x5e, 0xda, 0xde, 0x2a, 0x3c, 0x1e, 0x63, 0x5a, + 0x73, 0x04, 0x95, 0xc4, 0xbd, 0x23, 0x27, 0xf2, 0x61, 0x80, 0x92, 0xe7, 0x39, 0xe6, 0x52, 0x9b, + 0x99, 0x9b, 0x23, 0xdd, 0xe3, 0x64, 0x8b, 0xdb, 0x5b, 0x85, 0xd3, 0x7a, 0x40, 0x9e, 0x18, 0x2d, + 0x2b, 0xb1, 0x23, 0xaf, 0xc0, 0x10, 0x5b, 0xf1, 0xfc, 0x51, 0x28, 0xe2, 0x50, 0xd0, 0xc4, 0x64, + 0x6b, 0x63, 0xcd, 0x15, 0x08, 0xd9, 0xc4, 0x94, 0x1b, 0x90, 0x2f, 0x81, 0x91, 0xc8, 0x38, 0xe6, + 0xf5, 0x78, 0x24, 0x2f, 0xd4, 0x08, 0x96, 0xe7, 0xc4, 0xc6, 0xe8, 0x92, 0x80, 0x75, 0x24, 0x27, + 0x76, 0x94, 0x17, 0xf9, 0x70, 0x58, 0xac, 0xd9, 0x97, 0x0b, 0x2f, 0x2a, 0xc1, 0x0b, 0xf6, 0x3c, + 0xb1, 0xbd, 0x55, 0x78, 0x34, 0x2e, 0x5e, 0x2c, 0x33, 0x21, 0x6f, 0xd3, 0x12, 0x79, 0x14, 0xff, + 0x30, 0x0d, 0xa7, 0x4b, 0x6d, 0x6f, 0xb5, 0xe2, 0x50, 0x11, 0x9d, 0x75, 0x08, 0xc7, 0x87, 0xfb, + 0x5b, 0x46, 0xf6, 0xe9, 0x82, 0xa1, 0x4c, 0x36, 0xd9, 0x03, 0x4c, 0x36, 0xcb, 0x30, 0x5a, 0x32, + 0x9a, 0xa6, 0x55, 0xe2, 0xf5, 0xdf, 0xe7, 0x67, 0x4a, 0xb8, 0x2c, 0x49, 0xc9, 0x54, 0x14, 0xb4, + 0xf8, 0x1e, 0x5e, 0x79, 0x88, 0xa1, 0x6a, 0x3a, 0xc7, 0xd5, 0x9a, 0xcb, 0x7a, 0xad, 0xbe, 0xaa, + 0x5b, 0x2b, 0x6c, 0xe9, 0x51, 0x5a, 0x15, 0x3f, 0x9b, 0x86, 0x87, 0xfc, 0x8a, 0x11, 0xd4, 0x63, + 0x7d, 0x67, 0x5a, 0x2b, 0x91, 0x62, 0xa2, 0xf7, 0xa1, 0xf4, 0x8b, 0x9f, 0x49, 0xc3, 0x05, 0x35, + 0xde, 0xf6, 0xbe, 0x17, 0xca, 0x1f, 0xa4, 0x61, 0x9c, 0x1b, 0x7d, 0x15, 0xdd, 0x5b, 0x9d, 0xe4, + 0x1a, 0x75, 0x20, 0x49, 0x5c, 0x86, 0x7e, 0xce, 0x26, 0x08, 0x7e, 0xc1, 0x95, 0x8d, 0x2b, 0x2b, + 0xae, 0xa8, 0x3e, 0x9a, 0xdc, 0x84, 0xd3, 0xa5, 0xe5, 0x65, 0x5a, 0xf7, 0xc2, 0x5a, 0x3b, 0x37, + 0xc3, 0x92, 0x19, 0xbc, 0xa6, 0x89, 0xc0, 0x87, 0x35, 0xac, 0xd0, 0xcf, 0x3f, 0xb1, 0x1d, 0x59, + 0x84, 0xb3, 0x2a, 0xbc, 0xca, 0xb7, 0x97, 0x59, 0xa9, 0xcc, 0x49, 0x8c, 0x23, 0xff, 0x4f, 0xeb, + 0xd0, 0x36, 0xe9, 0x2d, 0x71, 0xd9, 0xee, 0xed, 0xf6, 0x96, 0xb8, 0x86, 0x27, 0xb6, 0x2b, 0xfe, + 0x78, 0x06, 0xa0, 0xda, 0xd2, 0x2d, 0x0b, 0xf3, 0xc7, 0xdd, 0xbf, 0xde, 0x8a, 0xcf, 0x45, 0x82, + 0x93, 0x76, 0x3b, 0x64, 0x9e, 0x17, 0xf9, 0x1e, 0x8d, 0xb6, 0xe3, 0xbb, 0xf3, 0x06, 0xf9, 0xe6, + 0x10, 0x28, 0xdb, 0x24, 0x01, 0x25, 0x29, 0x43, 0xb6, 0xe4, 0xac, 0xb8, 0x22, 0xe8, 0xb8, 0x7b, + 0x0a, 0x0c, 0xdd, 0x59, 0x49, 0x5e, 0xd4, 0x91, 0x45, 0xf1, 0x1b, 0xd3, 0xf0, 0x00, 0x1f, 0x32, + 0xaf, 0x39, 0x7a, 0x6b, 0xb5, 0x4a, 0x3d, 0x0f, 0x77, 0xf3, 0xf7, 0xef, 0x24, 0xf2, 0x2d, 0x69, + 0x38, 0xcb, 0xcd, 0xe4, 0x19, 0x6a, 0x50, 0x7e, 0x26, 0x72, 0xbc, 0x7d, 0x48, 0xdf, 0x62, 0xe1, + 0x1c, 0x6f, 0xa7, 0xd4, 0x43, 0x14, 0xce, 0xaf, 0xa4, 0xe1, 0x6c, 0x58, 0x35, 0x38, 0x52, 0x3b, + 0xfd, 0xbe, 0x17, 0xce, 0xfe, 0x26, 0xc8, 0x44, 0x91, 0xde, 0xc7, 0x36, 0xb7, 0xac, 0x0a, 0xbd, + 0xfb, 0xf4, 0x3d, 0x4c, 0x12, 0xe9, 0xc9, 0x10, 0x3e, 0x90, 0x96, 0xfe, 0x6a, 0x1a, 0xce, 0x85, + 0x22, 0x15, 0x17, 0x4e, 0x27, 0x23, 0xff, 0xf0, 0x65, 0x7a, 0x32, 0xf4, 0x0f, 0x34, 0xf4, 0x13, + 0x65, 0x7a, 0x32, 0xf6, 0x0f, 0xa4, 0xa7, 0xdf, 0x93, 0x86, 0x87, 0x42, 0x99, 0x96, 0x56, 0xa8, + 0xe5, 0x69, 0x76, 0xa3, 0x61, 0xb7, 0xbd, 0x45, 0xc7, 0x5c, 0x59, 0x39, 0xa2, 0x08, 0xae, 0xc3, + 0x3b, 0x53, 0x3e, 0x0b, 0x39, 0x71, 0x8b, 0x83, 0xc5, 0xed, 0x35, 0xf1, 0x6b, 0x9f, 0xd2, 0xfa, + 0x07, 0x69, 0x28, 0x24, 0x4b, 0x6b, 0xc6, 0x76, 0xea, 0x74, 0xca, 0xb6, 0xe8, 0x89, 0xbc, 0x24, + 0x79, 0x7d, 0x6f, 0x1a, 0x1e, 0x4e, 0x96, 0x17, 0xfb, 0x6f, 0x49, 0xaf, 0xaf, 0x9d, 0x88, 0x4b, + 0x12, 0xd7, 0x2f, 0xa5, 0xe1, 0x34, 0xfb, 0xd3, 0xac, 0xcf, 0xda, 0xae, 0xc7, 0x5e, 0xf5, 0x10, + 0x56, 0xe1, 0xfd, 0xad, 0x18, 0x87, 0xe1, 0xab, 0xef, 0x77, 0x4f, 0xf6, 0x40, 0xdd, 0xd3, 0x7b, + 0x80, 0x2d, 0x4d, 0x5c, 0xa0, 0x47, 0xb6, 0x04, 0xbf, 0x5d, 0x05, 0x7a, 0x08, 0xeb, 0xef, 0xfd, + 0x2c, 0xd0, 0x6f, 0xc8, 0xc0, 0xd8, 0xa4, 0x63, 0x6f, 0x58, 0xd7, 0xe9, 0x06, 0x6d, 0x1c, 0xd9, + 0x70, 0xbf, 0x27, 0x0c, 0x44, 0x67, 0x9f, 0x06, 0xa2, 0xdf, 0x8e, 0xbc, 0x02, 0xa3, 0xa1, 0x2c, + 0xe5, 0xa4, 0xe9, 0xe8, 0x27, 0x50, 0x67, 0xa8, 0xda, 0x1b, 0x0c, 0x27, 0xb2, 0xfb, 0xaa, 0xd4, + 0xc5, 0x3f, 0x8d, 0xf4, 0xc6, 0xfd, 0x6d, 0xae, 0x1f, 0xb8, 0x37, 0x6e, 0xc1, 0xd9, 0xc9, 0xb6, + 0xe3, 0x50, 0xcb, 0x4b, 0xee, 0x14, 0xbc, 0x49, 0xab, 0x73, 0x8a, 0x5a, 0xbc, 0x73, 0x3a, 0x34, + 0x66, 0x6c, 0x45, 0x9c, 0x9e, 0xca, 0xb6, 0x2f, 0x64, 0xdb, 0xe6, 0x14, 0x49, 0x6c, 0x93, 0x1b, + 0x17, 0x7f, 0x2e, 0x2d, 0x77, 0xfd, 0x91, 0xcd, 0x6a, 0x6f, 0x8b, 0xae, 0x2f, 0x7e, 0x22, 0x03, + 0x23, 0xec, 0xb5, 0x16, 0x75, 0x77, 0xed, 0xc4, 0x84, 0x39, 0xc8, 0x02, 0xc1, 0xbe, 0xc2, 0x97, + 0xa4, 0x48, 0x0f, 0x9b, 0x97, 0xdf, 0x82, 0xc1, 0x3b, 0x7d, 0x85, 0x8f, 0x2f, 0x7e, 0x5f, 0x36, + 0xec, 0x8e, 0x13, 0x03, 0xe8, 0xa8, 0xbb, 0x83, 0x2c, 0xc0, 0x69, 0x31, 0xb7, 0xf9, 0x20, 0x9e, + 0x06, 0xac, 0x4f, 0x2a, 0xc2, 0x2f, 0xa6, 0xc5, 0xb6, 0x4b, 0x9d, 0x9a, 0xa7, 0xbb, 0x6b, 0x22, + 0x1d, 0x58, 0x62, 0x43, 0xc6, 0x50, 0xcc, 0x6a, 0x51, 0x86, 0xfd, 0x21, 0x43, 0x7f, 0x42, 0x8c, + 0x31, 0x4c, 0x6a, 0x58, 0xfc, 0xc1, 0x14, 0x8c, 0xa9, 0x9f, 0x43, 0xae, 0x40, 0x3f, 0xfb, 0x1d, + 0x24, 0x90, 0x11, 0xee, 0xed, 0x21, 0x47, 0xee, 0x2b, 0xe5, 0xd3, 0x90, 0x17, 0x60, 0x00, 0xdd, + 0xd2, 0xb0, 0x41, 0x3a, 0xcc, 0xdb, 0x13, 0x36, 0x30, 0x19, 0x9a, 0x37, 0x0b, 0x49, 0xc9, 0x4b, + 0x30, 0x58, 0x0e, 0xfd, 0x79, 0xc5, 0x05, 0x34, 0x86, 0x11, 0x48, 0x2d, 0x43, 0x02, 0x4d, 0xa6, + 0x2e, 0x7e, 0x21, 0x1d, 0xaa, 0xfa, 0x89, 0x69, 0x7a, 0x20, 0xd3, 0xf4, 0x07, 0x32, 0x30, 0x3c, + 0x69, 0x5b, 0x9e, 0x5e, 0xf7, 0x4e, 0x0e, 0x83, 0x0f, 0x72, 0xc8, 0x46, 0x0a, 0xd0, 0x3b, 0xdd, + 0xd4, 0xcd, 0x86, 0x30, 0x7c, 0x30, 0x8d, 0x22, 0x65, 0x00, 0x8d, 0xc3, 0xc9, 0x6b, 0x98, 0x53, + 0x8b, 0x49, 0x3a, 0x70, 0x42, 0x1c, 0x09, 0xab, 0x71, 0x48, 0x28, 0x9e, 0x62, 0xaa, 0xce, 0x01, + 0x7c, 0xe4, 0xc8, 0x2d, 0xe5, 0x3e, 0x3b, 0x39, 0x18, 0x3d, 0x26, 0x7d, 0xf6, 0xb9, 0x0c, 0x9c, + 0x55, 0x1d, 0xf6, 0x4e, 0x06, 0x9c, 0xe8, 0xbc, 0xbf, 0x02, 0xa7, 0x55, 0xd9, 0x4c, 0x31, 0x69, + 0xf4, 0x76, 0xf7, 0x1d, 0xb9, 0xb2, 0xbd, 0x55, 0x78, 0x24, 0xee, 0x28, 0xc9, 0x1e, 0x96, 0xe8, + 0x4d, 0x92, 0xf8, 0x90, 0xc4, 0x9e, 0xb9, 0x47, 0x22, 0xae, 0xef, 0xf3, 0x9e, 0xf9, 0x96, 0x74, + 0xbc, 0x67, 0x4e, 0x26, 0x3c, 0xb1, 0x70, 0xff, 0x42, 0x1a, 0x1e, 0x57, 0x85, 0xf3, 0xfa, 0xf3, + 0x4f, 0xbf, 0xa8, 0x51, 0x3f, 0xf3, 0xf2, 0xc9, 0xf4, 0x22, 0x94, 0x18, 0xd3, 0x22, 0xeb, 0x6e, + 0x10, 0x85, 0x29, 0xd2, 0x22, 0x33, 0x88, 0x26, 0x30, 0xbb, 0x10, 0xe7, 0xc9, 0x9c, 0xb0, 0x07, + 0x71, 0x7e, 0xd7, 0x8e, 0xe2, 0x3c, 0x19, 0xc8, 0xbe, 0xbb, 0x5a, 0x16, 0xe0, 0x35, 0xd3, 0x13, + 0xf9, 0xe9, 0x8f, 0xf9, 0x55, 0x99, 0xe4, 0xe7, 0x9a, 0xdd, 0x97, 0x9f, 0x6b, 0x98, 0x7c, 0xaa, + 0x77, 0x1f, 0xc9, 0xa7, 0x5e, 0x81, 0x3e, 0x21, 0x47, 0xb1, 0x6f, 0x3f, 0x17, 0x7e, 0x05, 0x82, + 0x3b, 0x3d, 0xde, 0x97, 0xfe, 0x13, 0xd0, 0xe7, 0xf2, 0x84, 0x6c, 0x62, 0x5f, 0x8d, 0xb1, 0x44, + 0x02, 0xa4, 0xf9, 0x7f, 0x90, 0x0b, 0x80, 0x05, 0xe6, 0xe4, 0x50, 0x1f, 0x5e, 0x70, 0x8e, 0xfd, + 0x4b, 0xca, 0xd0, 0x27, 0xa2, 0x06, 0xf2, 0x80, 0x61, 0x23, 0x81, 0x5a, 0x86, 0xfd, 0xcc, 0x83, + 0x07, 0xf8, 0x99, 0xb5, 0x20, 0x96, 0x03, 0xc2, 0x05, 0xa8, 0xf8, 0x53, 0x29, 0x18, 0x53, 0x1b, + 0x91, 0x27, 0x21, 0xc7, 0xff, 0x12, 0x3b, 0x74, 0xcc, 0x0b, 0xcb, 0x5b, 0xc8, 0x79, 0x61, 0x05, + 0xf5, 0xf3, 0x30, 0xa0, 0xf9, 0xa1, 0x20, 0x62, 0x87, 0x8e, 0xfe, 0xbb, 0x41, 0x7c, 0x48, 0xa4, + 0x86, 0x8e, 0x0f, 0x24, 0x8f, 0x41, 0x66, 0xa1, 0x61, 0x88, 0x8d, 0x39, 0xc6, 0x2b, 0xd9, 0x0d, + 0x39, 0xe9, 0x2d, 0xc3, 0x32, 0xa2, 0x9b, 0x74, 0x43, 0x38, 0x7b, 0x23, 0x91, 0x45, 0x37, 0x64, + 0xa2, 0x9b, 0x74, 0xa3, 0xf8, 0x8b, 0x29, 0xdf, 0x7d, 0x77, 0xce, 0x74, 0xbd, 0xb2, 0xb5, 0xae, + 0x37, 0xcc, 0xa0, 0x23, 0xc8, 0x6b, 0x30, 0x12, 0x22, 0xa5, 0xfc, 0x09, 0xb1, 0x3c, 0x43, 0x18, + 0x61, 0xff, 0x88, 0x14, 0x55, 0x13, 0x6d, 0x46, 0x2e, 0x4a, 0xca, 0x2f, 0x9d, 0x5a, 0xc8, 0x41, + 0xf9, 0xc2, 0x15, 0x7b, 0x68, 0xde, 0x74, 0x5d, 0xd3, 0x5a, 0xe1, 0xb1, 0x9d, 0x19, 0x0c, 0xb3, + 0xc2, 0xf3, 0x93, 0x26, 0x87, 0xd7, 0x1c, 0x86, 0x90, 0x83, 0x83, 0xe4, 0x06, 0xc5, 0x3f, 0x4f, + 0xc1, 0x79, 0xc6, 0x09, 0x33, 0x9e, 0xc6, 0x3e, 0xec, 0x40, 0x03, 0xb8, 0xd9, 0x45, 0x52, 0x62, + 0x54, 0x3f, 0x1a, 0x4f, 0xf2, 0xa1, 0x10, 0x2a, 0xdc, 0xbb, 0xc8, 0x7e, 0x7f, 0x49, 0xe7, 0x7e, + 0x21, 0x85, 0xd7, 0x83, 0x4b, 0x0d, 0x7a, 0xeb, 0x66, 0xf9, 0xf5, 0x43, 0xba, 0xc0, 0xde, 0xef, + 0xd4, 0xf5, 0x2a, 0x8c, 0xb9, 0xf8, 0x2e, 0xb5, 0xb6, 0x65, 0xde, 0xc5, 0x93, 0x2f, 0xf1, 0x31, + 0x67, 0xa5, 0x8f, 0x91, 0xde, 0x55, 0x1b, 0xe1, 0xf4, 0xb7, 0x2c, 0xf3, 0x2e, 0x26, 0x7c, 0x7d, + 0x3f, 0x26, 0xdb, 0x97, 0x28, 0xc8, 0x79, 0xe8, 0xf7, 0xcb, 0x15, 0x89, 0xe2, 0x3f, 0xc1, 0x6f, + 0x32, 0x06, 0x99, 0xb6, 0x69, 0xe0, 0x6b, 0xf6, 0x6a, 0xec, 0xcf, 0xe2, 0xaf, 0x67, 0x60, 0xbc, + 0x74, 0xa7, 0x5a, 0x9e, 0x0c, 0xa2, 0x18, 0x44, 0xd0, 0x6e, 0x73, 0x8f, 0xb2, 0xf0, 0xe9, 0xc9, + 0x24, 0x8c, 0xf0, 0xa4, 0x0b, 0xa2, 0x22, 0x08, 0x3f, 0x97, 0xea, 0xe5, 0xd1, 0x14, 0x51, 0x8c, + 0xa4, 0xa4, 0xc3, 0x88, 0x11, 0x85, 0x43, 0x5c, 0x52, 0x87, 0x07, 0x22, 0xa4, 0x35, 0x3d, 0xc8, + 0x69, 0xe7, 0xe7, 0x13, 0x79, 0xc7, 0xf6, 0x56, 0xe1, 0xb1, 0x8e, 0x44, 0x12, 0xeb, 0x73, 0x32, + 0xeb, 0x30, 0x37, 0x9e, 0x4b, 0x6e, 0xc0, 0x38, 0x6f, 0x8f, 0x87, 0x76, 0x81, 0x93, 0x04, 0x63, + 0x2e, 0xe5, 0x8e, 0x90, 0x90, 0x72, 0x9e, 0x30, 0x44, 0x32, 0x81, 0x8b, 0x80, 0xeb, 0x3b, 0x70, + 0x86, 0xd3, 0xb7, 0xa8, 0x83, 0x23, 0xd1, 0xb6, 0x6a, 0x2e, 0xf5, 0x44, 0xdc, 0xf6, 0xc4, 0x63, + 0xdb, 0x5b, 0x85, 0x42, 0x22, 0x81, 0xc4, 0xf4, 0x14, 0x12, 0x54, 0x02, 0x7c, 0x95, 0x7a, 0xb2, + 0x9f, 0x46, 0x6e, 0x0f, 0x6a, 0xfe, 0xad, 0x69, 0x38, 0x37, 0x4b, 0xf5, 0x86, 0xb7, 0x3a, 0xb9, + 0x4a, 0xeb, 0x6b, 0x27, 0xae, 0xd2, 0x11, 0xab, 0x25, 0x51, 0x3a, 0x27, 0x26, 0x72, 0x37, 0xe9, + 0x9c, 0x58, 0xbc, 0x42, 0x3a, 0xdf, 0x9b, 0x86, 0x4b, 0x49, 0x9b, 0x03, 0xbc, 0x1d, 0x70, 0x16, + 0xd6, 0xa9, 0xe3, 0x98, 0x06, 0x3d, 0xc2, 0x45, 0xe5, 0xf0, 0xec, 0x61, 0xb9, 0xc3, 0xb2, 0xfb, + 0xf4, 0x88, 0xdd, 0x9d, 0xb8, 0x8e, 0xb0, 0x12, 0xd8, 0xbd, 0x25, 0xae, 0x6f, 0x4e, 0xc3, 0xe9, + 0xaa, 0xb9, 0xe2, 0x7a, 0xb6, 0x43, 0x2b, 0x98, 0xfd, 0xe4, 0x44, 0x93, 0x3a, 0x8a, 0xe6, 0x08, + 0x0b, 0xee, 0xdd, 0xeb, 0xa2, 0x39, 0x19, 0x50, 0x42, 0x34, 0xff, 0x2e, 0x0b, 0xa3, 0xf3, 0x93, + 0x15, 0xbf, 0x0e, 0xb0, 0xa7, 0x3b, 0xc7, 0xb4, 0x1c, 0x42, 0x78, 0xb6, 0x90, 0xdd, 0xc7, 0xd9, + 0xc2, 0xe1, 0xb9, 0x17, 0x88, 0x72, 0xfc, 0xb9, 0x3d, 0x95, 0xe3, 0x7f, 0x1c, 0x46, 0x9a, 0xf5, + 0x56, 0xcd, 0xcf, 0x78, 0x65, 0x1a, 0x22, 0xd7, 0xd4, 0x50, 0xb3, 0xde, 0xf2, 0x8b, 0x4c, 0x1b, + 0xa4, 0x00, 0x83, 0xf5, 0x86, 0x49, 0x2d, 0xaf, 0x66, 0x5a, 0xcb, 0xb6, 0xc8, 0x2f, 0x05, 0x1c, + 0x54, 0xb6, 0x96, 0x6d, 0x46, 0xe0, 0xe2, 0xf7, 0x70, 0x02, 0x9e, 0x4c, 0x0a, 0x38, 0x08, 0x09, + 0x2e, 0xc3, 0x18, 0x9e, 0xe6, 0xd7, 0xed, 0x86, 0x5f, 0x22, 0x4f, 0xe4, 0x92, 0x1a, 0xf5, 0xe1, + 0x7e, 0xb6, 0xba, 0x77, 0xc2, 0xb8, 0x69, 0xad, 0x38, 0x6c, 0x87, 0xae, 0xb7, 0x3d, 0x5e, 0xa5, + 0x47, 0xe4, 0x96, 0x1a, 0x15, 0x88, 0x52, 0xdb, 0xe3, 0x75, 0x7a, 0x2e, 0xc1, 0x18, 0x55, 0x49, + 0x31, 0x45, 0x89, 0x36, 0x42, 0x23, 0x94, 0xc5, 0xcf, 0x67, 0x61, 0x38, 0x54, 0xb7, 0xe9, 0x23, + 0x3a, 0x12, 0xbb, 0xbf, 0x95, 0x2d, 0xdc, 0x31, 0xf5, 0xed, 0xe1, 0x36, 0xf5, 0xe3, 0x29, 0xe8, + 0x5b, 0xa5, 0xba, 0x41, 0x1d, 0x9e, 0x0f, 0x6b, 0xf0, 0xda, 0x99, 0x2b, 0x1b, 0x8e, 0xde, 0x6a, + 0x51, 0x47, 0x64, 0x4c, 0xe1, 0x69, 0x8d, 0x26, 0x5e, 0x67, 0xcd, 0x7f, 0x6b, 0xab, 0xf0, 0xd2, + 0x8a, 0xe9, 0xad, 0xb6, 0x97, 0xae, 0xd4, 0xed, 0xe6, 0xd5, 0x15, 0x47, 0x5f, 0x37, 0x3d, 0x3c, + 0x1a, 0xd6, 0x1b, 0x57, 0x3d, 0xda, 0xc0, 0xbc, 0x90, 0x57, 0xf5, 0x96, 0x79, 0x15, 0xcb, 0xe8, + 0x5c, 0x0d, 0x38, 0x2d, 0x3a, 0xba, 0xe9, 0xb9, 0xdb, 0x5b, 0x85, 0x71, 0xf1, 0x30, 0xf9, 0xe0, + 0x4c, 0x80, 0x8a, 0xbf, 0x9a, 0x82, 0xf1, 0xf9, 0xc9, 0xca, 0xf5, 0xea, 0xc2, 0x4d, 0xad, 0x32, + 0x39, 0x4f, 0x5d, 0x57, 0x5f, 0xa1, 0xe4, 0x09, 0xe8, 0x13, 0x10, 0x71, 0xca, 0x84, 0xc7, 0x7b, + 0x6f, 0xb8, 0xb6, 0xe5, 0xb4, 0xea, 0x9a, 0x8f, 0x13, 0xe5, 0x9e, 0xd2, 0x5d, 0xca, 0x3d, 0x15, + 0x21, 0xd7, 0xa4, 0xde, 0xaa, 0xed, 0x1f, 0x90, 0xe1, 0x19, 0x38, 0x87, 0x68, 0xe2, 0x7f, 0xb2, + 0x00, 0xb9, 0x96, 0xee, 0xe8, 0x4d, 0x77, 0xa7, 0xdb, 0xb2, 0x87, 0xb1, 0x2e, 0x25, 0x92, 0x26, + 0xde, 0x8e, 0x09, 0x36, 0xc5, 0x6f, 0xcc, 0xe2, 0x37, 0x05, 0x29, 0x35, 0x3e, 0xd6, 0xa6, 0x3c, + 0x59, 0xef, 0xbe, 0x4f, 0x1a, 0x5e, 0x80, 0x6c, 0x7b, 0x8f, 0xa3, 0xa3, 0x2d, 0x46, 0x87, 0xbb, + 0xaf, 0xd1, 0x21, 0x5a, 0xf9, 0x5a, 0x99, 0xdd, 0xab, 0x56, 0xba, 0x7b, 0xb8, 0xe3, 0xe7, 0xb4, + 0xe4, 0x26, 0xf4, 0x35, 0x79, 0xf7, 0x8b, 0x41, 0x10, 0x64, 0xa1, 0x8c, 0xe9, 0xc7, 0xc4, 0x03, + 0xa2, 0x5e, 0xf4, 0xb8, 0x68, 0x21, 0x6b, 0x96, 0x00, 0x45, 0xb4, 0xbc, 0xef, 0x88, 0xb5, 0xfc, + 0xaf, 0x65, 0xe1, 0x6c, 0xa8, 0x11, 0x37, 0x6d, 0xcf, 0x5c, 0x36, 0xf9, 0xf5, 0xca, 0x7d, 0xa4, + 0x16, 0x52, 0x07, 0xf7, 0x1e, 0x46, 0x07, 0x87, 0x6a, 0x96, 0xdb, 0x83, 0x9a, 0xdd, 0x4b, 0x6a, + 0xf1, 0x33, 0x19, 0x78, 0x20, 0x54, 0x8b, 0x39, 0xac, 0xcb, 0x57, 0xad, 0x4e, 0x57, 0x3d, 0x87, + 0xea, 0xcd, 0x63, 0xab, 0x19, 0xfa, 0x9e, 0x34, 0x43, 0xdf, 0xf7, 0x84, 0x21, 0xf7, 0x64, 0xee, + 0x88, 0x7b, 0xf2, 0xdb, 0xb2, 0x70, 0x21, 0xec, 0x49, 0x71, 0x7e, 0x3f, 0xbb, 0xb8, 0x58, 0x39, + 0xee, 0xb3, 0xff, 0xde, 0x3b, 0xd3, 0xbf, 0x73, 0xeb, 0x4d, 0xbc, 0x73, 0x0b, 0x17, 0xe4, 0x5c, + 0xc7, 0x05, 0xf9, 0x9d, 0x30, 0xe0, 0xe8, 0x1b, 0x3c, 0x44, 0x40, 0xce, 0x01, 0x18, 0x00, 0xb5, + 0x7e, 0x47, 0xdf, 0xe0, 0x01, 0x08, 0x17, 0x20, 0xbb, 0x64, 0x1b, 0x9b, 0x68, 0xc7, 0x0c, 0xf1, + 0xa7, 0xb1, 0xdf, 0x1a, 0xfe, 0x1b, 0x51, 0x91, 0x81, 0x23, 0x56, 0x91, 0xef, 0xce, 0xc2, 0xe9, + 0x09, 0xbb, 0x6d, 0x19, 0x37, 0xe8, 0x66, 0x4b, 0x37, 0x1d, 0x8d, 0xd6, 0xed, 0x75, 0xf6, 0x09, + 0x7f, 0xf9, 0x2e, 0xb1, 0x87, 0xb7, 0x6f, 0x7d, 0x12, 0x06, 0x16, 0xed, 0x35, 0x6a, 0x49, 0x69, + 0xb6, 0xb1, 0x46, 0xba, 0xc7, 0x80, 0x3c, 0x23, 0x55, 0x48, 0x40, 0x9e, 0x86, 0xbe, 0x09, 0x9b, + 0x5f, 0x1e, 0x4a, 0xb5, 0x5e, 0x97, 0x6c, 0x71, 0x69, 0x28, 0x09, 0x4d, 0x90, 0x91, 0x17, 0x60, + 0xa0, 0xd2, 0x5e, 0x6a, 0x98, 0xf5, 0x1b, 0xd4, 0x8f, 0x54, 0x41, 0xb7, 0xe5, 0x16, 0x02, 0x6b, + 0x6b, 0x74, 0x33, 0x92, 0xd5, 0xc8, 0x27, 0xe5, 0xc5, 0xbc, 0xb9, 0x7c, 0x27, 0xed, 0xb6, 0xe5, + 0xa1, 0x1a, 0x0d, 0xfb, 0xc5, 0xbc, 0x39, 0xa2, 0x86, 0xd7, 0x24, 0x5a, 0x94, 0x90, 0x3c, 0x0f, + 0x43, 0x3e, 0x60, 0xde, 0x36, 0xa8, 0x9c, 0x54, 0x32, 0x68, 0xd8, 0xb4, 0x0d, 0xaa, 0x45, 0xc8, + 0xc8, 0x3c, 0x8c, 0x4d, 0xd8, 0x1e, 0xa6, 0xf6, 0x5b, 0x58, 0x5e, 0x70, 0xcc, 0x15, 0xd3, 0x12, + 0xb7, 0xce, 0x8f, 0x6e, 0x6f, 0x15, 0x1e, 0x62, 0xdf, 0x88, 0x89, 0x01, 0x6b, 0xf6, 0x72, 0xcd, + 0x46, 0xb4, 0x9c, 0xdc, 0x51, 0x6d, 0xca, 0x0c, 0x86, 0xa8, 0xb2, 0xd8, 0x5e, 0x60, 0x2e, 0x9c, + 0x28, 0x4b, 0x07, 0x65, 0x59, 0x80, 0xf1, 0x8a, 0x43, 0xd7, 0x4d, 0xbb, 0xed, 0xaa, 0x4a, 0x83, + 0x9d, 0xd0, 0x12, 0xc8, 0x5a, 0xa2, 0xf6, 0xc4, 0xdb, 0x92, 0x57, 0x61, 0xe8, 0x26, 0xdd, 0x08, + 0x79, 0xf5, 0x85, 0xc9, 0xd2, 0x2c, 0xba, 0x91, 0xcc, 0x26, 0xd2, 0x22, 0x51, 0x2d, 0xfa, 0x0f, + 0xa0, 0x16, 0x19, 0x78, 0x87, 0xac, 0x16, 0xd7, 0x6d, 0x93, 0x97, 0x22, 0xbf, 0x4d, 0x9d, 0xc0, + 0xa4, 0x9c, 0xd1, 0xcd, 0xc6, 0x01, 0x53, 0xd5, 0xbd, 0xdd, 0x35, 0x25, 0xa9, 0x5b, 0x72, 0xfb, + 0xef, 0x96, 0xdf, 0x4b, 0xc3, 0x60, 0x75, 0xb2, 0x3c, 0xef, 0x2f, 0xf6, 0x3b, 0x97, 0x21, 0x9e, + 0x80, 0x61, 0x9e, 0x44, 0xaf, 0x64, 0x18, 0x0e, 0x75, 0x5d, 0xb1, 0x89, 0x45, 0xd5, 0x12, 0x89, + 0xf2, 0x74, 0x8e, 0x91, 0x6f, 0x8e, 0x23, 0x4d, 0xd8, 0xdc, 0xc8, 0x4c, 0x00, 0x0c, 0xe4, 0x17, + 0xdb, 0xdb, 0x30, 0xa4, 0x43, 0x67, 0x50, 0x79, 0x6e, 0x0c, 0x48, 0xc9, 0x93, 0x90, 0x9b, 0xe7, + 0x4b, 0x70, 0x36, 0x74, 0x4b, 0xe1, 0x4b, 0xaf, 0xec, 0x96, 0xc2, 0x69, 0xc8, 0x45, 0xc8, 0x56, + 0xc2, 0xe5, 0x1c, 0x27, 0x50, 0xb6, 0x90, 0xcb, 0xee, 0x1a, 0x15, 0xee, 0x4c, 0x93, 0x9d, 0x60, + 0x0b, 0x71, 0x6e, 0x17, 0x79, 0xe4, 0xd8, 0xda, 0x9c, 0x9c, 0x47, 0x8e, 0xb1, 0x20, 0xa7, 0xfd, + 0xc2, 0x1b, 0xfc, 0xe4, 0x4c, 0x14, 0xf3, 0xd8, 0x84, 0x21, 0x2e, 0x63, 0xb7, 0x65, 0x5b, 0x2e, + 0x25, 0x0f, 0x03, 0x70, 0xbd, 0xc3, 0x32, 0xff, 0x58, 0x44, 0x59, 0x93, 0x20, 0xc1, 0x0b, 0xa5, + 0x0f, 0xfc, 0x42, 0xc5, 0xbf, 0x9d, 0x86, 0x11, 0xf6, 0xec, 0x49, 0xbb, 0xd9, 0xb4, 0xad, 0x29, + 0x36, 0x42, 0xa6, 0xa0, 0x4f, 0xf4, 0xb6, 0x50, 0xf4, 0xc0, 0x3c, 0x92, 0x14, 0x81, 0xbb, 0x0d, + 0x39, 0xfc, 0x87, 0xac, 0x87, 0xbe, 0xa2, 0x28, 0xd1, 0x35, 0xd9, 0x30, 0xba, 0x46, 0x8a, 0xa9, + 0x91, 0xcb, 0x77, 0x4b, 0xd4, 0xe4, 0x15, 0xb6, 0x52, 0xc5, 0xd2, 0x2e, 0xa2, 0x83, 0x4c, 0x24, + 0xdb, 0xa2, 0x3c, 0x39, 0xc9, 0x0d, 0xc8, 0x2c, 0x1e, 0x3a, 0xa3, 0x34, 0x45, 0xb7, 0x9d, 0x8e, + 0x7e, 0x04, 0xc7, 0xf1, 0xe1, 0xe4, 0x88, 0x5f, 0x72, 0xb9, 0x69, 0x9f, 0xa2, 0xf8, 0x2d, 0x19, + 0x18, 0x63, 0x4d, 0xd8, 0x16, 0xc6, 0xb4, 0x56, 0x30, 0x45, 0xfe, 0x11, 0x4c, 0x40, 0x2f, 0x43, + 0x8e, 0x77, 0x51, 0xcc, 0xb5, 0x24, 0xd2, 0x79, 0x6a, 0x6b, 0x8e, 0x61, 0xe3, 0x82, 0x67, 0xa3, + 0x97, 0xc7, 0x05, 0x4f, 0x5b, 0x2f, 0x8f, 0x0b, 0x4e, 0x43, 0x2e, 0x43, 0x2f, 0xb7, 0x2c, 0x7a, + 0xc3, 0xac, 0xe3, 0x68, 0x50, 0xc8, 0xd5, 0x61, 0xb8, 0x49, 0xf1, 0x22, 0x6a, 0xaa, 0xe3, 0x95, + 0x2d, 0x83, 0xde, 0x15, 0xc9, 0xd2, 0xb1, 0x93, 0x5d, 0x06, 0xad, 0x99, 0x0c, 0x2c, 0x57, 0x9f, + 0x0e, 0x89, 0xd9, 0x3c, 0xe1, 0x77, 0x99, 0x6c, 0xc7, 0xe0, 0x3c, 0x11, 0x74, 0xb2, 0xfa, 0xd8, + 0x68, 0x93, 0xe2, 0x76, 0x1a, 0xc6, 0x45, 0x7f, 0x22, 0xf4, 0x78, 0xf6, 0xce, 0x8b, 0x00, 0x8b, + 0xc2, 0x04, 0x0f, 0x32, 0x92, 0xa3, 0x10, 0x7d, 0xc3, 0xbc, 0x16, 0x99, 0x64, 0x25, 0x62, 0xac, + 0xfe, 0x2d, 0x4a, 0x59, 0x94, 0xa7, 0xe4, 0x1a, 0xf9, 0x54, 0x40, 0x95, 0xa6, 0x21, 0x31, 0xb9, + 0x0a, 0x7d, 0x53, 0xa6, 0xdb, 0x6a, 0xe8, 0x91, 0xe0, 0x75, 0x83, 0x83, 0xe4, 0x11, 0x2d, 0xa8, + 0x8a, 0x9f, 0xcf, 0xc2, 0xf9, 0x49, 0x7e, 0x8c, 0x5f, 0xd1, 0xa8, 0xeb, 0x39, 0x26, 0xf7, 0x10, + 0x3c, 0xb9, 0xc0, 0x8b, 0x95, 0x39, 0xdc, 0x4b, 0x00, 0xd0, 0x4d, 0x38, 0x9d, 0x24, 0x55, 0x91, + 0x48, 0x1e, 0x53, 0xcd, 0xfa, 0xf7, 0x29, 0xad, 0x9a, 0x23, 0x51, 0x68, 0x89, 0xed, 0xd8, 0xaa, + 0x86, 0xd6, 0x7d, 0x5f, 0xb8, 0xaa, 0x31, 0xa3, 0x5e, 0x5e, 0xd5, 0xd0, 0xac, 0x6f, 0x03, 0x99, + 0xb6, 0x96, 0x6d, 0xa7, 0x4e, 0x9b, 0xd4, 0xf2, 0xa6, 0xef, 0xb6, 0x4c, 0x87, 0xfa, 0x87, 0xe6, + 0xe7, 0x63, 0x4b, 0xca, 0xa2, 0xd9, 0xa4, 0xae, 0xa7, 0x37, 0x5b, 0x13, 0x97, 0xc5, 0xf9, 0xd5, + 0x43, 0x34, 0x6c, 0x5d, 0xa3, 0xbc, 0x79, 0xf8, 0x90, 0x4f, 0x7f, 0xb1, 0x90, 0xd2, 0x12, 0x1e, + 0x50, 0xfc, 0x5c, 0x1a, 0x4e, 0x95, 0x5a, 0xad, 0x52, 0xdb, 0x5b, 0x55, 0xfd, 0x99, 0xf6, 0x7d, + 0x8a, 0x20, 0x87, 0x92, 0xa7, 0xf7, 0x99, 0x45, 0xc0, 0x3f, 0x89, 0xc8, 0xec, 0xf1, 0x24, 0x62, + 0x16, 0xa0, 0x7e, 0x00, 0x9f, 0x94, 0xb0, 0x6d, 0x5c, 0x32, 0xe1, 0xc0, 0x3a, 0x91, 0x4c, 0x54, + 0x32, 0xe1, 0xc5, 0xf8, 0x7d, 0x2e, 0x99, 0x3f, 0x54, 0x25, 0x83, 0xbb, 0xa5, 0xcd, 0x03, 0x49, + 0xe6, 0x25, 0x18, 0xd5, 0x5b, 0x2d, 0x7e, 0x49, 0x5a, 0x47, 0xa6, 0x72, 0x99, 0x38, 0x05, 0xa5, + 0x0d, 0xeb, 0xf2, 0xe3, 0xf7, 0x2d, 0x12, 0xe9, 0x40, 0x2f, 0x7b, 0x90, 0x03, 0xbd, 0xde, 0x7d, + 0x9e, 0xce, 0xee, 0xe1, 0x9c, 0xbd, 0xf8, 0x35, 0x69, 0x18, 0xbb, 0x6d, 0x51, 0xef, 0xd0, 0xe6, + 0xaf, 0xf0, 0x35, 0xd2, 0x7b, 0x38, 0x24, 0x3e, 0x7a, 0xed, 0x8b, 0x0a, 0xe2, 0x10, 0xa6, 0xab, + 0xb7, 0x87, 0x20, 0x0e, 0x61, 0x76, 0x3a, 0xae, 0x82, 0xf8, 0x3b, 0x99, 0xb0, 0xb0, 0x88, 0xa8, + 0x46, 0x71, 0xe2, 0xaf, 0x7c, 0xa0, 0xf8, 0x70, 0x0d, 0xfa, 0x2a, 0xfa, 0x26, 0x93, 0xe7, 0x4e, + 0xe7, 0x0f, 0x05, 0x66, 0xbf, 0xb7, 0x38, 0x6d, 0xe2, 0x8e, 0xdf, 0x67, 0x94, 0xd4, 0x4b, 0x27, + 0x29, 0x63, 0x0f, 0x92, 0x32, 0xf6, 0x2d, 0xe9, 0xa5, 0x5f, 0x4e, 0xc7, 0x7a, 0xe9, 0xc4, 0x7f, + 0xfb, 0x40, 0x49, 0x68, 0xbf, 0x23, 0x03, 0xa7, 0xcb, 0x96, 0x08, 0x2f, 0x63, 0x9b, 0xa0, 0xc6, + 0xc9, 0xe4, 0x74, 0xcf, 0x4d, 0x4e, 0xf1, 0x4e, 0x3a, 0x89, 0xe9, 0xb8, 0xe7, 0x3a, 0xe9, 0x97, + 0xd2, 0x6a, 0x27, 0x9d, 0x4c, 0x4d, 0x07, 0x9a, 0x9a, 0xd8, 0x64, 0x1f, 0x08, 0xb4, 0x4a, 0xeb, + 0x0e, 0x3d, 0x49, 0x86, 0x74, 0xe8, 0x12, 0x3d, 0x99, 0x48, 0x0e, 0x5b, 0xa2, 0x27, 0xa3, 0xfe, + 0x40, 0x12, 0x65, 0x86, 0x78, 0x20, 0xd1, 0x43, 0x8b, 0x14, 0x3a, 0x59, 0xec, 0x0e, 0x7f, 0xbb, + 0xa4, 0xf4, 0xd2, 0xc9, 0x4c, 0x72, 0xcf, 0xf5, 0x52, 0x64, 0x76, 0x3a, 0xb4, 0xf8, 0xa9, 0xfb, + 0x7d, 0xbb, 0xa4, 0x51, 0xcf, 0x31, 0xe9, 0xba, 0xde, 0x38, 0xd9, 0x2e, 0xdd, 0xbb, 0xdb, 0xa5, + 0x68, 0x27, 0x9d, 0xcc, 0x4d, 0xf7, 0xe4, 0x76, 0x29, 0xda, 0x49, 0x27, 0x53, 0xd3, 0x81, 0xa6, + 0xa6, 0x7f, 0x9f, 0x86, 0xf3, 0x93, 0xd4, 0xf1, 0x4a, 0x6d, 0x6f, 0xd5, 0x76, 0x4c, 0x6f, 0xd3, + 0x8f, 0x41, 0x0f, 0x3c, 0x40, 0xfe, 0xd2, 0x5d, 0xd2, 0xe5, 0x0b, 0xc5, 0xcc, 0x3e, 0x2f, 0x14, + 0xc3, 0x63, 0xff, 0xec, 0x1e, 0x8e, 0xfd, 0x75, 0x18, 0xac, 0xeb, 0x35, 0x5b, 0x48, 0x40, 0xc8, + 0xf1, 0x89, 0xa0, 0x37, 0x92, 0xc4, 0x14, 0xbc, 0x09, 0x3a, 0x8b, 0x48, 0xad, 0x65, 0x67, 0x91, + 0xba, 0xee, 0x93, 0x17, 0x3f, 0x99, 0x86, 0x87, 0xfe, 0x3f, 0xf6, 0xae, 0xe6, 0xb7, 0x8d, 0x22, + 0x8a, 0x77, 0xed, 0xd8, 0x49, 0x9f, 0x93, 0x66, 0x32, 0x49, 0x68, 0xd2, 0x36, 0x69, 0xeb, 0xb6, + 0x7c, 0x58, 0x88, 0xa2, 0x22, 0x90, 0x90, 0x40, 0x62, 0xbc, 0x3b, 0x4e, 0x86, 0xec, 0x87, 0xd9, + 0x59, 0x27, 0x0d, 0x97, 0xd5, 0x66, 0xb3, 0x6d, 0x16, 0xdc, 0xd8, 0xda, 0x75, 0x5b, 0x38, 0x70, + 0x40, 0x42, 0xe2, 0x5f, 0x40, 0x1c, 0xb8, 0xf0, 0x3f, 0x70, 0xe6, 0xd8, 0x23, 0x7f, 0x01, 0x82, + 0x1e, 0xb9, 0x73, 0xe0, 0x82, 0xd0, 0xcc, 0x8e, 0xed, 0x5d, 0x37, 0x8e, 0x54, 0xb8, 0x14, 0xa5, + 0xb7, 0x99, 0xdf, 0x7b, 0xf3, 0xe6, 0xcd, 0x87, 0xd7, 0xfb, 0xf1, 0x7b, 0x6f, 0x4e, 0x35, 0x84, + 0xdf, 0x82, 0xd9, 0x30, 0xc8, 0x82, 0x2a, 0xb5, 0xdc, 0x59, 0x08, 0xc1, 0x24, 0x4f, 0xab, 0x1a, + 0x06, 0x92, 0xa1, 0xf5, 0x21, 0xcc, 0x87, 0xd9, 0x4b, 0x4d, 0x49, 0x65, 0x54, 0x9f, 0x18, 0x15, + 0xdd, 0x61, 0x8c, 0xe7, 0x19, 0x62, 0x0a, 0x97, 0x34, 0xc7, 0xaf, 0x60, 0x35, 0x8c, 0x12, 0x15, + 0x6c, 0x14, 0x8d, 0xdc, 0xcf, 0x72, 0x29, 0xd5, 0xee, 0xdc, 0xc8, 0x8f, 0x5e, 0x29, 0x3d, 0x33, + 0x76, 0x99, 0xce, 0xe5, 0x44, 0x2b, 0xb9, 0x5e, 0x57, 0xc2, 0x67, 0x2d, 0xa4, 0xf5, 0x3f, 0x35, + 0xb8, 0x7c, 0x8a, 0x69, 0xbc, 0x0f, 0xb5, 0x5c, 0x3b, 0xb5, 0x07, 0xaf, 0x0c, 0x9d, 0xba, 0xfb, + 0xee, 0xdb, 0xef, 0x4f, 0x59, 0x89, 0x71, 0xa3, 0xc2, 0xc8, 0xc7, 0x30, 0xde, 0x82, 0x4a, 0x78, + 0x14, 0xc4, 0xc7, 0x6b, 0x25, 0x39, 0xd2, 0xd3, 0x8d, 0x66, 0xdc, 0x2d, 0xa1, 0x9e, 0xe7, 0x6e, + 0x49, 0x00, 0xdf, 0x81, 0xb9, 0xc3, 0x38, 0x0d, 0x0e, 0xba, 0x51, 0x16, 0x42, 0x38, 0x97, 0xb1, + 0xe1, 0x86, 0x58, 0x9e, 0x0d, 0x37, 0xc4, 0xea, 0x7f, 0x6b, 0xb0, 0x72, 0x52, 0x47, 0xf8, 0x4d, + 0xa8, 0xca, 0x2c, 0xdb, 0x49, 0x3e, 0x21, 0x58, 0x86, 0xe4, 0x17, 0x3f, 0x43, 0xf0, 0x6d, 0x98, + 0x4d, 0x1f, 0x1e, 0x7c, 0x16, 0x85, 0x03, 0xb5, 0xee, 0x72, 0xb3, 0x28, 0x28, 0xcf, 0x3d, 0x52, + 0x10, 0xfe, 0x08, 0x16, 0xd2, 0x28, 0x89, 0x83, 0xae, 0x7f, 0xfc, 0xf0, 0xc1, 0x81, 0xba, 0x60, + 0x29, 0x46, 0x60, 0x41, 0x90, 0x67, 0x04, 0x66, 0x02, 0x5b, 0xe2, 0x98, 0xc2, 0xe2, 0x98, 0xd4, + 0xec, 0x1f, 0x05, 0xe9, 0x91, 0x62, 0x5a, 0xc9, 0x73, 0x1c, 0x26, 0x44, 0x79, 0xc6, 0x59, 0x7f, + 0xc8, 0x78, 0xde, 0x0e, 0xd2, 0xa3, 0xfa, 0xb7, 0x25, 0x58, 0x6a, 0x46, 0xc1, 0x83, 0xf4, 0xcc, + 0x7f, 0xfc, 0x9d, 0x98, 0x89, 0x33, 0xfc, 0xf5, 0x77, 0x62, 0x26, 0xce, 0xf0, 0xe7, 0xdf, 0x1f, + 0xca, 0x80, 0xf4, 0x6e, 0x90, 0xa6, 0xf1, 0xbd, 0xf8, 0xe5, 0x97, 0xdf, 0x17, 0xf0, 0x46, 0xb4, + 0xb8, 0x40, 0x2f, 0x9f, 0x14, 0x5e, 0xb8, 0x05, 0x92, 0xc7, 0x03, 0x8d, 0x16, 0xe8, 0xe5, 0x53, + 0xc2, 0x7f, 0x7a, 0x4a, 0xf8, 0xb9, 0x04, 0xf3, 0x32, 0x30, 0xa4, 0xd5, 0x4b, 0x1e, 0x07, 0xc9, + 0xff, 0x3d, 0xb3, 0xed, 0x78, 0x12, 0x66, 0x9e, 0x8f, 0x9b, 0xfe, 0xef, 0xd3, 0xd9, 0xd6, 0xbf, + 0xa9, 0xc0, 0x25, 0xd2, 0x1f, 0xa6, 0x77, 0xf1, 0x82, 0xe4, 0x7e, 0x34, 0x30, 0xe2, 0xa0, 0x6b, + 0x44, 0xc7, 0x71, 0x74, 0x26, 0xf3, 0xa2, 0xa8, 0x04, 0x01, 0x95, 0xe7, 0x4a, 0x10, 0x70, 0x1b, + 0x20, 0x9b, 0xbb, 0xed, 0x5e, 0x3a, 0x50, 0x7c, 0xfb, 0xc5, 0x3f, 0x7e, 0xbd, 0x5a, 0x1b, 0x48, + 0xd4, 0x3f, 0xea, 0xa5, 0x03, 0x37, 0xa7, 0x32, 0x6e, 0xd0, 0xee, 0x25, 0x03, 0xc5, 0xe5, 0xce, + 0x37, 0xe8, 0xf7, 0x92, 0x51, 0x03, 0xa1, 0x82, 0x3f, 0x80, 0x9a, 0xf8, 0xbd, 0x76, 0x1f, 0x45, + 0x87, 0xac, 0x9d, 0xae, 0xcd, 0x8d, 0xd9, 0xe3, 0x89, 0x82, 0xfd, 0xb8, 0x90, 0xfc, 0x32, 0xaf, + 0x8e, 0xeb, 0x50, 0xcd, 0x5e, 0x99, 0xaa, 0xc8, 0x4e, 0x19, 0xbb, 0xdc, 0x97, 0x88, 0xab, 0x24, + 0xf8, 0x3d, 0x38, 0xdf, 0xec, 0xf6, 0xc2, 0xcf, 0x45, 0x8b, 0x2c, 0x1f, 0x4f, 0x16, 0x94, 0x75, + 0x90, 0x81, 0x7e, 0xdc, 0xcf, 0x07, 0x65, 0x8d, 0x54, 0x71, 0x13, 0x16, 0x54, 0xa5, 0x9d, 0x44, + 0xf7, 0xe2, 0x2f, 0xb2, 0xfc, 0x3c, 0x59, 0xa0, 0xc7, 0xb0, 0x6d, 0x5f, 0x4a, 0xf2, 0xb7, 0xdd, + 0x85, 0x26, 0x8d, 0x30, 0x3b, 0x0e, 0x69, 0x27, 0x3e, 0x3e, 0xc4, 0xeb, 0xb0, 0xda, 0xe1, 0xd4, + 0xf5, 0x77, 0x98, 0x6d, 0xf8, 0x1d, 0x9b, 0xb7, 0xa9, 0xce, 0x5a, 0x8c, 0x1a, 0xe8, 0x1c, 0x5e, + 0x86, 0xc5, 0xb1, 0x68, 0xbb, 0x63, 0x11, 0x1b, 0x69, 0x78, 0x09, 0x16, 0xc6, 0x60, 0xd3, 0xf1, + 0x50, 0x09, 0xaf, 0x00, 0x1a, 0x43, 0x7c, 0x9f, 0x7b, 0xd4, 0x42, 0xe5, 0xc6, 0xf7, 0x1a, 0x80, + 0xe8, 0x25, 0x0b, 0x7d, 0xc3, 0x97, 0xe1, 0xa2, 0x54, 0x72, 0x5c, 0xb6, 0xc5, 0xec, 0x89, 0x9e, + 0x56, 0x61, 0x29, 0x2f, 0x34, 0x1d, 0x9d, 0x98, 0x48, 0x1b, 0x39, 0xa0, 0x60, 0xce, 0x9d, 0x5c, + 0x6f, 0x0a, 0x74, 0x76, 0x3c, 0x82, 0xca, 0x93, 0x28, 0xd7, 0x99, 0x85, 0x66, 0xf0, 0x45, 0x58, + 0xce, 0xa3, 0xd4, 0xf6, 0x5c, 0xc2, 0x0c, 0x54, 0x69, 0xbc, 0x06, 0x35, 0xf9, 0x6e, 0x43, 0xa5, + 0x54, 0x9e, 0x87, 0x39, 0xa7, 0xc9, 0xa9, 0xbb, 0x2b, 0xbd, 0x01, 0xa8, 0x1a, 0xd4, 0x16, 0x9e, + 0x69, 0x8d, 0x04, 0x6a, 0x56, 0x8b, 0xb4, 0xba, 0xbd, 0xc7, 0xf2, 0x39, 0x7b, 0x03, 0xd6, 0xad, + 0x16, 0xf1, 0x5b, 0xa6, 0xb3, 0xe7, 0x7b, 0xfb, 0x6d, 0x3a, 0x31, 0x8e, 0x37, 0xe0, 0x56, 0x51, + 0xdc, 0xa6, 0xae, 0xcf, 0x29, 0xe7, 0xcc, 0xb1, 0x7d, 0x9d, 0xba, 0x1e, 0x6b, 0x31, 0x9d, 0x78, + 0x14, 0x69, 0x62, 0xde, 0x8b, 0xaa, 0xcc, 0xf6, 0x9b, 0xc4, 0x36, 0x50, 0xa9, 0xf1, 0x97, 0x06, + 0xc0, 0x5b, 0x5e, 0x5b, 0x39, 0x57, 0x83, 0x59, 0x66, 0xef, 0x12, 0x93, 0x89, 0x1e, 0xe6, 0x60, + 0xc6, 0x69, 0x53, 0xb1, 0x10, 0xe7, 0xa1, 0xa2, 0x9b, 0x0e, 0xa7, 0xa8, 0x24, 0x40, 0x97, 0x12, + 0x03, 0x95, 0x05, 0xb8, 0xe7, 0x32, 0x8f, 0xa2, 0x19, 0x51, 0x34, 0xb9, 0x47, 0x3c, 0x54, 0x11, + 0xc5, 0x96, 0x2c, 0x56, 0x85, 0x31, 0x4e, 0x3d, 0x59, 0x99, 0x15, 0xc3, 0x6e, 0x0d, 0x6b, 0x73, + 0x42, 0x24, 0x4c, 0x1b, 0xcc, 0x45, 0xe7, 0x45, 0x45, 0x98, 0x14, 0x15, 0x10, 0x13, 0xe2, 0x52, + 0xcb, 0xd9, 0xa5, 0xa8, 0x26, 0x6c, 0x59, 0x3b, 0x02, 0x9e, 0x17, 0x45, 0xd7, 0x12, 0xc5, 0x05, + 0x61, 0xc9, 0xa5, 0xc4, 0x6c, 0x13, 0x6f, 0x1b, 0x5d, 0x10, 0xfe, 0x48, 0x9b, 0x8b, 0x59, 0x4b, + 0x9b, 0x58, 0x14, 0x21, 0xa5, 0x63, 0x98, 0xcc, 0xde, 0x41, 0x4b, 0xd2, 0x91, 0x7d, 0x4b, 0x56, + 0xb0, 0x68, 0x20, 0x4b, 0xcb, 0x8d, 0xaf, 0x35, 0xa8, 0x3a, 0x5c, 0xce, 0xf5, 0x45, 0x58, 0x76, + 0xf8, 0x49, 0xb3, 0xbc, 0x04, 0x0b, 0x43, 0x81, 0xc9, 0xec, 0xce, 0xdd, 0x6c, 0x57, 0x0e, 0x21, + 0x8b, 0xe8, 0x0e, 0x47, 0x25, 0xb1, 0x79, 0x86, 0xd0, 0x1e, 0xb3, 0x0d, 0x67, 0x8f, 0xa3, 0x32, + 0x5e, 0x84, 0xda, 0x10, 0x64, 0x0e, 0x47, 0x33, 0x18, 0xc3, 0x85, 0x11, 0xd0, 0x26, 0x86, 0xc3, + 0x51, 0xa5, 0xf1, 0x08, 0xe6, 0x8d, 0xe8, 0x51, 0x1c, 0x46, 0x6a, 0xeb, 0x6e, 0xc0, 0xba, 0x41, + 0x77, 0x99, 0x4e, 0xa7, 0x6e, 0xde, 0xa2, 0x98, 0xb4, 0x19, 0xd2, 0xf0, 0x2b, 0x80, 0x8b, 0xf0, + 0xc7, 0xc4, 0x6a, 0xa1, 0x12, 0x5e, 0x83, 0x95, 0x22, 0xce, 0x6c, 0xaf, 0x63, 0x53, 0x54, 0x6e, + 0xfc, 0xa8, 0xc1, 0x2a, 0xed, 0x06, 0xe9, 0x20, 0x0e, 0xd3, 0x28, 0x48, 0xc2, 0x23, 0x3d, 0x18, + 0x44, 0xf7, 0x7b, 0xc9, 0x97, 0xb8, 0x0e, 0x9b, 0xd4, 0x24, 0xdc, 0x63, 0x3a, 0xa7, 0xc4, 0xd5, + 0xb7, 0x7d, 0xb1, 0x89, 0xb6, 0x1c, 0x77, 0xdf, 0xdf, 0xa2, 0x36, 0x75, 0x89, 0x89, 0xce, 0xe1, + 0x1b, 0x70, 0x75, 0x8a, 0x0e, 0xa7, 0x7a, 0xc7, 0x65, 0xde, 0x3e, 0xd2, 0xf0, 0x75, 0xd8, 0x98, + 0xaa, 0x24, 0xea, 0xa8, 0x84, 0x37, 0xe1, 0xd2, 0x34, 0x95, 0x4f, 0x4c, 0x54, 0x6e, 0x7c, 0xa7, + 0x01, 0x76, 0xfa, 0xd1, 0x31, 0x2f, 0xba, 0x78, 0x0d, 0xae, 0x88, 0xdd, 0xe3, 0x4f, 0x77, 0xf0, + 0x3a, 0x6c, 0x9c, 0xa8, 0x91, 0x73, 0xef, 0x2a, 0x5c, 0x9e, 0xa2, 0xa2, 0x9c, 0xbb, 0x02, 0x6b, + 0x27, 0x2b, 0x48, 0xd7, 0x7e, 0xd2, 0x60, 0x95, 0x1c, 0x3e, 0x88, 0x8f, 0xb3, 0x5f, 0x4e, 0x6a, + 0xb5, 0x88, 0xfa, 0xdb, 0x7e, 0x1d, 0x6e, 0x12, 0xc3, 0x12, 0x6b, 0xa3, 0x7b, 0xcc, 0xb1, 0xb9, + 0x2f, 0x7e, 0x7b, 0x62, 0x8f, 0x76, 0xf8, 0xc4, 0x6a, 0xbe, 0x0a, 0xf5, 0x53, 0x34, 0xf5, 0x6d, + 0x62, 0x6f, 0x89, 0x0b, 0x03, 0xbe, 0x09, 0xd7, 0xa6, 0xea, 0x51, 0x9b, 0x34, 0x4d, 0x6a, 0xa0, + 0x12, 0xbe, 0x05, 0xd7, 0xa7, 0x6a, 0x19, 0x8c, 0x67, 0x6a, 0xe5, 0x46, 0x50, 0x38, 0x95, 0x48, + 0x8c, 0x52, 0x77, 0x6c, 0x8f, 0xe8, 0xde, 0x49, 0xdb, 0x7f, 0x1d, 0x56, 0x0b, 0xd2, 0x66, 0x87, + 0x33, 0x9b, 0x72, 0x9e, 0x5d, 0x54, 0x0a, 0xa2, 0xd1, 0xd4, 0x96, 0x9a, 0xc6, 0x93, 0xdf, 0x37, + 0xcf, 0x3d, 0x79, 0xba, 0xa9, 0xfd, 0xf2, 0x74, 0x53, 0xfb, 0xed, 0xe9, 0xa6, 0xf6, 0xe9, 0x9d, + 0xe7, 0x49, 0x61, 0x91, 0xfd, 0x31, 0x1f, 0x54, 0xe5, 0xbd, 0xf8, 0x3b, 0xff, 0x04, 0x00, 0x00, + 0xff, 0xff, 0xd3, 0xb1, 0x14, 0x1c, 0xef, 0x41, 0x02, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -53514,6 +53526,21 @@ func (m *ClientIPRestrictionsUpdate) MarshalToSizedBuffer(dAtA []byte) (int, err i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + n1291, err1291 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EnforcementExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EnforcementExpires):]) + if err1291 != nil { + return 0, err1291 + } + i -= n1291 + i = encodeVarintEvents(dAtA, i, uint64(n1291)) + i-- + dAtA[i] = 0x42 + if len(m.Mode) > 0 { + i -= len(m.Mode) + copy(dAtA[i:], m.Mode) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Mode))) + i-- + dAtA[i] = 0x3a + } if len(m.ClientIPRestrictions) > 0 { for iNdEx := len(m.ClientIPRestrictions) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.ClientIPRestrictions[iNdEx]) @@ -68114,6 +68141,12 @@ func (m *ClientIPRestrictionsUpdate) Size() (n int) { n += 1 + l + sovEvents(uint64(l)) } } + l = len(m.Mode) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EnforcementExpires) + n += 1 + l + sovEvents(uint64(l)) if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -149436,6 +149469,71 @@ func (m *ClientIPRestrictionsUpdate) Unmarshal(dAtA []byte) error { } m.ClientIPRestrictions = append(m.ClientIPRestrictions, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mode", 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.Mode = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EnforcementExpires", 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 := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EnforcementExpires, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/docs/pages/reference/audit-events.mdx b/docs/pages/reference/audit-events.mdx index b964adb46644..4ad8af289dad 100644 --- a/docs/pages/reference/audit-events.mdx +++ b/docs/pages/reference/audit-events.mdx @@ -1732,8 +1732,10 @@ Example: "cluster_name": "localhost", "code": "CIR001I", "ei": 0, + "enforcement_expires": "2026-10-21T20:45:14.775Z", "event": "cir.update", "expires": "0001-01-01T00:00:00Z", + "mode": "enforced", "name": "client_ip_restriction", "success": true, "time": "2025-10-21T20:45:14.775Z", diff --git a/lib/events/events_test.go b/lib/events/events_test.go index f74de3386e2c..3e53fa28516b 100644 --- a/lib/events/events_test.go +++ b/lib/events/events_test.go @@ -315,6 +315,7 @@ var eventsMap = map[string]apievents.AuditEvent{ BeamsConfigCreateEvent: &apievents.BeamsConfigCreate{}, BeamsConfigUpdateEvent: &apievents.BeamsConfigUpdate{}, BeamsConfigDeleteEvent: &apievents.BeamsConfigDelete{}, + ClientIPRestrictionsUpdateEvent: &apievents.ClientIPRestrictionsUpdate{}, } // TestJSON tests JSON marshal events diff --git a/web/packages/teleport/src/Audit/fixtures/index.ts b/web/packages/teleport/src/Audit/fixtures/index.ts index c113ef73dcbe..d5abb6adffc1 100644 --- a/web/packages/teleport/src/Audit/fixtures/index.ts +++ b/web/packages/teleport/src/Audit/fixtures/index.ts @@ -4576,8 +4576,10 @@ export const events = [ cluster_name: 'localhost', code: 'CIR001I', ei: 0, + enforcement_expires: '2026-10-21T20:45:14.775Z', event: 'cir.update', expires: '0001-01-01T00:00:00Z', + mode: 'enforced', name: 'client_ip_restriction', success: true, time: '2025-10-21T20:45:14.775Z', diff --git a/web/packages/teleport/src/services/audit/makeEvent.ts b/web/packages/teleport/src/services/audit/makeEvent.ts index 5e4d4712e220..e04b4ac74a77 100644 --- a/web/packages/teleport/src/services/audit/makeEvent.ts +++ b/web/packages/teleport/src/services/audit/makeEvent.ts @@ -2580,10 +2580,23 @@ export const formatters: Formatters = { [eventCodes.CLIENT_IP_RESTRICTIONS_UPDATE]: { type: 'cir.update', desc: 'Client IP Restrictions update', - format: ({ user, client_ip_restrictions, success }) => - success - ? `User [${user}] updated the Client IP Restrictions allowlist to [${client_ip_restrictions}].` - : `User [${user}] has failed to update Client IP Restrictions.`, + format: ({ + user, + client_ip_restrictions, + success, + mode, + enforcement_expires, + }) => { + const modeStr = mode ? ` in [${mode}] mode` : ''; + // The zero timestamp means no enforcement expiry was set. + const enforcementStr = + enforcement_expires && new Date(enforcement_expires).getFullYear() > 1 + ? `, with enforcement expiring on [${enforcement_expires}]` + : ''; + return success + ? `User [${user}] updated the Client IP Restrictions allowlist to [${client_ip_restrictions}]${modeStr}${enforcementStr}.` + : `User [${user}] has failed to update Client IP Restrictions.`; + }, }, [eventCodes.APPAUTHCONFIG_CREATE]: { type: 'app_auth_config.create', diff --git a/web/packages/teleport/src/services/audit/types.ts b/web/packages/teleport/src/services/audit/types.ts index 81ef817a140a..95c577b134a3 100644 --- a/web/packages/teleport/src/services/audit/types.ts +++ b/web/packages/teleport/src/services/audit/types.ts @@ -2351,6 +2351,8 @@ export type RawEvents = { { client_ip_restrictions: string[]; success: boolean; + mode?: string; + enforcement_expires?: string; } >; [eventCodes.APPAUTHCONFIG_CREATE]: RawEvent< From 02e35b560b8d41bdf4b48ada21fba5a021014c7f Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 3 Aug 2026 14:16:59 +0200 Subject: [PATCH 107/347] Add support for Linux desktop labels and logins in role editor (#68048) * Add support for Linux desktop labels and logins in role editor * test fix --- constants.go | 4 ++ gen/preset-roles.json | 6 ++ lib/services/presets.go | 2 + .../StandardEditor/Resources.story.tsx | 8 +++ .../StandardEditor/Resources.test.tsx | 70 +++++++++++++++++++ .../RoleEditor/StandardEditor/Resources.tsx | 63 +++++++++++++++++ .../StandardEditor/StandardEditor.test.tsx | 2 + .../StandardEditor/standardmodel.test.ts | 28 ++++++++ .../StandardEditor/standardmodel.ts | 48 +++++++++++++ .../StandardEditor/validation.test.ts | 22 +++++- .../RoleEditor/StandardEditor/validation.ts | 25 +++++++ web/packages/teleport/src/Support/Support.tsx | 2 +- .../teleport/src/services/resources/types.ts | 12 +++- 13 files changed, 289 insertions(+), 3 deletions(-) diff --git a/constants.go b/constants.go index 5d2f2a0bf5c6..c82fb14643b3 100644 --- a/constants.go +++ b/constants.go @@ -644,6 +644,10 @@ const ( // allowed Windows Desktop logins for local accounts. TraitInternalWindowsLoginsVariable = "{{internal.windows_logins}}" + // TraitInternalLinuxDesktopLoginsVariable is the variable used to store + // allowed Linux Desktop logins for local accounts. + TraitInternalLinuxDesktopLoginsVariable = "{{internal.linux_desktop_logins}}" + // TraitInternalKubeGroupsVariable is the variable used to store allowed // kubernetes groups for local accounts. TraitInternalKubeGroupsVariable = "{{internal.kubernetes_groups}}" diff --git a/gen/preset-roles.json b/gen/preset-roles.json index f6db78eea20b..4f90295a7ff7 100755 --- a/gen/preset-roles.json +++ b/gen/preset-roles.json @@ -150,6 +150,12 @@ "tools": [ "{{internal.mcp_tools}}" ] + }, + "linux_desktop_logins": [ + "{{internal.linux_desktop_logins}}" + ], + "linux_desktop_labels": { + "*": "*" } }, "deny": {} diff --git a/lib/services/presets.go b/lib/services/presets.go index cf579ba416c9..2462ae343779 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -291,6 +291,7 @@ func NewPresetAccessRole() types.Role { AppLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, KubernetesLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, WindowsDesktopLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + LinuxDesktopLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, DatabaseLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, DatabaseServiceLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, DatabaseNames: []string{teleport.TraitInternalDBNamesVariable}, @@ -330,6 +331,7 @@ func NewPresetAccessRole() types.Role { // YAML. role.SetLogins(types.Allow, []string{teleport.TraitInternalLoginsVariable}) role.SetWindowsLogins(types.Allow, []string{teleport.TraitInternalWindowsLoginsVariable}) + role.SetLinuxDesktopLogins(types.Allow, []string{teleport.TraitInternalLinuxDesktopLoginsVariable}) role.SetKubeUsers(types.Allow, []string{teleport.TraitInternalKubeUsersVariable}) role.SetKubeGroups(types.Allow, []string{teleport.TraitInternalKubeGroupsVariable}) role.SetAWSRoleARNs(types.Allow, []string{teleport.TraitInternalAWSRoleARNs}) diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.story.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.story.tsx index 6f3529fb6ab3..1f7bd7e12d4e 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.story.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.story.tsx @@ -25,6 +25,7 @@ import { DatabaseAccessSection, GitHubOrganizationAccessSection, KubernetesAccessSection, + LinuxDesktopAccessSection, ServerAccessSection, WindowsDesktopAccessSection, } from './Resources'; @@ -55,6 +56,7 @@ const meta: Meta = { 'app', 'db', 'windows_desktop', + 'linux_desktop', 'git_server', ], }, @@ -146,6 +148,12 @@ function getResourceSectionStates(kind: ResourceAccessKind) { defaultValue: newResourceAccess('windows_desktop', defaultRoleVersion), }; + case 'linux_desktop': + return { + component: LinuxDesktopAccessSection, + defaultValue: newResourceAccess('linux_desktop', defaultRoleVersion), + }; + default: kind satisfies never; } diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx index 74466f132d66..effea03eb8f3 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx @@ -29,6 +29,7 @@ import { DatabaseAccessSection, GitHubOrganizationAccessSection, KubernetesAccessSection, + LinuxDesktopAccessSection, ServerAccessSection, WindowsDesktopAccessSection, } from './Resources'; @@ -42,6 +43,8 @@ import { GitHubOrganizationAccessInputFields, KubernetesAccess, KubernetesAccessInputFields, + LinuxDesktopAccess, + LinuxDesktopAccessInputFields, newResourceAccess, ServerAccess, ServerAccessInputFields, @@ -724,6 +727,73 @@ describe('WindowsDesktopAccessSection', () => { }); }); +describe('LinuxDesktopAccessSection', () => { + const setup = (visibleInputFields?: LinuxDesktopAccessInputFields) => { + const onChange = jest.fn(); + let validator: Validator; + render( + + component={LinuxDesktopAccessSection} + defaultValue={newResourceAccess('linux_desktop', defaultRoleVersion)} + onChange={onChange} + validatorRef={v => { + validator = v; + }} + validate={validateResourceAccess} + visibleInputFields={visibleInputFields} + /> + ); + return { user: userEvent.setup(), onChange, validator }; + }; + + test('editing', async () => { + const { user, onChange } = setup(); + await user.type(screen.getByPlaceholderText('label key'), 'os'); + await user.type(screen.getByPlaceholderText('label value'), 'ubuntu'); + await selectEvent.create(screen.getByLabelText('Logins'), 'alice', { + createOptionText: 'Login: alice', + }); + expect(onChange).toHaveBeenLastCalledWith({ + kind: 'linux_desktop', + labels: [{ name: 'os', value: 'ubuntu' }], + logins: [ + expect.objectContaining({ value: '{{internal.logins}}' }), + expect.objectContaining({ label: 'alice', value: 'alice' }), + ], + hideValidationErrors: true, + } as LinuxDesktopAccess); + }); + + test('validation', async () => { + const { user, validator } = setup(); + await user.type(screen.getByPlaceholderText('label value'), 'some-value'); + act(() => validator.validate()); + expect( + screen.getByPlaceholderText('label key') + ).toHaveAccessibleDescription('required'); + }); + + test('hide all input fields', async () => { + setup({ labels: false, logins: false }); + expect(screen.queryByPlaceholderText('label key')).not.toBeInTheDocument(); + expect( + screen.queryByPlaceholderText('label value') + ).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/logins/i)).not.toBeInTheDocument(); + }); + + test('hide one input field', async () => { + setup({ labels: true, logins: false }); + expect(screen.getByPlaceholderText('label key')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('label value')).toBeInTheDocument(); + expect(screen.queryByLabelText(/logins/i)).not.toBeInTheDocument(); + }); +}); + describe('GitHubOrganizationAccessSection', () => { const setup = (visibleInputFields?: GitHubOrganizationAccessInputFields) => { const onChange = jest.fn(); diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx index 8137a09b6e13..35eb87adf5fb 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx @@ -61,6 +61,8 @@ import { KubernetesResourceModel, kubernetesVerbOptions, newKubernetesResourceModel, + LinuxDesktopAccess, + LinuxDesktopAccessInputFields, ResourceAccess, ResourceAccessKind, ServerAccess, @@ -76,6 +78,7 @@ import { GitHubOrganizationAccessValidationResult, KubernetesAccessValidationResult, KubernetesResourceValidationResult, + LinuxDesktopAccessValidationResult, ResourceAccessValidationResult, ServerAccessValidationResult, v7kubernetesClusterWideResourceKinds, @@ -174,6 +177,7 @@ const allResourceAccessKinds: ResourceAccessKind[] = [ 'app', 'db', 'windows_desktop', + 'linux_desktop', 'git_server', ]; @@ -205,6 +209,10 @@ export const resourceAccessSections: Record< title: 'Windows Desktop Access', component: WindowsDesktopAccessSection, }, + linux_desktop: { + title: 'Linux Desktop Access', + component: LinuxDesktopAccessSection, + }, git_server: { title: 'GitHub Organization Access', component: GitHubOrganizationAccessSection, @@ -931,6 +939,61 @@ export function WindowsDesktopAccessSection({ ); } +export function LinuxDesktopAccessSection({ + value, + isProcessing, + validation, + onChange, + readOnly = false, + visibleInputFields, +}: SectionProps< + LinuxDesktopAccess, + LinuxDesktopAccessValidationResult, + LinuxDesktopAccessInputFields +>) { + const show: LinuxDesktopAccessInputFields = visibleInputFields ?? { + labels: true, + logins: true, + }; + + return ( + <> + {show.labels && ( + + onChange?.({ ...value, labels })} + readOnly={readOnly} + rule={readOnly ? undefined : precomputed(validation.fields.labels)} + /> + + )} + {show.logins && ( + `Login: ${label}`} + components={{ + DropdownIndicator: null, + }} + openMenuOnClick={false} + value={value.logins} + onChange={logins => onChange?.({ ...value, logins: [...logins] })} + menuPosition="fixed" + readOnly={readOnly} + rule={readOnly ? undefined : precomputed(validation.fields.logins)} + /> + )} + + ); +} + export function GitHubOrganizationAccessSection({ value, isProcessing, diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx index 8d35584371db..9196a16e0083 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx @@ -86,6 +86,7 @@ test('adding and removing sections', async () => { 'Application Access', 'Database Access', 'Windows Desktop Access', + 'Linux Desktop Access', 'GitHub Organization Access', ]); @@ -100,6 +101,7 @@ test('adding and removing sections', async () => { 'Application Access', 'Database Access', 'Windows Desktop Access', + 'Linux Desktop Access', 'GitHub Organization Access', ]); diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts index 840c6c5d99c7..d48715651923 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts @@ -303,6 +303,34 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, }, + { + name: 'Linux desktop access', + role: { + ...minimalRole(), + spec: { + ...minimalRole().spec, + allow: { + linux_desktop_labels: { os: 'ubuntu' }, + linux_desktop_logins: ['alice', 'bob'], + }, + }, + }, + model: { + ...minimalRoleModel(), + resources: [ + { + kind: 'linux_desktop', + labels: [{ name: 'os', value: 'ubuntu' }], + logins: [ + { label: 'alice', value: 'alice' }, + { label: 'bob', value: 'bob' }, + ], + hideValidationErrors: false, + }, + ], + }, + }, + { name: 'GitHub organizations', role: { diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts index 971bb5a81872..82297aa3f4f4 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts @@ -140,6 +140,7 @@ export type ResourceAccess = | AppAccess | DatabaseAccess | WindowsDesktopAccess + | LinuxDesktopAccess | GitHubOrganizationAccess; /** @@ -165,6 +166,7 @@ export type ResourceAccessKind = | 'app' | 'db' | 'windows_desktop' + | 'linux_desktop' | 'git_server'; /* @@ -463,6 +465,20 @@ export type WindowsDesktopAccessInputFields = export type WindowsDesktopAccess = ResourceAccessBase<'windows_desktop'> & WindowsDesktopAccessFields; +/* + * Models for the linux desktop resource access section. + */ +type LinuxDesktopAccessFields = { + labels: UILabel[]; + logins: Option[]; +}; + +export type LinuxDesktopAccessInputFields = + FieldTypesToBoolean; + +export type LinuxDesktopAccess = ResourceAccessBase<'linux_desktop'> & + LinuxDesktopAccessFields; + /* * Models for the git server resource access section. */ @@ -651,6 +667,11 @@ export function newResourceAccess( roleVersion: RoleVersion ): WindowsDesktopAccess; +export function newResourceAccess( + kind: 'linux_desktop', + roleVersion: RoleVersion +): LinuxDesktopAccess; + export function newResourceAccess( kind: 'git_server', roleVersion: RoleVersion @@ -710,6 +731,13 @@ export function newResourceAccess( logins: [stringToOption('{{internal.windows_logins}}')], hideValidationErrors: true, }; + case 'linux_desktop': + return { + kind: 'linux_desktop', + labels: [], + logins: [stringToOption('{{internal.logins}}')], + hideValidationErrors: true, + }; case 'git_server': return { kind: 'git_server', @@ -865,6 +893,10 @@ function roleConditionsToModel( windows_desktop_labels, windows_desktop_logins, + // Linux desktop access + linux_desktop_labels, + linux_desktop_logins, + // GitHub organization access github_permissions, @@ -990,6 +1022,17 @@ function roleConditionsToModel( }); } + const linuxDesktopLabelsModel = labelsToModel(linux_desktop_labels); + const linuxDesktopLoginsModel = stringsToOptions(linux_desktop_logins ?? []); + if (someNonEmpty(linuxDesktopLabelsModel, linuxDesktopLoginsModel)) { + resources.push({ + kind: 'linux_desktop', + labels: linuxDesktopLabelsModel, + logins: linuxDesktopLoginsModel, + hideValidationErrors: false, + }); + } + const { model: gitHubOrganizationsModel, conversionErrors: gitHubOrganizationConversionErrors, @@ -1627,6 +1670,11 @@ export function roleEditorModelToRole(roleModel: RoleEditorModel): Role { role.spec.allow.windows_desktop_logins = optionsToStrings(res.logins); break; + case 'linux_desktop': + role.spec.allow.linux_desktop_labels = labelsModelToLabels(res.labels); + role.spec.allow.linux_desktop_logins = optionsToStrings(res.logins); + break; + case 'git_server': const orgs = optionsToStrings(res.organizations); if (orgs.length > 0) { diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.test.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.test.ts index 43ddc6cb8501..770945b15e1c 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.test.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.test.ts @@ -111,6 +111,12 @@ describe('validateRoleEditorModel', () => { logins: [], hideValidationErrors: false, }, + { + kind: 'linux_desktop', + labels: [{ name: 'foo', value: 'bar' }], + logins: [], + hideValidationErrors: false, + }, ]; model.rules = [ { @@ -146,7 +152,14 @@ describe('validateRoleEditorModel', () => { ]; const result = validateRoleEditorModel(model, undefined, undefined); expect(result.metadata.valid).toBe(true); - expect(validity(result.resources)).toEqual([true, true, true, true, true]); + expect(validity(result.resources)).toEqual([ + true, + true, + true, + true, + true, + true, + ]); expect(validity(result.rules)).toEqual([true, true]); expect(result.isValid).toBe(true); }); @@ -215,6 +228,12 @@ describe('validateRoleEditorModel', () => { logins: [], hideValidationErrors: false, }, + { + kind: 'linux_desktop', + labels: [], + logins: [], + hideValidationErrors: false, + }, ]; const result = validateRoleEditorModel(model, undefined, undefined); expect(validity(result.resources)).toEqual([ @@ -224,6 +243,7 @@ describe('validateRoleEditorModel', () => { false, false, false, + false, ]); expect(result.isValid).toBe(false); }); diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts index 841301f8aa6c..1820db37446f 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts @@ -44,6 +44,7 @@ import { ServerAccess, supportsKubernetesCustomResources, VerbModel, + LinuxDesktopAccess, WindowsDesktopAccess, } from './standardmodel'; @@ -175,6 +176,8 @@ export function validateResourceAccess( return validateDatabaseAccess(resource); case 'windows_desktop': return validateWindowsDesktopAccess(resource); + case 'linux_desktop': + return validateLinuxDesktopAccess(resource); case 'git_server': return runRules(resource, gitHubOrganizationAccessValidationRules); default: @@ -188,6 +191,7 @@ export type ResourceAccessValidationResult = | AppAccessValidationResult | DatabaseAccessValidationResult | WindowsDesktopAccessValidationResult + | LinuxDesktopAccessValidationResult | GitHubOrganizationAccessValidationResult; const validKubernetesResource = (res: KubernetesResourceModel) => () => { @@ -551,6 +555,27 @@ export type WindowsDesktopAccessValidationResult = RuleSetValidationResult< typeof windowsDesktopAccessValidationRules >; +const validateLinuxDesktopAccess = ( + a: LinuxDesktopAccess +): LinuxDesktopAccessValidationResult => { + const result = runRules(a, linuxDesktopAccessValidationRules); + if (a.labels.length === 0 && a.logins.length === 0) { + result.valid = false; + result.message = 'At least one label or login required'; + result.fields.labels.valid = false; + result.fields.logins.valid = false; + } + return result; +}; + +const linuxDesktopAccessValidationRules = { + labels: nonEmptyLabels, + logins: alwaysValid, +}; +export type LinuxDesktopAccessValidationResult = RuleSetValidationResult< + typeof linuxDesktopAccessValidationRules +>; + const gitHubOrganizationAccessValidationRules = { organizations: requiredField
    + Note that saving the new SCIM configuration will invalidate the existing SCIM token. -1. Copy the generated **Bearer Token** and store it for later. It will be required for the Okta Teleport app configuration. + -2. Configure the SCIM integration with the generated token by clicking **Save SCIM Configuration** (it can be regenerated by refreshing the page and clicking the button again). +Save the current settings by clicking **Save SCIM Configuration** button to make the generated **API Token** active. It will be needed in the next step. ![Generate SCIM token](../../../../img/okta/scim-gen-token.png) @@ -84,7 +84,7 @@ This step assumes you are still in the **Okta Integration** configuration wizard 2. Click on the Teleport application and go to the **Provisioning** tab. 3. Click **Configure API Integration**, check the **Enable API integration** box and: - 1. Paste the Bearer Token generated in Step 1 of this guide. + 1. Paste the API Token generated in Step 1 of this guide. 2. Uncheck "Import Groups" box, clicking **Continue** in the pop-up warning. ![Generate SCIM token](../../../../img/okta/scim-paste-token.png) @@ -144,7 +144,7 @@ This step assumes you are still in the **Okta Integration** configuration wizard At this point your Teleport Okta SCIM integration is complete. Users newly assigned/unassigned to the Okta Teleport app should be created/deleted in Teleport nearly instantly. -If you have users assigned to the Okta Teleport app before enabling SCIM, you may notice there are red question marks next to each user in the *Assignments* tab. To learn more about that read the **Retrospective user provisioning** section below. +If you have users assigned to the Okta Teleport app before enabling SCIM, you may notice there are red exclamation marks next to each user in the *Assignments* tab. To learn more about that read the **Retrospective user provisioning** section below. ## Supported features diff --git a/docs/pages/zero-trust-access/sso/integrate-idp/okta.mdx b/docs/pages/zero-trust-access/sso/integrate-idp/okta.mdx index ab5f3bcf32e5..4c629ee19ae0 100644 --- a/docs/pages/zero-trust-access/sso/integrate-idp/okta.mdx +++ b/docs/pages/zero-trust-access/sso/integrate-idp/okta.mdx @@ -79,7 +79,7 @@ Create two groups: "devs" and "admins". 2. Click **Browse App Catalog**. 3. Search for and select **Teleport**, and then click **Add Integration**. 4. Complete the fields on the settings page and click **Done**. - 5. Go to the **General** tab and click **Edit** on the top-right corner of the tab. + 5. Go to the **Sign On** tab and click **Edit** on the top-right corner of the tab. 6. Scroll down to the `groups` attribute, then select `Match regex` and type in `.*` in the input field. {/* NOTE: This image is shared in multiple guides. */} From bb63400e0f1894cf95a52474f71b03e5ea87bb73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 08:53:43 +0000 Subject: [PATCH 263/347] Bump the ui group across 1 directory with 47 updates (#69470) * Bump the ui group across 1 directory with 47 updates Bumps the ui group with 47 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@codemirror/view](https://github.com/codemirror/view) | `6.43.4` | `6.43.8` | | [@floating-ui/react](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/react) | `0.27.19` | `0.27.20` | | [@fontsource/ubuntu](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/ubuntu) | `5.2.8` | `5.3.0` | | [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.4.0` | `5.7.1` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.101.2` | `5.101.4` | | [@uiw/codemirror-themes](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` | | [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` | | [immer](https://github.com/immerjs/immer) | `11.1.8` | `11.1.16` | | [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.7` | `19.2.8` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.17` | `19.2.18` | | [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.7` | `19.2.8` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.3` | `19.2.4` | | [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.80.0` | `7.85.0` | | [react-is](https://github.com/react/react/tree/HEAD/packages/react-is) | `19.2.7` | `19.2.8` | | [styled-components](https://github.com/styled-components/styled-components) | `6.4.3` | `6.5.1` | | [@storybook/addon-vitest](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/vitest) | `10.4.6` | `10.5.7` | | [@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite) | `10.4.6` | `10.5.7` | | [@testing-library/user-event](https://github.com/testing-library/user-event) | `14.6.1` | `14.6.3` | | [eslint](https://github.com/eslint/eslint) | `10.6.0` | `10.8.1` | | [eslint-plugin-jest-dom](https://github.com/testing-library/eslint-plugin-jest-dom) | `5.5.0` | `5.10.1` | | [eslint-plugin-storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/eslint-plugin) | `10.4.6` | `10.5.7` | | [msw](https://github.com/mswjs/msw) | `2.14.6` | `2.15.0` | | [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.56.0` | `0.62.0` | | [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.71.0` | `1.77.0` | | [playwright](https://github.com/microsoft/playwright) | `1.61.1` | `1.62.1` | | [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core) | `10.4.6` | `10.5.7` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.1.0` | `8.2.1` | | [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core) | `1.15.43` | `1.15.47` | | [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react-swc) | `4.3.1` | `4.3.3` | | [globals](https://github.com/sindresorhus/globals) | `17.7.0` | `17.9.0` | | [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) | `7.7.1` | `7.8.0` | | [@opentelemetry/context-zone](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/exporter-trace-otlp-http](https://github.com/open-telemetry/opentelemetry-js) | `0.219.0` | `0.221.0` | | [@opentelemetry/instrumentation](https://github.com/open-telemetry/opentelemetry-js) | `0.219.0` | `0.221.0` | | [@opentelemetry/instrumentation-document-load](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-document-load) | `0.64.0` | `0.66.0` | | [@opentelemetry/instrumentation-fetch](https://github.com/open-telemetry/opentelemetry-js) | `0.219.0` | `0.221.0` | | [@opentelemetry/instrumentation-user-interaction](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-user-interaction) | `0.63.0` | `0.65.0` | | [@opentelemetry/instrumentation-xml-http-request](https://github.com/open-telemetry/opentelemetry-js) | `0.219.0` | `0.221.0` | | [@opentelemetry/propagator-b3](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/sdk-trace-web](https://github.com/open-telemetry/opentelemetry-js) | `2.8.0` | `2.10.0` | | [@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js) | `1.41.1` | `1.43.0` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.61.1` | `1.62.1` | | [cbor-x](https://github.com/kriszyp/cbor-x) | `1.6.4` | `1.6.5` | | [tsx](https://github.com/privatenumber/tsx) | `4.22.4` | `4.23.11` | Updates `@codemirror/view` from 6.43.4 to 6.43.8 - [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/view/commits) Updates `@floating-ui/react` from 0.27.19 to 0.27.20 - [Release notes](https://github.com/floating-ui/floating-ui/releases) - [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/react/CHANGELOG.md) - [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/react@0.27.20/packages/react) Updates `@fontsource/ubuntu` from 5.2.8 to 5.3.0 - [Changelog](https://github.com/fontsource/font-files/blob/main/CHANGELOG.md) - [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/ubuntu) Updates `@hookform/resolvers` from 5.4.0 to 5.7.1 - [Release notes](https://github.com/react-hook-form/resolvers/releases) - [Commits](https://github.com/react-hook-form/resolvers/compare/v5.4.0...v5.7.1) Updates `@tanstack/react-query` from 5.101.2 to 5.101.4 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.101.4/packages/react-query) Updates `@uiw/codemirror-themes` from 4.25.10 to 4.25.11 - [Release notes](https://github.com/uiwjs/react-codemirror/releases) - [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11) Updates `@uiw/react-codemirror` from 4.25.10 to 4.25.11 - [Release notes](https://github.com/uiwjs/react-codemirror/releases) - [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11) Updates `immer` from 11.1.8 to 11.1.16 - [Release notes](https://github.com/immerjs/immer/releases) - [Commits](https://github.com/immerjs/immer/compare/v11.1.8...v11.1.16) Updates `react` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react) Updates `@types/react` from 19.2.17 to 19.2.18 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `react-dom` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom) Updates `@types/react-dom` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `react-hook-form` from 7.80.0 to 7.85.0 - [Release notes](https://github.com/react-hook-form/react-hook-form/releases) - [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md) - [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.80.0...v7.85.0) Updates `react-is` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-is) Updates `styled-components` from 6.4.3 to 6.5.1 - [Release notes](https://github.com/styled-components/styled-components/releases) - [Commits](https://github.com/styled-components/styled-components/compare/styled-components@6.4.3...styled-components@6.5.1) Updates `@storybook/addon-vitest` from 10.4.6 to 10.5.7 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.7/code/addons/vitest) Updates `@storybook/react-vite` from 10.4.6 to 10.5.7 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.7/code/frameworks/react-vite) Updates `@testing-library/user-event` from 14.6.1 to 14.6.3 - [Release notes](https://github.com/testing-library/user-event/releases) - [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/user-event/compare/v14.6.1...v14.6.3) Updates `@types/react` from 19.2.17 to 19.2.18 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react-dom` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `eslint` from 10.6.0 to 10.8.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.6.0...v10.8.1) Updates `eslint-plugin-jest-dom` from 5.5.0 to 5.10.1 - [Release notes](https://github.com/testing-library/eslint-plugin-jest-dom/releases) - [Commits](https://github.com/testing-library/eslint-plugin-jest-dom/compare/v5.5.0...v5.10.1) Updates `eslint-plugin-storybook` from 10.4.6 to 10.5.7 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.7/code/lib/eslint-plugin) Updates `msw` from 2.14.6 to 2.15.0 - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v2.14.6...v2.15.0) Updates `oxfmt` from 0.56.0 to 0.62.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.62.0/npm/oxfmt) Updates `oxlint` from 1.71.0 to 1.77.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.77.0/npm/oxlint) Updates `playwright` from 1.61.1 to 1.62.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.1...v1.62.1) Updates `storybook` from 10.4.6 to 10.5.7 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.7/code/core) Updates `vite` from 8.1.0 to 8.2.1 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v8.2.1/packages/vite) Updates `@swc/core` from 1.15.43 to 1.15.47 - [Release notes](https://github.com/swc-project/swc/releases) - [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md) - [Commits](https://github.com/swc-project/swc/commits/v1.15.47/packages/core) Updates `@vitejs/plugin-react-swc` from 4.3.1 to 4.3.3 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.3/packages/plugin-react-swc) Updates `globals` from 17.7.0 to 17.9.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.7.0...v17.9.0) Updates `@types/semver` from 7.7.1 to 7.8.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver) Updates `@opentelemetry/context-zone` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/core` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/exporter-trace-otlp-http` from 0.219.0 to 0.221.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.219.0...experimental/v0.221.0) Updates `@opentelemetry/instrumentation` from 0.219.0 to 0.221.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.219.0...experimental/v0.221.0) Updates `@opentelemetry/instrumentation-document-load` from 0.64.0 to 0.66.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/packages/instrumentation-document-load/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/commits/contrib-test-utils-v0.66.0/packages/instrumentation-document-load) Updates `@opentelemetry/instrumentation-fetch` from 0.219.0 to 0.221.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.219.0...experimental/v0.221.0) Updates `@opentelemetry/instrumentation-user-interaction` from 0.63.0 to 0.65.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/packages/instrumentation-user-interaction/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/commits/instrumentation-pg-v0.65.0/packages/instrumentation-user-interaction) Updates `@opentelemetry/instrumentation-xml-http-request` from 0.219.0 to 0.221.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.219.0...experimental/v0.221.0) Updates `@opentelemetry/propagator-b3` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/resources` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/sdk-trace-base` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/sdk-trace-web` from 2.8.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v2.8.0...v2.10.0) Updates `@opentelemetry/semantic-conventions` from 1.41.1 to 1.43.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/semconv/v1.41.1...semconv/v1.43.0) Updates `@playwright/test` from 1.61.1 to 1.62.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.1...v1.62.1) Updates `cbor-x` from 1.6.4 to 1.6.5 - [Release notes](https://github.com/kriszyp/cbor-x/releases) - [Commits](https://github.com/kriszyp/cbor-x/commits/v1.6.5) Updates `tsx` from 4.22.4 to 4.23.11 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.22.4...v4.23.11) --- updated-dependencies: - dependency-name: "@codemirror/view" dependency-version: 6.43.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@floating-ui/react" dependency-version: 0.27.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@fontsource/ubuntu" dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@hookform/resolvers" dependency-version: 5.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/context-zone" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/core" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/exporter-trace-otlp-http" dependency-version: 0.221.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/instrumentation" dependency-version: 0.221.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/instrumentation-document-load" dependency-version: 0.66.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/instrumentation-fetch" dependency-version: 0.221.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/instrumentation-user-interaction" dependency-version: 0.65.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/instrumentation-xml-http-request" dependency-version: 0.221.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/propagator-b3" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/resources" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/sdk-trace-base" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/sdk-trace-web" dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@opentelemetry/semantic-conventions" dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@playwright/test" dependency-version: 1.62.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@storybook/addon-vitest" dependency-version: 10.5.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@storybook/react-vite" dependency-version: 10.5.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@swc/core" dependency-version: 1.15.47 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@tanstack/react-query" dependency-version: 5.101.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@testing-library/user-event" dependency-version: 14.6.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@types/react" dependency-version: 19.2.18 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@types/react" dependency-version: 19.2.18 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@types/react-dom" dependency-version: 19.2.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@types/react-dom" dependency-version: 19.2.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@types/semver" dependency-version: 7.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: "@uiw/codemirror-themes" dependency-version: 4.25.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@uiw/react-codemirror" dependency-version: 4.25.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: "@vitejs/plugin-react-swc" dependency-version: 4.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: cbor-x dependency-version: 1.6.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ui - dependency-name: eslint dependency-version: 10.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: eslint-plugin-jest-dom dependency-version: 5.10.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: eslint-plugin-storybook dependency-version: 10.5.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: globals dependency-version: 17.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: immer dependency-version: 11.1.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: msw dependency-version: 2.15.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: oxfmt dependency-version: 0.62.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: oxlint dependency-version: 1.77.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: playwright dependency-version: 1.62.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: react dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: react-dom dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: react-hook-form dependency-version: 7.84.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: react-is dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ui - dependency-name: storybook dependency-version: 10.5.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: styled-components dependency-version: 6.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ui - dependency-name: tsx dependency-version: 4.23.8 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui - dependency-name: vite dependency-version: 8.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ui ... Signed-off-by: dependabot[bot] * `pnpm dedupe` * `pnpm format` * Run `pnpm format` on icons --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Grzegorz Zdunek --- e2e/package.json | 6 +- package.json | 54 +- pnpm-lock.yaml | 2821 +++++++++-------- web/packages/build/package.json | 6 +- web/packages/design/src/Icon/Icons/Add.tsx | 1 + .../design/src/Icon/Icons/AddCircle.tsx | 1 + .../design/src/Icon/Icons/AddUsers.tsx | 1 + .../design/src/Icon/Icons/AlarmRing.tsx | 1 + .../design/src/Icon/Icons/AmazonAws.tsx | 1 + .../design/src/Icon/Icons/Apartment.tsx | 1 + web/packages/design/src/Icon/Icons/Apple.tsx | 1 + .../design/src/Icon/Icons/Application.tsx | 1 + .../design/src/Icon/Icons/Archive.tsx | 1 + .../design/src/Icon/Icons/ArrowBack.tsx | 1 + .../design/src/Icon/Icons/ArrowDown.tsx | 1 + .../design/src/Icon/Icons/ArrowFatLinesUp.tsx | 1 + .../design/src/Icon/Icons/ArrowForward.tsx | 1 + .../design/src/Icon/Icons/ArrowLeft.tsx | 1 + .../design/src/Icon/Icons/ArrowLineLeft.tsx | 1 + .../design/src/Icon/Icons/ArrowRight.tsx | 1 + .../design/src/Icon/Icons/ArrowSquareIn.tsx | 1 + .../design/src/Icon/Icons/ArrowSquareOut.tsx | 1 + .../design/src/Icon/Icons/ArrowUp.tsx | 1 + .../design/src/Icon/Icons/ArrowUpRight.tsx | 1 + .../design/src/Icon/Icons/ArrowsIn.tsx | 1 + .../design/src/Icon/Icons/ArrowsOut.tsx | 1 + web/packages/design/src/Icon/Icons/Beams.tsx | 1 + .../design/src/Icon/Icons/BedrockLogo.tsx | 1 + web/packages/design/src/Icon/Icons/Bell.tsx | 1 + .../design/src/Icon/Icons/BellRinging.tsx | 1 + .../design/src/Icon/Icons/BookOpenText.tsx | 1 + web/packages/design/src/Icon/Icons/Bots.tsx | 1 + .../design/src/Icon/Icons/Broadcast.tsx | 1 + .../design/src/Icon/Icons/BroadcastSlash.tsx | 1 + web/packages/design/src/Icon/Icons/Bubble.tsx | 1 + web/packages/design/src/Icon/Icons/CCAmex.tsx | 1 + .../design/src/Icon/Icons/CCDiscover.tsx | 1 + .../design/src/Icon/Icons/CCMasterCard.tsx | 1 + .../design/src/Icon/Icons/CCStripe.tsx | 1 + web/packages/design/src/Icon/Icons/CCVisa.tsx | 1 + .../design/src/Icon/Icons/Calendar.tsx | 1 + web/packages/design/src/Icon/Icons/Camera.tsx | 1 + .../design/src/Icon/Icons/CardView.tsx | 1 + web/packages/design/src/Icon/Icons/Cash.tsx | 1 + web/packages/design/src/Icon/Icons/Chart.tsx | 1 + .../design/src/Icon/Icons/ChatBubble.tsx | 1 + .../design/src/Icon/Icons/ChatCircle.tsx | 1 + .../src/Icon/Icons/ChatCircleSparkle.tsx | 1 + web/packages/design/src/Icon/Icons/Check.tsx | 1 + .../src/Icon/Icons/CheckCircleDotted.tsx | 1 + .../design/src/Icon/Icons/CheckThick.tsx | 1 + web/packages/design/src/Icon/Icons/Checks.tsx | 1 + .../src/Icon/Icons/ChevronCircleDown.tsx | 1 + .../src/Icon/Icons/ChevronCircleLeft.tsx | 1 + .../src/Icon/Icons/ChevronCircleRight.tsx | 1 + .../design/src/Icon/Icons/ChevronCircleUp.tsx | 1 + .../design/src/Icon/Icons/ChevronDown.tsx | 1 + .../design/src/Icon/Icons/ChevronLeft.tsx | 1 + .../design/src/Icon/Icons/ChevronRight.tsx | 1 + .../design/src/Icon/Icons/ChevronUp.tsx | 1 + .../src/Icon/Icons/ChevronsVertical.tsx | 1 + .../design/src/Icon/Icons/CircleArrowLeft.tsx | 1 + .../src/Icon/Icons/CircleArrowRight.tsx | 1 + .../design/src/Icon/Icons/CircleCheck.tsx | 1 + .../design/src/Icon/Icons/CircleCross.tsx | 1 + .../design/src/Icon/Icons/CircleDashed.tsx | 1 + .../design/src/Icon/Icons/CirclePause.tsx | 1 + .../design/src/Icon/Icons/CirclePlay.tsx | 1 + .../design/src/Icon/Icons/CircleStop.tsx | 1 + web/packages/design/src/Icon/Icons/Cli.tsx | 1 + .../design/src/Icon/Icons/Clipboard.tsx | 1 + .../design/src/Icon/Icons/ClipboardUser.tsx | 1 + web/packages/design/src/Icon/Icons/Clock.tsx | 1 + web/packages/design/src/Icon/Icons/Cloud.tsx | 1 + .../design/src/Icon/Icons/Cluster.tsx | 1 + web/packages/design/src/Icon/Icons/Code.tsx | 1 + web/packages/design/src/Icon/Icons/Cog.tsx | 1 + web/packages/design/src/Icon/Icons/Config.tsx | 1 + .../design/src/Icon/Icons/Contract.tsx | 1 + web/packages/design/src/Icon/Icons/Copy.tsx | 1 + .../design/src/Icon/Icons/CornersIn.tsx | 1 + .../design/src/Icon/Icons/CornersOut.tsx | 1 + .../design/src/Icon/Icons/CreditCard.tsx | 1 + web/packages/design/src/Icon/Icons/Cross.tsx | 1 + web/packages/design/src/Icon/Icons/Crown.tsx | 1 + .../design/src/Icon/Icons/Database.tsx | 1 + .../design/src/Icon/Icons/Desktop.tsx | 1 + .../design/src/Icon/Icons/Detective.tsx | 1 + .../src/Icon/Icons/DeviceMobileCamera.tsx | 1 + .../design/src/Icon/Icons/Devices.tsx | 1 + web/packages/design/src/Icon/Icons/Dot.tsx | 1 + .../design/src/Icon/Icons/Download.tsx | 1 + web/packages/design/src/Icon/Icons/Earth.tsx | 1 + web/packages/design/src/Icon/Icons/Edit.tsx | 1 + .../design/src/Icon/Icons/Ellipsis.tsx | 1 + .../design/src/Icon/Icons/EmailSolid.tsx | 1 + .../design/src/Icon/Icons/EnvelopeOpen.tsx | 1 + .../src/Icon/Icons/EqualizersVertical.tsx | 1 + web/packages/design/src/Icon/Icons/Expand.tsx | 1 + .../design/src/Icon/Icons/Facebook.tsx | 1 + .../design/src/Icon/Icons/FastForward.tsx | 1 + .../design/src/Icon/Icons/FilmStrip.tsx | 1 + .../src/Icon/Icons/FingerprintSimple.tsx | 1 + web/packages/design/src/Icon/Icons/Flag.tsx | 1 + web/packages/design/src/Icon/Icons/Floppy.tsx | 1 + .../design/src/Icon/Icons/FlowArrow.tsx | 1 + .../design/src/Icon/Icons/FolderPlus.tsx | 1 + .../design/src/Icon/Icons/FolderShared.tsx | 1 + web/packages/design/src/Icon/Icons/GitHub.tsx | 1 + web/packages/design/src/Icon/Icons/Google.tsx | 1 + web/packages/design/src/Icon/Icons/Graph.tsx | 1 + .../design/src/Icon/Icons/GridFour.tsx | 1 + .../design/src/Icon/Icons/GridNine.tsx | 1 + .../design/src/Icon/Icons/Hashtag.tsx | 1 + .../design/src/Icon/Icons/Headset.tsx | 1 + web/packages/design/src/Icon/Icons/Home.tsx | 1 + web/packages/design/src/Icon/Icons/Info.tsx | 1 + .../design/src/Icon/Icons/Integrations.tsx | 1 + .../design/src/Icon/Icons/Invoices.tsx | 1 + web/packages/design/src/Icon/Icons/Key.tsx | 1 + .../design/src/Icon/Icons/KeyHole.tsx | 1 + .../design/src/Icon/Icons/Keyboard.tsx | 1 + .../design/src/Icon/Icons/Keypair.tsx | 1 + .../design/src/Icon/Icons/Kubernetes.tsx | 1 + web/packages/design/src/Icon/Icons/Label.tsx | 1 + web/packages/design/src/Icon/Icons/Lan.tsx | 1 + web/packages/design/src/Icon/Icons/Laptop.tsx | 1 + web/packages/design/src/Icon/Icons/Layout.tsx | 1 + .../design/src/Icon/Icons/License.tsx | 1 + .../design/src/Icon/Icons/LineSegment.tsx | 1 + .../design/src/Icon/Icons/LineSegments.tsx | 1 + web/packages/design/src/Icon/Icons/Link.tsx | 1 + .../design/src/Icon/Icons/Linkedin.tsx | 1 + web/packages/design/src/Icon/Icons/Linux.tsx | 1 + .../design/src/Icon/Icons/ListAddCheck.tsx | 1 + .../src/Icon/Icons/ListMagnifyingGlass.tsx | 1 + .../design/src/Icon/Icons/ListThin.tsx | 1 + .../design/src/Icon/Icons/ListView.tsx | 1 + web/packages/design/src/Icon/Icons/Lock.tsx | 1 + .../design/src/Icon/Icons/LockKey.tsx | 1 + web/packages/design/src/Icon/Icons/Logout.tsx | 1 + .../design/src/Icon/Icons/Magnifier.tsx | 1 + .../design/src/Icon/Icons/MagnifyingMinus.tsx | 1 + .../design/src/Icon/Icons/MagnifyingPlus.tsx | 1 + web/packages/design/src/Icon/Icons/Memory.tsx | 1 + web/packages/design/src/Icon/Icons/Minus.tsx | 1 + .../design/src/Icon/Icons/MinusCircle.tsx | 1 + web/packages/design/src/Icon/Icons/Moon.tsx | 1 + .../design/src/Icon/Icons/MoreHoriz.tsx | 1 + .../design/src/Icon/Icons/MoreVert.tsx | 1 + web/packages/design/src/Icon/Icons/Mute.tsx | 1 + .../design/src/Icon/Icons/Network.tsx | 1 + web/packages/design/src/Icon/Icons/NewTab.tsx | 1 + .../design/src/Icon/Icons/NoteAdded.tsx | 1 + .../design/src/Icon/Icons/Notification.tsx | 1 + .../src/Icon/Icons/NotificationsActive.tsx | 1 + .../design/src/Icon/Icons/Package.tsx | 1 + .../design/src/Icon/Icons/PaperPlane.tsx | 1 + .../design/src/Icon/Icons/Password.tsx | 1 + web/packages/design/src/Icon/Icons/Pause.tsx | 1 + web/packages/design/src/Icon/Icons/Pencil.tsx | 1 + web/packages/design/src/Icon/Icons/Planet.tsx | 1 + web/packages/design/src/Icon/Icons/Play.tsx | 1 + web/packages/design/src/Icon/Icons/Plugs.tsx | 1 + .../design/src/Icon/Icons/PlugsConnected.tsx | 1 + web/packages/design/src/Icon/Icons/Plus.tsx | 1 + .../design/src/Icon/Icons/PowerSwitch.tsx | 1 + .../design/src/Icon/Icons/Printer.tsx | 1 + .../design/src/Icon/Icons/Profile.tsx | 1 + .../design/src/Icon/Icons/PushPin.tsx | 1 + .../design/src/Icon/Icons/PushPinFilled.tsx | 1 + .../design/src/Icon/Icons/Question.tsx | 1 + .../design/src/Icon/Icons/Refresh.tsx | 1 + .../design/src/Icon/Icons/Restore.tsx | 1 + .../design/src/Icon/Icons/RocketLaunch.tsx | 1 + web/packages/design/src/Icon/Icons/Rows.tsx | 1 + .../design/src/Icon/Icons/RowsComfortable.tsx | 1 + .../design/src/Icon/Icons/RowsDense.tsx | 1 + web/packages/design/src/Icon/Icons/Ruler.tsx | 1 + web/packages/design/src/Icon/Icons/Run.tsx | 1 + web/packages/design/src/Icon/Icons/Scan.tsx | 1 + web/packages/design/src/Icon/Icons/Server.tsx | 1 + web/packages/design/src/Icon/Icons/Share.tsx | 1 + .../design/src/Icon/Icons/ShieldCheck.tsx | 1 + .../design/src/Icon/Icons/ShieldWarning.tsx | 1 + .../design/src/Icon/Icons/Sliders.tsx | 1 + .../design/src/Icon/Icons/SlidersVertical.tsx | 1 + .../design/src/Icon/Icons/SortAscending.tsx | 1 + .../design/src/Icon/Icons/SortDescending.tsx | 1 + .../design/src/Icon/Icons/Sparkle.tsx | 1 + web/packages/design/src/Icon/Icons/Speed.tsx | 1 + .../design/src/Icon/Icons/Spinner.tsx | 1 + .../design/src/Icon/Icons/SquaresFour.tsx | 1 + web/packages/design/src/Icon/Icons/Stack.tsx | 1 + web/packages/design/src/Icon/Icons/Stars.tsx | 1 + web/packages/design/src/Icon/Icons/Sun.tsx | 1 + .../design/src/Icon/Icons/SyncAlt.tsx | 1 + web/packages/design/src/Icon/Icons/Table.tsx | 1 + web/packages/design/src/Icon/Icons/Tablet.tsx | 1 + web/packages/design/src/Icon/Icons/Tags.tsx | 1 + .../design/src/Icon/Icons/Terminal.tsx | 1 + web/packages/design/src/Icon/Icons/Trash.tsx | 1 + .../design/src/Icon/Icons/Twitter.tsx | 1 + .../design/src/Icon/Icons/Unarchive.tsx | 1 + web/packages/design/src/Icon/Icons/Unlink.tsx | 1 + web/packages/design/src/Icon/Icons/Unlock.tsx | 1 + web/packages/design/src/Icon/Icons/Upload.tsx | 1 + .../design/src/Icon/Icons/UsbDrive.tsx | 1 + web/packages/design/src/Icon/Icons/User.tsx | 1 + .../design/src/Icon/Icons/UserAdd.tsx | 1 + .../design/src/Icon/Icons/UserCheck.tsx | 1 + .../design/src/Icon/Icons/UserCircleGear.tsx | 1 + .../design/src/Icon/Icons/UserFocus.tsx | 1 + .../design/src/Icon/Icons/UserIdBadge.tsx | 1 + .../design/src/Icon/Icons/UserList.tsx | 1 + web/packages/design/src/Icon/Icons/Users.tsx | 1 + .../design/src/Icon/Icons/UsersTriple.tsx | 1 + web/packages/design/src/Icon/Icons/Vault.tsx | 1 + .../design/src/Icon/Icons/VideoGame.tsx | 1 + .../design/src/Icon/Icons/VolumeUp.tsx | 1 + web/packages/design/src/Icon/Icons/VpnKey.tsx | 1 + web/packages/design/src/Icon/Icons/Wand.tsx | 1 + .../design/src/Icon/Icons/Warning.tsx | 1 + .../design/src/Icon/Icons/WarningCircle.tsx | 1 + web/packages/design/src/Icon/Icons/Wifi.tsx | 1 + .../design/src/Icon/Icons/Windows.tsx | 1 + web/packages/design/src/Icon/Icons/Wrench.tsx | 1 + web/packages/design/src/Icon/Icons/XCheck.tsx | 1 + .../design/src/Icon/Icons/Youtube.tsx | 1 + .../CardsView/ResourceCard.tsx | 1 - .../UnifiedResources/shared/StatusInfo.tsx | 10 +- web/packages/shared/package.json | 2 +- web/packages/teleport/package.json | 26 +- web/packages/teleport/src/Main/Main.tsx | 16 +- .../teleport/src/Navigation/Section.tsx | 16 +- .../SessionRecordings/list/RecordingItem.tsx | 127 +- .../UnifiedResources/ResourceActionButton.tsx | 14 +- .../ExternalAuditStorageCta.tsx | 4 +- .../ConnectionStatusIndicator.tsx | 24 +- 239 files changed, 1846 insertions(+), 1506 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 7da3fee4d676..723cd646f47d 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -11,9 +11,9 @@ }, "license": "Apache-2.0", "devDependencies": { - "@playwright/test": "^1.61.1", - "cbor-x": "^1.6.4", - "tsx": "^4.22.4", + "@playwright/test": "^1.62.1", + "cbor-x": "^1.6.5", + "tsx": "^4.23.11", "undici": "^8.10.0" } } diff --git a/package.json b/package.json index 33f40208ff74..ee610fe52edd 100644 --- a/package.json +++ b/package.json @@ -34,70 +34,70 @@ "private": true, "devDependencies": { "@gravitational/build": "workspace:*", - "@storybook/addon-vitest": "^10.4.6", - "@storybook/react-vite": "^10.4.6", + "@storybook/addon-vitest": "^10.5.7", + "@storybook/react-vite": "^10.5.7", "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.1", + "@testing-library/user-event": "^14.6.3", "@types/history": "^4.7.11", "@types/jest": "^30.0.0", "@types/node": "^24.13.2", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", "@types/react-transition-group": "^4.4.12", "@types/wicg-file-system-access": "^2023.10.7", "cspell": "^10.0.1", - "eslint": "^10.6.0", - "eslint-plugin-jest-dom": "^5.5.0", - "eslint-plugin-storybook": "^10.4.6", + "eslint": "^10.8.1", + "eslint-plugin-jest-dom": "^5.10.1", + "eslint-plugin-storybook": "^10.5.7", "eslint-plugin-testing-library": "7.16.2", "eslint-plugin-unused-imports": "^4.4.1", "jest": "^30.4.2", "jsdom-testing-mocks": "^1.16.0", - "msw": "2.14.6", + "msw": "2.15.0", "msw-storybook-addon": "^3.0.0", - "oxfmt": "^0.56.0", - "oxlint": "^1.71.0", - "playwright": "^1.61.1", + "oxfmt": "^0.62.0", + "oxlint": "^1.77.0", + "playwright": "^1.62.1", "react-select-event": "^5.5.1", - "storybook": "^10.4.6", + "storybook": "^10.5.7", "svgo": "^4.0.2", "typescript": "^7.0.2", - "vite": "^8.1.0" + "vite": "^8.2.1" }, "dependencies": { "@codemirror/autocomplete": "^6.20.3", "@codemirror/lang-sql": "^6.10.0", - "@codemirror/view": "^6.43.4", - "@floating-ui/react": "^0.27.19", - "@fontsource/ubuntu": "^5.2.8", + "@codemirror/view": "^6.43.8", + "@floating-ui/react": "^0.27.20", + "@fontsource/ubuntu": "^5.3.0", "@gravitational/design-system": "https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz", "@grpc/grpc-js": "1.14.4", - "@hookform/resolvers": "^5.4.0", + "@hookform/resolvers": "^5.7.1", "@lezer/highlight": "^1.2.3", "@nivo/bar": "^0.99.0", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", - "@tanstack/react-query": "^5.101.2", - "@uiw/codemirror-themes": "^4.25.10", - "@uiw/react-codemirror": "^4.25.10", + "@tanstack/react-query": "^5.101.4", + "@uiw/codemirror-themes": "^4.25.11", + "@uiw/react-codemirror": "^4.25.11", "d3-scale": "^4.0.2", "d3-time-format": "^4.1.0", "date-fns": "^4.4.0", "highlight.js": "^11.11.1", "history": "^4.9.0", - "immer": "^11.1.8", + "immer": "^11.1.16", "prop-types": "^15.8.1", - "react": "^19.2.7", + "react": "^19.2.8", "react-day-picker": "^10.0.1", - "react-dom": "^19.2.7", + "react-dom": "^19.2.8", "react-error-boundary": "^6.1.2", - "react-hook-form": "^7.80.0", - "react-is": "^19.2.7", + "react-hook-form": "^7.85.0", + "react-is": "^19.2.8", "react-router": "^8.3.0", "react-select": "^5.10.2", "react-transition-group": "^4.4.5", - "styled-components": "^6.4.3", + "styled-components": "^6.5.1", "tslib": "^2.8.1", "use-immer": "^0.11.0", "usehooks-ts": "^3.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b08d3fcfa361..591bdaa5b764 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,29 +19,29 @@ importers: specifier: ^6.10.0 version: 6.10.0 '@codemirror/view': - specifier: ^6.43.4 - version: 6.43.4 + specifier: ^6.43.8 + version: 6.43.8 '@floating-ui/react': - specifier: ^0.27.19 - version: 0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^0.27.20 + version: 0.27.20(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@fontsource/ubuntu': - specifier: ^5.2.8 - version: 5.2.8 + specifier: ^5.3.0 + version: 5.3.0 '@gravitational/design-system': specifier: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz - version: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(@fontsource/ubuntu@5.2.8)(@tanstack/react-query@5.101.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(supports-color@8.1.1)(tslib@2.8.1)(zod@4.4.3) + version: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(@fontsource/ubuntu@5.3.0)(@sinclair/typebox@0.34.49)(@tanstack/react-query@5.101.4(react@19.2.8))(ajv@8.20.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1)(tslib@2.8.1)(zod@4.4.3) '@grpc/grpc-js': specifier: 1.14.4 version: 1.14.4 '@hookform/resolvers': - specifier: ^5.4.0 - version: 5.4.0(react-hook-form@7.80.0(react@19.2.7))(zod@4.4.3) + specifier: ^5.7.1 + version: 5.7.1(@sinclair/typebox@0.34.49)(ajv@8.20.0)(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3) '@lezer/highlight': specifier: ^1.2.3 version: 1.2.3 '@nivo/bar': specifier: ^0.99.0 - version: 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@protobuf-ts/runtime': specifier: ^2.11.1 version: 2.11.1 @@ -49,14 +49,14 @@ importers: specifier: ^2.11.1 version: 2.11.1 '@tanstack/react-query': - specifier: ^5.101.2 - version: 5.101.2(react@19.2.7) + specifier: ^5.101.4 + version: 5.101.4(react@19.2.8) '@uiw/codemirror-themes': - specifier: ^4.25.10 - version: 4.25.10(@codemirror/view@6.43.4) + specifier: ^4.25.11 + version: 4.25.11(@codemirror/view@6.43.8) '@uiw/react-codemirror': - specifier: ^4.25.10 - version: 4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^4.25.11 + version: 4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.8)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) d3-scale: specifier: ^4.0.2 version: 4.0.2 @@ -73,50 +73,50 @@ importers: specifier: ^4.9.0 version: 4.10.1 immer: - specifier: ^11.1.8 - version: 11.1.8 + specifier: ^11.1.16 + version: 11.1.16 prop-types: specifier: ^15.8.1 version: 15.8.1 react: - specifier: ^19.2.7 - version: 19.2.7 + specifier: ^19.2.8 + version: 19.2.8 react-day-picker: specifier: ^10.0.1 - version: 10.0.1(@types/react@19.2.17)(react@19.2.7) + version: 10.0.1(@types/react@19.2.18)(react@19.2.8) react-dom: - specifier: ^19.2.7 - version: 19.2.7(react@19.2.7) + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) react-error-boundary: specifier: ^6.1.2 - version: 6.1.2(react@19.2.7) + version: 6.1.2(react@19.2.8) react-hook-form: - specifier: ^7.80.0 - version: 7.80.0(react@19.2.7) + specifier: ^7.85.0 + version: 7.85.0(react@19.2.8) react-is: - specifier: ^19.2.7 - version: 19.2.7 + specifier: ^19.2.8 + version: 19.2.8 react-router: specifier: ^8.3.0 - version: 8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react-select: specifier: ^5.10.2 - version: 5.10.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(supports-color@8.1.1) + version: 5.10.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 4.4.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) styled-components: - specifier: ^6.4.3 - version: 6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^6.5.1 + version: 6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) tslib: specifier: ^2.8.1 version: 2.8.1 use-immer: specifier: ^0.11.0 - version: 0.11.0(immer@11.1.8)(react@19.2.7) + version: 0.11.0(immer@11.1.16)(react@19.2.8) usehooks-ts: specifier: ^3.1.1 - version: 3.1.1(react@19.2.7) + version: 3.1.1(react@19.2.8) whatwg-fetch: specifier: ^3.6.20 version: 3.6.20 @@ -128,20 +128,20 @@ importers: specifier: workspace:* version: link:web/packages/build '@storybook/addon-vitest': - specifier: ^10.4.6 - version: 10.4.6(react@19.2.7)(storybook@10.4.6) + specifier: ^10.5.7 + version: 10.5.7(react@19.2.8)(storybook@10.5.7) '@storybook/react-vite': - specifier: ^10.4.6 - version: 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + specifier: ^10.5.7 + version: 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) '@testing-library/jest-dom': specifier: ^7.0.0 - version: 7.0.0(@testing-library/dom@10.1.0) + version: 7.0.0(@testing-library/dom@10.4.1) '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.1.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@testing-library/user-event': - specifier: ^14.6.1 - version: 14.6.1(@testing-library/dom@10.1.0) + specifier: ^14.6.3 + version: 14.6.3(@testing-library/dom@10.4.1) '@types/history': specifier: ^4.7.11 version: 4.7.11 @@ -152,14 +152,14 @@ importers: specifier: ^24.13.2 version: 24.13.3 '@types/react': - specifier: ^19.2.17 - version: 19.2.17 + specifier: ^19.2.18 + version: 19.2.18 '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.17) + specifier: ^19.2.4 + version: 19.2.4(@types/react@19.2.18) '@types/react-transition-group': specifier: ^4.4.12 - version: 4.4.12(@types/react@19.2.17) + version: 4.4.12(@types/react@19.2.18) '@types/wicg-file-system-access': specifier: ^2023.10.7 version: 2023.10.7 @@ -167,20 +167,20 @@ importers: specifier: ^10.0.1 version: 10.0.1 eslint: - specifier: ^10.6.0 - version: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + specifier: ^10.8.1 + version: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) eslint-plugin-jest-dom: - specifier: ^5.5.0 - version: 5.5.0(@testing-library/dom@10.1.0)(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)) + specifier: ^5.10.1 + version: 5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) eslint-plugin-storybook: - specifier: ^10.4.6 - version: 10.4.6(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2) + specifier: ^10.5.7 + version: 10.5.7(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2) eslint-plugin-testing-library: specifier: 7.16.2 - version: 7.16.2(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + version: 7.16.2(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) eslint-plugin-unused-imports: specifier: ^4.4.1 - version: 4.4.1(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)) + version: 4.4.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) jest: specifier: ^30.4.2 version: 30.4.2(@types/node@24.13.3)(babel-plugin-macros@3.1.0)(supports-color@8.1.1) @@ -188,26 +188,26 @@ importers: specifier: ^1.16.0 version: 1.16.0 msw: - specifier: 2.14.6 - version: 2.14.6(@types/node@24.13.3)(typescript@7.0.2) + specifier: 2.15.0 + version: 2.15.0(@types/node@24.13.3)(typescript@7.0.2) msw-storybook-addon: specifier: ^3.0.0 - version: 3.0.0(msw@2.14.6(@types/node@24.13.3)(typescript@7.0.2))(storybook@10.4.6) + version: 3.0.0(msw@2.15.0(@types/node@24.13.3)(typescript@7.0.2))(storybook@10.5.7) oxfmt: - specifier: ^0.56.0 - version: 0.56.0 + specifier: ^0.62.0 + version: 0.62.0 oxlint: - specifier: ^1.71.0 - version: 1.71.0 + specifier: ^1.77.0 + version: 1.77.0 playwright: - specifier: ^1.61.1 - version: 1.61.1 + specifier: ^1.62.1 + version: 1.62.1 react-select-event: specifier: ^5.5.1 version: 5.5.1 storybook: - specifier: ^10.4.6 - version: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + specifier: ^10.5.7 + version: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) svgo: specifier: ^4.0.2 version: 4.0.2 @@ -215,8 +215,8 @@ importers: specifier: ^7.0.2 version: 7.0.2 vite: - specifier: ^8.1.0 - version: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + specifier: ^8.2.1 + version: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) e/e2e: dependencies: @@ -229,14 +229,14 @@ importers: e2e: devDependencies: '@playwright/test': - specifier: ^1.61.1 - version: 1.61.1 + specifier: ^1.62.1 + version: 1.62.1 cbor-x: - specifier: ^1.6.4 - version: 1.6.4 + specifier: ^1.6.5 + version: 1.6.5 tsx: - specifier: ^4.22.4 - version: 4.22.4 + specifier: ^4.23.11 + version: 4.23.11 undici: specifier: ^8.10.0 version: 8.10.0 @@ -262,17 +262,17 @@ importers: specifier: https://github.com/gravitational/design-system/releases/download/@gravitational/swc-plugin-styled-components@0.1.0/swc-plugin-styled-components.tgz version: https://github.com/gravitational/design-system/releases/download/@gravitational/swc-plugin-styled-components@0.1.0/swc-plugin-styled-components.tgz '@swc/core': - specifier: ^1.15.43 - version: 1.15.43(@swc/helpers@0.5.23) + specifier: ^1.15.47 + version: 1.15.47(@swc/helpers@0.5.23) '@vitejs/plugin-react-swc': - specifier: ^4.3.1 - version: 4.3.1(@swc/helpers@0.5.23)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + specifier: ^4.3.3 + version: 4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) babel-plugin-styled-components: specifier: ^2.3.0 - version: 2.3.0(@babel/core@7.29.7(supports-color@8.1.1))(styled-components@6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(supports-color@8.1.1) + version: 2.3.0(@babel/core@7.29.7(supports-color@8.1.1))(styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(supports-color@8.1.1) globals: - specifier: ^17.7.0 - version: 17.7.0 + specifier: ^17.9.0 + version: 17.9.0 jest-environment-jsdom: specifier: ^30.4.1 version: 30.4.1(supports-color@8.1.1) @@ -281,13 +281,13 @@ importers: version: 3.3.4(@jest/globals@30.4.1(supports-color@8.1.1)) rollup-plugin-visualizer: specifier: ^7.0.1 - version: 7.0.1(rolldown@1.1.3) + version: 7.0.1(rolldown@1.2.3) vite-plugin-compression2: specifier: ^2.5.3 version: 2.5.3 vite-plugin-wasm: specifier: ^3.6.0 - version: 3.6.0(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) web/packages/design: dependencies: @@ -309,7 +309,7 @@ importers: version: 5.1.25 jest-styled-components: specifier: ^7.4.0 - version: 7.4.0(styled-components@6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + version: 7.4.0(styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) web/packages/shared: dependencies: @@ -333,8 +333,8 @@ importers: version: 7.8.5 devDependencies: '@types/semver': - specifier: ^7.7.1 - version: 7.7.1 + specifier: ^7.8.0 + version: 7.8.0 web/packages/teleport: dependencies: @@ -348,44 +348,44 @@ importers: specifier: ^1.9.1 version: 1.9.1 '@opentelemetry/context-zone': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/core': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-trace-otlp-http': - specifier: ^0.219.0 - version: 0.219.0(@opentelemetry/api@1.9.1) + specifier: ^0.221.0 + version: 0.221.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation': - specifier: ^0.219.0 - version: 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + specifier: ^0.221.0 + version: 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) '@opentelemetry/instrumentation-document-load': - specifier: ^0.64.0 - version: 0.64.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + specifier: ^0.66.0 + version: 0.66.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) '@opentelemetry/instrumentation-fetch': - specifier: ^0.219.0 - version: 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + specifier: ^0.221.0 + version: 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) '@opentelemetry/instrumentation-user-interaction': - specifier: ^0.63.0 - version: 0.63.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)(zone.js@0.16.1) + specifier: ^0.65.0 + version: 0.65.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)(zone.js@0.16.2) '@opentelemetry/instrumentation-xml-http-request': - specifier: ^0.219.0 - version: 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + specifier: ^0.221.0 + version: 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) '@opentelemetry/propagator-b3': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-web': - specifier: ^2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.1) + specifier: ^2.10.0 + version: 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': - specifier: ^1.41.1 - version: 1.41.1 + specifier: ^1.43.0 + version: 1.43.0 '@xterm/addon-fit': specifier: ^0.11.0 version: 0.11.0 @@ -403,7 +403,7 @@ importers: version: 6.0.0 create-react-class: specifier: ^15.6.3 - version: 15.7.0(react@19.2.7) + version: 15.7.0(react@19.2.8) events: specifier: 3.3.0 version: 3.3.0 @@ -492,13 +492,13 @@ importers: version: 6.8.9(supports-color@8.1.1) electron-vite: specifier: ^5.0.0 - version: 5.0.0(@swc/core@1.15.43(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) events: specifier: 3.3.0 version: 3.3.0 react-dnd: specifier: ^16.0.1 - version: 16.0.1(@types/node@24.13.3)(@types/react@19.2.17)(react@19.2.7) + version: 16.0.1(@types/node@24.13.3)(@types/react@19.2.18)(react@19.2.8) react-dnd-html5-backend: specifier: ^16.0.1 version: 16.0.1 @@ -535,8 +535,8 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.29.7': @@ -626,8 +626,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1143,12 +1143,12 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -1215,20 +1215,20 @@ packages: '@codemirror/language@6.12.4': resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} - '@codemirror/lint@6.9.6': - resolution: {integrity: sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==} + '@codemirror/lint@6.9.7': + resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} - '@codemirror/search@6.7.0': - resolution: {integrity: sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==} + '@codemirror/search@6.7.1': + resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==} - '@codemirror/state@6.7.0': - resolution: {integrity: sha512-Zbl9NyscLMZkfXPQnNAIIAFftidrA1UbcJEIMp24C0Bukc2I5T8wJS0wsXYsnDOqCFJUeJ1BITGNs5CqPDSmSg==} + '@codemirror/state@6.7.1': + resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} '@codemirror/theme-one-dark@6.1.3': resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} - '@codemirror/view@6.43.4': - resolution: {integrity: sha512-YImu23iyKfncJzT7sRy+rEqEhSc8RhOHqDxwy4WzXRKJwYm6iwf/9OJk5ctCAdZ6yi2ZqaGEvmf55fSVqMDrgg==} + '@codemirror/view@6.43.8': + resolution: {integrity: sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==} '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} @@ -1544,8 +1544,8 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} @@ -1553,8 +1553,8 @@ packages: '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} @@ -2077,8 +2077,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -2091,8 +2091,8 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.6.0': - resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': @@ -2107,29 +2107,29 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.27.19': - resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==} + '@floating-ui/react@0.27.20': + resolution: {integrity: sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==} peerDependencies: react: '>=17.0.0' react-dom: '>=17.0.0' - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@fontsource/ubuntu@5.2.8': - resolution: {integrity: sha512-esSJCpgigwWOm4Ug9f7AMozUyBq5Iobea5C9QYsl2OlCb62wZABBUbXahHLdFv5Ru4lJCD9tUX6xEAeLwUTNBg==} + '@fontsource/ubuntu@5.3.0': + resolution: {integrity: sha512-LsjJXgE430QLs8LKfn1RIFbbdGAiZBU1gao8ASa4Z2kGNGh7lMb7Rzx09v3bLLuuK+3XA/JSLgPWTAbLD6r1fg==} '@gravitational/design-system@https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz': resolution: {integrity: sha512-LPvUKuu8sVRnbi6BBicyvLIF1uC+Kd2H6962hKE9Qhr+DYG2tzRrKuNpXCKoPORn7QEPolqs/Voqh8Ve/0WUkg==, tarball: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz} @@ -2161,11 +2161,83 @@ packages: engines: {node: '>=6'} hasBin: true - '@hookform/resolvers@5.4.0': - resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} + '@hookform/resolvers@5.7.1': + resolution: {integrity: sha512-8wS/P4UDr5sQDe4nFaV51TVyfDPrWgNIXweqG0Bs9Z5LSuzKLb+RQNPvkN2oHM5SRrJyWrVH/F+LOUcFjUyvwQ==} peerDependencies: + '@sinclair/typebox': '>=0.25.24' + '@standard-schema/spec': ^1.0.0 + '@typeschema/main': '>=0.13.7' + '@vinejs/vine': ^2.0.0 || ^3.0.0 || ^4.0.0 + ajv: ^8.12.0 + ajv-errors: ^3.0.0 + ajv-formats: ^2.1.1 + arktype: ^2.0.0 + ata-validator: ^1.2.0 + class-transformer: '>=0.4.0' + class-validator: '>=0.12.0' + computed-types: ^1.0.0 + effect: ^3.10.3 + fluentvalidation-ts: ^3.0.0 + fp-ts: ^2.7.0 + io-ts: ^2.0.0 + joi: ^17.0.0 + nope-validator: '>=0.12.0' react-hook-form: ^7.55.0 - zod: '*' + superstruct: '>=0.12.0' + typanion: ^3.3.2 + valibot: '>=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc' + vest: '>=3.0.0' + yup: ^1.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + '@standard-schema/spec': + optional: true + '@typeschema/main': + optional: true + '@vinejs/vine': + optional: true + ajv: + optional: true + ajv-errors: + optional: true + ajv-formats: + optional: true + arktype: + optional: true + ata-validator: + optional: true + class-transformer: + optional: true + class-validator: + optional: true + computed-types: + optional: true + effect: + optional: true + fluentvalidation-ts: + optional: true + fp-ts: + optional: true + io-ts: + optional: true + joi: + optional: true + nope-validator: + optional: true + superstruct: + optional: true + typanion: + optional: true + valibot: + optional: true + vest: + optional: true + yup: + optional: true + zod: + optional: true '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -2187,35 +2259,35 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@inquirer/ansi@2.0.6': - resolution: {integrity: sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/confirm@6.1.0': - resolution: {integrity: sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@11.2.0': - resolution: {integrity: sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@2.0.6': - resolution: {integrity: sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/type@4.0.6': - resolution: {integrity: sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2398,11 +2470,12 @@ packages: resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + '@napi-rs/wasm-runtime@1.2.2': + resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 '@nivo/annotations@0.99.0': resolution: {integrity: sha512-jCuuXPbvpaqaz4xF7k5dv0OT2ubn5Nt0gWryuTe/8oVsC/9bzSuK8bM9vBty60m9tfO+X8vUYliuaCDwGksC2g==} @@ -2487,118 +2560,124 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@opentelemetry/api-logs@0.219.0': - resolution: {integrity: sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==} + '@opentelemetry/api-logs@0.221.0': + resolution: {integrity: sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-zone-peer-dep@2.8.0': - resolution: {integrity: sha512-SnaZDuBMYJlsDsgVeZ2IgFseAEXHBkYEn3nCon9YR3L2zdP/UpxZVWwbCWu2HOELmV1jy3RWmk2FusFpSrfh5Q==} + '@opentelemetry/context-zone-peer-dep@2.10.0': + resolution: {integrity: sha512-dyt6PJxfocKhQJjoMEIdPbcKnwJKV9waF6vXL9g5sSTUsp4GZXsx/faRR95vl41NANzvUYXVnhulhp57xW+G+w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' zone.js: ^0.10.2 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 - '@opentelemetry/context-zone@2.8.0': - resolution: {integrity: sha512-ddfwDmm6zVVf3J7+ik1SQRRv6Xrn/YpWxuoXvk4vhJK8LKUt8oqFMuofiDY5RbEleOahWfQnsI69uBEWmm00Nw==} + '@opentelemetry/context-zone@2.10.0': + resolution: {integrity: sha512-Ats9++P6RdfCqWb5jGGwTFVgZJTkIONFFNyRjJTAJZnSwkg3/APWNc7gCGPfUZuCdQA0+nhGTrRk3bSHcoSUuA==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/core@2.8.0': - resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} + '@opentelemetry/core@2.10.0': + resolution: {integrity: sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-trace-otlp-http@0.219.0': - resolution: {integrity: sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==} + '@opentelemetry/exporter-trace-otlp-http@0.221.0': + resolution: {integrity: sha512-AySXiKoC+meiWm6zdVj5T2LnPDZuatveBby1cMOeQteIWsYXAUxs8Sru13G2pVSPrUXz6vF+og7QVBX6GdC/oQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-document-load@0.64.0': - resolution: {integrity: sha512-I0fTRpfgY7Qr8qaTJBPgDi+Eo4YjOqHSm3OB9U5JaJmCfm0ryDGFMKef8OBS+DeDqYZvaHWyq/zg7YJqnCWaoA==} + '@opentelemetry/instrumentation-document-load@0.66.0': + resolution: {integrity: sha512-e4ymSy64SFKd1TYnbFOpdInuw0x8mRzONdHnfsaTyFojiizDjwNv9klYUrIxmVQ/J2RLFvnKYHwgrgc/u2yFZQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fetch@0.219.0': - resolution: {integrity: sha512-nhjrhJ/tlE+1I1950dJkcl0xCGf7IrZGwWHS0X5J4gZtqP4YD+qp/gVfXkzZZBjFDx+39IaDQLS296E+Pcw5Tw==} + '@opentelemetry/instrumentation-fetch@0.221.0': + resolution: {integrity: sha512-PPfEC0dIvhieidxtajOsbgFZ2RBUhCsk1ztHE6I5uinNc4JocL+UNRQY6iCBsckI0rjTwYvsYRYOJUjGyLhIug==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-user-interaction@0.63.0': - resolution: {integrity: sha512-lSFNi+SHG0DwUYnbzKFxocvd+p8PZXyUAXj7tZDxi5/iu9j5KATKD096GbZt2DCKpq5S3tjl3ApKmj3Z+h4rhg==} + '@opentelemetry/instrumentation-user-interaction@0.65.0': + resolution: {integrity: sha512-FlbDfgqALbJbPwU7sEESgrc7TLhExwn88vf0f0W5JcHUbVdF1SZaES4kJlDT6Kx8Q6r9OMiG588jDZ0WSmBYCA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 zone.js: ^0.11.4 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 - '@opentelemetry/instrumentation-xml-http-request@0.219.0': - resolution: {integrity: sha512-R1qn0xVh4OPFta1A2hMgYiOpxUWdZlYuol4ZqQLYonklgo+gQTCtQyAVco/5FqM34h2mYd6KRQfo3kjiQdUe4A==} + '@opentelemetry/instrumentation-xml-http-request@0.221.0': + resolution: {integrity: sha512-IDUcARVE3B8N4TqNseM6Gh9kH5IdLsI6tQrBs4MNNoZHTzs8j6Byv7oCtcP2A46SdT75LBdEPtNquG3/g93cVQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.219.0': - resolution: {integrity: sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==} + '@opentelemetry/instrumentation@0.221.0': + resolution: {integrity: sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.219.0': - resolution: {integrity: sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==} + '@opentelemetry/otlp-exporter-base@0.221.0': + resolution: {integrity: sha512-UFPIq80OH3Ns/oPFHRj14d4DTOxUo+MUFU8hUiCq5jTqFhdeJnfVSANHT+xp92409cA+oxzvlZCe6NM1wvCuBA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.219.0': - resolution: {integrity: sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==} + '@opentelemetry/otlp-transformer@0.221.0': + resolution: {integrity: sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.8.0': - resolution: {integrity: sha512-SazlvuSKi5533rPHTW2TwBwdMakhjZST4SYs0YauuvfGDkT13KbG1gJS75hV0uWVeevhtVP9sAIlaZLTHdSbMg==} + '@opentelemetry/propagator-b3@2.10.0': + resolution: {integrity: sha512-GnA5B24H+1w8BO21J0q+IWNB0z1v+AGbcquTdIt/dufibhnhgxaA8YKvz0I3akRZhB1jHT+/tlzK+qlAjEDybQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/resources@2.8.0': - resolution: {integrity: sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==} + '@opentelemetry/resources@2.10.0': + resolution: {integrity: sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.219.0': - resolution: {integrity: sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==} + '@opentelemetry/sdk-logs@0.221.0': + resolution: {integrity: sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.8.0': - resolution: {integrity: sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==} + '@opentelemetry/sdk-metrics@2.10.0': + resolution: {integrity: sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.8.0': - resolution: {integrity: sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==} + '@opentelemetry/sdk-trace-base@2.10.0': + resolution: {integrity: sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-web@2.8.0': - resolution: {integrity: sha512-P3ZM8BGJ5mwjtyfAxRyxsCyWHvaj+xahdhLoS3YiPsEyTHcWTVzx2691C8SrGkpvro3tNFCsWuNNrvM+spKODg==} + '@opentelemetry/sdk-trace-web@2.10.0': + resolution: {integrity: sha512-6WqXdWSlBH0GNjDhv1gg6SLVtMtIBVhLaWqd1hiKSXS8meXXP13sVPQoYFHz1uVo0feUTUCdP9vMS54KflisAw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.41.1': - resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + '@opentelemetry/sdk-trace@2.10.0': + resolution: {integrity: sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} engines: {node: '>=14'} '@oxc-parser/binding-android-arm-eabi@0.127.0': @@ -2731,357 +2810,352 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@oxc-project/types@0.137.0': - resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': - resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] os: [android] - '@oxc-resolver/binding-android-arm64@11.19.1': - resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==} + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} cpu: [arm64] os: [android] - '@oxc-resolver/binding-darwin-arm64@11.19.1': - resolution: {integrity: sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==} + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} cpu: [arm64] os: [darwin] - '@oxc-resolver/binding-darwin-x64@11.19.1': - resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==} + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} cpu: [x64] os: [darwin] - '@oxc-resolver/binding-freebsd-x64@11.19.1': - resolution: {integrity: sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==} + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} cpu: [x64] os: [freebsd] - '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': - resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': - resolution: {integrity: sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==} + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': - resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-arm64-musl@11.19.1': - resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': - resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': - resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': - resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': - resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-gnu@11.19.1': - resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-musl@11.19.1': - resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} cpu: [x64] os: [linux] libc: [musl] - '@oxc-resolver/binding-openharmony-arm64@11.19.1': - resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} cpu: [arm64] os: [openharmony] - '@oxc-resolver/binding-wasm32-wasi@11.19.1': - resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==} + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': - resolution: {integrity: sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==} + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} cpu: [arm64] os: [win32] - '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': - resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==} - cpu: [ia32] - os: [win32] - - '@oxc-resolver/binding-win32-x64-msvc@11.19.1': - resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==} + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.56.0': - resolution: {integrity: sha512-CSCxi7ovYojgfdPOdUb9T508HKeAdDIKeRGg7x8IZwVJrWz9gVgX7MbUnFqtQAE4QvoNo07mj2JlwnOzJw4qqA==} + '@oxfmt/binding-android-arm-eabi@0.62.0': + resolution: {integrity: sha512-pdsv0C4gPjJ8H1+sd8u0BDx+yLACTL+rgeMIOL1ln4ihSnhw8CWXtYWgvcSkyTfgGBIzFKab+d8rx9Xl4en/Kw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.56.0': - resolution: {integrity: sha512-HYJFnd+PkDwf6S9ZPGzXXtjNqvRWFnnhdbWaouh4mi/SxU8wmDuzlMn3xo/wDTGnr4Q1VA7ZzOaE/D4biW0W6A==} + '@oxfmt/binding-android-arm64@0.62.0': + resolution: {integrity: sha512-WC3YQ7uS/KtDrjmqwBviwFKe9qeoi+eXx8aX1z/ffG23Md75myjrJaQqTuJvdOLPoa4EYTjDWH0dHXfwulCVog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.56.0': - resolution: {integrity: sha512-sftR/bEOr+t1gs+evwsHi/Xbq2FAPA2uU3VMr8n6ZU9PoK/IMSfnfu7+OEe/uy1+knhrFl4Wvy7Vkm3uo9mJ7g==} + '@oxfmt/binding-darwin-arm64@0.62.0': + resolution: {integrity: sha512-GM8Yf3LjjaR1I8PD0SfeoIlwhsh9GvSF+cQ8sf624Yxnjsyumn95aFzYfKJVefblfDIiOAnZ7QVm2sa21Er/0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.56.0': - resolution: {integrity: sha512-z66SdjLqa3MUPKvTp3Mbb5nSjKSbnYxJGeB+Wx987s8T5hPcIRiBMfnJ6zcPgYtQn3x5xjvdzNVkXrSeYH6ZFg==} + '@oxfmt/binding-darwin-x64@0.62.0': + resolution: {integrity: sha512-d5THp7F8bCxLqNogEXDORRsQD6dosf3EyFtnXfBer6v+8tGdcWIjoDX9WaXrrF/26zOmL8qHpPTKCEvpBDmZkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.56.0': - resolution: {integrity: sha512-t2tkrV1vtZyaItSQ71dTi2ZVKZEI39b/LqLT12V5KMfIeXK6N32TUC1jhOXKVQmhECq9j2ZXMQV3JeT1kh9Vmg==} + '@oxfmt/binding-freebsd-x64@0.62.0': + resolution: {integrity: sha512-1DnrtXGZooOZ0fHgAXZUaDQzBVh1CM2MNW4oBXyQ2aWKvCHjyljvT9fgBkOM0fEOb96X5eqtcfJ0YUVt9jj66g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': - resolution: {integrity: sha512-+gCy+Tp3RHeXQ9y/QrS76lXIpZkbziTyp6hIgjB2MssCwfMph3vG/GEfkhO34Rai1vhYIaUkvv8UT1BcDorJPw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': + resolution: {integrity: sha512-4pQDHOYRH+Huqe0StIaWyvk2CVl/aTaqSrbZpA3/pLS2xH24ME7lBgYprhQF2fRkHBzhGGGKliwxFsDdHwx59g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.56.0': - resolution: {integrity: sha512-0kKkVvQ2I+FJ2sxQyUu1zJ0yWP5kcWse/yVFnGQSFCXMwSSkfEaUGu0dW774O7nyy3jrcBGap7OSc8dZmU/CdA==} + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': + resolution: {integrity: sha512-X0jAaZJFMCVKhB6YyWVTQ/wN2DLsBcZKSMqTS76bF6riT+XZdtg2FPEdjDvdVbunO9cG+tWiVaEs4Zs38lxYog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.56.0': - resolution: {integrity: sha512-npkA2siMbyWRh+wEhi1aTAx4RirukGcGNt8V4Ch86pG+xU9aurqS1MZOnKYMu03ISwat3rB6zkQx51SsB9obNw==} + '@oxfmt/binding-linux-arm64-gnu@0.62.0': + resolution: {integrity: sha512-682Z8T5s8T5ATArYtsejKvbIfd8LEAXyyDkKkoZVq8HND7Vx8TYLlrDjDSeYfodMeVwHOgkj13lJYR8cj6vUSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.56.0': - resolution: {integrity: sha512-UekqOjGkV4/MkqreCV9SPIB2jlR3/HbXrmhV1rVXJZ9wfDXMyCMriLtq3tHqLY4PkbVWNtfcm1kMojJ26KLSJw==} + '@oxfmt/binding-linux-arm64-musl@0.62.0': + resolution: {integrity: sha512-lk25fAl7KWaLWVJcW0CHEXB7QlQZtx5eDkjpaGMK0hzXTjUe0Wmlu8IKuFHoviSOcEJedRTs4VE/506VqGxGew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.56.0': - resolution: {integrity: sha512-XSzveSpeZMD5XJpew5lRFVtNnT04xd3rJxENXmk7wkZzN9oWzv2aFJyoNDhOtoz69BYaS/fg4SYl+CfEZRpB0Q==} + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': + resolution: {integrity: sha512-SFyNqHQLwySceWNLhiSldx7wPXRAzP0L0WcW9GegP3uWrpZGJiZlQO85NbHAFPEfxR9PhZ9qSnZryEh7+v+4Gw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.56.0': - resolution: {integrity: sha512-EkQ0nJa7k7HDDIVuPF7WY+k4k+bzdclLYtyIXNt7/OqVghfNiMym6YGppFBgx1XRIHW6QylxBz5OogumPjPJbQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': + resolution: {integrity: sha512-KYj55C1ywJfHo6+aKDuEmUtVEdJALsC5GwayDGsI6FGz2GxFqNr/mA8nxVsNbJzm7sE5MRqTQ9ziImSzhYXysA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.56.0': - resolution: {integrity: sha512-dyjAGW8jKRge0ik6U/dgvQG0nVpA3iBlRskQTz5qJLvQWIrySxX5jpqzPetLBNIIZ231KA82fDdi1nLTk8ENCw==} + '@oxfmt/binding-linux-riscv64-musl@0.62.0': + resolution: {integrity: sha512-BhZDNo5GOU5nC378RhD0/XpvaEBHsH3HLgJp8YZX3A0InC7oivzA63HsRmiXFLtLSHAstEVrDf6fbC7Rs8Jh/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.56.0': - resolution: {integrity: sha512-60ZGH3LtfqlW8X6vcLdSFY4lvCQYINurttYBKaALnHCDVAUCYJ1LsUgS6p1XOzVlzEDx3yNUZvDF1Lvt59zoZw==} + '@oxfmt/binding-linux-s390x-gnu@0.62.0': + resolution: {integrity: sha512-UyAFmyHkgSgUJ/wOM4p3U8AC2yAFvRH5PNBs7TnK0fObTT/XSWcdr/lAzPSWaekHaZFaMeFZyk9n93Joq3J93A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.56.0': - resolution: {integrity: sha512-u1suj1tgJHK4ZqB7buCtdbNef2n8+d0lXTPJwLHNmtyK6p+DTpsaoDvmqhQrA56fgKYv4LuRxNtL8YooebKOew==} + '@oxfmt/binding-linux-x64-gnu@0.62.0': + resolution: {integrity: sha512-1iYMP0leytWazFubD/WnINJuIrzRPuoL1aWEJdlGezEzDbTxcd29R4r8IUzP2oWeKst5V02uMJgR2NILlPlG6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.56.0': - resolution: {integrity: sha512-aYGLvlQHt80y+qKEtfJY/Nm27G0125Lv+qyh9SJ4Cjc6lXnXjD+ndfhqQnbV24POpMi7rNRi0jvx/0d70FRpCQ==} + '@oxfmt/binding-linux-x64-musl@0.62.0': + resolution: {integrity: sha512-4rA/URtJSTVNVAQz6Q8wf7SaRvOXVy+TizriT9hs/Y1XhLR/R+92uWKRQG8yFWRAIEBbFHJ6WevQcl/G9SXEfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.56.0': - resolution: {integrity: sha512-H/re/gO+7ysVc+kywHNuzY3C33EN9sQcZhg0kp1ZwOZl7y998ZE5mhnBiuGR/nYI0pqLL5xQfrHVUOJ/cIJsCA==} + '@oxfmt/binding-openharmony-arm64@0.62.0': + resolution: {integrity: sha512-mSZuFHU2ar1KLUjXpI2QBQcJ1VsOB3mOCgQXuXCpKs19dgh4u+OaovNfrWDfiJb+ihJ2+f7YFcaO9bS2dlTCXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.56.0': - resolution: {integrity: sha512-6qLNXfXmtAs8jXDvYMkxk6Wec5SUJoew+ZX1uOZmqaR7ks0EJFbAohuOCELDyJMWyVlxotVG8Xf8m74Bfq0O2w==} + '@oxfmt/binding-win32-arm64-msvc@0.62.0': + resolution: {integrity: sha512-OfwuhkcjDlqC4EgDojtiV9mzpLqeB9KqTOWPOjLEYBVdDCVSxqW3qzp/xcIxsbtI0UgGCnKvAqYKyY25kf5JZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.56.0': - resolution: {integrity: sha512-UXEXuKphAe15bsob4AswNMArCw38XSmUIs3wk1s6e6MX9OWGW/IRWU95s1hZDiVg09STy1jHgyN2qkqbu1FT0w==} + '@oxfmt/binding-win32-ia32-msvc@0.62.0': + resolution: {integrity: sha512-P9uDDNFRzghO3X8QAzhkjKhK7JvtABsVn8UYtFX7uor12IAnwNt8nNIctvfWj1JkQU/kE+fmLRPiw7XlrIHsZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.56.0': - resolution: {integrity: sha512-HPyNDjky+NIOuaMvHZflR+kst3YWdUOH2JUQYkf99grqZ5mEBTQM6h9iGy501Z8Xt5xMScrwHOuVCOlqDrktRw==} + '@oxfmt/binding-win32-x64-msvc@0.62.0': + resolution: {integrity: sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.71.0': - resolution: {integrity: sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==} + '@oxlint/binding-android-arm-eabi@1.77.0': + resolution: {integrity: sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.71.0': - resolution: {integrity: sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==} + '@oxlint/binding-android-arm64@1.77.0': + resolution: {integrity: sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.71.0': - resolution: {integrity: sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==} + '@oxlint/binding-darwin-arm64@1.77.0': + resolution: {integrity: sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.71.0': - resolution: {integrity: sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==} + '@oxlint/binding-darwin-x64@1.77.0': + resolution: {integrity: sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.71.0': - resolution: {integrity: sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==} + '@oxlint/binding-freebsd-x64@1.77.0': + resolution: {integrity: sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': - resolution: {integrity: sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==} + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + resolution: {integrity: sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.71.0': - resolution: {integrity: sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==} + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + resolution: {integrity: sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.71.0': - resolution: {integrity: sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==} + '@oxlint/binding-linux-arm64-gnu@1.77.0': + resolution: {integrity: sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.71.0': - resolution: {integrity: sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==} + '@oxlint/binding-linux-arm64-musl@1.77.0': + resolution: {integrity: sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.71.0': - resolution: {integrity: sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==} + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + resolution: {integrity: sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.71.0': - resolution: {integrity: sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==} + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + resolution: {integrity: sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.71.0': - resolution: {integrity: sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==} + '@oxlint/binding-linux-riscv64-musl@1.77.0': + resolution: {integrity: sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.71.0': - resolution: {integrity: sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==} + '@oxlint/binding-linux-s390x-gnu@1.77.0': + resolution: {integrity: sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.71.0': - resolution: {integrity: sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==} + '@oxlint/binding-linux-x64-gnu@1.77.0': + resolution: {integrity: sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.71.0': - resolution: {integrity: sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==} + '@oxlint/binding-linux-x64-musl@1.77.0': + resolution: {integrity: sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.71.0': - resolution: {integrity: sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==} + '@oxlint/binding-openharmony-arm64@1.77.0': + resolution: {integrity: sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.71.0': - resolution: {integrity: sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==} + '@oxlint/binding-win32-arm64-msvc@1.77.0': + resolution: {integrity: sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.71.0': - resolution: {integrity: sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==} + '@oxlint/binding-win32-ia32-msvc@1.77.0': + resolution: {integrity: sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.71.0': - resolution: {integrity: sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==} + '@oxlint/binding-win32-x64-msvc@1.77.0': + resolution: {integrity: sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3125,9 +3199,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.61.1': - resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} - engines: {node: '>=18'} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} hasBin: true '@protobuf-ts/grpc-transport@2.11.1': @@ -3204,97 +3278,92 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@rolldown/binding-android-arm64@1.1.3': - resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} + '@rolldown/binding-android-arm64@1.2.3': + resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.3': - resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} + '@rolldown/binding-darwin-arm64@1.2.3': + resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.3': - resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} + '@rolldown/binding-darwin-x64@1.2.3': + resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.3': - resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} + '@rolldown/binding-freebsd-x64@1.2.3': + resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.3': - resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.3': - resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} + '@rolldown/binding-linux-arm64-gnu@1.2.3': + resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.3': - resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} + '@rolldown/binding-linux-arm64-musl@1.2.3': + resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.3': - resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.3': - resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} + '@rolldown/binding-linux-s390x-gnu@1.2.3': + resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.3': - resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} + '@rolldown/binding-linux-x64-gnu@1.2.3': + resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.3': - resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} + '@rolldown/binding-linux-x64-musl@1.2.3': + resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.3': - resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} + '@rolldown/binding-openharmony-arm64@1.2.3': + resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.3': - resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.1.3': - resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} + '@rolldown/binding-win32-arm64-msvc@1.2.3': + resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.3': - resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} + '@rolldown/binding-win32-x64-msvc@1.2.3': + resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3337,13 +3406,13 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@storybook/addon-vitest@10.4.6': - resolution: {integrity: sha512-VvskHge0GZy86LG6kcY5Ww34z8rDV8JBxqSdUpcJVsWfIvyX6MfAbqI76LlereSyBIJGZJZsqaLwRXsQoVY+0Q==} + '@storybook/addon-vitest@10.5.7': + resolution: {integrity: sha512-7NK7Kzazc2vb2h8nGlH15QlUn5J6/LV0xF70VziAK0bxu3r1JupLCoBvckX39vHzFXiAZljXZtt1Wq/OidBxow==} peerDependencies: '@vitest/browser': ^3.0.0 || ^4.0.0 '@vitest/browser-playwright': ^4.0.0 '@vitest/runner': ^3.0.0 || ^4.0.0 - storybook: ^10.4.6 + storybook: ^10.5.7 vitest: ^3.0.0 || ^4.0.0 peerDependenciesMeta: '@vitest/browser': @@ -3355,18 +3424,18 @@ packages: vitest: optional: true - '@storybook/builder-vite@10.4.6': - resolution: {integrity: sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg==} + '@storybook/builder-vite@10.5.7': + resolution: {integrity: sha512-fShF/aQaITqcJuMCLr42BGNUAbhDi4IboqvlbZqXAwgrrTslnZEUnY8GcEcvpZmjl11VwlmazhMJdH50fIgBPg==} peerDependencies: - storybook: ^10.4.6 + storybook: ^10.5.7 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@storybook/csf-plugin@10.4.6': - resolution: {integrity: sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA==} + '@storybook/csf-plugin@10.5.7': + resolution: {integrity: sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag==} peerDependencies: esbuild: '*' rollup: '*' - storybook: ^10.4.6 + storybook: ^10.5.7 vite: '*' webpack: '*' peerDependenciesMeta: @@ -3387,36 +3456,40 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/react-dom-shim@10.4.6': - resolution: {integrity: sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg==} + '@storybook/react-dom-shim@10.5.7': + resolution: {integrity: sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.4.6 + storybook: ^10.5.7 peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@storybook/react-vite@10.4.6': - resolution: {integrity: sha512-0arEQtybqGYXHbXpTot+Wv9YtG+V5Vp43QayXavPKQ20M8mpEzhyCPKd0EhqMGSC1Z1UEt0hm365WUBhI9LfKA==} + '@storybook/react-vite@10.5.7': + resolution: {integrity: sha512-eEo3eVa2pvqrzQukKxAzx7YvswDAA1s6k/y+tdMxmRvWyHX6QEOsb9Tda6wcVaa7c8BeJM7Ggq+289cRMTH6Iw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.4.6 + storybook: ^10.5.7 + typescript: '>= 4.9.x' vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true - '@storybook/react@10.4.6': - resolution: {integrity: sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w==} + '@storybook/react@10.5.7': + resolution: {integrity: sha512-uFvty2MMdFXzW5PcQe1JqDAZkz6cQq7q/9G/cbGVnBEvP6zsOVeL+bmrQ0/WBlFQN0Ko9+ZoCTvaQ9s65zBa5g==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.4.6 + storybook: ^10.5.7 typescript: '>= 4.9.x' peerDependenciesMeta: '@types/react': @@ -3465,86 +3538,86 @@ packages: '@styled-system/variant@5.1.5': resolution: {integrity: sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==} - '@swc/core-darwin-arm64@1.15.43': - resolution: {integrity: sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==} + '@swc/core-darwin-arm64@1.15.47': + resolution: {integrity: sha512-GsoMtan3ojGGMGFbl31mmRu5ctZ56re8grGE8mO/OHJ8O+JRkzod02fe7X6ZQ8JvamA3imkEkx/h3u+vsOgPgA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.43': - resolution: {integrity: sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==} + '@swc/core-darwin-x64@1.15.47': + resolution: {integrity: sha512-leTi7Rx3KF4zcC637iqWgk9SoV8VXAD8ppQYXsep63px5A/UftOcxLN1pmr8Z1si/YvX90ompP/rHgpYkgwXWg==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.43': - resolution: {integrity: sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==} + '@swc/core-linux-arm-gnueabihf@1.15.47': + resolution: {integrity: sha512-hBqHuoWKKIsKmDBn9qVeWqj5GWZhtlcczVaqQmNRXsDfq+voR5CxKRfamA367QjJXtceYuliLFfEL8QsskRM2g==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.43': - resolution: {integrity: sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==} + '@swc/core-linux-arm64-gnu@1.15.47': + resolution: {integrity: sha512-TBxvRz+B4K205TWHHZxWVxkC2RFNP/Mz3PNcECBos5PsKwxjg3QSJzdoebr0VCf0Bfh8HOPldKxAP/8XkFe9gA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [glibc] - '@swc/core-linux-arm64-musl@1.15.43': - resolution: {integrity: sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==} + '@swc/core-linux-arm64-musl@1.15.47': + resolution: {integrity: sha512-3Yu3Uq/VgytqsPjTMbkPU1ExADytbdWbruJYhA584E9jrpE2Ki+R6VVPoZCeAVk1Cb7QxcRTgblw6bSa6a/R+w==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [musl] - '@swc/core-linux-ppc64-gnu@1.15.43': - resolution: {integrity: sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==} + '@swc/core-linux-ppc64-gnu@1.15.47': + resolution: {integrity: sha512-wfdMi5IaOaNtmh2/6geRoxIdNfqylUZFdtzTKS655y1axWfIWyx7As74vv0wVdjeCIZ3WmCI9odDd4rUttXOSQ==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] libc: [glibc] - '@swc/core-linux-s390x-gnu@1.15.43': - resolution: {integrity: sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==} + '@swc/core-linux-s390x-gnu@1.15.47': + resolution: {integrity: sha512-3hHYBY0yx8Ez7GMRrkhXHQzMdR5IZA6Wq5Ee4svlgwvSECLpnAJ9+0AimEGUFDvuLwE7nV/2+PYe8+Nm4rvNcQ==} engines: {node: '>=10'} cpu: [s390x] os: [linux] libc: [glibc] - '@swc/core-linux-x64-gnu@1.15.43': - resolution: {integrity: sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==} + '@swc/core-linux-x64-gnu@1.15.47': + resolution: {integrity: sha512-TjfhjgP/jGCfFHYC3JQPhJA1HwErbIJ9JfREDc1KNkvY6P0LodCgKVIlQ5deeTbkG7ih3bF5PHJLuLpaZjdRyQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [glibc] - '@swc/core-linux-x64-musl@1.15.43': - resolution: {integrity: sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==} + '@swc/core-linux-x64-musl@1.15.47': + resolution: {integrity: sha512-CQpS8Ge/avfjZd0UEwG/sds83Uu32deQXcV1Jo3jD0mmvQQqtYAjpsDZXugmheeAwmt+YIuoVtVHro8LMYHqsQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [musl] - '@swc/core-win32-arm64-msvc@1.15.43': - resolution: {integrity: sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==} + '@swc/core-win32-arm64-msvc@1.15.47': + resolution: {integrity: sha512-0W8IKHsUTYiT7G2RqtOoVWk+89yzZikIiDUb/sCK6BmQDBhN91hQSfyUtW12jhEWLzYgcfmisfsZrmZE+84U1A==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.43': - resolution: {integrity: sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==} + '@swc/core-win32-ia32-msvc@1.15.47': + resolution: {integrity: sha512-ZIp49d2Z4/ka2jO9otOg4hDvTdPmp86kVOgS2M5FCPI7eKKZ1W0boxWn+8XeZrfERtFGW0AlMRm4JhlJa7l3NA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.43': - resolution: {integrity: sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==} + '@swc/core-win32-x64-msvc@1.15.47': + resolution: {integrity: sha512-2h8Iek95vnixkBRCo+H8p09+Q5ll2NgSMFrWTy0iKt7+/t+8/T5mBpiT6c0ZxSS7wcWjwZ9sGZkK70tTSYHdDw==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.43': - resolution: {integrity: sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==} + '@swc/core@1.15.47': + resolution: {integrity: sha512-FbsO5JcfOjfH38W/rohBRBweJeERsAuIP4f377lmkmxTcq9exjtx4SkRuZY5CdfhR2CBVwDIJegBpJDffwNsOg==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -3558,18 +3631,18 @@ packages: '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - '@swc/types@0.1.27': - resolution: {integrity: sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==} + '@swc/types@0.1.28': + resolution: {integrity: sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==} '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} - '@tanstack/query-core@5.101.2': - resolution: {integrity: sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==} + '@tanstack/query-core@5.101.4': + resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} - '@tanstack/react-query@5.101.2': - resolution: {integrity: sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==} + '@tanstack/react-query@5.101.4': + resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} peerDependencies: react: ^18 || ^19 @@ -3584,8 +3657,8 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} - '@testing-library/dom@10.1.0': - resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} '@testing-library/jest-dom@6.9.1': @@ -3613,8 +3686,8 @@ packages: '@types/react-dom': optional: true - '@testing-library/user-event@14.6.1': - resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + '@testing-library/user-event@14.6.3': + resolution: {integrity: sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' @@ -3736,8 +3809,8 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} peerDependencies: '@types/react': ^19.2.0 @@ -3746,8 +3819,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/resolve@1.20.6': resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} @@ -3755,8 +3828,8 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/semver@7.8.0': + resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} @@ -3794,41 +3867,41 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/project-service@8.62.0': - resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==} + '@typescript-eslint/project-service@8.66.0': + resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.62.0': - resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==} + '@typescript-eslint/scope-manager@8.66.0': + resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.62.0': - resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==} + '@typescript-eslint/tsconfig-utils@8.66.0': + resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.62.0': - resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==} + '@typescript-eslint/types@8.66.0': + resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.62.0': - resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==} + '@typescript-eslint/typescript-estree@8.66.0': + resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.62.0': - resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==} + '@typescript-eslint/utils@8.66.0': + resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.62.0': - resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==} + '@typescript-eslint/visitor-keys@8.66.0': + resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript/typescript-aix-ppc64@7.0.2': @@ -3951,19 +4024,19 @@ packages: cpu: [x64] os: [win32] - '@uiw/codemirror-extensions-basic-setup@4.25.10': - resolution: {integrity: sha512-P3vytLlpE62KYSWrMUnwDCv2lvaQDuDZzyj03mHntuHo5bSl34fRZpjTY3kQTPGuXHxkGSYpoPFFj+hMTqaaMQ==} + '@uiw/codemirror-extensions-basic-setup@4.25.11': + resolution: {integrity: sha512-otyFa+n9IOYtEjaKOxPedHkj15fTPUF21wdR9pv0GpZPfuGl27cvmcv6+tognbRu9VvEcsHKE+ESoszeo3KfTw==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' '@codemirror/view': '>=6.0.0' - '@uiw/codemirror-themes@4.25.10': - resolution: {integrity: sha512-Fqiz1HIuDlDftcL+/O53V333UOH6MqQ84VbiQB5egn6u+uDwAqACp1FrdAoi4wgpR3b3TGW4Gr0wIYcrJSSz1A==} + '@uiw/codemirror-themes@4.25.11': + resolution: {integrity: sha512-SBNCOgRsCtewGNocRbmjbCkltGXlFcPJsvhxQ351VynQjnWUiPbUrFcEU/haQ3HanROdAAjWXZJPk5bMBxl2jw==} peerDependencies: '@codemirror/view': '>=6.0.0' - '@uiw/react-codemirror@4.25.10': - resolution: {integrity: sha512-DzgSMwM5qzB7v1FIb4gEeriYt67iiay756/HIOM9mAbeOVK0MO7rqefHf0O5c0269pJKMW7AH9FjclExD23V9w==} + '@uiw/react-codemirror@4.25.11': + resolution: {integrity: sha512-DYVFAKLX+F/4JS9N/7xexh+TICrlncwkX9HKKInrP1bwO0tSfc3k0GB6oawTYhelVKh20cX3TuRx+NJSkVXuMw==} peerDependencies: '@codemirror/view': '>=6.0.0' react: '>=17.0.0' @@ -4097,8 +4170,8 @@ packages: engines: {node: '>=20.18 <=25.x'} os: [darwin, linux, win32] - '@vitejs/plugin-react-swc@4.3.1': - resolution: {integrity: sha512-PaeokKjAGraNN+s5SIApgsktnJprIyt3zgEIu7awnEdfn29QiB2crTcCzyi2XGpX9rUnTc0cKU07Wm0N0g7H2w==} + '@vitejs/plugin-react-swc@4.3.3': + resolution: {integrity: sha512-bti8ZAcvz4Lh6/e4Uk2k3aa1TiUXbbMsahuqOHvd3MveFTkKDZOA6wQVkpj7J/+tepX/wGfe+lsGh/t24HTXMQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 || ^8 @@ -4615,18 +4688,13 @@ packages: ace-builds@1.44.0: resolution: {integrity: sha512-PFNMSYqFdEUkul2Ntud0HvA09AgY+F1ag0UYdpMH60wNI/qOA8cB8tlTgoALMEwIdUPJK2CjrIQ7OnbiSS/ugQ==} - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -4937,8 +5005,8 @@ packages: resolution: {integrity: sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==} hasBin: true - cbor-x@1.6.4: - resolution: {integrity: sha512-UGKHjp6RHC6QuZ2yy5LCKm7MojM4716DwoSaqwQpaH4DvZvbBTGcoDNTiG9Y2lByXZYFEs9WRkS5tLl96IrF1Q==} + cbor-x@1.6.5: + resolution: {integrity: sha512-yO64CxnSh6kp+pHNRK9IfwnMvCB+c8HvmUjQY/9l9YRF0/cAPka/tUHLwS64QqUpFCq3/OtbKziVJYXH2EaRig==} chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} @@ -5481,6 +5549,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5519,21 +5590,21 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-jest-dom@5.5.0: - resolution: {integrity: sha512-CRlXfchTr7EgC3tDI7MGHY6QjdJU5Vv2RPaeeGtkXUHnKZf04kgzMPIJUXt4qKCvYWVVIEo9ut9Oq1vgXAykEA==} + eslint-plugin-jest-dom@5.10.1: + resolution: {integrity: sha512-IIJdzbACbhJUJyMAqpA9E9gxp1Gv29TeQ3DtL+pepwP8Oq2WiqjOB2HbnEIs23h0ewdKIZRDI56yLro2eUO+DA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6', yarn: '>=1'} peerDependencies: '@testing-library/dom': ^8.0.0 || ^9.0.0 || ^10.0.0 - eslint: ^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + eslint: ^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: '@testing-library/dom': optional: true - eslint-plugin-storybook@10.4.6: - resolution: {integrity: sha512-CfGSXn6zFspeYTU8R7v797MOmJFj8xc6MWf/oGuRwbKeMoSwnliR+OlXSjMZYM1D6gfmwiuH1VX58LSHdn+ZPg==} + eslint-plugin-storybook@10.5.7: + resolution: {integrity: sha512-mLpamG1Rsica2jYbUzIZOEuy7Fm1IMtVLMvvxGTpjTVKUMxTXJsANx3MBpH2VSbGQB8Yzlt5399WL/O07K97Ig==} peerDependencies: eslint: '>=8' - storybook: ^10.4.6 + storybook: ^10.5.7 eslint-plugin-testing-library@7.16.2: resolution: {integrity: sha512-8gleGnQXK2ZA3hHwjCwpYTZvM+9VsrJ+/9kDI8CjqAQGAdMQOdn/rJNu7ZySENuiWlGKQWyZJ4ZjEg2zamaRHw==} @@ -5562,8 +5633,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.6.0: - resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + eslint@10.8.1: + resolution: {integrity: sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -5702,8 +5773,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -5817,8 +5888,8 @@ packages: resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} engines: {node: '>=20'} - globals@17.7.0: - resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + globals@17.9.0: + resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} engines: {node: '>=18'} globalthis@1.0.4: @@ -5840,8 +5911,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql@16.14.0: - resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} has-flag@4.0.0: @@ -5921,8 +5992,8 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - immer@11.1.8: - resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + immer@11.1.16: + resolution: {integrity: sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -5932,8 +6003,8 @@ packages: resolution: {integrity: sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==} engines: {node: '>=22.15'} - import-in-the-middle@3.0.1: - resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} + import-in-the-middle@3.3.3: + resolution: {integrity: sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==} engines: {node: '>=18'} import-local@3.2.0: @@ -6299,6 +6370,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -6322,78 +6396,78 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: @@ -6447,8 +6521,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -6529,8 +6603,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -6572,8 +6646,8 @@ packages: msw: '>=2' storybook: '>=9' - msw@2.14.6: - resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==} + msw@2.15.0: + resolution: {integrity: sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -6582,12 +6656,12 @@ packages: typescript: optional: true - mute-stream@4.0.0: - resolution: {integrity: sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.17: - resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -6707,11 +6781,11 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-resolver@11.19.1: - resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} - oxfmt@0.56.0: - resolution: {integrity: sha512-9Dv0wV3zKiyvhjD7bRKaInKmHQ1sPx3RGOjQkGFJbbdQ16576yf8qhMSO9Q9cvHcs+1NpBsRTkuDDYFFPTJ6gw==} + oxfmt@0.62.0: + resolution: {integrity: sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6723,12 +6797,12 @@ packages: vite-plus: optional: true - oxlint@1.71.0: - resolution: {integrity: sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==} + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=7.0.2001' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -6825,8 +6899,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pirates@4.0.7: @@ -6841,14 +6915,14 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} - playwright-core@1.61.1: - resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} - engines: {node: '>=18'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} hasBin: true - playwright@1.61.1: - resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} - engines: {node: '>=18'} + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} hasBin: true plist@3.1.0: @@ -6858,8 +6932,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -6979,18 +7053,18 @@ packages: resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} engines: {node: ^20.9.0 || >=22} - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: - react: ^19.2.7 + react: ^19.2.8 react-error-boundary@6.1.2: resolution: {integrity: sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==} peerDependencies: react: ^18.0.0 || ^19.0.0 - react-hook-form@7.80.0: - resolution: {integrity: sha512-4P+fk6oXsxY+6xSj7Euhc2sumQD8zQqCuVHoJwoyp9EchP+IUW9OESB7uHFJOKsIBQ4MQqYE84INJFqUCYNoOg==} + react-hook-form@7.85.0: + resolution: {integrity: sha512-U2MTriFXnclmV4rOE20p2DcRFv5WEg3FIcBFOKcOLFHDVvGIMPvLTkTWefUsonmlaVy23khVDxDWym6uJVGOzw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -7004,8 +7078,8 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.2.7: - resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==} + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} react-router@8.3.0: resolution: {integrity: sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==} @@ -7038,8 +7112,8 @@ packages: react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} engines: {node: '>=0.10.0'} read-binary-file-arch@1.0.6: @@ -7057,8 +7131,8 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - recast@0.23.11: - resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + recast@0.23.19: + resolution: {integrity: sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==} engines: {node: '>= 4'} redent@3.0.0: @@ -7147,8 +7221,8 @@ packages: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} - rolldown@1.1.3: - resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} + rolldown@1.2.3: + resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7230,8 +7304,8 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -7314,14 +7388,14 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - storybook@10.4.6: - resolution: {integrity: sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A==} + storybook@10.5.7: + resolution: {integrity: sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==} hasBin: true peerDependencies: '@storybook/react-vite': '*' '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 prettier: ^2 || ^3 - vite-plus: ^0.1.15 + vite-plus: ^0.1.15 || ^0.2.0 peerDependenciesMeta: '@types/react': optional: true @@ -7393,8 +7467,8 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} - styled-components@6.4.3: - resolution: {integrity: sha512-wYXrhu+JmDjZ1Tv7O0OopGTfztbzun43Pjjhh2H+xc0h5A09dwpZ5FJbrifJDcL8g5TA9btpWOX2+iSRuJTExw==} + styled-components@6.5.1: + resolution: {integrity: sha512-6AsQEXcG7QhtdzjwJ4s5Amx5R9veYH9AP3+U6nF2BAMU0i1iM3cAXKJXuAKZCdv3Y2ntVMcL44cPx4cIDxgYFg==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -7443,8 +7517,8 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tabbable@6.5.0: + resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==} tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} @@ -7510,15 +7584,15 @@ packages: tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - tldts-core@7.4.0: - resolution: {integrity: sha512-/mb9kRld+x1sIMXxWNOAp5m6C+D4GrAORWlJkOJ5dElvxdN1eutz/o7qHLp9gFvDF4Y3/L2xeScoxz6AbEo8rQ==} + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} tldts@6.1.86: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true - tldts@7.4.0: - resolution: {integrity: sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==} + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} hasBin: true tmp-promise@3.0.3: @@ -7539,8 +7613,8 @@ packages: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} - tough-cookie@6.0.1: - resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} tr46@5.1.1: @@ -7582,8 +7656,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.11: + resolution: {integrity: sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==} engines: {node: '>=18.0.0'} hasBin: true @@ -7603,8 +7677,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@5.6.0: - resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} engines: {node: '>=20'} typescript@5.9.3: @@ -7741,13 +7815,13 @@ packages: peerDependencies: vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - vite@8.1.0: - resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 + '@vitejs/devtools': ^0.4.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -7881,8 +7955,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -7947,8 +8021,8 @@ packages: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} yargs@18.0.0: @@ -7965,14 +8039,14 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - zone.js@0.16.1: - resolution: {integrity: sha512-dpvY17vxYIW3+bNrP0ClUlaiY0CiIRK3tnoLaGoQsQcY9/I/NpzIWQ7tQNhbV7LacQMpCII6wVzuL3tuWOyfuA==} + zone.js@0.16.2: + resolution: {integrity: sha512-Eky7p2Z1Ig3NnbfodSPoARCjKBSTFMnE/ACsP1L/XJEfY4SdOFce19BsUCWVwL6K5ABZFy5J3bjcMWffX+YM3Q==} snapshots: '@adobe/css-tools@4.5.0': {} - '@ark-ui/react@5.36.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@ark-ui/react@5.36.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@internationalized/date': 3.12.0 '@zag-js/accordion': 1.40.0 @@ -8022,7 +8096,7 @@ snapshots: '@zag-js/qr-code': 1.40.0 '@zag-js/radio-group': 1.40.0 '@zag-js/rating-group': 1.40.0 - '@zag-js/react': 1.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@zag-js/react': 1.40.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@zag-js/scroll-area': 1.40.0 '@zag-js/select': 1.40.0 '@zag-js/signature-pad': 1.40.0 @@ -8041,10 +8115,10 @@ snapshots: '@zag-js/tree-view': 1.40.0 '@zag-js/types': 1.40.0 '@zag-js/utils': 1.40.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - '@ark-ui/react@5.37.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@ark-ui/react@5.37.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@internationalized/date': 3.12.2 '@zag-js/accordion': 1.41.2 @@ -8094,7 +8168,7 @@ snapshots: '@zag-js/qr-code': 1.41.2 '@zag-js/radio-group': 1.41.2 '@zag-js/rating-group': 1.41.2 - '@zag-js/react': 1.41.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@zag-js/react': 1.41.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@zag-js/scroll-area': 1.41.2 '@zag-js/select': 1.41.2 '@zag-js/signature-pad': 1.41.2 @@ -8113,8 +8187,8 @@ snapshots: '@zag-js/tree-view': 1.41.2 '@zag-js/types': 1.41.2 '@zag-js/utils': 1.41.2 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@asamuzakjp/css-color@3.2.0': dependencies: @@ -8135,14 +8209,14 @@ snapshots: '@babel/core@7.29.7(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@8.1.1) @@ -8152,17 +8226,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-compilation-targets@7.29.7': dependencies: @@ -8180,7 +8254,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -8207,15 +8281,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -8224,13 +8298,13 @@ snapshots: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-plugin-utils@7.29.7': {} @@ -8239,7 +8313,7 @@ snapshots: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-wrap-function': 7.29.7(supports-color@8.1.1) - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8248,14 +8322,14 @@ snapshots: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -8268,25 +8342,25 @@ snapshots: '@babel/helper-wrap-function@7.29.7(supports-color@8.1.1)': dependencies: '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.7': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8321,7 +8395,7 @@ snapshots: dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8435,7 +8509,7 @@ snapshots: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8482,7 +8556,7 @@ snapshots: '@babel/helper-globals': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8496,7 +8570,7 @@ snapshots: dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8553,7 +8627,7 @@ snapshots: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8599,7 +8673,7 @@ snapshots: '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8639,7 +8713,7 @@ snapshots: '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/traverse': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8710,7 +8784,7 @@ snapshots: '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -8879,7 +8953,7 @@ snapshots: dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 esutils: 2.0.3 '@babel/preset-react@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': @@ -8910,22 +8984,22 @@ snapshots: '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.7(supports-color@8.1.1)': + '@babel/traverse@7.29.8(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 @@ -8950,12 +9024,12 @@ snapshots: '@cbor-extract/cbor-extract-win32-x64@2.2.2': optional: true - '@chakra-ui/cli@3.35.0(@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(supports-color@8.1.1)(typescript@5.9.3)': + '@chakra-ui/cli@3.35.0(@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3)': dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - '@chakra-ui/react': 3.35.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@chakra-ui/react': 3.35.0(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@clack/prompts': 1.0.1 '@pandacss/is-valid-prop': 1.10.0 '@types/babel__core': 7.20.5 @@ -8974,7 +9048,7 @@ snapshots: node-fetch: 3.3.2 package-manager-detector: 1.6.0 prettier: 3.8.1 - recast: 0.23.11 + recast: 0.23.19 scule: 1.3.0 tsconfck: 3.1.6(typescript@5.9.3) zod: 3.25.76 @@ -8982,18 +9056,18 @@ snapshots: - supports-color - typescript - '@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@ark-ui/react': 5.36.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@ark-ui/react': 5.36.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1) + '@emotion/react': 11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.8) '@emotion/utils': 1.4.2 '@pandacss/is-valid-prop': 1.11.3 csstype: 3.2.3 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@clack/core@1.0.1': dependencies: @@ -9009,61 +9083,61 @@ snapshots: '@codemirror/autocomplete@6.20.3': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 '@lezer/common': 1.5.2 '@codemirror/commands@6.10.4': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 '@lezer/common': 1.5.2 '@codemirror/lang-sql@6.10.0': dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.0 + '@codemirror/state': 6.7.1 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 '@codemirror/language@6.12.4': dependencies: - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 style-mod: 4.1.3 - '@codemirror/lint@6.9.6': + '@codemirror/lint@6.9.7': dependencies: - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 crelt: 1.0.7 - '@codemirror/search@6.7.0': + '@codemirror/search@6.7.1': dependencies: - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 crelt: 1.0.7 - '@codemirror/state@6.7.0': + '@codemirror/state@6.7.1': dependencies: '@marijn/find-cluster-break': 1.0.3 '@codemirror/theme-one-dark@6.1.3': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 '@lezer/highlight': 1.2.3 - '@codemirror/view@6.43.4': + '@codemirror/view@6.43.8': dependencies: - '@codemirror/state': 6.7.0 + '@codemirror/state': 6.7.1 crelt: 1.0.7 style-mod: 4.1.3 w3c-keyname: 2.2.8 @@ -9409,7 +9483,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/core@1.11.1': + '@emnapi/core@1.11.2': dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 @@ -9426,7 +9500,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.1': + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 optional: true @@ -9478,19 +9552,19 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1)': + '@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)': dependencies: '@babel/runtime': 7.29.7 '@emotion/babel-plugin': 11.13.5(supports-color@8.1.1) '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.8) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 19.2.7 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 transitivePeerDependencies: - supports-color @@ -9506,9 +9580,9 @@ snapshots: '@emotion/unitless@0.10.0': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.7)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.8)': dependencies: - react: 19.2.7 + react: 19.2.8 '@emotion/utils@1.4.2': {} @@ -9748,9 +9822,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))': dependencies: - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -9759,11 +9833,11 @@ snapshots: dependencies: '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.5 + minimatch: 10.2.6 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.6.0': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -9778,54 +9852,77 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@floating-ui/core@1.7.5': + '@floating-ui/core@1.8.0': dependencies: - '@floating-ui/utils': 0.2.11 + '@floating-ui/utils': 0.2.12 - '@floating-ui/dom@1.7.6': + '@floating-ui/dom@1.8.0': dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + '@floating-ui/dom': 1.8.0 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - '@floating-ui/react@0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@floating-ui/react@0.27.20(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/utils': 0.2.11 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - tabbable: 6.4.0 + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@floating-ui/utils': 0.2.12 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + tabbable: 6.5.0 - '@floating-ui/utils@0.2.11': {} + '@floating-ui/utils@0.2.12': {} - '@fontsource/ubuntu@5.2.8': {} + '@fontsource/ubuntu@5.3.0': {} - '@gravitational/design-system@https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(@fontsource/ubuntu@5.2.8)(@tanstack/react-query@5.101.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(supports-color@8.1.1)(tslib@2.8.1)(zod@4.4.3)': + '@gravitational/design-system@https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(@fontsource/ubuntu@5.3.0)(@sinclair/typebox@0.34.49)(@tanstack/react-query@5.101.4(react@19.2.8))(ajv@8.20.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1)(tslib@2.8.1)(zod@4.4.3)': dependencies: - '@ark-ui/react': 5.37.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@chakra-ui/cli': 3.35.0(@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(supports-color@8.1.1)(typescript@5.9.3) - '@chakra-ui/react': 3.35.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1) - '@fontsource/ubuntu': 5.2.8 - '@hookform/resolvers': 5.4.0(react-hook-form@7.80.0(react@19.2.7))(zod@4.4.3) + '@ark-ui/react': 5.37.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@chakra-ui/cli': 3.35.0(@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3) + '@chakra-ui/react': 3.35.0(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@emotion/react': 11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) + '@fontsource/ubuntu': 5.3.0 + '@hookform/resolvers': 5.7.1(@sinclair/typebox@0.34.49)(ajv@8.20.0)(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3) '@phosphor-icons/core': 2.1.1 - '@phosphor-icons/react': 2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tanstack/react-query': 5.101.2(react@19.2.7) - '@tanstack/react-table': 8.21.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - next-themes: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-hook-form: 7.80.0(react@19.2.7) + '@phosphor-icons/react': 2.1.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@tanstack/react-query': 5.101.4(react@19.2.8) + '@tanstack/react-table': 8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next-themes: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-hook-form: 7.85.0(react@19.2.8) tslib: 2.8.1 typescript: 5.9.3 zod: 4.4.3 transitivePeerDependencies: + - '@sinclair/typebox' + - '@standard-schema/spec' + - '@typeschema/main' + - '@vinejs/vine' + - ajv + - ajv-errors + - ajv-formats + - arktype + - ata-validator + - class-transformer + - class-validator + - computed-types + - effect + - fluentvalidation-ts + - fp-ts + - io-ts + - joi + - nope-validator + - superstruct - supports-color + - typanion + - valibot + - vest + - yup '@gravitational/swc-plugin-jsx-marker@https://github.com/gravitational/design-system/releases/download/@gravitational/swc-plugin-jsx-marker@0.1.0/swc-plugin-jsx-marker.tgz': dependencies: @@ -9845,12 +9942,15 @@ snapshots: lodash.camelcase: 4.3.0 long: 5.3.2 protobufjs: 7.6.5 - yargs: 17.7.2 + yargs: 17.7.3 - '@hookform/resolvers@5.4.0(react-hook-form@7.80.0(react@19.2.7))(zod@4.4.3)': + '@hookform/resolvers@5.7.1(@sinclair/typebox@0.34.49)(ajv@8.20.0)(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)': dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.80.0(react@19.2.7) + react-hook-form: 7.85.0(react@19.2.8) + optionalDependencies: + '@sinclair/typebox': 0.34.49 + ajv: 8.20.0 zod: 4.4.3 '@humanfs/core@0.19.2': @@ -9869,30 +9969,30 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@2.0.6': {} + '@inquirer/ansi@2.0.7': {} - '@inquirer/confirm@6.1.0(@types/node@24.13.3)': + '@inquirer/confirm@6.1.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.0(@types/node@24.13.3) - '@inquirer/type': 4.0.6(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/core@11.2.0(@types/node@24.13.3)': + '@inquirer/core@11.2.1(@types/node@24.13.3)': dependencies: - '@inquirer/ansi': 2.0.6 - '@inquirer/figures': 2.0.6 - '@inquirer/type': 4.0.6(@types/node@24.13.3) + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@24.13.3) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 - mute-stream: 4.0.0 + mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: '@types/node': 24.13.3 - '@inquirer/figures@2.0.6': {} + '@inquirer/figures@2.0.7': {} - '@inquirer/type@4.0.6(@types/node@24.13.3)': + '@inquirer/type@4.0.7(@types/node@24.13.3)': optionalDependencies: '@types/node': 24.13.3 @@ -10128,11 +10228,11 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@7.0.2) - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) optionalDependencies: typescript: 7.0.2 @@ -10191,81 +10291,81 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 '@tybys/wasm-util': 0.10.3 optional: true - '@nivo/annotations@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/annotations@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/colors': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/colors': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) lodash: 4.18.1 - react: 19.2.7 + react: 19.2.8 transitivePeerDependencies: - react-dom - '@nivo/axes@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/axes@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@nivo/scales': 0.99.0 - '@nivo/text': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/text': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/d3-format': 1.4.5 '@types/d3-time-format': 2.3.4 d3-format: 1.4.5 d3-time-format: 3.0.0 - react: 19.2.7 + react: 19.2.8 transitivePeerDependencies: - react-dom - '@nivo/bar@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/bar@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/annotations': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/axes': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/annotations': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/axes': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@nivo/canvas': 0.99.0 - '@nivo/colors': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/legends': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/colors': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/legends': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@nivo/scales': 0.99.0 - '@nivo/text': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) - '@nivo/tooltip': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/text': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) + '@nivo/tooltip': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/d3-scale': 4.0.9 '@types/d3-shape': 3.1.7 d3-scale: 4.0.2 d3-shape: 3.2.0 lodash: 4.18.1 - react: 19.2.7 + react: 19.2.8 transitivePeerDependencies: - react-dom '@nivo/canvas@0.99.0': {} - '@nivo/colors@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/colors@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) '@types/d3-color': 3.1.3 '@types/d3-scale': 4.0.9 '@types/d3-scale-chromatic': 3.1.0 @@ -10273,15 +10373,15 @@ snapshots: d3-scale: 4.0.2 d3-scale-chromatic: 3.1.0 lodash: 4.18.1 - react: 19.2.7 + react: 19.2.8 transitivePeerDependencies: - react-dom - '@nivo/core@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/core@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/theming': 0.99.0(react@19.2.7) - '@nivo/tooltip': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@nivo/theming': 0.99.0(react@19.2.8) + '@nivo/tooltip': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/d3-shape': 3.1.7 d3-color: 3.1.0 d3-format: 1.4.5 @@ -10291,21 +10391,21 @@ snapshots: d3-shape: 3.2.0 d3-time-format: 3.0.0 lodash: 4.18.1 - react: 19.2.7 - react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - use-debounce: 10.0.4(react@19.2.7) + react: 19.2.8 + react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + use-debounce: 10.0.4(react@19.2.8) transitivePeerDependencies: - react-dom - '@nivo/legends@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/legends@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/colors': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/text': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) + '@nivo/colors': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/text': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) '@types/d3-scale': 4.0.9 d3-scale: 4.0.2 - react: 19.2.7 + react: 19.2.8 transitivePeerDependencies: - react-dom @@ -10321,26 +10421,26 @@ snapshots: d3-time-format: 3.0.0 lodash: 4.18.1 - '@nivo/text@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/text@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 transitivePeerDependencies: - react-dom - '@nivo/theming@0.99.0(react@19.2.7)': + '@nivo/theming@0.99.0(react@19.2.8)': dependencies: lodash: 4.18.1 - react: 19.2.7 + react: 19.2.8 - '@nivo/tooltip@0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@nivo/tooltip@0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@nivo/core': 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@nivo/theming': 0.99.0(react@19.2.7) - '@react-spring/web': 10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 + '@nivo/core': 0.99.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@nivo/theming': 0.99.0(react@19.2.8) + '@react-spring/web': 10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 transitivePeerDependencies: - react-dom @@ -10371,142 +10471,148 @@ snapshots: '@open-draft/until@2.1.0': {} - '@opentelemetry/api-logs@0.219.0': + '@opentelemetry/api-logs@0.221.0': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/api@1.9.1': {} - '@opentelemetry/context-zone-peer-dep@2.8.0(@opentelemetry/api@1.9.1)(zone.js@0.16.1)': + '@opentelemetry/context-zone-peer-dep@2.10.0(@opentelemetry/api@1.9.1)(zone.js@0.16.2)': dependencies: '@opentelemetry/api': 1.9.1 - zone.js: 0.16.1 + zone.js: 0.16.2 - '@opentelemetry/context-zone@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/context-zone@2.10.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/context-zone-peer-dep': 2.8.0(@opentelemetry/api@1.9.1)(zone.js@0.16.1) - zone.js: 0.16.1 + '@opentelemetry/context-zone-peer-dep': 2.10.0(@opentelemetry/api@1.9.1)(zone.js@0.16.2) + zone.js: 0.16.2 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.221.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.221.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.221.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-document-load@0.64.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': + '@opentelemetry/instrumentation-document-load@0.66.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) - '@opentelemetry/sdk-trace-web': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-web': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fetch@0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': + '@opentelemetry/instrumentation-fetch@0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) - '@opentelemetry/sdk-trace-web': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-web': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-user-interaction@0.63.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)(zone.js@0.16.1)': + '@opentelemetry/instrumentation-user-interaction@0.65.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)(zone.js@0.16.2)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) - '@opentelemetry/sdk-trace-web': 2.8.0(@opentelemetry/api@1.9.1) - zone.js: 0.16.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-web': 2.10.0(@opentelemetry/api@1.9.1) + zone.js: 0.16.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-xml-http-request@0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': + '@opentelemetry/instrumentation-xml-http-request@0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) - '@opentelemetry/sdk-trace-web': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-web': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.219.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': + '@opentelemetry/instrumentation@0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.219.0 - import-in-the-middle: 3.0.1 + '@opentelemetry/api-logs': 0.221.0 + import-in-the-middle: 3.3.3 require-in-the-middle: 8.0.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.219.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.221.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.221.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.221.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.221.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.221.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.219.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-b3@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.219.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/resources@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.221.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/api-logs': 0.221.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/sdk-logs@0.219.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-metrics@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.219.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-web@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-web@2.8.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace@2.10.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/semantic-conventions@1.41.1': {} + '@opentelemetry/semantic-conventions@1.43.0': {} '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true @@ -10560,7 +10666,7 @@ snapshots: dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true '@oxc-parser/binding-win32-arm64-msvc@0.127.0': @@ -10574,185 +10680,181 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@oxc-project/types@0.137.0': {} + '@oxc-project/types@0.143.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': + '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true - '@oxc-resolver/binding-android-arm64@11.19.1': + '@oxc-resolver/binding-android-arm64@11.24.2': optional: true - '@oxc-resolver/binding-darwin-arm64@11.19.1': + '@oxc-resolver/binding-darwin-arm64@11.24.2': optional: true - '@oxc-resolver/binding-darwin-x64@11.19.1': + '@oxc-resolver/binding-darwin-x64@11.24.2': optional: true - '@oxc-resolver/binding-freebsd-x64@11.19.1': + '@oxc-resolver/binding-freebsd-x64@11.24.2': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.19.1': + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.19.1': + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.19.1': + '@oxc-resolver/binding-linux-x64-musl@11.24.2': optional: true - '@oxc-resolver/binding-openharmony-arm64@11.19.1': + '@oxc-resolver/binding-openharmony-arm64@11.24.2': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + '@oxc-resolver/binding-wasm32-wasi@11.24.2': dependencies: - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': optional: true - '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.19.1': + '@oxfmt/binding-android-arm-eabi@0.62.0': optional: true - '@oxfmt/binding-android-arm-eabi@0.56.0': + '@oxfmt/binding-android-arm64@0.62.0': optional: true - '@oxfmt/binding-android-arm64@0.56.0': + '@oxfmt/binding-darwin-arm64@0.62.0': optional: true - '@oxfmt/binding-darwin-arm64@0.56.0': + '@oxfmt/binding-darwin-x64@0.62.0': optional: true - '@oxfmt/binding-darwin-x64@0.56.0': + '@oxfmt/binding-freebsd-x64@0.62.0': optional: true - '@oxfmt/binding-freebsd-x64@0.56.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.56.0': + '@oxfmt/binding-linux-arm64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.56.0': + '@oxfmt/binding-linux-arm64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.56.0': + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.56.0': + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.56.0': + '@oxfmt/binding-linux-riscv64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.56.0': + '@oxfmt/binding-linux-s390x-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.56.0': + '@oxfmt/binding-linux-x64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.56.0': + '@oxfmt/binding-linux-x64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.56.0': + '@oxfmt/binding-openharmony-arm64@0.62.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.56.0': + '@oxfmt/binding-win32-arm64-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.56.0': + '@oxfmt/binding-win32-ia32-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.56.0': + '@oxfmt/binding-win32-x64-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.56.0': + '@oxlint/binding-android-arm-eabi@1.77.0': optional: true - '@oxlint/binding-android-arm-eabi@1.71.0': + '@oxlint/binding-android-arm64@1.77.0': optional: true - '@oxlint/binding-android-arm64@1.71.0': + '@oxlint/binding-darwin-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-arm64@1.71.0': + '@oxlint/binding-darwin-x64@1.77.0': optional: true - '@oxlint/binding-darwin-x64@1.71.0': + '@oxlint/binding-freebsd-x64@1.77.0': optional: true - '@oxlint/binding-freebsd-x64@1.71.0': + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': + '@oxlint/binding-linux-arm-musleabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.71.0': + '@oxlint/binding-linux-arm64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.71.0': + '@oxlint/binding-linux-arm64-musl@1.77.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.71.0': + '@oxlint/binding-linux-ppc64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.71.0': + '@oxlint/binding-linux-riscv64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.71.0': + '@oxlint/binding-linux-riscv64-musl@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.71.0': + '@oxlint/binding-linux-s390x-gnu@1.77.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.71.0': + '@oxlint/binding-linux-x64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.71.0': + '@oxlint/binding-linux-x64-musl@1.77.0': optional: true - '@oxlint/binding-linux-x64-musl@1.71.0': + '@oxlint/binding-openharmony-arm64@1.77.0': optional: true - '@oxlint/binding-openharmony-arm64@1.71.0': + '@oxlint/binding-win32-arm64-msvc@1.77.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.71.0': + '@oxlint/binding-win32-ia32-msvc@1.77.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.71.0': - optional: true - - '@oxlint/binding-win32-x64-msvc@1.71.0': + '@oxlint/binding-win32-x64-msvc@1.77.0': optional: true '@pandacss/is-valid-prop@1.10.0': {} @@ -10783,19 +10885,19 @@ snapshots: '@phosphor-icons/core@2.1.1': {} - '@phosphor-icons/react@2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@phosphor-icons/react@2.1.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@pkgjs/parseargs@0.11.0': optional: true '@pkgr/core@0.2.9': {} - '@playwright/test@1.61.1': + '@playwright/test@1.62.1': dependencies: - playwright: 1.61.1 + playwright: 1.62.1 '@protobuf-ts/grpc-transport@2.11.1(@grpc/grpc-js@1.14.4)': dependencies: @@ -10835,85 +10937,78 @@ snapshots: '@react-dnd/shallowequal@4.0.2': {} - '@react-spring/animated@10.0.1(react@19.2.7)': + '@react-spring/animated@10.0.1(react@19.2.8)': dependencies: - '@react-spring/shared': 10.0.1(react@19.2.7) + '@react-spring/shared': 10.0.1(react@19.2.8) '@react-spring/types': 10.0.1 - react: 19.2.7 + react: 19.2.8 - '@react-spring/core@10.0.1(react@19.2.7)': + '@react-spring/core@10.0.1(react@19.2.8)': dependencies: - '@react-spring/animated': 10.0.1(react@19.2.7) - '@react-spring/shared': 10.0.1(react@19.2.7) + '@react-spring/animated': 10.0.1(react@19.2.8) + '@react-spring/shared': 10.0.1(react@19.2.8) '@react-spring/types': 10.0.1 - react: 19.2.7 + react: 19.2.8 '@react-spring/rafz@10.0.1': {} - '@react-spring/shared@10.0.1(react@19.2.7)': + '@react-spring/shared@10.0.1(react@19.2.8)': dependencies: '@react-spring/rafz': 10.0.1 '@react-spring/types': 10.0.1 - react: 19.2.7 + react: 19.2.8 '@react-spring/types@10.0.1': {} - '@react-spring/web@10.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@react-spring/web@10.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@react-spring/animated': 10.0.1(react@19.2.7) - '@react-spring/core': 10.0.1(react@19.2.7) - '@react-spring/shared': 10.0.1(react@19.2.7) + '@react-spring/animated': 10.0.1(react@19.2.8) + '@react-spring/core': 10.0.1(react@19.2.8) + '@react-spring/shared': 10.0.1(react@19.2.8) '@react-spring/types': 10.0.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rolldown/binding-android-arm64@1.1.3': - optional: true + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - '@rolldown/binding-darwin-arm64@1.1.3': + '@rolldown/binding-android-arm64@1.2.3': optional: true - '@rolldown/binding-darwin-x64@1.1.3': + '@rolldown/binding-darwin-arm64@1.2.3': optional: true - '@rolldown/binding-freebsd-x64@1.1.3': + '@rolldown/binding-darwin-x64@1.2.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + '@rolldown/binding-freebsd-x64@1.2.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.3': + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.3': + '@rolldown/binding-linux-arm64-gnu@1.2.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.3': + '@rolldown/binding-linux-arm64-musl@1.2.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.3': + '@rolldown/binding-linux-ppc64-gnu@1.2.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.3': + '@rolldown/binding-linux-s390x-gnu@1.2.3': optional: true - '@rolldown/binding-linux-x64-musl@1.1.3': + '@rolldown/binding-linux-x64-gnu@1.2.3': optional: true - '@rolldown/binding-openharmony-arm64@1.1.3': + '@rolldown/binding-linux-x64-musl@1.2.3': optional: true - '@rolldown/binding-wasm32-wasi@1.1.3': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@rolldown/binding-openharmony-arm64@1.2.3': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.3': + '@rolldown/binding-win32-arm64-msvc@1.2.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.3': + '@rolldown/binding-win32-x64-msvc@1.2.3': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -10922,7 +11017,7 @@ snapshots: dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.4 + picomatch: 4.0.5 '@sinclair/typebox@0.27.10': {} @@ -10947,84 +11042,85 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@storybook/addon-vitest@10.4.6(react@19.2.7)(storybook@10.4.6)': + '@storybook/addon-vitest@10.5.7(react@19.2.8)(storybook@10.5.7)': dependencies: '@storybook/global': 5.0.0 - '@storybook/icons': 2.1.0(react@19.2.7) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + '@storybook/icons': 2.1.0(react@19.2.8) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) transitivePeerDependencies: - react - '@storybook/builder-vite@10.4.6(esbuild@0.28.1)(storybook@10.4.6)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0))': + '@storybook/builder-vite@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': dependencies: - '@storybook/csf-plugin': 10.4.6(esbuild@0.28.1)(storybook@10.4.6)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + '@storybook/csf-plugin': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) ts-dedent: 2.3.0 - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.4.6(esbuild@0.28.1)(storybook@10.4.6)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0))': + '@storybook/csf-plugin@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': dependencies: - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.1 - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) '@storybook/global@5.0.0': {} - '@storybook/icons@2.1.0(react@19.2.7)': + '@storybook/icons@2.1.0(react@19.2.8)': dependencies: - react: 19.2.7 + react: 19.2.8 - '@storybook/react-dom-shim@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)': + '@storybook/react-dom-shim@10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)': dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@storybook/react-vite@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0))': + '@storybook/react-vite@10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) '@rollup/pluginutils': 5.4.0 - '@storybook/builder-vite': 10.4.6(esbuild@0.28.1)(storybook@10.4.6)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) - '@storybook/react': 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2) + '@storybook/builder-vite': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@storybook/react': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2) empathic: 2.0.1 magic-string: 0.30.21 - react: 19.2.7 + react: 19.2.8 react-docgen: 8.0.3(supports-color@8.1.1) - react-dom: 19.2.7(react@19.2.7) + react-dom: 19.2.8(react@19.2.8) resolve: 1.22.12 - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) tsconfig-paths: 4.2.0 - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - '@types/react' - '@types/react-dom' - esbuild - rollup - supports-color - - typescript - webpack - '@storybook/react@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2)': + '@storybook/react@10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6) - react: 19.2.7 + '@storybook/react-dom-shim': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7) + react: 19.2.8 react-docgen: 8.0.3(supports-color@8.1.1) react-docgen-typescript: 2.4.0(typescript@7.0.2) - react-dom: 19.2.7(react@19.2.7) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + react-dom: 19.2.8(react@19.2.8) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -11080,59 +11176,59 @@ snapshots: '@styled-system/core': 5.1.2 '@styled-system/css': 5.1.5 - '@swc/core-darwin-arm64@1.15.43': + '@swc/core-darwin-arm64@1.15.47': optional: true - '@swc/core-darwin-x64@1.15.43': + '@swc/core-darwin-x64@1.15.47': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.43': + '@swc/core-linux-arm-gnueabihf@1.15.47': optional: true - '@swc/core-linux-arm64-gnu@1.15.43': + '@swc/core-linux-arm64-gnu@1.15.47': optional: true - '@swc/core-linux-arm64-musl@1.15.43': + '@swc/core-linux-arm64-musl@1.15.47': optional: true - '@swc/core-linux-ppc64-gnu@1.15.43': + '@swc/core-linux-ppc64-gnu@1.15.47': optional: true - '@swc/core-linux-s390x-gnu@1.15.43': + '@swc/core-linux-s390x-gnu@1.15.47': optional: true - '@swc/core-linux-x64-gnu@1.15.43': + '@swc/core-linux-x64-gnu@1.15.47': optional: true - '@swc/core-linux-x64-musl@1.15.43': + '@swc/core-linux-x64-musl@1.15.47': optional: true - '@swc/core-win32-arm64-msvc@1.15.43': + '@swc/core-win32-arm64-msvc@1.15.47': optional: true - '@swc/core-win32-ia32-msvc@1.15.43': + '@swc/core-win32-ia32-msvc@1.15.47': optional: true - '@swc/core-win32-x64-msvc@1.15.43': + '@swc/core-win32-x64-msvc@1.15.47': optional: true - '@swc/core@1.15.43(@swc/helpers@0.5.23)': + '@swc/core@1.15.47(@swc/helpers@0.5.23)': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.27 + '@swc/types': 0.1.28 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.43 - '@swc/core-darwin-x64': 1.15.43 - '@swc/core-linux-arm-gnueabihf': 1.15.43 - '@swc/core-linux-arm64-gnu': 1.15.43 - '@swc/core-linux-arm64-musl': 1.15.43 - '@swc/core-linux-ppc64-gnu': 1.15.43 - '@swc/core-linux-s390x-gnu': 1.15.43 - '@swc/core-linux-x64-gnu': 1.15.43 - '@swc/core-linux-x64-musl': 1.15.43 - '@swc/core-win32-arm64-msvc': 1.15.43 - '@swc/core-win32-ia32-msvc': 1.15.43 - '@swc/core-win32-x64-msvc': 1.15.43 + '@swc/core-darwin-arm64': 1.15.47 + '@swc/core-darwin-x64': 1.15.47 + '@swc/core-linux-arm-gnueabihf': 1.15.47 + '@swc/core-linux-arm64-gnu': 1.15.47 + '@swc/core-linux-arm64-musl': 1.15.47 + '@swc/core-linux-ppc64-gnu': 1.15.47 + '@swc/core-linux-s390x-gnu': 1.15.47 + '@swc/core-linux-x64-gnu': 1.15.47 + '@swc/core-linux-x64-musl': 1.15.47 + '@swc/core-win32-arm64-msvc': 1.15.47 + '@swc/core-win32-ia32-msvc': 1.15.47 + '@swc/core-win32-x64-msvc': 1.15.47 '@swc/helpers': 0.5.23 '@swc/counter@0.1.3': {} @@ -11141,7 +11237,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/types@0.1.27': + '@swc/types@0.1.28': dependencies: '@swc/counter': 0.1.3 @@ -11149,30 +11245,30 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tanstack/query-core@5.101.2': {} + '@tanstack/query-core@5.101.4': {} - '@tanstack/react-query@5.101.2(react@19.2.7)': + '@tanstack/react-query@5.101.4(react@19.2.8)': dependencies: - '@tanstack/query-core': 5.101.2 - react: 19.2.7 + '@tanstack/query-core': 5.101.4 + react: 19.2.8 - '@tanstack/react-table@8.21.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@tanstack/react-table@8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@tanstack/table-core': 8.21.3 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@tanstack/table-core@8.21.3': {} - '@testing-library/dom@10.1.0': + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 - chalk: 4.1.2 dom-accessibility-api: 0.5.16 lz-string: 1.5.0 + picocolors: 1.1.1 pretty-format: 27.5.1 '@testing-library/jest-dom@6.9.1': @@ -11184,29 +11280,29 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/jest-dom@7.0.0(@testing-library/dom@10.1.0)': + '@testing-library/jest-dom@7.0.0(@testing-library/dom@10.4.1)': dependencies: '@adobe/css-tools': 4.5.0 - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.1 aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.1.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 - '@testing-library/dom': 10.1.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + '@testing-library/dom': 10.4.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@testing-library/user-event@14.6.1(@testing-library/dom@10.1.0)': + '@testing-library/user-event@14.6.3(@testing-library/dom@10.4.1)': dependencies: - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.1 '@tybys/wasm-util@0.10.3': dependencies: @@ -11217,24 +11313,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/cacheable-request@6.0.3': dependencies: @@ -11337,15 +11433,15 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/react-dom@19.2.3(@types/react@19.2.17)': + '@types/react-dom@19.2.4(@types/react@19.2.18)': dependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@types/react-transition-group@4.4.12(@types/react@19.2.17)': + '@types/react-transition-group@4.4.12(@types/react@19.2.18)': dependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@types/react@19.2.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 @@ -11355,7 +11451,7 @@ snapshots: dependencies: '@types/node': 24.13.3 - '@types/semver@7.7.1': {} + '@types/semver@7.8.0': {} '@types/set-cookie-parser@2.4.10': dependencies: @@ -11392,34 +11488,34 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/project-service@8.62.0(supports-color@8.1.1)(typescript@7.0.2)': + '@typescript-eslint/project-service@8.66.0(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@7.0.2) - '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@7.0.2) + '@typescript-eslint/types': 8.66.0 debug: 4.4.3(supports-color@8.1.1) typescript: 7.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.62.0': + '@typescript-eslint/scope-manager@8.66.0': dependencies: - '@typescript-eslint/types': 8.62.0 - '@typescript-eslint/visitor-keys': 8.62.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 - '@typescript-eslint/tsconfig-utils@8.62.0(typescript@7.0.2)': + '@typescript-eslint/tsconfig-utils@8.66.0(typescript@7.0.2)': dependencies: typescript: 7.0.2 - '@typescript-eslint/types@8.62.0': {} + '@typescript-eslint/types@8.66.0': {} - '@typescript-eslint/typescript-estree@8.62.0(supports-color@8.1.1)(typescript@7.0.2)': + '@typescript-eslint/typescript-estree@8.66.0(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@typescript-eslint/project-service': 8.62.0(supports-color@8.1.1)(typescript@7.0.2) - '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@7.0.2) - '@typescript-eslint/types': 8.62.0 - '@typescript-eslint/visitor-keys': 8.62.0 + '@typescript-eslint/project-service': 8.66.0(supports-color@8.1.1)(typescript@7.0.2) + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@7.0.2) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.5 + minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@7.0.2) @@ -11427,20 +11523,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.62.0(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2)': + '@typescript-eslint/utils@8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)) - '@typescript-eslint/scope-manager': 8.62.0 - '@typescript-eslint/types': 8.62.0 - '@typescript-eslint/typescript-estree': 8.62.0(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(supports-color@8.1.1)(typescript@7.0.2) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) typescript: 7.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.62.0': + '@typescript-eslint/visitor-keys@8.66.0': dependencies: - '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 '@typescript/typescript-aix-ppc64@7.0.2': @@ -11503,33 +11599,33 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@uiw/codemirror-extensions-basic-setup@4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.4)': + '@uiw/codemirror-extensions-basic-setup@4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.8)': dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/commands': 6.10.4 '@codemirror/language': 6.12.4 - '@codemirror/lint': 6.9.6 - '@codemirror/search': 6.7.0 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 - '@uiw/codemirror-themes@4.25.10(@codemirror/view@6.43.4)': + '@uiw/codemirror-themes@4.25.11(@codemirror/view@6.43.8)': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 - '@uiw/react-codemirror@4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@uiw/react-codemirror@4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.8)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 '@codemirror/commands': 6.10.4 - '@codemirror/state': 6.7.0 + '@codemirror/state': 6.7.1 '@codemirror/theme-one-dark': 6.1.3 - '@codemirror/view': 6.43.4 - '@uiw/codemirror-extensions-basic-setup': 4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.4) + '@codemirror/view': 6.43.8 + '@uiw/codemirror-extensions-basic-setup': 4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/view@6.43.8) codemirror: 6.0.2 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: - '@codemirror/autocomplete' @@ -11593,7 +11689,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': @@ -11607,11 +11703,11 @@ snapshots: '@visulima/boxen@2.0.10': {} - '@vitejs/plugin-react-swc@4.3.1(@swc/helpers@0.5.23)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0))': + '@vitejs/plugin-react-swc@4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - '@swc/core': 1.15.43(@swc/helpers@0.5.23) - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + '@swc/core': 1.15.47(@swc/helpers@0.5.23) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) transitivePeerDependencies: - '@swc/helpers' @@ -12347,13 +12443,13 @@ snapshots: '@zag-js/popper@1.40.0': dependencies: - '@floating-ui/dom': 1.7.6 + '@floating-ui/dom': 1.8.0 '@zag-js/dom-query': 1.40.0 '@zag-js/utils': 1.40.0 '@zag-js/popper@1.41.2': dependencies: - '@floating-ui/dom': 1.7.6 + '@floating-ui/dom': 1.8.0 '@zag-js/dom-query': 1.41.2 '@zag-js/utils': 1.41.2 @@ -12439,23 +12535,23 @@ snapshots: '@zag-js/types': 1.41.2 '@zag-js/utils': 1.41.2 - '@zag-js/react@1.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@zag-js/react@1.40.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@zag-js/core': 1.40.0 '@zag-js/store': 1.40.0 '@zag-js/types': 1.40.0 '@zag-js/utils': 1.40.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - '@zag-js/react@1.41.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@zag-js/react@1.41.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@zag-js/core': 1.41.2 '@zag-js/store': 1.41.2 '@zag-js/types': 1.41.2 '@zag-js/utils': 1.41.2 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@zag-js/rect-utils@1.40.0': {} @@ -12791,15 +12887,11 @@ snapshots: ace-builds@1.44.0: {} - acorn-import-attributes@1.9.5(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 - acorn-jsx@5.3.2(acorn@8.17.0): - dependencies: - acorn: 8.17.0 - - acorn@8.17.0: {} + acorn@8.18.0: {} agent-base@7.1.4: {} @@ -12871,7 +12963,7 @@ snapshots: js-yaml: 4.3.1 json5: 2.2.3 lazy-val: 1.0.5 - minimatch: 10.2.5 + minimatch: 10.2.6 pkijs: 3.4.0 plist: 3.1.0 proper-lockfile: 4.1.2 @@ -12980,14 +13072,14 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-styled-components@2.3.0(@babel/core@7.29.7(supports-color@8.1.1))(styled-components@6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(supports-color@8.1.1): + babel-plugin-styled-components@2.3.0(@babel/core@7.29.7(supports-color@8.1.1))(styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(supports-color@8.1.1): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - picomatch: 4.0.4 - styled-components: 6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + picomatch: 4.0.5 + styled-components: 6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) transitivePeerDependencies: - supports-color @@ -13186,7 +13278,7 @@ snapshots: '@cbor-extract/cbor-extract-win32-x64': 2.2.2 optional: true - cbor-x@1.6.4: + cbor-x@1.6.5: optionalDependencies: cbor-extract: 2.2.2 @@ -13256,10 +13348,10 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/commands': 6.10.4 '@codemirror/language': 6.12.4 - '@codemirror/lint': 6.9.6 - '@codemirror/search': 6.7.0 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.8 collect-v8-coverage@1.0.3: {} @@ -13327,11 +13419,11 @@ snapshots: path-type: 4.0.0 yaml: 1.10.3 - create-react-class@15.7.0(react@19.2.7): + create-react-class@15.7.0(react@19.2.8): dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react: 19.2.7 + react: 19.2.8 crelt@1.0.7: {} @@ -13365,7 +13457,7 @@ snapshots: cspell-glob@10.0.1: dependencies: '@cspell/url': 10.0.1 - picomatch: 4.0.4 + picomatch: 4.0.5 cspell-grammar@10.0.1: dependencies: @@ -13427,7 +13519,7 @@ snapshots: cspell-io: 10.0.1 cspell-lib: 10.0.1 fast-json-stable-stringify: 2.1.0 - flatted: 3.4.2 + flatted: 3.4.4 semver: 7.8.5 tinyglobby: 0.2.17 @@ -13685,7 +13777,7 @@ snapshots: fs-extra: 10.1.0 lazy-val: 1.0.5 simple-update-notifier: 2.0.0 - yargs: 17.7.2 + yargs: 17.7.3 transitivePeerDependencies: - electron-builder-squirrel-windows - supports-color @@ -13719,7 +13811,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(@swc/core@1.15.43(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)): + electron-vite@5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) @@ -13727,9 +13819,9 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) optionalDependencies: - '@swc/core': 1.15.43(@swc/helpers@0.5.23) + '@swc/core': 1.15.47(@swc/helpers@0.5.23) transitivePeerDependencies: - supports-color @@ -13779,6 +13871,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -13886,35 +13980,35 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-jest-dom@5.5.0(@testing-library/dom@10.1.0)(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)): + eslint-plugin-jest-dom@5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)): dependencies: - '@babel/runtime': 7.29.7 - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) requireindex: 1.2.0 optionalDependencies: - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.1 - eslint-plugin-storybook@10.4.6(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2): + eslint-plugin-storybook@10.5.7(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2): dependencies: - '@typescript-eslint/utils': 8.62.0(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-testing-library@7.16.2(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2): + eslint-plugin-testing-library@7.16.2(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2): dependencies: - '@typescript-eslint/scope-manager': 8.62.0 - '@typescript-eslint/utils': 8.62.0(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-unused-imports@4.4.1(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)): + eslint-plugin-unused-imports@4.4.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)): dependencies: - eslint: 10.6.0(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) eslint-scope@9.1.2: dependencies: @@ -13927,12 +14021,12 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1): + eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.6.1)(supports-color@8.1.1)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@8.1.1) - '@eslint/config-helpers': 0.6.0 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 @@ -13956,7 +14050,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.5 + minimatch: 10.2.6 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -13966,8 +14060,8 @@ snapshots: espree@11.2.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 5.0.1 esprima@4.0.1: {} @@ -14057,9 +14151,9 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 fecha@4.2.3: {} @@ -14094,10 +14188,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 - flatted@3.4.2: {} + flatted@3.4.4: {} fn.name@1.1.0: {} @@ -14206,7 +14300,7 @@ snapshots: glob@13.0.6: dependencies: - minimatch: 10.2.5 + minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 @@ -14233,7 +14327,7 @@ snapshots: dependencies: ini: 6.0.0 - globals@17.7.0: {} + globals@17.9.0: {} globalthis@1.0.4: dependencies: @@ -14268,7 +14362,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql@16.14.0: {} + graphql@16.14.2: {} has-flag@4.0.0: {} @@ -14290,7 +14384,7 @@ snapshots: headers-polyfill@5.0.1: dependencies: '@types/set-cookie-parser': 2.4.10 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.1.2 highlight-words-core@1.2.3: {} @@ -14350,7 +14444,7 @@ snapshots: ignore@7.0.5: {} - immer@11.1.8: {} + immer@11.1.16: {} import-fresh@3.3.1: dependencies: @@ -14359,11 +14453,10 @@ snapshots: import-fresh@4.0.0: {} - import-in-the-middle@3.0.1: + import-in-the-middle@3.3.3: dependencies: - acorn: 8.17.0 - acorn-import-attributes: 1.9.5(acorn@8.17.0) cjs-module-lexer: 2.2.0 + es-module-lexer: 2.3.1 module-details-from-path: 1.0.4 import-local@3.2.0: @@ -14447,7 +14540,7 @@ snapshots: istanbul-lib-instrument@6.0.3(supports-color@8.1.1): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 semver: 7.8.5 @@ -14529,7 +14622,7 @@ snapshots: jest-config: 30.4.2(@types/node@24.13.3)(babel-plugin-macros@3.1.0)(supports-color@8.1.1) jest-util: 30.4.1 jest-validate: 30.4.1 - yargs: 17.7.2 + yargs: 17.7.3 transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -14630,7 +14723,7 @@ snapshots: jest-regex-util: 30.4.0 jest-util: 30.4.1 jest-worker: 30.4.1 - picomatch: 4.0.4 + picomatch: 4.0.5 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 @@ -14655,7 +14748,7 @@ snapshots: chalk: 4.1.2 graceful-fs: 4.2.11 jest-util: 30.4.1 - picomatch: 4.0.4 + picomatch: 4.0.5 pretty-format: 30.4.1 slash: 3.0.0 stack-utils: 2.0.6 @@ -14747,10 +14840,10 @@ snapshots: jest-snapshot@30.4.1(supports-color@8.1.1): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@jest/expect-utils': 30.4.1 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.4.1 @@ -14770,10 +14863,10 @@ snapshots: transitivePeerDependencies: - supports-color - jest-styled-components@7.4.0(styled-components@6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)): + jest-styled-components@7.4.0(styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)): dependencies: '@adobe/css-tools': 4.5.0 - styled-components: 6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + styled-components: 6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) jest-util@30.4.1: dependencies: @@ -14782,7 +14875,7 @@ snapshots: chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 - picomatch: 4.0.4 + picomatch: 4.0.5 jest-validate@30.4.1: dependencies: @@ -14868,7 +14961,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.21.0 + ws: 8.21.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -14892,6 +14985,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -14917,54 +15012,54 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.32.0: + lightningcss-android-arm64@1.33.0: optional: true - lightningcss-darwin-arm64@1.32.0: + lightningcss-darwin-arm64@1.33.0: optional: true - lightningcss-darwin-x64@1.32.0: + lightningcss-darwin-x64@1.33.0: optional: true - lightningcss-freebsd-x64@1.32.0: + lightningcss-freebsd-x64@1.33.0: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: + lightningcss-linux-arm-gnueabihf@1.33.0: optional: true - lightningcss-linux-arm64-gnu@1.32.0: + lightningcss-linux-arm64-gnu@1.33.0: optional: true - lightningcss-linux-arm64-musl@1.32.0: + lightningcss-linux-arm64-musl@1.33.0: optional: true - lightningcss-linux-x64-gnu@1.32.0: + lightningcss-linux-x64-gnu@1.33.0: optional: true - lightningcss-linux-x64-musl@1.32.0: + lightningcss-linux-x64-musl@1.33.0: optional: true - lightningcss-win32-arm64-msvc@1.32.0: + lightningcss-win32-arm64-msvc@1.33.0: optional: true - lightningcss-win32-x64-msvc@1.32.0: + lightningcss-win32-x64-msvc@1.33.0: optional: true - lightningcss@1.32.0: + lightningcss@1.33.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 lines-and-columns@1.2.4: {} @@ -15009,7 +15104,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.5.1: {} + lru-cache@11.5.2: {} lru-cache@5.1.1: dependencies: @@ -15071,7 +15166,7 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.2.5: + minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 @@ -15101,19 +15196,19 @@ snapshots: ms@2.1.3: {} - msw-storybook-addon@3.0.0(msw@2.14.6(@types/node@24.13.3)(typescript@7.0.2))(storybook@10.4.6): + msw-storybook-addon@3.0.0(msw@2.15.0(@types/node@24.13.3)(typescript@7.0.2))(storybook@10.5.7): dependencies: - msw: 2.14.6(@types/node@24.13.3)(typescript@7.0.2) - storybook: 10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7) + msw: 2.15.0(@types/node@24.13.3)(typescript@7.0.2) + storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) - msw@2.14.6(@types/node@24.13.3)(typescript@7.0.2): + msw@2.15.0(@types/node@24.13.3)(typescript@7.0.2): dependencies: - '@inquirer/confirm': 6.1.0(@types/node@24.13.3) + '@inquirer/confirm': 6.1.1(@types/node@24.13.3) '@mswjs/interceptors': 0.41.9 '@open-draft/deferred-promise': 3.0.0 '@types/statuses': 2.0.6 cookie: 1.1.1 - graphql: 16.14.0 + graphql: 16.14.2 headers-polyfill: 5.0.1 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -15122,27 +15217,27 @@ snapshots: rettime: 0.11.11 statuses: 2.0.2 strict-event-emitter: 0.5.1 - tough-cookie: 6.0.1 - type-fest: 5.6.0 + tough-cookie: 6.0.2 + type-fest: 5.8.0 until-async: 3.0.2 - yargs: 17.7.2 + yargs: 17.7.3 optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: - '@types/node' - mute-stream@4.0.0: {} + mute-stream@3.0.0: {} - nanoid@3.3.17: {} + nanoid@3.3.18: {} napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} - next-themes@0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next-themes@0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) node-abi@4.33.0: dependencies: @@ -15277,77 +15372,73 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 - oxc-resolver@11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1): + oxc-resolver@11.24.2: optionalDependencies: - '@oxc-resolver/binding-android-arm-eabi': 11.19.1 - '@oxc-resolver/binding-android-arm64': 11.19.1 - '@oxc-resolver/binding-darwin-arm64': 11.19.1 - '@oxc-resolver/binding-darwin-x64': 11.19.1 - '@oxc-resolver/binding-freebsd-x64': 11.19.1 - '@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.1 - '@oxc-resolver/binding-linux-arm-musleabihf': 11.19.1 - '@oxc-resolver/binding-linux-arm64-gnu': 11.19.1 - '@oxc-resolver/binding-linux-arm64-musl': 11.19.1 - '@oxc-resolver/binding-linux-ppc64-gnu': 11.19.1 - '@oxc-resolver/binding-linux-riscv64-gnu': 11.19.1 - '@oxc-resolver/binding-linux-riscv64-musl': 11.19.1 - '@oxc-resolver/binding-linux-s390x-gnu': 11.19.1 - '@oxc-resolver/binding-linux-x64-gnu': 11.19.1 - '@oxc-resolver/binding-linux-x64-musl': 11.19.1 - '@oxc-resolver/binding-openharmony-arm64': 11.19.1 - '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - '@oxc-resolver/binding-win32-arm64-msvc': 11.19.1 - '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 - '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - oxfmt@0.56.0: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + + oxfmt@0.62.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.56.0 - '@oxfmt/binding-android-arm64': 0.56.0 - '@oxfmt/binding-darwin-arm64': 0.56.0 - '@oxfmt/binding-darwin-x64': 0.56.0 - '@oxfmt/binding-freebsd-x64': 0.56.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.56.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.56.0 - '@oxfmt/binding-linux-arm64-gnu': 0.56.0 - '@oxfmt/binding-linux-arm64-musl': 0.56.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.56.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.56.0 - '@oxfmt/binding-linux-riscv64-musl': 0.56.0 - '@oxfmt/binding-linux-s390x-gnu': 0.56.0 - '@oxfmt/binding-linux-x64-gnu': 0.56.0 - '@oxfmt/binding-linux-x64-musl': 0.56.0 - '@oxfmt/binding-openharmony-arm64': 0.56.0 - '@oxfmt/binding-win32-arm64-msvc': 0.56.0 - '@oxfmt/binding-win32-ia32-msvc': 0.56.0 - '@oxfmt/binding-win32-x64-msvc': 0.56.0 - - oxlint@1.71.0: + '@oxfmt/binding-android-arm-eabi': 0.62.0 + '@oxfmt/binding-android-arm64': 0.62.0 + '@oxfmt/binding-darwin-arm64': 0.62.0 + '@oxfmt/binding-darwin-x64': 0.62.0 + '@oxfmt/binding-freebsd-x64': 0.62.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.62.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.62.0 + '@oxfmt/binding-linux-arm64-gnu': 0.62.0 + '@oxfmt/binding-linux-arm64-musl': 0.62.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-musl': 0.62.0 + '@oxfmt/binding-linux-s390x-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-musl': 0.62.0 + '@oxfmt/binding-openharmony-arm64': 0.62.0 + '@oxfmt/binding-win32-arm64-msvc': 0.62.0 + '@oxfmt/binding-win32-ia32-msvc': 0.62.0 + '@oxfmt/binding-win32-x64-msvc': 0.62.0 + + oxlint@1.77.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.71.0 - '@oxlint/binding-android-arm64': 1.71.0 - '@oxlint/binding-darwin-arm64': 1.71.0 - '@oxlint/binding-darwin-x64': 1.71.0 - '@oxlint/binding-freebsd-x64': 1.71.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.71.0 - '@oxlint/binding-linux-arm-musleabihf': 1.71.0 - '@oxlint/binding-linux-arm64-gnu': 1.71.0 - '@oxlint/binding-linux-arm64-musl': 1.71.0 - '@oxlint/binding-linux-ppc64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-musl': 1.71.0 - '@oxlint/binding-linux-s390x-gnu': 1.71.0 - '@oxlint/binding-linux-x64-gnu': 1.71.0 - '@oxlint/binding-linux-x64-musl': 1.71.0 - '@oxlint/binding-openharmony-arm64': 1.71.0 - '@oxlint/binding-win32-arm64-msvc': 1.71.0 - '@oxlint/binding-win32-ia32-msvc': 1.71.0 - '@oxlint/binding-win32-x64-msvc': 1.71.0 + '@oxlint/binding-android-arm-eabi': 1.77.0 + '@oxlint/binding-android-arm64': 1.77.0 + '@oxlint/binding-darwin-arm64': 1.77.0 + '@oxlint/binding-darwin-x64': 1.77.0 + '@oxlint/binding-freebsd-x64': 1.77.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.77.0 + '@oxlint/binding-linux-arm-musleabihf': 1.77.0 + '@oxlint/binding-linux-arm64-gnu': 1.77.0 + '@oxlint/binding-linux-arm64-musl': 1.77.0 + '@oxlint/binding-linux-ppc64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-musl': 1.77.0 + '@oxlint/binding-linux-s390x-gnu': 1.77.0 + '@oxlint/binding-linux-x64-gnu': 1.77.0 + '@oxlint/binding-linux-x64-musl': 1.77.0 + '@oxlint/binding-openharmony-arm64': 1.77.0 + '@oxlint/binding-win32-arm64-msvc': 1.77.0 + '@oxlint/binding-win32-ia32-msvc': 1.77.0 + '@oxlint/binding-win32-x64-msvc': 1.77.0 p-cancelable@2.1.1: {} @@ -15403,7 +15494,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.5.1 + lru-cache: 11.5.2 minipass: 7.1.3 path-to-regexp@6.3.0: {} @@ -15420,7 +15511,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pirates@4.0.7: {} @@ -15437,11 +15528,11 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 - playwright-core@1.61.1: {} + playwright-core@1.62.1: {} - playwright@1.61.1: + playwright@1.62.1: dependencies: - playwright-core: 1.61.1 + playwright-core: 1.62.1 optionalDependencies: fsevents: 2.3.2 @@ -15453,9 +15544,9 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.25: + postcss@8.5.26: dependencies: - nanoid: 3.3.17 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -15482,7 +15573,7 @@ snapshots: '@jest/schemas': 30.4.1 ansi-styles: 5.2.0 react-is-18: react-is@18.3.1 - react-is-19: react-is@19.2.7 + react-is-19: react-is@19.2.8 proc-log@6.1.0: {} @@ -15546,29 +15637,29 @@ snapshots: quick-lru@5.1.1: {} - react-day-picker@10.0.1(@types/react@19.2.17)(react@19.2.7): + react-day-picker@10.0.1(@types/react@19.2.18)(react@19.2.8): dependencies: '@date-fns/tz': 1.4.1 date-fns: 4.4.0 - react: 19.2.7 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 react-dnd-html5-backend@16.0.1: dependencies: dnd-core: 16.0.1 - react-dnd@16.0.1(@types/node@24.13.3)(@types/react@19.2.17)(react@19.2.7): + react-dnd@16.0.1(@types/node@24.13.3)(@types/react@19.2.18)(react@19.2.8): dependencies: '@react-dnd/invariant': 4.0.2 '@react-dnd/shallowequal': 4.0.2 dnd-core: 16.0.1 fast-deep-equal: 3.1.3 hoist-non-react-statics: 3.3.2 - react: 19.2.7 + react: 19.2.8 optionalDependencies: '@types/node': 24.13.3 - '@types/react': 19.2.17 + '@types/react': 19.2.18 react-docgen-typescript@2.4.0(typescript@7.0.2): dependencies: @@ -15577,8 +15668,8 @@ snapshots: react-docgen@8.0.3(supports-color@8.1.1): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) - '@babel/traverse': 7.29.7(supports-color@8.1.1) - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 '@types/doctrine': 0.0.9 @@ -15589,18 +15680,18 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@19.2.7(react@19.2.7): + react-dom@19.2.8(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 scheduler: 0.27.0 - react-error-boundary@6.1.2(react@19.2.7): + react-error-boundary@6.1.2(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 - react-hook-form@7.80.0(react@19.2.7): + react-hook-form@7.85.0(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 react-is@16.13.1: {} @@ -15608,51 +15699,51 @@ snapshots: react-is@18.3.1: {} - react-is@19.2.7: {} + react-is@19.2.8: {} - react-router@8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: cookie-es: 3.1.1 - react: 19.2.7 + react: 19.2.8 optionalDependencies: - react-dom: 19.2.7(react@19.2.7) + react-dom: 19.2.8(react@19.2.8) react-select-event@5.5.1: dependencies: - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.1 - react-select@5.10.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(supports-color@8.1.1): + react-select@5.10.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1): dependencies: '@babel/runtime': 7.29.7 '@emotion/cache': 11.14.0 - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)(supports-color@8.1.1) - '@floating-ui/dom': 1.7.6 - '@types/react-transition-group': 4.4.12(@types/react@19.2.17) + '@emotion/react': 11.14.0(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) + '@floating-ui/dom': 1.8.0 + '@types/react-transition-group': 4.4.12(@types/react@19.2.18) memoize-one: 6.0.0 prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.17)(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-transition-group: 4.4.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.18)(react@19.2.8) transitivePeerDependencies: - '@types/react' - supports-color - react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-transition-group@4.4.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@babel/runtime': 7.29.7 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - react-virtualized-auto-sizer@1.0.26(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-virtualized-auto-sizer@1.0.26(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - react@19.2.7: {} + react@19.2.8: {} read-binary-file-arch@1.0.6(supports-color@8.1.1): dependencies: @@ -15678,7 +15769,7 @@ snapshots: readdirp@5.0.0: {} - recast@0.23.11: + recast@0.23.19: dependencies: ast-types: 0.16.1 esprima: 4.0.1 @@ -15772,35 +15863,34 @@ snapshots: sprintf-js: 1.1.3 optional: true - rolldown@1.1.3: + rolldown@1.2.3: dependencies: - '@oxc-project/types': 0.137.0 + '@oxc-project/types': 0.143.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.3 - '@rolldown/binding-darwin-arm64': 1.1.3 - '@rolldown/binding-darwin-x64': 1.1.3 - '@rolldown/binding-freebsd-x64': 1.1.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 - '@rolldown/binding-linux-arm64-gnu': 1.1.3 - '@rolldown/binding-linux-arm64-musl': 1.1.3 - '@rolldown/binding-linux-ppc64-gnu': 1.1.3 - '@rolldown/binding-linux-s390x-gnu': 1.1.3 - '@rolldown/binding-linux-x64-gnu': 1.1.3 - '@rolldown/binding-linux-x64-musl': 1.1.3 - '@rolldown/binding-openharmony-arm64': 1.1.3 - '@rolldown/binding-wasm32-wasi': 1.1.3 - '@rolldown/binding-win32-arm64-msvc': 1.1.3 - '@rolldown/binding-win32-x64-msvc': 1.1.3 - - rollup-plugin-visualizer@7.0.1(rolldown@1.1.3): + '@rolldown/binding-android-arm64': 1.2.3 + '@rolldown/binding-darwin-arm64': 1.2.3 + '@rolldown/binding-darwin-x64': 1.2.3 + '@rolldown/binding-freebsd-x64': 1.2.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 + '@rolldown/binding-linux-arm64-gnu': 1.2.3 + '@rolldown/binding-linux-arm64-musl': 1.2.3 + '@rolldown/binding-linux-ppc64-gnu': 1.2.3 + '@rolldown/binding-linux-s390x-gnu': 1.2.3 + '@rolldown/binding-linux-x64-gnu': 1.2.3 + '@rolldown/binding-linux-x64-musl': 1.2.3 + '@rolldown/binding-openharmony-arm64': 1.2.3 + '@rolldown/binding-win32-arm64-msvc': 1.2.3 + '@rolldown/binding-win32-x64-msvc': 1.2.3 + + rollup-plugin-visualizer@7.0.1(rolldown@1.2.3): dependencies: open: 11.0.0 - picomatch: 4.0.4 + picomatch: 4.0.5 source-map: 0.7.4 yargs: 18.0.0 optionalDependencies: - rolldown: 1.1.3 + rolldown: 1.2.3 rrweb-cssom@0.8.0: {} @@ -15848,7 +15938,7 @@ snapshots: type-fest: 0.13.1 optional: true - set-cookie-parser@3.1.0: {} + set-cookie-parser@3.1.2: {} shebang-command@2.0.0: dependencies: @@ -15907,31 +15997,30 @@ snapshots: statuses@2.0.2: {} - storybook@10.4.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@storybook/react-vite@10.4.6)(@testing-library/dom@10.1.0)(@types/react@19.2.17)(prettier@3.8.1)(react@19.2.7): + storybook@10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8): dependencies: '@storybook/global': 5.0.0 - '@storybook/icons': 2.1.0(react@19.2.7) - '@storybook/react-vite': 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)) + '@storybook/icons': 2.1.0(react@19.2.8) + '@storybook/react-vite': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@testing-library/dom': 10.4.1 '@testing-library/jest-dom': 6.9.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.1.0) + '@testing-library/user-event': 14.6.3(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 '@vitest/spy': 3.2.4 '@webcontainer/env': 1.1.1 esbuild: 0.28.1 + jsonc-parser: 3.3.1 open: 10.2.0 oxc-parser: 0.127.0 - oxc-resolver: 11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - recast: 0.23.11 + oxc-resolver: 11.24.2 + recast: 0.23.19 semver: 7.8.5 - use-sync-external-store: 1.6.0(react@19.2.7) - ws: 8.21.0 + use-sync-external-store: 1.6.0(react@19.2.8) + ws: 8.21.3 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 prettier: 3.8.1 transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@testing-library/dom' - bufferutil - react - utf-8-validate @@ -16002,15 +16091,15 @@ snapshots: style-mod@4.1.3: {} - styled-components@6.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@emotion/is-prop-valid': 1.4.0 css-to-react-native: 3.2.0 csstype: 3.2.3 - react: 19.2.7 + react: 19.2.8 stylis: 4.3.6 optionalDependencies: - react-dom: 19.2.7(react@19.2.7) + react-dom: 19.2.8(react@19.2.8) styled-system@5.1.5: dependencies: @@ -16064,7 +16153,7 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - tabbable@6.4.0: {} + tabbable@6.5.0: {} tagged-tag@1.0.0: {} @@ -16139,8 +16228,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinypool@2.1.0: {} @@ -16150,15 +16239,15 @@ snapshots: tldts-core@6.1.86: {} - tldts-core@7.4.0: {} + tldts-core@7.4.10: {} tldts@6.1.86: dependencies: tldts-core: 6.1.86 - tldts@7.4.0: + tldts@7.4.10: dependencies: - tldts-core: 7.4.0 + tldts-core: 7.4.10 tmp-promise@3.0.3: dependencies: @@ -16176,9 +16265,9 @@ snapshots: dependencies: tldts: 6.1.86 - tough-cookie@6.0.1: + tough-cookie@6.0.2: dependencies: - tldts: 7.4.0 + tldts: 7.4.10 tr46@5.1.1: dependencies: @@ -16208,7 +16297,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.22.4: + tsx@4.23.11: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -16225,7 +16314,7 @@ snapshots: type-fest@0.21.3: {} - type-fest@5.6.0: + type-fest@5.8.0: dependencies: tagged-tag: 1.0.0 @@ -16283,8 +16372,8 @@ snapshots: unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 - acorn: 8.17.0 - picomatch: 4.0.4 + acorn: 8.18.0 + picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 unrs-resolver@1.12.2: @@ -16338,29 +16427,29 @@ snapshots: dependencies: punycode: 2.3.1 - use-debounce@10.0.4(react@19.2.7): + use-debounce@10.0.4(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 - use-immer@0.11.0(immer@11.1.8)(react@19.2.7): + use-immer@0.11.0(immer@11.1.16)(react@19.2.8): dependencies: - immer: 11.1.8 - react: 19.2.7 + immer: 11.1.16 + react: 19.2.8 - use-isomorphic-layout-effect@1.2.1(@types/react@19.2.17)(react@19.2.7): + use-isomorphic-layout-effect@1.2.1(@types/react@19.2.18)(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - use-sync-external-store@1.6.0(react@19.2.7): + use-sync-external-store@1.6.0(react@19.2.8): dependencies: - react: 19.2.7 + react: 19.2.8 - usehooks-ts@3.1.1(react@19.2.7): + usehooks-ts@3.1.1(react@19.2.8): dependencies: lodash.debounce: 4.0.8 - react: 19.2.7 + react: 19.2.8 utf8-byte-length@1.0.5: {} @@ -16381,23 +16470,23 @@ snapshots: transitivePeerDependencies: - rollup - vite-plugin-wasm@3.6.0(vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0)): + vite-plugin-wasm@3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)): dependencies: - vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) - vite@8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.22.4)(yaml@2.9.0): + vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0): dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.25 - rolldown: 1.1.3 + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.3 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.13.3 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.6.1 - tsx: 4.22.4 + tsx: 4.23.11 yaml: 2.9.0 vscode-languageserver-textdocument@1.0.12: {} @@ -16504,7 +16593,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.21.0: {} + ws@8.21.3: {} wsl-utils@0.1.0: dependencies: @@ -16539,7 +16628,7 @@ snapshots: yargs-parser@22.0.0: {} - yargs@17.7.2: + yargs@17.7.3: dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -16564,4 +16653,4 @@ snapshots: zod@4.4.3: {} - zone.js@0.16.1: {} + zone.js@0.16.2: {} diff --git a/web/packages/build/package.json b/web/packages/build/package.json index e530d093ec81..4e842388b198 100644 --- a/web/packages/build/package.json +++ b/web/packages/build/package.json @@ -16,10 +16,10 @@ "@babel/preset-typescript": "^7.29.7", "@gravitational/swc-plugin-jsx-marker": "https://github.com/gravitational/design-system/releases/download/@gravitational/swc-plugin-jsx-marker@0.1.0/swc-plugin-jsx-marker.tgz", "@gravitational/swc-plugin-styled-components": "https://github.com/gravitational/design-system/releases/download/@gravitational/swc-plugin-styled-components@0.1.0/swc-plugin-styled-components.tgz", - "@swc/core": "^1.15.43", - "@vitejs/plugin-react-swc": "^4.3.1", + "@swc/core": "^1.15.47", + "@vitejs/plugin-react-swc": "^4.3.3", "babel-plugin-styled-components": "^2.3.0", - "globals": "^17.7.0", + "globals": "^17.9.0", "jest-environment-jsdom": "^30.4.1", "jest-fail-on-console": "^3.3.4", "rollup-plugin-visualizer": "^7.0.1", diff --git a/web/packages/design/src/Icon/Icons/Add.tsx b/web/packages/design/src/Icon/Icons/Add.tsx index 2174b097cfd9..24ed1351b9af 100644 --- a/web/packages/design/src/Icon/Icons/Add.tsx +++ b/web/packages/design/src/Icon/Icons/Add.tsx @@ -56,6 +56,7 @@ export const Add = forwardRef( size={size} color={color} className="icon icon-add" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/AddCircle.tsx b/web/packages/design/src/Icon/Icons/AddCircle.tsx index 9d81bdb3117f..253152a4134b 100644 --- a/web/packages/design/src/Icon/Icons/AddCircle.tsx +++ b/web/packages/design/src/Icon/Icons/AddCircle.tsx @@ -56,6 +56,7 @@ export const AddCircle = forwardRef( size={size} color={color} className="icon icon-addcircle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/AddUsers.tsx b/web/packages/design/src/Icon/Icons/AddUsers.tsx index bd5f08ecf9a2..4d1a76c05da7 100644 --- a/web/packages/design/src/Icon/Icons/AddUsers.tsx +++ b/web/packages/design/src/Icon/Icons/AddUsers.tsx @@ -56,6 +56,7 @@ export const AddUsers = forwardRef( size={size} color={color} className="icon icon-addusers" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/AlarmRing.tsx b/web/packages/design/src/Icon/Icons/AlarmRing.tsx index 45ceaae91fd3..3c11da8533b0 100644 --- a/web/packages/design/src/Icon/Icons/AlarmRing.tsx +++ b/web/packages/design/src/Icon/Icons/AlarmRing.tsx @@ -56,6 +56,7 @@ export const AlarmRing = forwardRef( size={size} color={color} className="icon icon-alarmring" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/AmazonAws.tsx b/web/packages/design/src/Icon/Icons/AmazonAws.tsx index 4118516ef80b..23c5c0228c64 100644 --- a/web/packages/design/src/Icon/Icons/AmazonAws.tsx +++ b/web/packages/design/src/Icon/Icons/AmazonAws.tsx @@ -56,6 +56,7 @@ export const AmazonAws = forwardRef( size={size} color={color} className="icon icon-amazonaws" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Apartment.tsx b/web/packages/design/src/Icon/Icons/Apartment.tsx index e11eba96c72c..c1ebf04dc47d 100644 --- a/web/packages/design/src/Icon/Icons/Apartment.tsx +++ b/web/packages/design/src/Icon/Icons/Apartment.tsx @@ -56,6 +56,7 @@ export const Apartment = forwardRef( size={size} color={color} className="icon icon-apartment" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Apple.tsx b/web/packages/design/src/Icon/Icons/Apple.tsx index 6ca984ad8735..1782175b794d 100644 --- a/web/packages/design/src/Icon/Icons/Apple.tsx +++ b/web/packages/design/src/Icon/Icons/Apple.tsx @@ -56,6 +56,7 @@ export const Apple = forwardRef( size={size} color={color} className="icon icon-apple" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Application.tsx b/web/packages/design/src/Icon/Icons/Application.tsx index 9a716aae0dd4..d047f27f8000 100644 --- a/web/packages/design/src/Icon/Icons/Application.tsx +++ b/web/packages/design/src/Icon/Icons/Application.tsx @@ -56,6 +56,7 @@ export const Application = forwardRef( size={size} color={color} className="icon icon-application" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Archive.tsx b/web/packages/design/src/Icon/Icons/Archive.tsx index 04ce58c97a3b..e2acb3dce215 100644 --- a/web/packages/design/src/Icon/Icons/Archive.tsx +++ b/web/packages/design/src/Icon/Icons/Archive.tsx @@ -56,6 +56,7 @@ export const Archive = forwardRef( size={size} color={color} className="icon icon-archive" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowBack.tsx b/web/packages/design/src/Icon/Icons/ArrowBack.tsx index 5d040eaf31ab..bc4798c680a5 100644 --- a/web/packages/design/src/Icon/Icons/ArrowBack.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowBack.tsx @@ -56,6 +56,7 @@ export const ArrowBack = forwardRef( size={size} color={color} className="icon icon-arrowback" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowDown.tsx b/web/packages/design/src/Icon/Icons/ArrowDown.tsx index 7217995c1687..7ffbd0873f5b 100644 --- a/web/packages/design/src/Icon/Icons/ArrowDown.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowDown.tsx @@ -56,6 +56,7 @@ export const ArrowDown = forwardRef( size={size} color={color} className="icon icon-arrowdown" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx b/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx index de42df82db72..35e75e2f8f03 100644 --- a/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx @@ -56,6 +56,7 @@ export const ArrowFatLinesUp = forwardRef( size={size} color={color} className="icon icon-arrowfatlinesup" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowForward.tsx b/web/packages/design/src/Icon/Icons/ArrowForward.tsx index aef1aec1b74b..3760682db4a8 100644 --- a/web/packages/design/src/Icon/Icons/ArrowForward.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowForward.tsx @@ -56,6 +56,7 @@ export const ArrowForward = forwardRef( size={size} color={color} className="icon icon-arrowforward" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowLeft.tsx b/web/packages/design/src/Icon/Icons/ArrowLeft.tsx index c98c1ac9b7b1..440652ed307f 100644 --- a/web/packages/design/src/Icon/Icons/ArrowLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowLeft.tsx @@ -56,6 +56,7 @@ export const ArrowLeft = forwardRef( size={size} color={color} className="icon icon-arrowleft" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx index 5f0ddd9b8c1b..1b083ebee65d 100644 --- a/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx @@ -56,6 +56,7 @@ export const ArrowLineLeft = forwardRef( size={size} color={color} className="icon icon-arrowlineleft" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowRight.tsx b/web/packages/design/src/Icon/Icons/ArrowRight.tsx index adba3cff621e..fc2f475cacf5 100644 --- a/web/packages/design/src/Icon/Icons/ArrowRight.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowRight.tsx @@ -56,6 +56,7 @@ export const ArrowRight = forwardRef( size={size} color={color} className="icon icon-arrowright" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx b/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx index fe949d5611c6..8a281444e795 100644 --- a/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowSquareIn.tsx @@ -56,6 +56,7 @@ export const ArrowSquareIn = forwardRef( size={size} color={color} className="icon icon-arrowsquarein" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx b/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx index a898295cd234..a4c4e16936e7 100644 --- a/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx @@ -56,6 +56,7 @@ export const ArrowSquareOut = forwardRef( size={size} color={color} className="icon icon-arrowsquareout" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowUp.tsx b/web/packages/design/src/Icon/Icons/ArrowUp.tsx index b93c9c4a0861..d4c1da949637 100644 --- a/web/packages/design/src/Icon/Icons/ArrowUp.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowUp.tsx @@ -56,6 +56,7 @@ export const ArrowUp = forwardRef( size={size} color={color} className="icon icon-arrowup" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowUpRight.tsx b/web/packages/design/src/Icon/Icons/ArrowUpRight.tsx index 22fc13df7a9f..34bfa2cd03a6 100644 --- a/web/packages/design/src/Icon/Icons/ArrowUpRight.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowUpRight.tsx @@ -56,6 +56,7 @@ export const ArrowUpRight = forwardRef( size={size} color={color} className="icon icon-arrowupright" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowsIn.tsx b/web/packages/design/src/Icon/Icons/ArrowsIn.tsx index f2103d8b1d82..a4382aedd35a 100644 --- a/web/packages/design/src/Icon/Icons/ArrowsIn.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowsIn.tsx @@ -56,6 +56,7 @@ export const ArrowsIn = forwardRef( size={size} color={color} className="icon icon-arrowsin" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ArrowsOut.tsx b/web/packages/design/src/Icon/Icons/ArrowsOut.tsx index 034b2aa72091..cb2168e2a6a6 100644 --- a/web/packages/design/src/Icon/Icons/ArrowsOut.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowsOut.tsx @@ -56,6 +56,7 @@ export const ArrowsOut = forwardRef( size={size} color={color} className="icon icon-arrowsout" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Beams.tsx b/web/packages/design/src/Icon/Icons/Beams.tsx index 3f22c0362b5d..2ee2b09c7802 100644 --- a/web/packages/design/src/Icon/Icons/Beams.tsx +++ b/web/packages/design/src/Icon/Icons/Beams.tsx @@ -56,6 +56,7 @@ export const Beams = forwardRef( size={size} color={color} className="icon icon-beams" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/BedrockLogo.tsx b/web/packages/design/src/Icon/Icons/BedrockLogo.tsx index e632cfa93e92..af480c63996a 100644 --- a/web/packages/design/src/Icon/Icons/BedrockLogo.tsx +++ b/web/packages/design/src/Icon/Icons/BedrockLogo.tsx @@ -56,6 +56,7 @@ export const BedrockLogo = forwardRef( size={size} color={color} className="icon icon-bedrocklogo" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Bell.tsx b/web/packages/design/src/Icon/Icons/Bell.tsx index 6791b9322058..abd3f20efd9d 100644 --- a/web/packages/design/src/Icon/Icons/Bell.tsx +++ b/web/packages/design/src/Icon/Icons/Bell.tsx @@ -56,6 +56,7 @@ export const Bell = forwardRef( size={size} color={color} className="icon icon-bell" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/BellRinging.tsx b/web/packages/design/src/Icon/Icons/BellRinging.tsx index 32a4b63eb261..b750c414d56d 100644 --- a/web/packages/design/src/Icon/Icons/BellRinging.tsx +++ b/web/packages/design/src/Icon/Icons/BellRinging.tsx @@ -56,6 +56,7 @@ export const BellRinging = forwardRef( size={size} color={color} className="icon icon-bellringing" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/BookOpenText.tsx b/web/packages/design/src/Icon/Icons/BookOpenText.tsx index 525569030400..ab5343093ff7 100644 --- a/web/packages/design/src/Icon/Icons/BookOpenText.tsx +++ b/web/packages/design/src/Icon/Icons/BookOpenText.tsx @@ -56,6 +56,7 @@ export const BookOpenText = forwardRef( size={size} color={color} className="icon icon-bookopentext" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Bots.tsx b/web/packages/design/src/Icon/Icons/Bots.tsx index 5a64c208330e..bc2bcb3e3556 100644 --- a/web/packages/design/src/Icon/Icons/Bots.tsx +++ b/web/packages/design/src/Icon/Icons/Bots.tsx @@ -56,6 +56,7 @@ export const Bots = forwardRef( size={size} color={color} className="icon icon-bots" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Broadcast.tsx b/web/packages/design/src/Icon/Icons/Broadcast.tsx index 6e7193ba64d1..855b9311ec29 100644 --- a/web/packages/design/src/Icon/Icons/Broadcast.tsx +++ b/web/packages/design/src/Icon/Icons/Broadcast.tsx @@ -56,6 +56,7 @@ export const Broadcast = forwardRef( size={size} color={color} className="icon icon-broadcast" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx b/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx index 09c9a5a3f7ab..d25556c7d859 100644 --- a/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx +++ b/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx @@ -56,6 +56,7 @@ export const BroadcastSlash = forwardRef( size={size} color={color} className="icon icon-broadcastslash" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Bubble.tsx b/web/packages/design/src/Icon/Icons/Bubble.tsx index b56aceb95ae8..1833cb067774 100644 --- a/web/packages/design/src/Icon/Icons/Bubble.tsx +++ b/web/packages/design/src/Icon/Icons/Bubble.tsx @@ -56,6 +56,7 @@ export const Bubble = forwardRef( size={size} color={color} className="icon icon-bubble" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CCAmex.tsx b/web/packages/design/src/Icon/Icons/CCAmex.tsx index d59856e757f1..51a665bff4b1 100644 --- a/web/packages/design/src/Icon/Icons/CCAmex.tsx +++ b/web/packages/design/src/Icon/Icons/CCAmex.tsx @@ -56,6 +56,7 @@ export const CCAmex = forwardRef( size={size} color={color} className="icon icon-ccamex" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CCDiscover.tsx b/web/packages/design/src/Icon/Icons/CCDiscover.tsx index f82332c8d54e..2580109a6e6b 100644 --- a/web/packages/design/src/Icon/Icons/CCDiscover.tsx +++ b/web/packages/design/src/Icon/Icons/CCDiscover.tsx @@ -56,6 +56,7 @@ export const CCDiscover = forwardRef( size={size} color={color} className="icon icon-ccdiscover" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CCMasterCard.tsx b/web/packages/design/src/Icon/Icons/CCMasterCard.tsx index d8d169fbc2e3..0c990f8499b5 100644 --- a/web/packages/design/src/Icon/Icons/CCMasterCard.tsx +++ b/web/packages/design/src/Icon/Icons/CCMasterCard.tsx @@ -56,6 +56,7 @@ export const CCMasterCard = forwardRef( size={size} color={color} className="icon icon-ccmastercard" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CCStripe.tsx b/web/packages/design/src/Icon/Icons/CCStripe.tsx index 61b02c4ccbce..e8ef284d3c54 100644 --- a/web/packages/design/src/Icon/Icons/CCStripe.tsx +++ b/web/packages/design/src/Icon/Icons/CCStripe.tsx @@ -56,6 +56,7 @@ export const CCStripe = forwardRef( size={size} color={color} className="icon icon-ccstripe" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CCVisa.tsx b/web/packages/design/src/Icon/Icons/CCVisa.tsx index 1e9a7afff263..dc27e87e712f 100644 --- a/web/packages/design/src/Icon/Icons/CCVisa.tsx +++ b/web/packages/design/src/Icon/Icons/CCVisa.tsx @@ -56,6 +56,7 @@ export const CCVisa = forwardRef( size={size} color={color} className="icon icon-ccvisa" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Calendar.tsx b/web/packages/design/src/Icon/Icons/Calendar.tsx index c7817c4e6b2d..1cd8a9cbcb03 100644 --- a/web/packages/design/src/Icon/Icons/Calendar.tsx +++ b/web/packages/design/src/Icon/Icons/Calendar.tsx @@ -56,6 +56,7 @@ export const Calendar = forwardRef( size={size} color={color} className="icon icon-calendar" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Camera.tsx b/web/packages/design/src/Icon/Icons/Camera.tsx index f27591cc50c5..af7b19337f8d 100644 --- a/web/packages/design/src/Icon/Icons/Camera.tsx +++ b/web/packages/design/src/Icon/Icons/Camera.tsx @@ -56,6 +56,7 @@ export const Camera = forwardRef( size={size} color={color} className="icon icon-camera" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CardView.tsx b/web/packages/design/src/Icon/Icons/CardView.tsx index a707b7d38f51..d388ccb87cf2 100644 --- a/web/packages/design/src/Icon/Icons/CardView.tsx +++ b/web/packages/design/src/Icon/Icons/CardView.tsx @@ -56,6 +56,7 @@ export const CardView = forwardRef( size={size} color={color} className="icon icon-cardview" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cash.tsx b/web/packages/design/src/Icon/Icons/Cash.tsx index 68a5063c2764..da535dcbd214 100644 --- a/web/packages/design/src/Icon/Icons/Cash.tsx +++ b/web/packages/design/src/Icon/Icons/Cash.tsx @@ -56,6 +56,7 @@ export const Cash = forwardRef( size={size} color={color} className="icon icon-cash" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Chart.tsx b/web/packages/design/src/Icon/Icons/Chart.tsx index ab4e116013b7..051a5814c0e4 100644 --- a/web/packages/design/src/Icon/Icons/Chart.tsx +++ b/web/packages/design/src/Icon/Icons/Chart.tsx @@ -56,6 +56,7 @@ export const Chart = forwardRef( size={size} color={color} className="icon icon-chart" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChatBubble.tsx b/web/packages/design/src/Icon/Icons/ChatBubble.tsx index 6f2a8f13a57a..88db40dec1b7 100644 --- a/web/packages/design/src/Icon/Icons/ChatBubble.tsx +++ b/web/packages/design/src/Icon/Icons/ChatBubble.tsx @@ -56,6 +56,7 @@ export const ChatBubble = forwardRef( size={size} color={color} className="icon icon-chatbubble" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChatCircle.tsx b/web/packages/design/src/Icon/Icons/ChatCircle.tsx index 8318aed4c56e..db8240491bb2 100644 --- a/web/packages/design/src/Icon/Icons/ChatCircle.tsx +++ b/web/packages/design/src/Icon/Icons/ChatCircle.tsx @@ -56,6 +56,7 @@ export const ChatCircle = forwardRef( size={size} color={color} className="icon icon-chatcircle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx b/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx index 4041de0b3ada..4aa9abbc8dd9 100644 --- a/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx +++ b/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx @@ -56,6 +56,7 @@ export const ChatCircleSparkle = forwardRef( size={size} color={color} className="icon icon-chatcirclesparkle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Check.tsx b/web/packages/design/src/Icon/Icons/Check.tsx index 4932d112d508..f307c577ab2b 100644 --- a/web/packages/design/src/Icon/Icons/Check.tsx +++ b/web/packages/design/src/Icon/Icons/Check.tsx @@ -56,6 +56,7 @@ export const Check = forwardRef( size={size} color={color} className="icon icon-check" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CheckCircleDotted.tsx b/web/packages/design/src/Icon/Icons/CheckCircleDotted.tsx index 94dcfb0219dc..8f2d6e1519ad 100644 --- a/web/packages/design/src/Icon/Icons/CheckCircleDotted.tsx +++ b/web/packages/design/src/Icon/Icons/CheckCircleDotted.tsx @@ -56,6 +56,7 @@ export const CheckCircleDotted = forwardRef( size={size} color={color} className="icon icon-checkcircledotted" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CheckThick.tsx b/web/packages/design/src/Icon/Icons/CheckThick.tsx index fff24bfe0506..de962bc51620 100644 --- a/web/packages/design/src/Icon/Icons/CheckThick.tsx +++ b/web/packages/design/src/Icon/Icons/CheckThick.tsx @@ -56,6 +56,7 @@ export const CheckThick = forwardRef( size={size} color={color} className="icon icon-checkthick" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Checks.tsx b/web/packages/design/src/Icon/Icons/Checks.tsx index 230c9880728f..369aa7d52633 100644 --- a/web/packages/design/src/Icon/Icons/Checks.tsx +++ b/web/packages/design/src/Icon/Icons/Checks.tsx @@ -56,6 +56,7 @@ export const Checks = forwardRef( size={size} color={color} className="icon icon-checks" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx index c4916ab5df59..50fb410e8367 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx @@ -56,6 +56,7 @@ export const ChevronCircleDown = forwardRef( size={size} color={color} className="icon icon-chevroncircledown" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx index 16dc7c8bc0a4..5aab59db3f75 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx @@ -56,6 +56,7 @@ export const ChevronCircleLeft = forwardRef( size={size} color={color} className="icon icon-chevroncircleleft" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx index bcc15473ec57..8cf9f3ad2ccb 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx @@ -56,6 +56,7 @@ export const ChevronCircleRight = forwardRef( size={size} color={color} className="icon icon-chevroncircleright" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx index 214a6d6382ef..059d2153f027 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx @@ -56,6 +56,7 @@ export const ChevronCircleUp = forwardRef( size={size} color={color} className="icon icon-chevroncircleup" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronDown.tsx b/web/packages/design/src/Icon/Icons/ChevronDown.tsx index a7e2654f5ee4..95934c78be39 100644 --- a/web/packages/design/src/Icon/Icons/ChevronDown.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronDown.tsx @@ -56,6 +56,7 @@ export const ChevronDown = forwardRef( size={size} color={color} className="icon icon-chevrondown" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronLeft.tsx b/web/packages/design/src/Icon/Icons/ChevronLeft.tsx index d58a2721f996..fae4d9ba660b 100644 --- a/web/packages/design/src/Icon/Icons/ChevronLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronLeft.tsx @@ -56,6 +56,7 @@ export const ChevronLeft = forwardRef( size={size} color={color} className="icon icon-chevronleft" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronRight.tsx b/web/packages/design/src/Icon/Icons/ChevronRight.tsx index ef3ae4799727..1760a3eedaf1 100644 --- a/web/packages/design/src/Icon/Icons/ChevronRight.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronRight.tsx @@ -56,6 +56,7 @@ export const ChevronRight = forwardRef( size={size} color={color} className="icon icon-chevronright" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronUp.tsx b/web/packages/design/src/Icon/Icons/ChevronUp.tsx index dbd76b88d1b7..8b255a62479e 100644 --- a/web/packages/design/src/Icon/Icons/ChevronUp.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronUp.tsx @@ -56,6 +56,7 @@ export const ChevronUp = forwardRef( size={size} color={color} className="icon icon-chevronup" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx b/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx index 81be5d03a624..905d91fcdc2f 100644 --- a/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx @@ -56,6 +56,7 @@ export const ChevronsVertical = forwardRef( size={size} color={color} className="icon icon-chevronsvertical" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx b/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx index cf22b2867d26..6b42bd8d1b48 100644 --- a/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx +++ b/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx @@ -56,6 +56,7 @@ export const CircleArrowLeft = forwardRef( size={size} color={color} className="icon icon-circlearrowleft" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx b/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx index 411ef150c767..946e9e0cfe39 100644 --- a/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx +++ b/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx @@ -56,6 +56,7 @@ export const CircleArrowRight = forwardRef( size={size} color={color} className="icon icon-circlearrowright" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleCheck.tsx b/web/packages/design/src/Icon/Icons/CircleCheck.tsx index 4c6182f8733c..656b1b401c85 100644 --- a/web/packages/design/src/Icon/Icons/CircleCheck.tsx +++ b/web/packages/design/src/Icon/Icons/CircleCheck.tsx @@ -56,6 +56,7 @@ export const CircleCheck = forwardRef( size={size} color={color} className="icon icon-circlecheck" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleCross.tsx b/web/packages/design/src/Icon/Icons/CircleCross.tsx index 607b8a744755..83f8d278c7fc 100644 --- a/web/packages/design/src/Icon/Icons/CircleCross.tsx +++ b/web/packages/design/src/Icon/Icons/CircleCross.tsx @@ -56,6 +56,7 @@ export const CircleCross = forwardRef( size={size} color={color} className="icon icon-circlecross" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleDashed.tsx b/web/packages/design/src/Icon/Icons/CircleDashed.tsx index 5d9cae95e647..ab898fb6da04 100644 --- a/web/packages/design/src/Icon/Icons/CircleDashed.tsx +++ b/web/packages/design/src/Icon/Icons/CircleDashed.tsx @@ -56,6 +56,7 @@ export const CircleDashed = forwardRef( size={size} color={color} className="icon icon-circledashed" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CirclePause.tsx b/web/packages/design/src/Icon/Icons/CirclePause.tsx index 2b1f716f0169..802b17c44e63 100644 --- a/web/packages/design/src/Icon/Icons/CirclePause.tsx +++ b/web/packages/design/src/Icon/Icons/CirclePause.tsx @@ -56,6 +56,7 @@ export const CirclePause = forwardRef( size={size} color={color} className="icon icon-circlepause" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CirclePlay.tsx b/web/packages/design/src/Icon/Icons/CirclePlay.tsx index 848cb2145698..b8bee67bddbd 100644 --- a/web/packages/design/src/Icon/Icons/CirclePlay.tsx +++ b/web/packages/design/src/Icon/Icons/CirclePlay.tsx @@ -56,6 +56,7 @@ export const CirclePlay = forwardRef( size={size} color={color} className="icon icon-circleplay" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CircleStop.tsx b/web/packages/design/src/Icon/Icons/CircleStop.tsx index fd3ae6ec4978..0f216ab80cb9 100644 --- a/web/packages/design/src/Icon/Icons/CircleStop.tsx +++ b/web/packages/design/src/Icon/Icons/CircleStop.tsx @@ -56,6 +56,7 @@ export const CircleStop = forwardRef( size={size} color={color} className="icon icon-circlestop" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cli.tsx b/web/packages/design/src/Icon/Icons/Cli.tsx index 0b76377e60a2..0b64a4141ddc 100644 --- a/web/packages/design/src/Icon/Icons/Cli.tsx +++ b/web/packages/design/src/Icon/Icons/Cli.tsx @@ -56,6 +56,7 @@ export const Cli = forwardRef( size={size} color={color} className="icon icon-cli" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Clipboard.tsx b/web/packages/design/src/Icon/Icons/Clipboard.tsx index 553dc67729d0..b2bc29535aeb 100644 --- a/web/packages/design/src/Icon/Icons/Clipboard.tsx +++ b/web/packages/design/src/Icon/Icons/Clipboard.tsx @@ -56,6 +56,7 @@ export const Clipboard = forwardRef( size={size} color={color} className="icon icon-clipboard" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ClipboardUser.tsx b/web/packages/design/src/Icon/Icons/ClipboardUser.tsx index 1fe1d7ff7a7b..1ecfef7dca4e 100644 --- a/web/packages/design/src/Icon/Icons/ClipboardUser.tsx +++ b/web/packages/design/src/Icon/Icons/ClipboardUser.tsx @@ -56,6 +56,7 @@ export const ClipboardUser = forwardRef( size={size} color={color} className="icon icon-clipboarduser" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Clock.tsx b/web/packages/design/src/Icon/Icons/Clock.tsx index cc7b74208811..8394f45648a1 100644 --- a/web/packages/design/src/Icon/Icons/Clock.tsx +++ b/web/packages/design/src/Icon/Icons/Clock.tsx @@ -56,6 +56,7 @@ export const Clock = forwardRef( size={size} color={color} className="icon icon-clock" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cloud.tsx b/web/packages/design/src/Icon/Icons/Cloud.tsx index 6549c8db7991..34186155707f 100644 --- a/web/packages/design/src/Icon/Icons/Cloud.tsx +++ b/web/packages/design/src/Icon/Icons/Cloud.tsx @@ -56,6 +56,7 @@ export const Cloud = forwardRef( size={size} color={color} className="icon icon-cloud" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cluster.tsx b/web/packages/design/src/Icon/Icons/Cluster.tsx index 73a365d54f2f..1f311c6c0ef0 100644 --- a/web/packages/design/src/Icon/Icons/Cluster.tsx +++ b/web/packages/design/src/Icon/Icons/Cluster.tsx @@ -56,6 +56,7 @@ export const Cluster = forwardRef( size={size} color={color} className="icon icon-cluster" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Code.tsx b/web/packages/design/src/Icon/Icons/Code.tsx index f11aef667c01..32212e9746be 100644 --- a/web/packages/design/src/Icon/Icons/Code.tsx +++ b/web/packages/design/src/Icon/Icons/Code.tsx @@ -56,6 +56,7 @@ export const Code = forwardRef( size={size} color={color} className="icon icon-code" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cog.tsx b/web/packages/design/src/Icon/Icons/Cog.tsx index bcf3c89bdfcd..e32ea310a16e 100644 --- a/web/packages/design/src/Icon/Icons/Cog.tsx +++ b/web/packages/design/src/Icon/Icons/Cog.tsx @@ -56,6 +56,7 @@ export const Cog = forwardRef( size={size} color={color} className="icon icon-cog" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Config.tsx b/web/packages/design/src/Icon/Icons/Config.tsx index bdffc1a7e70a..44e8e6ce91d3 100644 --- a/web/packages/design/src/Icon/Icons/Config.tsx +++ b/web/packages/design/src/Icon/Icons/Config.tsx @@ -56,6 +56,7 @@ export const Config = forwardRef( size={size} color={color} className="icon icon-config" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Contract.tsx b/web/packages/design/src/Icon/Icons/Contract.tsx index bd77e1a3d5fc..1fbde62e33a3 100644 --- a/web/packages/design/src/Icon/Icons/Contract.tsx +++ b/web/packages/design/src/Icon/Icons/Contract.tsx @@ -56,6 +56,7 @@ export const Contract = forwardRef( size={size} color={color} className="icon icon-contract" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Copy.tsx b/web/packages/design/src/Icon/Icons/Copy.tsx index ce6c1d3c04a1..05de40cee59d 100644 --- a/web/packages/design/src/Icon/Icons/Copy.tsx +++ b/web/packages/design/src/Icon/Icons/Copy.tsx @@ -56,6 +56,7 @@ export const Copy = forwardRef( size={size} color={color} className="icon icon-copy" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CornersIn.tsx b/web/packages/design/src/Icon/Icons/CornersIn.tsx index adbb7170d769..7975b365066c 100644 --- a/web/packages/design/src/Icon/Icons/CornersIn.tsx +++ b/web/packages/design/src/Icon/Icons/CornersIn.tsx @@ -56,6 +56,7 @@ export const CornersIn = forwardRef( size={size} color={color} className="icon icon-cornersin" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CornersOut.tsx b/web/packages/design/src/Icon/Icons/CornersOut.tsx index 36d50b03731f..4c46257e3e32 100644 --- a/web/packages/design/src/Icon/Icons/CornersOut.tsx +++ b/web/packages/design/src/Icon/Icons/CornersOut.tsx @@ -56,6 +56,7 @@ export const CornersOut = forwardRef( size={size} color={color} className="icon icon-cornersout" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/CreditCard.tsx b/web/packages/design/src/Icon/Icons/CreditCard.tsx index 83ef3d87f828..de081bcdf927 100644 --- a/web/packages/design/src/Icon/Icons/CreditCard.tsx +++ b/web/packages/design/src/Icon/Icons/CreditCard.tsx @@ -56,6 +56,7 @@ export const CreditCard = forwardRef( size={size} color={color} className="icon icon-creditcard" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Cross.tsx b/web/packages/design/src/Icon/Icons/Cross.tsx index c3ea260e3668..7eca3fe53e6e 100644 --- a/web/packages/design/src/Icon/Icons/Cross.tsx +++ b/web/packages/design/src/Icon/Icons/Cross.tsx @@ -56,6 +56,7 @@ export const Cross = forwardRef( size={size} color={color} className="icon icon-cross" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Crown.tsx b/web/packages/design/src/Icon/Icons/Crown.tsx index dc625113a5d1..3a59d8476646 100644 --- a/web/packages/design/src/Icon/Icons/Crown.tsx +++ b/web/packages/design/src/Icon/Icons/Crown.tsx @@ -56,6 +56,7 @@ export const Crown = forwardRef( size={size} color={color} className="icon icon-crown" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Database.tsx b/web/packages/design/src/Icon/Icons/Database.tsx index cd878232aac6..97c403f5ecb8 100644 --- a/web/packages/design/src/Icon/Icons/Database.tsx +++ b/web/packages/design/src/Icon/Icons/Database.tsx @@ -56,6 +56,7 @@ export const Database = forwardRef( size={size} color={color} className="icon icon-database" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Desktop.tsx b/web/packages/design/src/Icon/Icons/Desktop.tsx index 6bb454631695..e523ce2937f8 100644 --- a/web/packages/design/src/Icon/Icons/Desktop.tsx +++ b/web/packages/design/src/Icon/Icons/Desktop.tsx @@ -56,6 +56,7 @@ export const Desktop = forwardRef( size={size} color={color} className="icon icon-desktop" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Detective.tsx b/web/packages/design/src/Icon/Icons/Detective.tsx index c1d902a95544..c679c50ae1b5 100644 --- a/web/packages/design/src/Icon/Icons/Detective.tsx +++ b/web/packages/design/src/Icon/Icons/Detective.tsx @@ -56,6 +56,7 @@ export const Detective = forwardRef( size={size} color={color} className="icon icon-detective" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx b/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx index 4c8ecc30486e..f7ba7cef3a08 100644 --- a/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx +++ b/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx @@ -56,6 +56,7 @@ export const DeviceMobileCamera = forwardRef( size={size} color={color} className="icon icon-devicemobilecamera" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Devices.tsx b/web/packages/design/src/Icon/Icons/Devices.tsx index 720e67046073..d94c5b4f4118 100644 --- a/web/packages/design/src/Icon/Icons/Devices.tsx +++ b/web/packages/design/src/Icon/Icons/Devices.tsx @@ -56,6 +56,7 @@ export const Devices = forwardRef( size={size} color={color} className="icon icon-devices" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Dot.tsx b/web/packages/design/src/Icon/Icons/Dot.tsx index f8caef495eb5..4b00718a2bde 100644 --- a/web/packages/design/src/Icon/Icons/Dot.tsx +++ b/web/packages/design/src/Icon/Icons/Dot.tsx @@ -56,6 +56,7 @@ export const Dot = forwardRef( size={size} color={color} className="icon icon-dot" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Download.tsx b/web/packages/design/src/Icon/Icons/Download.tsx index 7863559b1c47..87f271862ca7 100644 --- a/web/packages/design/src/Icon/Icons/Download.tsx +++ b/web/packages/design/src/Icon/Icons/Download.tsx @@ -56,6 +56,7 @@ export const Download = forwardRef( size={size} color={color} className="icon icon-download" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Earth.tsx b/web/packages/design/src/Icon/Icons/Earth.tsx index 6b787c573a2d..831aa5d0812a 100644 --- a/web/packages/design/src/Icon/Icons/Earth.tsx +++ b/web/packages/design/src/Icon/Icons/Earth.tsx @@ -56,6 +56,7 @@ export const Earth = forwardRef( size={size} color={color} className="icon icon-earth" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Edit.tsx b/web/packages/design/src/Icon/Icons/Edit.tsx index 13377c3ce7a3..6bb6ae8dd294 100644 --- a/web/packages/design/src/Icon/Icons/Edit.tsx +++ b/web/packages/design/src/Icon/Icons/Edit.tsx @@ -56,6 +56,7 @@ export const Edit = forwardRef( size={size} color={color} className="icon icon-edit" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Ellipsis.tsx b/web/packages/design/src/Icon/Icons/Ellipsis.tsx index 3845e7e99fda..4ffe3e2f5a67 100644 --- a/web/packages/design/src/Icon/Icons/Ellipsis.tsx +++ b/web/packages/design/src/Icon/Icons/Ellipsis.tsx @@ -56,6 +56,7 @@ export const Ellipsis = forwardRef( size={size} color={color} className="icon icon-ellipsis" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/EmailSolid.tsx b/web/packages/design/src/Icon/Icons/EmailSolid.tsx index 3e9a7925e839..f8d0bb6e8272 100644 --- a/web/packages/design/src/Icon/Icons/EmailSolid.tsx +++ b/web/packages/design/src/Icon/Icons/EmailSolid.tsx @@ -56,6 +56,7 @@ export const EmailSolid = forwardRef( size={size} color={color} className="icon icon-emailsolid" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx b/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx index f6ad8bad82ee..2aa46576a494 100644 --- a/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx +++ b/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx @@ -56,6 +56,7 @@ export const EnvelopeOpen = forwardRef( size={size} color={color} className="icon icon-envelopeopen" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx b/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx index 0be55850400e..71ca370f93ad 100644 --- a/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx +++ b/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx @@ -56,6 +56,7 @@ export const EqualizersVertical = forwardRef( size={size} color={color} className="icon icon-equalizersvertical" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Expand.tsx b/web/packages/design/src/Icon/Icons/Expand.tsx index 0cec4170df7b..b6fb01b66ba5 100644 --- a/web/packages/design/src/Icon/Icons/Expand.tsx +++ b/web/packages/design/src/Icon/Icons/Expand.tsx @@ -56,6 +56,7 @@ export const Expand = forwardRef( size={size} color={color} className="icon icon-expand" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Facebook.tsx b/web/packages/design/src/Icon/Icons/Facebook.tsx index 7ae9928ee32a..cace74c66df0 100644 --- a/web/packages/design/src/Icon/Icons/Facebook.tsx +++ b/web/packages/design/src/Icon/Icons/Facebook.tsx @@ -56,6 +56,7 @@ export const Facebook = forwardRef( size={size} color={color} className="icon icon-facebook" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FastForward.tsx b/web/packages/design/src/Icon/Icons/FastForward.tsx index 38ec3b7bd1b8..181b3da4fb74 100644 --- a/web/packages/design/src/Icon/Icons/FastForward.tsx +++ b/web/packages/design/src/Icon/Icons/FastForward.tsx @@ -56,6 +56,7 @@ export const FastForward = forwardRef( size={size} color={color} className="icon icon-fastforward" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FilmStrip.tsx b/web/packages/design/src/Icon/Icons/FilmStrip.tsx index fb99cb85eda1..dd97dd4d889f 100644 --- a/web/packages/design/src/Icon/Icons/FilmStrip.tsx +++ b/web/packages/design/src/Icon/Icons/FilmStrip.tsx @@ -56,6 +56,7 @@ export const FilmStrip = forwardRef( size={size} color={color} className="icon icon-filmstrip" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx b/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx index 4779ffa9a741..c809faa6876b 100644 --- a/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx +++ b/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx @@ -56,6 +56,7 @@ export const FingerprintSimple = forwardRef( size={size} color={color} className="icon icon-fingerprintsimple" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Flag.tsx b/web/packages/design/src/Icon/Icons/Flag.tsx index e2ac3c40c8f2..b16c32fa5e7d 100644 --- a/web/packages/design/src/Icon/Icons/Flag.tsx +++ b/web/packages/design/src/Icon/Icons/Flag.tsx @@ -56,6 +56,7 @@ export const Flag = forwardRef( size={size} color={color} className="icon icon-flag" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Floppy.tsx b/web/packages/design/src/Icon/Icons/Floppy.tsx index f59deb3aa791..a0736576299b 100644 --- a/web/packages/design/src/Icon/Icons/Floppy.tsx +++ b/web/packages/design/src/Icon/Icons/Floppy.tsx @@ -56,6 +56,7 @@ export const Floppy = forwardRef( size={size} color={color} className="icon icon-floppy" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FlowArrow.tsx b/web/packages/design/src/Icon/Icons/FlowArrow.tsx index 0265dda4c0b5..306f0e16eaad 100644 --- a/web/packages/design/src/Icon/Icons/FlowArrow.tsx +++ b/web/packages/design/src/Icon/Icons/FlowArrow.tsx @@ -56,6 +56,7 @@ export const FlowArrow = forwardRef( size={size} color={color} className="icon icon-flowarrow" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FolderPlus.tsx b/web/packages/design/src/Icon/Icons/FolderPlus.tsx index b4e2a65dbce6..dbc6829ae57f 100644 --- a/web/packages/design/src/Icon/Icons/FolderPlus.tsx +++ b/web/packages/design/src/Icon/Icons/FolderPlus.tsx @@ -56,6 +56,7 @@ export const FolderPlus = forwardRef( size={size} color={color} className="icon icon-folderplus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/FolderShared.tsx b/web/packages/design/src/Icon/Icons/FolderShared.tsx index 846c2cba4e4d..5fc4b4303e62 100644 --- a/web/packages/design/src/Icon/Icons/FolderShared.tsx +++ b/web/packages/design/src/Icon/Icons/FolderShared.tsx @@ -56,6 +56,7 @@ export const FolderShared = forwardRef( size={size} color={color} className="icon icon-foldershared" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/GitHub.tsx b/web/packages/design/src/Icon/Icons/GitHub.tsx index c218b88e0661..ba2d418c21d7 100644 --- a/web/packages/design/src/Icon/Icons/GitHub.tsx +++ b/web/packages/design/src/Icon/Icons/GitHub.tsx @@ -56,6 +56,7 @@ export const GitHub = forwardRef( size={size} color={color} className="icon icon-github" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Google.tsx b/web/packages/design/src/Icon/Icons/Google.tsx index 30f76f765409..57bb5d542454 100644 --- a/web/packages/design/src/Icon/Icons/Google.tsx +++ b/web/packages/design/src/Icon/Icons/Google.tsx @@ -56,6 +56,7 @@ export const Google = forwardRef( size={size} color={color} className="icon icon-google" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Graph.tsx b/web/packages/design/src/Icon/Icons/Graph.tsx index 1ee3931db252..c3befc398b84 100644 --- a/web/packages/design/src/Icon/Icons/Graph.tsx +++ b/web/packages/design/src/Icon/Icons/Graph.tsx @@ -56,6 +56,7 @@ export const Graph = forwardRef( size={size} color={color} className="icon icon-graph" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/GridFour.tsx b/web/packages/design/src/Icon/Icons/GridFour.tsx index 7ca7dad77e33..7ac7e62cd55b 100644 --- a/web/packages/design/src/Icon/Icons/GridFour.tsx +++ b/web/packages/design/src/Icon/Icons/GridFour.tsx @@ -56,6 +56,7 @@ export const GridFour = forwardRef( size={size} color={color} className="icon icon-gridfour" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/GridNine.tsx b/web/packages/design/src/Icon/Icons/GridNine.tsx index 5ae72c127a82..f4c5917084c2 100644 --- a/web/packages/design/src/Icon/Icons/GridNine.tsx +++ b/web/packages/design/src/Icon/Icons/GridNine.tsx @@ -56,6 +56,7 @@ export const GridNine = forwardRef( size={size} color={color} className="icon icon-gridnine" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Hashtag.tsx b/web/packages/design/src/Icon/Icons/Hashtag.tsx index 8d396a69e1c4..50f2b12efaab 100644 --- a/web/packages/design/src/Icon/Icons/Hashtag.tsx +++ b/web/packages/design/src/Icon/Icons/Hashtag.tsx @@ -56,6 +56,7 @@ export const Hashtag = forwardRef( size={size} color={color} className="icon icon-hashtag" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Headset.tsx b/web/packages/design/src/Icon/Icons/Headset.tsx index 3a39a9f13779..2927cb04bc24 100644 --- a/web/packages/design/src/Icon/Icons/Headset.tsx +++ b/web/packages/design/src/Icon/Icons/Headset.tsx @@ -56,6 +56,7 @@ export const Headset = forwardRef( size={size} color={color} className="icon icon-headset" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Home.tsx b/web/packages/design/src/Icon/Icons/Home.tsx index 2a7f90269a2a..de52b1214a26 100644 --- a/web/packages/design/src/Icon/Icons/Home.tsx +++ b/web/packages/design/src/Icon/Icons/Home.tsx @@ -56,6 +56,7 @@ export const Home = forwardRef( size={size} color={color} className="icon icon-home" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Info.tsx b/web/packages/design/src/Icon/Icons/Info.tsx index 04b34132fe8b..6b43c39efda1 100644 --- a/web/packages/design/src/Icon/Icons/Info.tsx +++ b/web/packages/design/src/Icon/Icons/Info.tsx @@ -56,6 +56,7 @@ export const Info = forwardRef( size={size} color={color} className="icon icon-info" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Integrations.tsx b/web/packages/design/src/Icon/Icons/Integrations.tsx index d6f021699a01..06b5b9012df4 100644 --- a/web/packages/design/src/Icon/Icons/Integrations.tsx +++ b/web/packages/design/src/Icon/Icons/Integrations.tsx @@ -56,6 +56,7 @@ export const Integrations = forwardRef( size={size} color={color} className="icon icon-integrations" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Invoices.tsx b/web/packages/design/src/Icon/Icons/Invoices.tsx index 5198386afd0a..66d251777be9 100644 --- a/web/packages/design/src/Icon/Icons/Invoices.tsx +++ b/web/packages/design/src/Icon/Icons/Invoices.tsx @@ -56,6 +56,7 @@ export const Invoices = forwardRef( size={size} color={color} className="icon icon-invoices" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Key.tsx b/web/packages/design/src/Icon/Icons/Key.tsx index baf3e2dd809c..47a667d99727 100644 --- a/web/packages/design/src/Icon/Icons/Key.tsx +++ b/web/packages/design/src/Icon/Icons/Key.tsx @@ -56,6 +56,7 @@ export const Key = forwardRef( size={size} color={color} className="icon icon-key" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/KeyHole.tsx b/web/packages/design/src/Icon/Icons/KeyHole.tsx index 6ac2d8434ca8..ab2eebccda39 100644 --- a/web/packages/design/src/Icon/Icons/KeyHole.tsx +++ b/web/packages/design/src/Icon/Icons/KeyHole.tsx @@ -56,6 +56,7 @@ export const KeyHole = forwardRef( size={size} color={color} className="icon icon-keyhole" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Keyboard.tsx b/web/packages/design/src/Icon/Icons/Keyboard.tsx index f48c4e57b28b..43e34df418a0 100644 --- a/web/packages/design/src/Icon/Icons/Keyboard.tsx +++ b/web/packages/design/src/Icon/Icons/Keyboard.tsx @@ -56,6 +56,7 @@ export const Keyboard = forwardRef( size={size} color={color} className="icon icon-keyboard" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Keypair.tsx b/web/packages/design/src/Icon/Icons/Keypair.tsx index 690a2d650e99..78ad5e793ddb 100644 --- a/web/packages/design/src/Icon/Icons/Keypair.tsx +++ b/web/packages/design/src/Icon/Icons/Keypair.tsx @@ -56,6 +56,7 @@ export const Keypair = forwardRef( size={size} color={color} className="icon icon-keypair" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Kubernetes.tsx b/web/packages/design/src/Icon/Icons/Kubernetes.tsx index f6c44b2aa8d2..d09433cb7300 100644 --- a/web/packages/design/src/Icon/Icons/Kubernetes.tsx +++ b/web/packages/design/src/Icon/Icons/Kubernetes.tsx @@ -56,6 +56,7 @@ export const Kubernetes = forwardRef( size={size} color={color} className="icon icon-kubernetes" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Label.tsx b/web/packages/design/src/Icon/Icons/Label.tsx index d0d17d8c17a4..7a672f326f62 100644 --- a/web/packages/design/src/Icon/Icons/Label.tsx +++ b/web/packages/design/src/Icon/Icons/Label.tsx @@ -56,6 +56,7 @@ export const Label = forwardRef( size={size} color={color} className="icon icon-label" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Lan.tsx b/web/packages/design/src/Icon/Icons/Lan.tsx index 956d73afe18c..cbfa3e2a8c71 100644 --- a/web/packages/design/src/Icon/Icons/Lan.tsx +++ b/web/packages/design/src/Icon/Icons/Lan.tsx @@ -56,6 +56,7 @@ export const Lan = forwardRef( size={size} color={color} className="icon icon-lan" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Laptop.tsx b/web/packages/design/src/Icon/Icons/Laptop.tsx index 723b4e514c8b..fff8217af1dc 100644 --- a/web/packages/design/src/Icon/Icons/Laptop.tsx +++ b/web/packages/design/src/Icon/Icons/Laptop.tsx @@ -56,6 +56,7 @@ export const Laptop = forwardRef( size={size} color={color} className="icon icon-laptop" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Layout.tsx b/web/packages/design/src/Icon/Icons/Layout.tsx index 0ffb243c632f..6009faadd556 100644 --- a/web/packages/design/src/Icon/Icons/Layout.tsx +++ b/web/packages/design/src/Icon/Icons/Layout.tsx @@ -56,6 +56,7 @@ export const Layout = forwardRef( size={size} color={color} className="icon icon-layout" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/License.tsx b/web/packages/design/src/Icon/Icons/License.tsx index 652adb3b499d..bf3a775fbf57 100644 --- a/web/packages/design/src/Icon/Icons/License.tsx +++ b/web/packages/design/src/Icon/Icons/License.tsx @@ -56,6 +56,7 @@ export const License = forwardRef( size={size} color={color} className="icon icon-license" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/LineSegment.tsx b/web/packages/design/src/Icon/Icons/LineSegment.tsx index 6b3be8334596..4f42e9c45820 100644 --- a/web/packages/design/src/Icon/Icons/LineSegment.tsx +++ b/web/packages/design/src/Icon/Icons/LineSegment.tsx @@ -56,6 +56,7 @@ export const LineSegment = forwardRef( size={size} color={color} className="icon icon-linesegment" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/LineSegments.tsx b/web/packages/design/src/Icon/Icons/LineSegments.tsx index f91c3f14a717..66f26194a203 100644 --- a/web/packages/design/src/Icon/Icons/LineSegments.tsx +++ b/web/packages/design/src/Icon/Icons/LineSegments.tsx @@ -56,6 +56,7 @@ export const LineSegments = forwardRef( size={size} color={color} className="icon icon-linesegments" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Link.tsx b/web/packages/design/src/Icon/Icons/Link.tsx index c17c2fab1f9c..19aec4d36d94 100644 --- a/web/packages/design/src/Icon/Icons/Link.tsx +++ b/web/packages/design/src/Icon/Icons/Link.tsx @@ -56,6 +56,7 @@ export const Link = forwardRef( size={size} color={color} className="icon icon-link" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Linkedin.tsx b/web/packages/design/src/Icon/Icons/Linkedin.tsx index 99eeeeb09f7e..7c206e5f1e7d 100644 --- a/web/packages/design/src/Icon/Icons/Linkedin.tsx +++ b/web/packages/design/src/Icon/Icons/Linkedin.tsx @@ -56,6 +56,7 @@ export const Linkedin = forwardRef( size={size} color={color} className="icon icon-linkedin" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Linux.tsx b/web/packages/design/src/Icon/Icons/Linux.tsx index bfb5b1371a89..5cf080820123 100644 --- a/web/packages/design/src/Icon/Icons/Linux.tsx +++ b/web/packages/design/src/Icon/Icons/Linux.tsx @@ -56,6 +56,7 @@ export const Linux = forwardRef( size={size} color={color} className="icon icon-linux" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ListAddCheck.tsx b/web/packages/design/src/Icon/Icons/ListAddCheck.tsx index 86722b9765ab..a353c48e0ba6 100644 --- a/web/packages/design/src/Icon/Icons/ListAddCheck.tsx +++ b/web/packages/design/src/Icon/Icons/ListAddCheck.tsx @@ -56,6 +56,7 @@ export const ListAddCheck = forwardRef( size={size} color={color} className="icon icon-listaddcheck" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx b/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx index 8a7b26d00177..9c672b62ecba 100644 --- a/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx +++ b/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx @@ -56,6 +56,7 @@ export const ListMagnifyingGlass = forwardRef( size={size} color={color} className="icon icon-listmagnifyingglass" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ListThin.tsx b/web/packages/design/src/Icon/Icons/ListThin.tsx index 32da05c8a1be..c378f6bd0ee4 100644 --- a/web/packages/design/src/Icon/Icons/ListThin.tsx +++ b/web/packages/design/src/Icon/Icons/ListThin.tsx @@ -56,6 +56,7 @@ export const ListThin = forwardRef( size={size} color={color} className="icon icon-listthin" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ListView.tsx b/web/packages/design/src/Icon/Icons/ListView.tsx index 1174a3e68536..2040e4e5f9be 100644 --- a/web/packages/design/src/Icon/Icons/ListView.tsx +++ b/web/packages/design/src/Icon/Icons/ListView.tsx @@ -56,6 +56,7 @@ export const ListView = forwardRef( size={size} color={color} className="icon icon-listview" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Lock.tsx b/web/packages/design/src/Icon/Icons/Lock.tsx index d01c80c3aa53..c9282044bfbb 100644 --- a/web/packages/design/src/Icon/Icons/Lock.tsx +++ b/web/packages/design/src/Icon/Icons/Lock.tsx @@ -56,6 +56,7 @@ export const Lock = forwardRef( size={size} color={color} className="icon icon-lock" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/LockKey.tsx b/web/packages/design/src/Icon/Icons/LockKey.tsx index 1618ad32708f..4bfe19f6aa93 100644 --- a/web/packages/design/src/Icon/Icons/LockKey.tsx +++ b/web/packages/design/src/Icon/Icons/LockKey.tsx @@ -56,6 +56,7 @@ export const LockKey = forwardRef( size={size} color={color} className="icon icon-lockkey" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Logout.tsx b/web/packages/design/src/Icon/Icons/Logout.tsx index 00fa322ed3a0..167b505edaeb 100644 --- a/web/packages/design/src/Icon/Icons/Logout.tsx +++ b/web/packages/design/src/Icon/Icons/Logout.tsx @@ -56,6 +56,7 @@ export const Logout = forwardRef( size={size} color={color} className="icon icon-logout" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Magnifier.tsx b/web/packages/design/src/Icon/Icons/Magnifier.tsx index 5e462d678914..59c8365daa46 100644 --- a/web/packages/design/src/Icon/Icons/Magnifier.tsx +++ b/web/packages/design/src/Icon/Icons/Magnifier.tsx @@ -56,6 +56,7 @@ export const Magnifier = forwardRef( size={size} color={color} className="icon icon-magnifier" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx b/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx index 1c6ba71e57d2..538eb78a1446 100644 --- a/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx +++ b/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx @@ -56,6 +56,7 @@ export const MagnifyingMinus = forwardRef( size={size} color={color} className="icon icon-magnifyingminus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx b/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx index b2e43307e015..e87f57eb8449 100644 --- a/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx +++ b/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx @@ -56,6 +56,7 @@ export const MagnifyingPlus = forwardRef( size={size} color={color} className="icon icon-magnifyingplus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Memory.tsx b/web/packages/design/src/Icon/Icons/Memory.tsx index 705e68832889..274ecc42e677 100644 --- a/web/packages/design/src/Icon/Icons/Memory.tsx +++ b/web/packages/design/src/Icon/Icons/Memory.tsx @@ -56,6 +56,7 @@ export const Memory = forwardRef( size={size} color={color} className="icon icon-memory" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Minus.tsx b/web/packages/design/src/Icon/Icons/Minus.tsx index fe0e609c58a9..b753f0f9d9aa 100644 --- a/web/packages/design/src/Icon/Icons/Minus.tsx +++ b/web/packages/design/src/Icon/Icons/Minus.tsx @@ -56,6 +56,7 @@ export const Minus = forwardRef( size={size} color={color} className="icon icon-minus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/MinusCircle.tsx b/web/packages/design/src/Icon/Icons/MinusCircle.tsx index dfbcf8239770..102b70b775a8 100644 --- a/web/packages/design/src/Icon/Icons/MinusCircle.tsx +++ b/web/packages/design/src/Icon/Icons/MinusCircle.tsx @@ -56,6 +56,7 @@ export const MinusCircle = forwardRef( size={size} color={color} className="icon icon-minuscircle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Moon.tsx b/web/packages/design/src/Icon/Icons/Moon.tsx index 7e649b091ddb..07177428298b 100644 --- a/web/packages/design/src/Icon/Icons/Moon.tsx +++ b/web/packages/design/src/Icon/Icons/Moon.tsx @@ -56,6 +56,7 @@ export const Moon = forwardRef( size={size} color={color} className="icon icon-moon" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/MoreHoriz.tsx b/web/packages/design/src/Icon/Icons/MoreHoriz.tsx index b63899bd1f51..76d06d7dc664 100644 --- a/web/packages/design/src/Icon/Icons/MoreHoriz.tsx +++ b/web/packages/design/src/Icon/Icons/MoreHoriz.tsx @@ -56,6 +56,7 @@ export const MoreHoriz = forwardRef( size={size} color={color} className="icon icon-morehoriz" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/MoreVert.tsx b/web/packages/design/src/Icon/Icons/MoreVert.tsx index 242630d7c9c7..7b2924f261fc 100644 --- a/web/packages/design/src/Icon/Icons/MoreVert.tsx +++ b/web/packages/design/src/Icon/Icons/MoreVert.tsx @@ -56,6 +56,7 @@ export const MoreVert = forwardRef( size={size} color={color} className="icon icon-morevert" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Mute.tsx b/web/packages/design/src/Icon/Icons/Mute.tsx index 70d45f194592..0bc7ed8cbd77 100644 --- a/web/packages/design/src/Icon/Icons/Mute.tsx +++ b/web/packages/design/src/Icon/Icons/Mute.tsx @@ -56,6 +56,7 @@ export const Mute = forwardRef( size={size} color={color} className="icon icon-mute" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Network.tsx b/web/packages/design/src/Icon/Icons/Network.tsx index 39e918965595..3cf77dd1ffad 100644 --- a/web/packages/design/src/Icon/Icons/Network.tsx +++ b/web/packages/design/src/Icon/Icons/Network.tsx @@ -56,6 +56,7 @@ export const Network = forwardRef( size={size} color={color} className="icon icon-network" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/NewTab.tsx b/web/packages/design/src/Icon/Icons/NewTab.tsx index c23169dadfce..735638bfc953 100644 --- a/web/packages/design/src/Icon/Icons/NewTab.tsx +++ b/web/packages/design/src/Icon/Icons/NewTab.tsx @@ -56,6 +56,7 @@ export const NewTab = forwardRef( size={size} color={color} className="icon icon-newtab" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/NoteAdded.tsx b/web/packages/design/src/Icon/Icons/NoteAdded.tsx index a1f09c8dbdc9..286c02d19a85 100644 --- a/web/packages/design/src/Icon/Icons/NoteAdded.tsx +++ b/web/packages/design/src/Icon/Icons/NoteAdded.tsx @@ -56,6 +56,7 @@ export const NoteAdded = forwardRef( size={size} color={color} className="icon icon-noteadded" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Notification.tsx b/web/packages/design/src/Icon/Icons/Notification.tsx index 18d407d37a97..06c840649c98 100644 --- a/web/packages/design/src/Icon/Icons/Notification.tsx +++ b/web/packages/design/src/Icon/Icons/Notification.tsx @@ -56,6 +56,7 @@ export const Notification = forwardRef( size={size} color={color} className="icon icon-notification" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/NotificationsActive.tsx b/web/packages/design/src/Icon/Icons/NotificationsActive.tsx index b25bb32ef904..dbd3e70fda1a 100644 --- a/web/packages/design/src/Icon/Icons/NotificationsActive.tsx +++ b/web/packages/design/src/Icon/Icons/NotificationsActive.tsx @@ -56,6 +56,7 @@ export const NotificationsActive = forwardRef( size={size} color={color} className="icon icon-notificationsactive" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Package.tsx b/web/packages/design/src/Icon/Icons/Package.tsx index ccf1ce0305eb..b7275b7e3510 100644 --- a/web/packages/design/src/Icon/Icons/Package.tsx +++ b/web/packages/design/src/Icon/Icons/Package.tsx @@ -56,6 +56,7 @@ export const Package = forwardRef( size={size} color={color} className="icon icon-package" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/PaperPlane.tsx b/web/packages/design/src/Icon/Icons/PaperPlane.tsx index b2e8b2841c68..09028a70f772 100644 --- a/web/packages/design/src/Icon/Icons/PaperPlane.tsx +++ b/web/packages/design/src/Icon/Icons/PaperPlane.tsx @@ -56,6 +56,7 @@ export const PaperPlane = forwardRef( size={size} color={color} className="icon icon-paperplane" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Password.tsx b/web/packages/design/src/Icon/Icons/Password.tsx index cdec6727d952..513c0e25afd9 100644 --- a/web/packages/design/src/Icon/Icons/Password.tsx +++ b/web/packages/design/src/Icon/Icons/Password.tsx @@ -56,6 +56,7 @@ export const Password = forwardRef( size={size} color={color} className="icon icon-password" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Pause.tsx b/web/packages/design/src/Icon/Icons/Pause.tsx index c4b928266348..cb5cd50bcb9e 100644 --- a/web/packages/design/src/Icon/Icons/Pause.tsx +++ b/web/packages/design/src/Icon/Icons/Pause.tsx @@ -56,6 +56,7 @@ export const Pause = forwardRef( size={size} color={color} className="icon icon-pause" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Pencil.tsx b/web/packages/design/src/Icon/Icons/Pencil.tsx index 2cecb9879c43..258e3282e459 100644 --- a/web/packages/design/src/Icon/Icons/Pencil.tsx +++ b/web/packages/design/src/Icon/Icons/Pencil.tsx @@ -56,6 +56,7 @@ export const Pencil = forwardRef( size={size} color={color} className="icon icon-pencil" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Planet.tsx b/web/packages/design/src/Icon/Icons/Planet.tsx index b2100b6f7d11..a2f690202e46 100644 --- a/web/packages/design/src/Icon/Icons/Planet.tsx +++ b/web/packages/design/src/Icon/Icons/Planet.tsx @@ -56,6 +56,7 @@ export const Planet = forwardRef( size={size} color={color} className="icon icon-planet" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Play.tsx b/web/packages/design/src/Icon/Icons/Play.tsx index bb793099541b..33da2f38a033 100644 --- a/web/packages/design/src/Icon/Icons/Play.tsx +++ b/web/packages/design/src/Icon/Icons/Play.tsx @@ -56,6 +56,7 @@ export const Play = forwardRef( size={size} color={color} className="icon icon-play" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Plugs.tsx b/web/packages/design/src/Icon/Icons/Plugs.tsx index 4fa8ffb3fee4..319664c3fabf 100644 --- a/web/packages/design/src/Icon/Icons/Plugs.tsx +++ b/web/packages/design/src/Icon/Icons/Plugs.tsx @@ -56,6 +56,7 @@ export const Plugs = forwardRef( size={size} color={color} className="icon icon-plugs" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/PlugsConnected.tsx b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx index d90852eec7cc..d834f57c6865 100644 --- a/web/packages/design/src/Icon/Icons/PlugsConnected.tsx +++ b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx @@ -56,6 +56,7 @@ export const PlugsConnected = forwardRef( size={size} color={color} className="icon icon-plugsconnected" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Plus.tsx b/web/packages/design/src/Icon/Icons/Plus.tsx index d914db1bc68d..cd32c777bab0 100644 --- a/web/packages/design/src/Icon/Icons/Plus.tsx +++ b/web/packages/design/src/Icon/Icons/Plus.tsx @@ -56,6 +56,7 @@ export const Plus = forwardRef( size={size} color={color} className="icon icon-plus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/PowerSwitch.tsx b/web/packages/design/src/Icon/Icons/PowerSwitch.tsx index b9385d4dcb75..7f1fc78fd188 100644 --- a/web/packages/design/src/Icon/Icons/PowerSwitch.tsx +++ b/web/packages/design/src/Icon/Icons/PowerSwitch.tsx @@ -56,6 +56,7 @@ export const PowerSwitch = forwardRef( size={size} color={color} className="icon icon-powerswitch" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Printer.tsx b/web/packages/design/src/Icon/Icons/Printer.tsx index f6894468f563..f85315c125ed 100644 --- a/web/packages/design/src/Icon/Icons/Printer.tsx +++ b/web/packages/design/src/Icon/Icons/Printer.tsx @@ -56,6 +56,7 @@ export const Printer = forwardRef( size={size} color={color} className="icon icon-printer" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Profile.tsx b/web/packages/design/src/Icon/Icons/Profile.tsx index 576aff7e24f1..3ee97d7faae7 100644 --- a/web/packages/design/src/Icon/Icons/Profile.tsx +++ b/web/packages/design/src/Icon/Icons/Profile.tsx @@ -56,6 +56,7 @@ export const Profile = forwardRef( size={size} color={color} className="icon icon-profile" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/PushPin.tsx b/web/packages/design/src/Icon/Icons/PushPin.tsx index 07b6fe470e8b..7cd9e87d73ca 100644 --- a/web/packages/design/src/Icon/Icons/PushPin.tsx +++ b/web/packages/design/src/Icon/Icons/PushPin.tsx @@ -56,6 +56,7 @@ export const PushPin = forwardRef( size={size} color={color} className="icon icon-pushpin" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/PushPinFilled.tsx b/web/packages/design/src/Icon/Icons/PushPinFilled.tsx index 869aa3db6168..59621d5b22d7 100644 --- a/web/packages/design/src/Icon/Icons/PushPinFilled.tsx +++ b/web/packages/design/src/Icon/Icons/PushPinFilled.tsx @@ -56,6 +56,7 @@ export const PushPinFilled = forwardRef( size={size} color={color} className="icon icon-pushpinfilled" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Question.tsx b/web/packages/design/src/Icon/Icons/Question.tsx index 7bb567f6cb2b..6ce84a1c9a5e 100644 --- a/web/packages/design/src/Icon/Icons/Question.tsx +++ b/web/packages/design/src/Icon/Icons/Question.tsx @@ -56,6 +56,7 @@ export const Question = forwardRef( size={size} color={color} className="icon icon-question" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Refresh.tsx b/web/packages/design/src/Icon/Icons/Refresh.tsx index 9aeef712ac88..cb86c4838493 100644 --- a/web/packages/design/src/Icon/Icons/Refresh.tsx +++ b/web/packages/design/src/Icon/Icons/Refresh.tsx @@ -56,6 +56,7 @@ export const Refresh = forwardRef( size={size} color={color} className="icon icon-refresh" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Restore.tsx b/web/packages/design/src/Icon/Icons/Restore.tsx index f725f98e365b..19006748dfa7 100644 --- a/web/packages/design/src/Icon/Icons/Restore.tsx +++ b/web/packages/design/src/Icon/Icons/Restore.tsx @@ -56,6 +56,7 @@ export const Restore = forwardRef( size={size} color={color} className="icon icon-restore" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/RocketLaunch.tsx b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx index a489d3e0de2e..939af64e28c1 100644 --- a/web/packages/design/src/Icon/Icons/RocketLaunch.tsx +++ b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx @@ -56,6 +56,7 @@ export const RocketLaunch = forwardRef( size={size} color={color} className="icon icon-rocketlaunch" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Rows.tsx b/web/packages/design/src/Icon/Icons/Rows.tsx index 8e0961b689a3..def324ba7714 100644 --- a/web/packages/design/src/Icon/Icons/Rows.tsx +++ b/web/packages/design/src/Icon/Icons/Rows.tsx @@ -56,6 +56,7 @@ export const Rows = forwardRef( size={size} color={color} className="icon icon-rows" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/RowsComfortable.tsx b/web/packages/design/src/Icon/Icons/RowsComfortable.tsx index 77a887c37596..feeb80cf4fe7 100644 --- a/web/packages/design/src/Icon/Icons/RowsComfortable.tsx +++ b/web/packages/design/src/Icon/Icons/RowsComfortable.tsx @@ -56,6 +56,7 @@ export const RowsComfortable = forwardRef( size={size} color={color} className="icon icon-rowscomfortable" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/RowsDense.tsx b/web/packages/design/src/Icon/Icons/RowsDense.tsx index 8dc0140d9262..575eee4d496c 100644 --- a/web/packages/design/src/Icon/Icons/RowsDense.tsx +++ b/web/packages/design/src/Icon/Icons/RowsDense.tsx @@ -56,6 +56,7 @@ export const RowsDense = forwardRef( size={size} color={color} className="icon icon-rowsdense" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Ruler.tsx b/web/packages/design/src/Icon/Icons/Ruler.tsx index 20e2eb5ff4dd..34c5cacdc595 100644 --- a/web/packages/design/src/Icon/Icons/Ruler.tsx +++ b/web/packages/design/src/Icon/Icons/Ruler.tsx @@ -56,6 +56,7 @@ export const Ruler = forwardRef( size={size} color={color} className="icon icon-ruler" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Run.tsx b/web/packages/design/src/Icon/Icons/Run.tsx index 246a61230eba..3fbe30e87404 100644 --- a/web/packages/design/src/Icon/Icons/Run.tsx +++ b/web/packages/design/src/Icon/Icons/Run.tsx @@ -56,6 +56,7 @@ export const Run = forwardRef( size={size} color={color} className="icon icon-run" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Scan.tsx b/web/packages/design/src/Icon/Icons/Scan.tsx index 3bf4f0af1272..7b52ce344c48 100644 --- a/web/packages/design/src/Icon/Icons/Scan.tsx +++ b/web/packages/design/src/Icon/Icons/Scan.tsx @@ -56,6 +56,7 @@ export const Scan = forwardRef( size={size} color={color} className="icon icon-scan" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Server.tsx b/web/packages/design/src/Icon/Icons/Server.tsx index 588e5281b01e..33bc1dac0a6f 100644 --- a/web/packages/design/src/Icon/Icons/Server.tsx +++ b/web/packages/design/src/Icon/Icons/Server.tsx @@ -56,6 +56,7 @@ export const Server = forwardRef( size={size} color={color} className="icon icon-server" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Share.tsx b/web/packages/design/src/Icon/Icons/Share.tsx index 30b7ebd88660..c6f7e55a6f28 100644 --- a/web/packages/design/src/Icon/Icons/Share.tsx +++ b/web/packages/design/src/Icon/Icons/Share.tsx @@ -56,6 +56,7 @@ export const Share = forwardRef( size={size} color={color} className="icon icon-share" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ShieldCheck.tsx b/web/packages/design/src/Icon/Icons/ShieldCheck.tsx index ae862faaa14c..00f25fa84d39 100644 --- a/web/packages/design/src/Icon/Icons/ShieldCheck.tsx +++ b/web/packages/design/src/Icon/Icons/ShieldCheck.tsx @@ -56,6 +56,7 @@ export const ShieldCheck = forwardRef( size={size} color={color} className="icon icon-shieldcheck" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/ShieldWarning.tsx b/web/packages/design/src/Icon/Icons/ShieldWarning.tsx index 71c947be4252..9d663ed70e30 100644 --- a/web/packages/design/src/Icon/Icons/ShieldWarning.tsx +++ b/web/packages/design/src/Icon/Icons/ShieldWarning.tsx @@ -56,6 +56,7 @@ export const ShieldWarning = forwardRef( size={size} color={color} className="icon icon-shieldwarning" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Sliders.tsx b/web/packages/design/src/Icon/Icons/Sliders.tsx index 4becf391054b..e2c8f59cad62 100644 --- a/web/packages/design/src/Icon/Icons/Sliders.tsx +++ b/web/packages/design/src/Icon/Icons/Sliders.tsx @@ -56,6 +56,7 @@ export const Sliders = forwardRef( size={size} color={color} className="icon icon-sliders" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/SlidersVertical.tsx b/web/packages/design/src/Icon/Icons/SlidersVertical.tsx index 109c635df987..15ce3b6f6f01 100644 --- a/web/packages/design/src/Icon/Icons/SlidersVertical.tsx +++ b/web/packages/design/src/Icon/Icons/SlidersVertical.tsx @@ -56,6 +56,7 @@ export const SlidersVertical = forwardRef( size={size} color={color} className="icon icon-slidersvertical" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/SortAscending.tsx b/web/packages/design/src/Icon/Icons/SortAscending.tsx index edd8f9eb0d85..e2da259d6916 100644 --- a/web/packages/design/src/Icon/Icons/SortAscending.tsx +++ b/web/packages/design/src/Icon/Icons/SortAscending.tsx @@ -56,6 +56,7 @@ export const SortAscending = forwardRef( size={size} color={color} className="icon icon-sortascending" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/SortDescending.tsx b/web/packages/design/src/Icon/Icons/SortDescending.tsx index e0f8349efc7d..941467a8f3d6 100644 --- a/web/packages/design/src/Icon/Icons/SortDescending.tsx +++ b/web/packages/design/src/Icon/Icons/SortDescending.tsx @@ -56,6 +56,7 @@ export const SortDescending = forwardRef( size={size} color={color} className="icon icon-sortdescending" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Sparkle.tsx b/web/packages/design/src/Icon/Icons/Sparkle.tsx index 98cdfef1518d..5455a5f92bb2 100644 --- a/web/packages/design/src/Icon/Icons/Sparkle.tsx +++ b/web/packages/design/src/Icon/Icons/Sparkle.tsx @@ -56,6 +56,7 @@ export const Sparkle = forwardRef( size={size} color={color} className="icon icon-sparkle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Speed.tsx b/web/packages/design/src/Icon/Icons/Speed.tsx index f7ad45387c99..fc9a72e429b3 100644 --- a/web/packages/design/src/Icon/Icons/Speed.tsx +++ b/web/packages/design/src/Icon/Icons/Speed.tsx @@ -56,6 +56,7 @@ export const Speed = forwardRef( size={size} color={color} className="icon icon-speed" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Spinner.tsx b/web/packages/design/src/Icon/Icons/Spinner.tsx index 78ac696baa1c..d161c6d1aa5f 100644 --- a/web/packages/design/src/Icon/Icons/Spinner.tsx +++ b/web/packages/design/src/Icon/Icons/Spinner.tsx @@ -56,6 +56,7 @@ export const Spinner = forwardRef( size={size} color={color} className="icon icon-spinner" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/SquaresFour.tsx b/web/packages/design/src/Icon/Icons/SquaresFour.tsx index dab6771f2a65..caf12d44a94e 100644 --- a/web/packages/design/src/Icon/Icons/SquaresFour.tsx +++ b/web/packages/design/src/Icon/Icons/SquaresFour.tsx @@ -56,6 +56,7 @@ export const SquaresFour = forwardRef( size={size} color={color} className="icon icon-squaresfour" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Stack.tsx b/web/packages/design/src/Icon/Icons/Stack.tsx index e061a00a2841..190db81bb73d 100644 --- a/web/packages/design/src/Icon/Icons/Stack.tsx +++ b/web/packages/design/src/Icon/Icons/Stack.tsx @@ -56,6 +56,7 @@ export const Stack = forwardRef( size={size} color={color} className="icon icon-stack" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Stars.tsx b/web/packages/design/src/Icon/Icons/Stars.tsx index 195c37014b65..41c0336eec21 100644 --- a/web/packages/design/src/Icon/Icons/Stars.tsx +++ b/web/packages/design/src/Icon/Icons/Stars.tsx @@ -56,6 +56,7 @@ export const Stars = forwardRef( size={size} color={color} className="icon icon-stars" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Sun.tsx b/web/packages/design/src/Icon/Icons/Sun.tsx index 0d47928b2edb..bd914b17670d 100644 --- a/web/packages/design/src/Icon/Icons/Sun.tsx +++ b/web/packages/design/src/Icon/Icons/Sun.tsx @@ -56,6 +56,7 @@ export const Sun = forwardRef( size={size} color={color} className="icon icon-sun" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/SyncAlt.tsx b/web/packages/design/src/Icon/Icons/SyncAlt.tsx index d45ae7bf2fcf..ccfd908a3b37 100644 --- a/web/packages/design/src/Icon/Icons/SyncAlt.tsx +++ b/web/packages/design/src/Icon/Icons/SyncAlt.tsx @@ -56,6 +56,7 @@ export const SyncAlt = forwardRef( size={size} color={color} className="icon icon-syncalt" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Table.tsx b/web/packages/design/src/Icon/Icons/Table.tsx index a61d97197c43..8259af59defc 100644 --- a/web/packages/design/src/Icon/Icons/Table.tsx +++ b/web/packages/design/src/Icon/Icons/Table.tsx @@ -56,6 +56,7 @@ export const Table = forwardRef( size={size} color={color} className="icon icon-table" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Tablet.tsx b/web/packages/design/src/Icon/Icons/Tablet.tsx index 851053f77e71..02da11f0de08 100644 --- a/web/packages/design/src/Icon/Icons/Tablet.tsx +++ b/web/packages/design/src/Icon/Icons/Tablet.tsx @@ -56,6 +56,7 @@ export const Tablet = forwardRef( size={size} color={color} className="icon icon-tablet" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Tags.tsx b/web/packages/design/src/Icon/Icons/Tags.tsx index d9cd9a288e93..693a03a94a2c 100644 --- a/web/packages/design/src/Icon/Icons/Tags.tsx +++ b/web/packages/design/src/Icon/Icons/Tags.tsx @@ -56,6 +56,7 @@ export const Tags = forwardRef( size={size} color={color} className="icon icon-tags" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Terminal.tsx b/web/packages/design/src/Icon/Icons/Terminal.tsx index d2205ec7c686..32497b62ac90 100644 --- a/web/packages/design/src/Icon/Icons/Terminal.tsx +++ b/web/packages/design/src/Icon/Icons/Terminal.tsx @@ -56,6 +56,7 @@ export const Terminal = forwardRef( size={size} color={color} className="icon icon-terminal" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Trash.tsx b/web/packages/design/src/Icon/Icons/Trash.tsx index 183d8788fc18..b0332dc77a40 100644 --- a/web/packages/design/src/Icon/Icons/Trash.tsx +++ b/web/packages/design/src/Icon/Icons/Trash.tsx @@ -56,6 +56,7 @@ export const Trash = forwardRef( size={size} color={color} className="icon icon-trash" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Twitter.tsx b/web/packages/design/src/Icon/Icons/Twitter.tsx index ee396788a4c1..4741f8cd2988 100644 --- a/web/packages/design/src/Icon/Icons/Twitter.tsx +++ b/web/packages/design/src/Icon/Icons/Twitter.tsx @@ -56,6 +56,7 @@ export const Twitter = forwardRef( size={size} color={color} className="icon icon-twitter" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Unarchive.tsx b/web/packages/design/src/Icon/Icons/Unarchive.tsx index 46dd60108cd0..9dc807db6d68 100644 --- a/web/packages/design/src/Icon/Icons/Unarchive.tsx +++ b/web/packages/design/src/Icon/Icons/Unarchive.tsx @@ -56,6 +56,7 @@ export const Unarchive = forwardRef( size={size} color={color} className="icon icon-unarchive" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Unlink.tsx b/web/packages/design/src/Icon/Icons/Unlink.tsx index 71c25958c2de..58ed40ea3690 100644 --- a/web/packages/design/src/Icon/Icons/Unlink.tsx +++ b/web/packages/design/src/Icon/Icons/Unlink.tsx @@ -56,6 +56,7 @@ export const Unlink = forwardRef( size={size} color={color} className="icon icon-unlink" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Unlock.tsx b/web/packages/design/src/Icon/Icons/Unlock.tsx index 65ebdba6e297..c2200fc571ce 100644 --- a/web/packages/design/src/Icon/Icons/Unlock.tsx +++ b/web/packages/design/src/Icon/Icons/Unlock.tsx @@ -56,6 +56,7 @@ export const Unlock = forwardRef( size={size} color={color} className="icon icon-unlock" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Upload.tsx b/web/packages/design/src/Icon/Icons/Upload.tsx index 14d27e104f67..5ee3b3b78fe6 100644 --- a/web/packages/design/src/Icon/Icons/Upload.tsx +++ b/web/packages/design/src/Icon/Icons/Upload.tsx @@ -56,6 +56,7 @@ export const Upload = forwardRef( size={size} color={color} className="icon icon-upload" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UsbDrive.tsx b/web/packages/design/src/Icon/Icons/UsbDrive.tsx index 5796bef08def..ab7f73ceef8d 100644 --- a/web/packages/design/src/Icon/Icons/UsbDrive.tsx +++ b/web/packages/design/src/Icon/Icons/UsbDrive.tsx @@ -56,6 +56,7 @@ export const UsbDrive = forwardRef( size={size} color={color} className="icon icon-usbdrive" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/User.tsx b/web/packages/design/src/Icon/Icons/User.tsx index fe10dc329e71..f1a6b0e9d2fc 100644 --- a/web/packages/design/src/Icon/Icons/User.tsx +++ b/web/packages/design/src/Icon/Icons/User.tsx @@ -56,6 +56,7 @@ export const User = forwardRef( size={size} color={color} className="icon icon-user" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserAdd.tsx b/web/packages/design/src/Icon/Icons/UserAdd.tsx index 473279325081..2b2eacf3c72e 100644 --- a/web/packages/design/src/Icon/Icons/UserAdd.tsx +++ b/web/packages/design/src/Icon/Icons/UserAdd.tsx @@ -56,6 +56,7 @@ export const UserAdd = forwardRef( size={size} color={color} className="icon icon-useradd" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserCheck.tsx b/web/packages/design/src/Icon/Icons/UserCheck.tsx index 15312dcc0d71..5be689b283f5 100644 --- a/web/packages/design/src/Icon/Icons/UserCheck.tsx +++ b/web/packages/design/src/Icon/Icons/UserCheck.tsx @@ -56,6 +56,7 @@ export const UserCheck = forwardRef( size={size} color={color} className="icon icon-usercheck" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserCircleGear.tsx b/web/packages/design/src/Icon/Icons/UserCircleGear.tsx index 74d4451f0272..1b46098ee891 100644 --- a/web/packages/design/src/Icon/Icons/UserCircleGear.tsx +++ b/web/packages/design/src/Icon/Icons/UserCircleGear.tsx @@ -56,6 +56,7 @@ export const UserCircleGear = forwardRef( size={size} color={color} className="icon icon-usercirclegear" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserFocus.tsx b/web/packages/design/src/Icon/Icons/UserFocus.tsx index 343569a2e2bb..d1c09af3158f 100644 --- a/web/packages/design/src/Icon/Icons/UserFocus.tsx +++ b/web/packages/design/src/Icon/Icons/UserFocus.tsx @@ -56,6 +56,7 @@ export const UserFocus = forwardRef( size={size} color={color} className="icon icon-userfocus" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserIdBadge.tsx b/web/packages/design/src/Icon/Icons/UserIdBadge.tsx index ef36c8fe535a..62f2db4bf2cf 100644 --- a/web/packages/design/src/Icon/Icons/UserIdBadge.tsx +++ b/web/packages/design/src/Icon/Icons/UserIdBadge.tsx @@ -56,6 +56,7 @@ export const UserIdBadge = forwardRef( size={size} color={color} className="icon icon-useridbadge" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UserList.tsx b/web/packages/design/src/Icon/Icons/UserList.tsx index 4d80d775fdf5..426214907ef3 100644 --- a/web/packages/design/src/Icon/Icons/UserList.tsx +++ b/web/packages/design/src/Icon/Icons/UserList.tsx @@ -56,6 +56,7 @@ export const UserList = forwardRef( size={size} color={color} className="icon icon-userlist" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Users.tsx b/web/packages/design/src/Icon/Icons/Users.tsx index 4665d556aab1..5a16ca7dc5c4 100644 --- a/web/packages/design/src/Icon/Icons/Users.tsx +++ b/web/packages/design/src/Icon/Icons/Users.tsx @@ -56,6 +56,7 @@ export const Users = forwardRef( size={size} color={color} className="icon icon-users" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/UsersTriple.tsx b/web/packages/design/src/Icon/Icons/UsersTriple.tsx index af227ab7b074..af400a38f98e 100644 --- a/web/packages/design/src/Icon/Icons/UsersTriple.tsx +++ b/web/packages/design/src/Icon/Icons/UsersTriple.tsx @@ -56,6 +56,7 @@ export const UsersTriple = forwardRef( size={size} color={color} className="icon icon-userstriple" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Vault.tsx b/web/packages/design/src/Icon/Icons/Vault.tsx index 9d7f8f4bac88..4cbdbf2557da 100644 --- a/web/packages/design/src/Icon/Icons/Vault.tsx +++ b/web/packages/design/src/Icon/Icons/Vault.tsx @@ -56,6 +56,7 @@ export const Vault = forwardRef( size={size} color={color} className="icon icon-vault" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/VideoGame.tsx b/web/packages/design/src/Icon/Icons/VideoGame.tsx index a2566ab1558b..0d2daaa9bc64 100644 --- a/web/packages/design/src/Icon/Icons/VideoGame.tsx +++ b/web/packages/design/src/Icon/Icons/VideoGame.tsx @@ -56,6 +56,7 @@ export const VideoGame = forwardRef( size={size} color={color} className="icon icon-videogame" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/VolumeUp.tsx b/web/packages/design/src/Icon/Icons/VolumeUp.tsx index dc0fb1e90e59..6a74a54b48b6 100644 --- a/web/packages/design/src/Icon/Icons/VolumeUp.tsx +++ b/web/packages/design/src/Icon/Icons/VolumeUp.tsx @@ -56,6 +56,7 @@ export const VolumeUp = forwardRef( size={size} color={color} className="icon icon-volumeup" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/VpnKey.tsx b/web/packages/design/src/Icon/Icons/VpnKey.tsx index 64b21ca6d6f1..315a40fd81f8 100644 --- a/web/packages/design/src/Icon/Icons/VpnKey.tsx +++ b/web/packages/design/src/Icon/Icons/VpnKey.tsx @@ -56,6 +56,7 @@ export const VpnKey = forwardRef( size={size} color={color} className="icon icon-vpnkey" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Wand.tsx b/web/packages/design/src/Icon/Icons/Wand.tsx index b203151adbaf..eee927f1f0f3 100644 --- a/web/packages/design/src/Icon/Icons/Wand.tsx +++ b/web/packages/design/src/Icon/Icons/Wand.tsx @@ -56,6 +56,7 @@ export const Wand = forwardRef( size={size} color={color} className="icon icon-wand" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Warning.tsx b/web/packages/design/src/Icon/Icons/Warning.tsx index a50c4668062a..4b5885361019 100644 --- a/web/packages/design/src/Icon/Icons/Warning.tsx +++ b/web/packages/design/src/Icon/Icons/Warning.tsx @@ -56,6 +56,7 @@ export const Warning = forwardRef( size={size} color={color} className="icon icon-warning" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/WarningCircle.tsx b/web/packages/design/src/Icon/Icons/WarningCircle.tsx index cd04ec6ba1dd..f8e0eabd006e 100644 --- a/web/packages/design/src/Icon/Icons/WarningCircle.tsx +++ b/web/packages/design/src/Icon/Icons/WarningCircle.tsx @@ -56,6 +56,7 @@ export const WarningCircle = forwardRef( size={size} color={color} className="icon icon-warningcircle" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Wifi.tsx b/web/packages/design/src/Icon/Icons/Wifi.tsx index 5fbec35e2e4e..c07b74062823 100644 --- a/web/packages/design/src/Icon/Icons/Wifi.tsx +++ b/web/packages/design/src/Icon/Icons/Wifi.tsx @@ -56,6 +56,7 @@ export const Wifi = forwardRef( size={size} color={color} className="icon icon-wifi" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Windows.tsx b/web/packages/design/src/Icon/Icons/Windows.tsx index 515fcaa738fb..49a9564b46be 100644 --- a/web/packages/design/src/Icon/Icons/Windows.tsx +++ b/web/packages/design/src/Icon/Icons/Windows.tsx @@ -56,6 +56,7 @@ export const Windows = forwardRef( size={size} color={color} className="icon icon-windows" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Wrench.tsx b/web/packages/design/src/Icon/Icons/Wrench.tsx index dccf666166b3..ed923c86e323 100644 --- a/web/packages/design/src/Icon/Icons/Wrench.tsx +++ b/web/packages/design/src/Icon/Icons/Wrench.tsx @@ -56,6 +56,7 @@ export const Wrench = forwardRef( size={size} color={color} className="icon icon-wrench" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/XCheck.tsx b/web/packages/design/src/Icon/Icons/XCheck.tsx index 1d2636f81978..9197f70dac2d 100644 --- a/web/packages/design/src/Icon/Icons/XCheck.tsx +++ b/web/packages/design/src/Icon/Icons/XCheck.tsx @@ -56,6 +56,7 @@ export const XCheck = forwardRef( size={size} color={color} className="icon icon-xcheck" + {...otherProps} ref={ref} > diff --git a/web/packages/design/src/Icon/Icons/Youtube.tsx b/web/packages/design/src/Icon/Icons/Youtube.tsx index 8b8b884dcc81..55dd03d652e0 100644 --- a/web/packages/design/src/Icon/Icons/Youtube.tsx +++ b/web/packages/design/src/Icon/Icons/Youtube.tsx @@ -56,6 +56,7 @@ export const Youtube = forwardRef( size={size} color={color} className="icon icon-youtube" + {...otherProps} ref={ref} > diff --git a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx index 8670e609c755..9c0b455a3449 100644 --- a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx +++ b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx @@ -469,7 +469,6 @@ const CardInnerContainer = styled(Flex)< css` border: 2px solid ${p.theme.colors.interactive.solid.alert.active}; `} - &:hover { // Make the border invisible instead of removing it, this is to prevent things from shifting due to the size change. diff --git a/web/packages/shared/components/UnifiedResources/shared/StatusInfo.tsx b/web/packages/shared/components/UnifiedResources/shared/StatusInfo.tsx index 822ee16d9adf..0bb0fdb141bc 100644 --- a/web/packages/shared/components/UnifiedResources/shared/StatusInfo.tsx +++ b/web/packages/shared/components/UnifiedResources/shared/StatusInfo.tsx @@ -291,10 +291,12 @@ function UnhealthyServerList({ servers }: { servers: SharedResourceServer[] }) { padding: ${p => p.theme.space[3]}px; border-left: 4px solid ${p => p.theme.colors.interactive.solid.alert.default}; - ${index !== lastServerInList && - css` - border-bottom: 1px solid ${p => p.theme.colors.spotBackground[1]}; - `} + ${ + index !== lastServerInList && + css` + border-bottom: 1px solid ${p => p.theme.colors.spotBackground[1]}; + ` + } `} > diff --git a/web/packages/shared/package.json b/web/packages/shared/package.json index 4bba4c54e03d..09d216a9f65b 100644 --- a/web/packages/shared/package.json +++ b/web/packages/shared/package.json @@ -21,6 +21,6 @@ "semver": "^7.8.5" }, "devDependencies": { - "@types/semver": "^7.7.1" + "@types/semver": "^7.8.0" } } diff --git a/web/packages/teleport/package.json b/web/packages/teleport/package.json index bd6f4715fce9..eef51b123048 100644 --- a/web/packages/teleport/package.json +++ b/web/packages/teleport/package.json @@ -22,19 +22,19 @@ "@gravitational/design": "workspace:*", "@gravitational/shared": "workspace:*", "@opentelemetry/api": "^1.9.1", - "@opentelemetry/context-zone": "^2.8.0", - "@opentelemetry/core": "^2.8.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.219.0", - "@opentelemetry/instrumentation": "^0.219.0", - "@opentelemetry/instrumentation-document-load": "^0.64.0", - "@opentelemetry/instrumentation-fetch": "^0.219.0", - "@opentelemetry/instrumentation-user-interaction": "^0.63.0", - "@opentelemetry/instrumentation-xml-http-request": "^0.219.0", - "@opentelemetry/propagator-b3": "^2.8.0", - "@opentelemetry/resources": "^2.8.0", - "@opentelemetry/sdk-trace-base": "^2.8.0", - "@opentelemetry/sdk-trace-web": "^2.8.0", - "@opentelemetry/semantic-conventions": "^1.41.1", + "@opentelemetry/context-zone": "^2.10.0", + "@opentelemetry/core": "^2.10.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.221.0", + "@opentelemetry/instrumentation": "^0.221.0", + "@opentelemetry/instrumentation-document-load": "^0.66.0", + "@opentelemetry/instrumentation-fetch": "^0.221.0", + "@opentelemetry/instrumentation-user-interaction": "^0.65.0", + "@opentelemetry/instrumentation-xml-http-request": "^0.221.0", + "@opentelemetry/propagator-b3": "^2.10.0", + "@opentelemetry/resources": "^2.10.0", + "@opentelemetry/sdk-trace-base": "^2.10.0", + "@opentelemetry/sdk-trace-web": "^2.10.0", + "@opentelemetry/semantic-conventions": "^1.43.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-image": "^0.9.0", "@xterm/addon-web-links": "^0.12.0", diff --git a/web/packages/teleport/src/Main/Main.tsx b/web/packages/teleport/src/Main/Main.tsx index 0fcebcf06d57..9eaf9e284862 100644 --- a/web/packages/teleport/src/Main/Main.tsx +++ b/web/packages/teleport/src/Main/Main.tsx @@ -171,15 +171,13 @@ export function Main(props: MainProps) { return 'danger'; }; - const banners: BannerType[] = alerts.map( - (alert): BannerType => ({ - message: alert.spec.message, - severity: mapSeverity(alert.spec.severity), - linkDestination: alert.metadata.labels[LINK_DESTINATION_LABEL], - linkText: alert.metadata.labels[LINK_TEXT_LABEL], - id: alert.metadata.name, - }) - ); + const banners: BannerType[] = alerts.map((alert): BannerType => ({ + message: alert.spec.message, + severity: mapSeverity(alert.spec.severity), + linkDestination: alert.metadata.labels[LINK_DESTINATION_LABEL], + linkText: alert.metadata.labels[LINK_TEXT_LABEL], + id: alert.metadata.name, + })); return ( diff --git a/web/packages/teleport/src/Navigation/Section.tsx b/web/packages/teleport/src/Navigation/Section.tsx index d8c2a884784b..9fbea57297b0 100644 --- a/web/packages/teleport/src/Navigation/Section.tsx +++ b/web/packages/teleport/src/Navigation/Section.tsx @@ -345,11 +345,13 @@ export function getCategoryStyles( background: ${theme.colors.interactive.tonal.primary[2]}; color: ${theme.colors.interactive.solid.primary.active}; } - ${isExpanded && - ` + ${ + isExpanded && + ` background: ${theme.colors.interactive.tonal.primary[1]}; color: ${theme.colors.interactive.solid.primary.default}; - `} + ` + } `; } @@ -365,11 +367,13 @@ export function getCategoryStyles( background: ${theme.colors.interactive.tonal.neutral[1]}; color: ${theme.colors.text.main}; } - ${isExpanded && - ` + ${ + isExpanded && + ` background: ${theme.colors.interactive.tonal.neutral[0]}; color: ${theme.colors.text.main}; - `} + ` + } `; } diff --git a/web/packages/teleport/src/SessionRecordings/list/RecordingItem.tsx b/web/packages/teleport/src/SessionRecordings/list/RecordingItem.tsx index 6af0536a6386..deac294deeb9 100644 --- a/web/packages/teleport/src/SessionRecordings/list/RecordingItem.tsx +++ b/web/packages/teleport/src/SessionRecordings/list/RecordingItem.tsx @@ -243,17 +243,24 @@ export const RecordingItemContainer = styled(Link).withConfig({ box-shadow: ${props => props.theme.boxShadow[3]}; } - ${p.viewMode === ViewMode.List - ? css` - padding: ${p.density === Density.Compact - ? `${p.theme.space[2]}px` - : `calc(${p.theme.space[2]}px + 2px) ${p.theme.space[2]}px`}; - gap: ${p.theme.space[3]}px; - ` - : css` - flex-direction: column; - `} - transition: background-color 150ms, border-color 150ms, box-shadow 150ms; + ${ + p.viewMode === ViewMode.List + ? css` + padding: ${ + p.density === Density.Compact + ? `${p.theme.space[2]}px` + : `calc(${p.theme.space[2]}px + 2px) ${p.theme.space[2]}px` + }; + gap: ${p.theme.space[3]}px; + ` + : css` + flex-direction: column; + ` + } + transition: + background-color 150ms, + border-color 150ms, + box-shadow 150ms; ` ); @@ -274,20 +281,22 @@ export const ThumbnailContainer = styled.div< position: relative; overflow: hidden; - ${p.viewMode === ViewMode.List - ? css` - border: 1px solid ${p.theme.colors.interactive.tonal.neutral[0]}; - border-radius: ${p.theme.radii[2]}px; - height: 100%; - width: ${p.density === Density.Compact ? '256px' : '320px'}; - ` - : css` - border-bottom: 1px solid - ${p.theme.colors.interactive.tonal.neutral[0]}; - flex: 1; - height: ${p.density === Density.Compact ? '90px' : '120px'}; - width: 100%; - `} + ${ + p.viewMode === ViewMode.List + ? css` + border: 1px solid ${p.theme.colors.interactive.tonal.neutral[0]}; + border-radius: ${p.theme.radii[2]}px; + height: 100%; + width: ${p.density === Density.Compact ? '256px' : '320px'}; + ` + : css` + border-bottom: 1px solid + ${p.theme.colors.interactive.tonal.neutral[0]}; + flex: 1; + height: ${p.density === Density.Compact ? '90px' : '120px'}; + width: 100%; + ` + } ${RecordingItemContainer}:hover & { border-color: transparent; @@ -306,21 +315,27 @@ export const RecordingDetails = styled.div< flex-shrink: 0; font-size: ${p.density === Density.Compact ? '13px' : '15px'}; - ${p.viewMode === ViewMode.List - ? css` - gap: ${p.density === Density.Compact - ? p.theme.space[1] - : p.theme.space[2]}px; - padding-top: ${p.density === Density.Compact - ? p.theme.space[0] - : p.theme.space[2]}px; - padding-right: ${p.theme.space[1]}px; - ` - : css` - padding: ${p.theme.space[3]}px ${p.theme.space[2]}px - ${p.theme.space[2]}px ${p.theme.space[3]}px; - gap: ${p.theme.space[1]}px; - `} + ${ + p.viewMode === ViewMode.List + ? css` + gap: ${ + p.density === Density.Compact + ? p.theme.space[1] + : p.theme.space[2] + }px; + padding-top: ${ + p.density === Density.Compact + ? p.theme.space[0] + : p.theme.space[2] + }px; + padding-right: ${p.theme.space[1]}px; + ` + : css` + padding: ${p.theme.space[3]}px ${p.theme.space[2]}px + ${p.theme.space[2]}px ${p.theme.space[3]}px; + gap: ${p.theme.space[1]}px; + ` + } ` ); @@ -332,13 +347,15 @@ export const BadgeContainer = styled.div>( display: flex; gap: ${p.theme.space[1]}px; - ${p.viewMode === ViewMode.List - ? css` - bottom: ${p.theme.space[2]}px; - ` - : css` - top: ${p.theme.space[2]}px; - `} + ${ + p.viewMode === ViewMode.List + ? css` + bottom: ${p.theme.space[2]}px; + ` + : css` + top: ${p.theme.space[2]}px; + ` + } ` ); @@ -351,13 +368,15 @@ export const Duration = styled.div>( line-height: 1; padding: ${p.theme.space[1]}px ${p.theme.space[2]}px; - ${p.viewMode === ViewMode.List - ? css` - bottom: ${p.theme.space[2]}px; - ` - : css` - top: ${p.theme.space[2]}px; - `} + ${ + p.viewMode === ViewMode.List + ? css` + bottom: ${p.theme.space[2]}px; + ` + : css` + top: ${p.theme.space[2]}px; + ` + } ` ); diff --git a/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx index 52a4d5583b6f..3b1b5a076fd1 100644 --- a/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx +++ b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx @@ -196,14 +196,12 @@ const AppLaunch = ({ app }: AppLaunchProps) => { ps => !ps.requiresRequest ); if (isAwsIdentityCenterApp) { - awsConsoleOrIdentityCenterRoles = permissionSets.map( - (ps): AwsRole => ({ - name: ps.name, - arn: ps.name, - display: ps.name, - accountId: name, - }) - ); + awsConsoleOrIdentityCenterRoles = permissionSets.map((ps): AwsRole => ({ + name: ps.name, + arn: ps.name, + display: ps.name, + accountId: name, + })); } function getAwsLaunchUrl(arnOrPermSetName: string) { if (isAwsIdentityCenterApp) { diff --git a/web/packages/teleport/src/components/ExternalAuditStorageCta/ExternalAuditStorageCta.tsx b/web/packages/teleport/src/components/ExternalAuditStorageCta/ExternalAuditStorageCta.tsx index 526a861352ed..22529e0d4315 100644 --- a/web/packages/teleport/src/components/ExternalAuditStorageCta/ExternalAuditStorageCta.tsx +++ b/web/packages/teleport/src/components/ExternalAuditStorageCta/ExternalAuditStorageCta.tsx @@ -64,7 +64,7 @@ export const ExternalAuditStorageCta = (props: BoxProps) => { css={` grid-column: span 2; @media screen and (max-width: ${props => - props.theme.breakpoints.mobile}) { + props.theme.breakpoints.mobile}) { grid-column: auto; } `} @@ -73,7 +73,7 @@ export const ExternalAuditStorageCta = (props: BoxProps) => { justifyContent="space-between" css={` @media screen and (max-width: ${props => - props.theme.breakpoints.mobile}) { + props.theme.breakpoints.mobile}) { flex-direction: column; gap: ${props => props.theme.space[3]}px; } diff --git a/web/packages/teleterm/src/ui/TopBar/Connections/ConnectionsFilterableList/ConnectionStatusIndicator.tsx b/web/packages/teleterm/src/ui/TopBar/Connections/ConnectionsFilterableList/ConnectionStatusIndicator.tsx index 5880fb2fe610..7aabc073becb 100644 --- a/web/packages/teleterm/src/ui/TopBar/Connections/ConnectionsFilterableList/ConnectionStatusIndicator.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Connections/ConnectionsFilterableList/ConnectionStatusIndicator.tsx @@ -96,10 +96,12 @@ const StyledStatus = styled(Box)<{ content: '✕'; font-size: 12px; - ${!props.$inline && - `position: absolute; + ${ + !props.$inline && + `position: absolute; top: -8px; - `} + ` + } } `; } @@ -109,25 +111,29 @@ const StyledStatus = styled(Box)<{ &:after { content: '⚠'; font-size: 12px; - ${props.$inline && - ` + ${ + props.$inline && + ` // This cuts out a little portion of the icon on the left. This is most clearly visible // on Windows. But at least it better aligns with the other statuses. // // TODO(ravicious): Rewrite this to not use weird characters to represent different // statuses so that all statuses properly align together. margin: -1px; - `} - - ${!props.$inline && ` + } + + ${ + !props.$inline && + ` position: absolute; top: -1px; // Visually, -1px seems to be better aligned than -2px, especially when looking at // VnetWarning story. left: -1px; line-height: 8px; - `} + ` + } } `; } From 289411af80a0023c3b2c11f171b502075dda90bc Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 12 Aug 2026 10:48:35 +0000 Subject: [PATCH 264/347] Update `e` (#69596) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 68b046e99099..42c822cecac2 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 68b046e99099ea09798f9e59f8efabf512b682e2 +Subproject commit 42c822cecac2ca29701d159b17f70d89553e123f From d73b1246b9f96b439019f483c83753e5c18fc7aa Mon Sep 17 00:00:00 2001 From: Michael Patterson-Muir Date: Wed, 12 Aug 2026 11:57:22 +0000 Subject: [PATCH 265/347] discovery: include Azure identity in subscription failure UserTasks (#69056) * Add tenant_id and client_id fields to Azure VM discovery tasks and update related handling * Update lib/usertasks/descriptions/azure-vm-subscription-list-denied.md Co-authored-by: alex hemard --------- Co-authored-by: alex hemard --- .../usertasks/v1/user_tasks_protohybrid.pb.go | 40 ++++++++++++- .../usertasks/v1/user_tasks_protoopaque.pb.go | 36 +++++++++++- .../teleport/usertasks/v1/user_tasks.proto | 4 ++ lib/srv/discovery/discovery.go | 14 ++++- lib/srv/discovery/status.go | 10 +++- lib/srv/discovery/status_test.go | 37 ++++++++++++ .../azure-vm-subscription-list-denied.md | 2 +- .../Discover/Overview/ActivityTab.test.tsx | 56 +++++++++++++++++++ .../src/Discover/Overview/UserTaskDrawer.tsx | 6 ++ .../src/services/integrations/integrations.ts | 2 + .../src/services/integrations/types.ts | 4 ++ 11 files changed, 203 insertions(+), 8 deletions(-) diff --git a/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protohybrid.pb.go b/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protohybrid.pb.go index d0d1fb28e618..91b5f1142553 100644 --- a/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protohybrid.pb.go @@ -1293,7 +1293,11 @@ type DiscoverAzureVM struct { // ResourceGroup is the Azure Resource Group where VMs are located. ResourceGroup string `protobuf:"bytes,3,opt,name=resource_group,json=resourceGroup,proto3" json:"resource_group,omitempty"` // Region is the Azure Region where Teleport failed to enroll VMs. - Region string `protobuf:"bytes,4,opt,name=region,proto3" json:"region,omitempty"` + Region string `protobuf:"bytes,4,opt,name=region,proto3" json:"region,omitempty"` + // TenantID is the Microsoft Entra tenant ID used by the Azure integration. + TenantId string `protobuf:"bytes,5,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + // ClientID is the client ID of the Azure integration's managed identity or service principal. + ClientId string `protobuf:"bytes,6,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1351,6 +1355,20 @@ func (x *DiscoverAzureVM) GetRegion() string { return "" } +func (x *DiscoverAzureVM) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +func (x *DiscoverAzureVM) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + func (x *DiscoverAzureVM) SetInstances(v map[string]*DiscoverAzureVMInstance) { x.Instances = v } @@ -1367,6 +1385,14 @@ func (x *DiscoverAzureVM) SetRegion(v string) { x.Region = v } +func (x *DiscoverAzureVM) SetTenantId(v string) { + x.TenantId = v +} + +func (x *DiscoverAzureVM) SetClientId(v string) { + x.ClientId = v +} + type DiscoverAzureVM_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -1378,6 +1404,10 @@ type DiscoverAzureVM_builder struct { ResourceGroup string // Region is the Azure Region where Teleport failed to enroll VMs. Region string + // TenantID is the Microsoft Entra tenant ID used by the Azure integration. + TenantId string + // ClientID is the client ID of the Azure integration's managed identity or service principal. + ClientId string } func (b0 DiscoverAzureVM_builder) Build() *DiscoverAzureVM { @@ -1388,6 +1418,8 @@ func (b0 DiscoverAzureVM_builder) Build() *DiscoverAzureVM { x.SubscriptionId = b.SubscriptionId x.ResourceGroup = b.ResourceGroup x.Region = b.Region + x.TenantId = b.TenantId + x.ClientId = b.ClientId return m0 } @@ -1684,12 +1716,14 @@ const file_teleport_usertasks_v1_user_tasks_proto_rawDesc = "" + "\x06engine\x18\x03 \x01(\tR\x06engine\x12)\n" + "\x10discovery_config\x18\x04 \x01(\tR\x0fdiscoveryConfig\x12'\n" + "\x0fdiscovery_group\x18\x05 \x01(\tR\x0ediscoveryGroup\x127\n" + - "\tsync_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xbc\x02\n" + + "\tsync_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xf6\x02\n" + "\x0fDiscoverAzureVM\x12S\n" + "\tinstances\x18\x01 \x03(\v25.teleport.usertasks.v1.DiscoverAzureVM.InstancesEntryR\tinstances\x12'\n" + "\x0fsubscription_id\x18\x02 \x01(\tR\x0esubscriptionId\x12%\n" + "\x0eresource_group\x18\x03 \x01(\tR\rresourceGroup\x12\x16\n" + - "\x06region\x18\x04 \x01(\tR\x06region\x1al\n" + + "\x06region\x18\x04 \x01(\tR\x06region\x12\x1b\n" + + "\ttenant_id\x18\x05 \x01(\tR\btenantId\x12\x1b\n" + + "\tclient_id\x18\x06 \x01(\tR\bclientId\x1al\n" + "\x0eInstancesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12D\n" + "\x05value\x18\x02 \x01(\v2..teleport.usertasks.v1.DiscoverAzureVMInstanceR\x05value:\x028\x01\"\x9a\x03\n" + diff --git a/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protoopaque.pb.go b/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protoopaque.pb.go index 990f920e0ec5..191ba0799a01 100644 --- a/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/usertasks/v1/user_tasks_protoopaque.pb.go @@ -1229,6 +1229,8 @@ type DiscoverAzureVM struct { xxx_hidden_SubscriptionId string `protobuf:"bytes,2,opt,name=subscription_id,json=subscriptionId,proto3"` xxx_hidden_ResourceGroup string `protobuf:"bytes,3,opt,name=resource_group,json=resourceGroup,proto3"` xxx_hidden_Region string `protobuf:"bytes,4,opt,name=region,proto3"` + xxx_hidden_TenantId string `protobuf:"bytes,5,opt,name=tenant_id,json=tenantId,proto3"` + xxx_hidden_ClientId string `protobuf:"bytes,6,opt,name=client_id,json=clientId,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1286,6 +1288,20 @@ func (x *DiscoverAzureVM) GetRegion() string { return "" } +func (x *DiscoverAzureVM) GetTenantId() string { + if x != nil { + return x.xxx_hidden_TenantId + } + return "" +} + +func (x *DiscoverAzureVM) GetClientId() string { + if x != nil { + return x.xxx_hidden_ClientId + } + return "" +} + func (x *DiscoverAzureVM) SetInstances(v map[string]*DiscoverAzureVMInstance) { x.xxx_hidden_Instances = v } @@ -1302,6 +1318,14 @@ func (x *DiscoverAzureVM) SetRegion(v string) { x.xxx_hidden_Region = v } +func (x *DiscoverAzureVM) SetTenantId(v string) { + x.xxx_hidden_TenantId = v +} + +func (x *DiscoverAzureVM) SetClientId(v string) { + x.xxx_hidden_ClientId = v +} + type DiscoverAzureVM_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -1313,6 +1337,10 @@ type DiscoverAzureVM_builder struct { ResourceGroup string // Region is the Azure Region where Teleport failed to enroll VMs. Region string + // TenantID is the Microsoft Entra tenant ID used by the Azure integration. + TenantId string + // ClientID is the client ID of the Azure integration's managed identity or service principal. + ClientId string } func (b0 DiscoverAzureVM_builder) Build() *DiscoverAzureVM { @@ -1323,6 +1351,8 @@ func (b0 DiscoverAzureVM_builder) Build() *DiscoverAzureVM { x.xxx_hidden_SubscriptionId = b.SubscriptionId x.xxx_hidden_ResourceGroup = b.ResourceGroup x.xxx_hidden_Region = b.Region + x.xxx_hidden_TenantId = b.TenantId + x.xxx_hidden_ClientId = b.ClientId return m0 } @@ -1610,12 +1640,14 @@ const file_teleport_usertasks_v1_user_tasks_proto_rawDesc = "" + "\x06engine\x18\x03 \x01(\tR\x06engine\x12)\n" + "\x10discovery_config\x18\x04 \x01(\tR\x0fdiscoveryConfig\x12'\n" + "\x0fdiscovery_group\x18\x05 \x01(\tR\x0ediscoveryGroup\x127\n" + - "\tsync_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xbc\x02\n" + + "\tsync_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xf6\x02\n" + "\x0fDiscoverAzureVM\x12S\n" + "\tinstances\x18\x01 \x03(\v25.teleport.usertasks.v1.DiscoverAzureVM.InstancesEntryR\tinstances\x12'\n" + "\x0fsubscription_id\x18\x02 \x01(\tR\x0esubscriptionId\x12%\n" + "\x0eresource_group\x18\x03 \x01(\tR\rresourceGroup\x12\x16\n" + - "\x06region\x18\x04 \x01(\tR\x06region\x1al\n" + + "\x06region\x18\x04 \x01(\tR\x06region\x12\x1b\n" + + "\ttenant_id\x18\x05 \x01(\tR\btenantId\x12\x1b\n" + + "\tclient_id\x18\x06 \x01(\tR\bclientId\x1al\n" + "\x0eInstancesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12D\n" + "\x05value\x18\x02 \x01(\v2..teleport.usertasks.v1.DiscoverAzureVMInstanceR\x05value:\x028\x01\"\x9a\x03\n" + diff --git a/api/proto/teleport/usertasks/v1/user_tasks.proto b/api/proto/teleport/usertasks/v1/user_tasks.proto index a077539f6854..6e2142c2d127 100644 --- a/api/proto/teleport/usertasks/v1/user_tasks.proto +++ b/api/proto/teleport/usertasks/v1/user_tasks.proto @@ -179,6 +179,10 @@ message DiscoverAzureVM { string resource_group = 3; // Region is the Azure Region where Teleport failed to enroll VMs. string region = 4; + // TenantID is the Microsoft Entra tenant ID used by the Azure integration. + string tenant_id = 5; + // ClientID is the client ID of the Azure integration's managed identity or service principal. + string client_id = 6; } // DiscoverAzureVMInstance contains the result of enrolling an Azure VM. diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index 100f7ef17517..e8cac0cbb1bb 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -885,7 +885,19 @@ func (s *Server) handleAzureSubscriptionListError(integration string, err error) return } - if err := s.taskUpdater().upsertAzureSubscriptionListTask(integration, issueType); err != nil { + var tenantID, clientID string + integrationResource, err := s.Config.AccessPoint.GetIntegration(s.ctx, integration) + if err != nil { + s.Log.WarnContext(s.ctx, "Failed to get Azure integration identity for User Task", + "integration", integration, + "error", err, + ) + } else if azureOIDCSpec := integrationResource.GetAzureOIDCIntegrationSpec(); azureOIDCSpec != nil { + tenantID = azureOIDCSpec.TenantID + clientID = azureOIDCSpec.ClientID + } + + if err := s.taskUpdater().upsertAzureSubscriptionListTask(integration, issueType, tenantID, clientID); err != nil { s.Log.WarnContext(s.ctx, "Failed to upsert Azure subscription list permission User Task", "integration", integration, "error", err, diff --git a/lib/srv/discovery/status.go b/lib/srv/discovery/status.go index 2edb4c7d86a6..17b6c4e0acac 100644 --- a/lib/srv/discovery/status.go +++ b/lib/srv/discovery/status.go @@ -1049,7 +1049,7 @@ type taskUpdater struct { Log *slog.Logger } -func (s *taskUpdater) upsertAzureSubscriptionListTask(integration, issueType string) error { +func (s *taskUpdater) upsertAzureSubscriptionListTask(integration, issueType, tenantID, clientID string) error { task, err := usertasks.NewDiscoverAzureVMUserTask( usertasks.TaskGroup{ Integration: integration, @@ -1058,6 +1058,8 @@ func (s *taskUpdater) upsertAzureSubscriptionListTask(integration, issueType str s.clock.Now().Add(2*s.PollInterval), usertasksv1.DiscoverAzureVM_builder{ Instances: map[string]*usertasksv1.DiscoverAzureVMInstance{}, + TenantId: tenantID, + ClientId: clientID, }.Build(), ) if err != nil { @@ -1106,6 +1108,12 @@ func (s *taskUpdater) mergeAzure(oldSpec *usertasksv1.UserTaskSpec, newSpec *use if newSpec.GetDiscoverAzureVm().GetInstances() == nil { newSpec.GetDiscoverAzureVm().SetInstances(make(map[string]*usertasksv1.DiscoverAzureVMInstance)) } + if newSpec.GetDiscoverAzureVm().GetTenantId() == "" { + newSpec.GetDiscoverAzureVm().SetTenantId(oldSpec.GetDiscoverAzureVm().GetTenantId()) + } + if newSpec.GetDiscoverAzureVm().GetClientId() == "" { + newSpec.GetDiscoverAzureVm().SetClientId(oldSpec.GetDiscoverAzureVm().GetClientId()) + } mergeExistingInstances(s, oldSpec.GetDiscoverAzureVm().GetInstances(), newSpec.GetDiscoverAzureVm().GetInstances()) } diff --git a/lib/srv/discovery/status_test.go b/lib/srv/discovery/status_test.go index c773d7d3d31b..610a1492f52e 100644 --- a/lib/srv/discovery/status_test.go +++ b/lib/srv/discovery/status_test.go @@ -346,6 +346,8 @@ func TestUpsertAzureSubscriptionListTask(t *testing.T) { require.NoError(t, updater.upsertAzureSubscriptionListTask( "azure-integration", usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied, + "azure-tenant-id", + "azure-client-id", )) require.Len(t, ap.tasks, 1) @@ -355,10 +357,45 @@ func TestUpsertAzureSubscriptionListTask(t *testing.T) { require.Equal(t, "azure-integration", task.GetSpec().GetIntegration()) require.Empty(t, task.GetSpec().GetDiscoverAzureVm().GetSubscriptionId()) require.Empty(t, task.GetSpec().GetDiscoverAzureVm().GetInstances()) + require.Equal(t, "azure-tenant-id", task.GetSpec().GetDiscoverAzureVm().GetTenantId()) + require.Equal(t, "azure-client-id", task.GetSpec().GetDiscoverAzureVm().GetClientId()) require.True(t, updater.clock.Now().Add(2*updater.PollInterval).Equal(task.GetMetadata().GetExpires().AsTime())) } } +func TestUpsertAzureSubscriptionListTaskPreservesIdentity(t *testing.T) { + t.Parallel() + + const ( + integration = "azure-integration" + issueType = usertasks.AutoDiscoverAzureVMIssueSubscriptionListDenied + tenantID = "azure-tenant-id" + clientID = "azure-client-id" + ) + + existingTask, err := usertasks.NewDiscoverAzureVMUserTask( + usertasks.TaskGroup{ + Integration: integration, + IssueType: issueType, + }, + time.Now().Add(time.Hour), + usertasksv1.DiscoverAzureVM_builder{ + Instances: map[string]*usertasksv1.DiscoverAzureVMInstance{}, + TenantId: tenantID, + ClientId: clientID, + }.Build(), + ) + require.NoError(t, err) + + updater, ap := newTaskUpdater(t, existingTask) + require.NoError(t, updater.upsertAzureSubscriptionListTask(integration, issueType, "", "")) + + task, err := ap.GetUserTask(t.Context(), existingTask.GetMetadata().GetName()) + require.NoError(t, err) + require.Equal(t, tenantID, task.GetSpec().GetDiscoverAzureVm().GetTenantId()) + require.Equal(t, clientID, task.GetSpec().GetDiscoverAzureVm().GetClientId()) +} + func TestAWSEC2Tasks_AddFailedEnrollment(t *testing.T) { t.Parallel() diff --git a/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md b/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md index e2847f2bb9e6..1106093f6eda 100644 --- a/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md +++ b/lib/usertasks/descriptions/azure-vm-subscription-list-denied.md @@ -2,7 +2,7 @@ Teleport could not resolve the wildcard subscription matcher because the Azure integration cannot access any subscriptions. -Verify that the integration uses the intended Azure tenant and managed identity or service principal. Grant that identity a role with the following actions at the management group or subscription scope that Teleport should discover: +This task's Tenant ID and Client ID fields identify the Microsoft Entra tenant and managed identity or service principal used by the integration. Verify that these identifiers are correct, then grant that identity a role with the following actions at the management group or subscription scope that Teleport should discover: - `Microsoft.Compute/virtualMachines/read` - `Microsoft.Compute/virtualMachines/runCommands/write` diff --git a/web/packages/teleport/src/Discover/Overview/ActivityTab.test.tsx b/web/packages/teleport/src/Discover/Overview/ActivityTab.test.tsx index 922abdb2683f..389d35694843 100644 --- a/web/packages/teleport/src/Discover/Overview/ActivityTab.test.tsx +++ b/web/packages/teleport/src/Discover/Overview/ActivityTab.test.tsx @@ -121,6 +121,62 @@ test('opens issue drawer and resolves a task', async () => { ); }); +test('shows Azure integration identity in the issue drawer', async () => { + (useToastNotifications as jest.Mock).mockReturnValue({ + add: jest.fn(), + }); + + jest.spyOn(integrationService, 'fetchUserTask').mockResolvedValueOnce({ + name: 'task-azure', + taskType: 'discover-azure-vm', + state: 'OPEN', + issueType: 'azure-vm-subscription-list-denied', + title: 'Cannot access Azure subscriptions', + integration: 'azure-integration', + lastStateChange: '2025-10-24T12:30:00Z', + description: 'Details', + discoverAzureVm: { + instances: {}, + subscription_id: '', + resource_group: '', + region: '', + tenant_id: 'azure-tenant-id', + client_id: 'azure-client-id', + }, + }); + + render( + + + + ); + + await userEvent.click(screen.getByText('Details')); + await screen.findByRole('heading', { + name: 'Cannot access Azure subscriptions', + }); + + expect(screen.getByText('Tenant ID:')).toBeVisible(); + expect(screen.getByText('azure-tenant-id')).toBeVisible(); + expect(screen.getByText('Client ID:')).toBeVisible(); + expect(screen.getByText('azure-client-id')).toBeVisible(); +}); + test('keeps failed tasks selected when bulk resolve partially fails', async () => { const addToast = jest.fn(); (useToastNotifications as jest.Mock).mockReturnValue({ diff --git a/web/packages/teleport/src/Discover/Overview/UserTaskDrawer.tsx b/web/packages/teleport/src/Discover/Overview/UserTaskDrawer.tsx index 4e236febdd53..fbad1e593ce2 100644 --- a/web/packages/teleport/src/Discover/Overview/UserTaskDrawer.tsx +++ b/web/packages/teleport/src/Discover/Overview/UserTaskDrawer.tsx @@ -197,6 +197,8 @@ function DetailsTab({ task }: { task: UserTaskDetail }) { + {info.tenantId && } + {info.clientId && } @@ -254,6 +256,8 @@ function getTaskInfo(task: UserTaskDetail): { resourceType: string; region?: string; account?: string; + tenantId?: string; + clientId?: string; } { const taskType = (task.taskType || '').toLowerCase(); if (taskType.includes('ec2')) { @@ -282,6 +286,8 @@ function getTaskInfo(task: UserTaskDetail): { resourceType: 'Azure VM', region: task.discoverAzureVm?.region, account: task.discoverAzureVm?.subscription_id, + tenantId: task.discoverAzureVm?.tenant_id, + clientId: task.discoverAzureVm?.client_id, }; } return { resourceType: '', region: undefined, account: undefined }; diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts index eb2814ed3d1f..a303f3b2fb33 100644 --- a/web/packages/teleport/src/services/integrations/integrations.ts +++ b/web/packages/teleport/src/services/integrations/integrations.ts @@ -550,6 +550,8 @@ export const integrationService = { subscription_id: resp.discoverAzureVm?.subscription_id, resource_group: resp.discoverAzureVm?.resource_group, region: resp.discoverAzureVm?.region, + tenant_id: resp.discoverAzureVm?.tenant_id, + client_id: resp.discoverAzureVm?.client_id, }, }; }); diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index 293d4ddf5569..9a22e7341e90 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -815,6 +815,10 @@ export type DiscoverAzureVm = { resource_group: string; // region is the Azure Region where Teleport failed to enroll VMs. region: string; + // tenant_id is the Microsoft Entra tenant ID used by the Azure integration. + tenant_id?: string; + // client_id is the client ID of the Azure integration's managed identity or service principal. + client_id?: string; }; // DiscoverAzureVmInstance contains the result of enrolling an Azure VM. From 9fe54634694a5ffb707df472b44b73eafbc381ed Mon Sep 17 00:00:00 2001 From: Adam Carheden Date: Wed, 12 Aug 2026 14:44:32 +0000 Subject: [PATCH 266/347] Clarified reporting service for duplicate k8s metrics (#69533) * Clarified reporting service for duplicate k8s metrics * Clarified language for k8s metrics * Fixed absolute links * fixed links (redux) * links fix reduxx2 * Fixed section nesting * clarified woding --- docs/pages/includes/metrics.mdx | 64 +++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/pages/includes/metrics.mdx b/docs/pages/includes/metrics.mdx index 343924ae38f6..d675f465f11e 100644 --- a/docs/pages/includes/metrics.mdx +++ b/docs/pages/includes/metrics.mdx @@ -150,24 +150,14 @@ OpenAI-compatible proxies. | `teleport_proxy_db_active_connections_total` | gauge | Teleport Proxy | Number of currently active connections to DB service from Proxy service. | | `trusted_clusters` | gauge | Teleport Proxy | Number of outbound connections to leaf clusters. | -## Database Service - -| Name | Type | Component | Description | -| ------------------------------------------- | --------- | ------------------------- | ---------------------------------------------------------------------- | -| `teleport_db_messages_from_client_total` | counter | Teleport Database Service | Number of messages (packets) received from the DB client. | -| `teleport_db_messages_from_server_total` | counter | Teleport Database Service | Number of messages (packets) received from the DB server. | -| `teleport_db_method_call_count_total` | counter | Teleport Database Service | Number of times a DB method was called. | -| `teleport_db_method_call_latency_seconds` | histogram | Teleport Database Service | Call latency for a DB method calls. | -| `teleport_db_initialized_connections_total` | counter | Teleport Database Service | Number of initialized DB connections. | -| `teleport_db_active_connections_total` | gauge | Teleport Database Service | Number of active DB connections. | -| `teleport_db_connection_durations_seconds` | histogram | Teleport Database Service | Duration of DB connection. | -| `teleport_db_connection_setup_time_seconds` | histogram | Teleport Database Service | Initial time to setup DB connection, before any requests are handled. | -| `teleport_db_errors_total` | counter | Teleport Database Service | Number of synthetic DB errors sent to the client. | +### Kubernetes -### Kubernetes access +The following tables identify all metrics available in the [Teleport Proxy +Service](../reference/deployment/config.mdx#proxy-service) +if at least one Kubernetes cluster is enrolled in your Teleport cluster. -The following tables identify all metrics available in the Teleport Proxy -Service if at least one Kubernetes cluster is enrolled in your Teleport cluster. +The same metrics are also reported by the `kubernetes_service`, but apply to the +agent leg of Kubernetes connections. #### Client @@ -186,7 +176,8 @@ to upstream servers. In the case of `proxy`, the upstream server can be a #### Server -The following table identifies all metrics available for incoming connections. +The following table identifies all metrics available for incoming connections +from clients (`tsh`, `kubectl`) to a Teleport Proxy. | Name | Type | Component | Description | | ----------------------------------------------------------- | --------- | ------------------------- | --------------------------------------------------- | @@ -202,6 +193,23 @@ The following table identifies all metrics available for incoming connections. | `teleport_kubernetes_server_join_sessions_total` | counter | Teleport Kubernetes Proxy | Total number of joining sessions. | + + +## Database Service + +| Name | Type | Component | Description | +| ------------------------------------------- | --------- | ------------------------- | ---------------------------------------------------------------------- | +| `teleport_db_messages_from_client_total` | counter | Teleport Database Service | Number of messages (packets) received from the DB client. | +| `teleport_db_messages_from_server_total` | counter | Teleport Database Service | Number of messages (packets) received from the DB server. | +| `teleport_db_method_call_count_total` | counter | Teleport Database Service | Number of times a DB method was called. | +| `teleport_db_method_call_latency_seconds` | histogram | Teleport Database Service | Call latency for a DB method calls. | +| `teleport_db_initialized_connections_total` | counter | Teleport Database Service | Number of initialized DB connections. | +| `teleport_db_active_connections_total` | gauge | Teleport Database Service | Number of active DB connections. | +| `teleport_db_connection_durations_seconds` | histogram | Teleport Database Service | Duration of DB connection. | +| `teleport_db_connection_setup_time_seconds` | histogram | Teleport Database Service | Initial time to setup DB connection, before any requests are handled. | +| `teleport_db_errors_total` | counter | Teleport Database Service | Number of synthetic DB errors sent to the client. | + + ## Application Service | Name | Type | Component | Description | @@ -216,20 +224,32 @@ The following table identifies all metrics available for incoming connections. ## Teleport Kubernetes Service -The following table identifies all metrics available when the service connects -to upstream servers. In the case of `kubernetes_service`, the upstream server -is always a Kubernetes cluster. +These metrics are available when the Teleport process is configured with +[kubernetes_service](../reference/deployment/config.mdx#kubernetes-service) +and apply to traffic between the agent and Kubernetes API. + +The same metrics are also reported by the Proxy service, but apply to the proxy +leg of Kubernetes connections. + +#### Client {/* Intentionally #### to match Proxy Service/Kubernetes above */} + +The following table identifies all metrics available when the +`kubernetes_service` connects to the API of a Kubernetes cluster. | Name | Type | Component | Description | | ----------------------------------------------------------------- | --------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | | `teleport_kubernetes_client_in_flight_requests` | gauge | Teleport Kubernetes Service | In-flight requests waiting for the upstream response. | | `teleport_kubernetes_client_requests_total` | counter | Teleport Kubernetes Service | Total number of requests sent to the upstream teleport proxy, kube_service or Kubernetes Cluster servers. | | `teleport_kubernetes_client_tls_duration_seconds` | histogram | Teleport Kubernetes Service | Latency distribution of TLS handshakes. | -| `teleport_kubernetes_client_got_conn_duration_seconds` | histogram | Teleport Kubernetes Service | Latency distribution of time to dial to the upstream server - using reversetunnel or direct dialer. | +| `teleport_kubernetes_client_got_conn_duration_seconds` | histogram | Teleport Kubernetes Service | Latency distribution of time to dial to the upstream server - using reverse tunnel or direct dialer. | | `teleport_kubernetes_client_first_byte_response_duration_seconds` | histogram | Teleport Kubernetes Service | Latency distribution of time to receive the first response byte from the upstream server. | | `teleport_kubernetes_client_request_duration_seconds` | histogram | Teleport Kubernetes Service | Latency distribution of the upstream request time. | -The following table identifies all metrics available for incoming connections. + +#### Server {/* Intentionally #### to match Proxy Service/Kubernetes above */} + +The following table identifies all metrics available for connections from +clients/Teleport proxies to the Teleport `kubernetes_service`. | Name | Type | Component | Description | | ----------------------------------------------------------- | --------- | --------------------------- | --------------------------------------------------- | From efe65981702db5b464cc45ed963cff3106f14e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:11:58 +0000 Subject: [PATCH 267/347] Add audit events for approving & denying an `EnrollPairing` (#69505) * Add audit events for approving & denying an `EnrollPairing` * Add web changes * Drop `device.enroll_pairing.approve` from `eventGroupTypes` * "not approved" -> "denied" * Update wording around `device.enroll_pairing.approve` emitted on failure While working on the RPC handler implementation, I realized that `TV011W` is not emitted on MFA failures. This commit updates the godoc and the fixture accordingly. * Update `deny` audit event wording around expiry In a typical scenario there will be no one to observe expiry, so this event won't be emitted on enroll pairing expiry. I'll update the RFD accordingly soon. For now just putting this here since I have no other outstanding OSS PRs. * Remove no longer necessary TODO comment "Retryability" will be implemented one level above the storage layer, in the RPC handler, so the comment here is no longer necessary. * Remove `error` field from `device.enroll_pairing.deny` Now that it's only going to be used for an explicit user denial, there's no need to carry the `error` field. --- docs/pages/reference/audit-events.mdx | 77 +++++++++++++++++++ lib/events/api.go | 8 ++ lib/events/codes.go | 7 ++ lib/events/dynamic.go | 4 +- lib/events/events_test.go | 2 + lib/services/local/enroll_pairing.go | 2 - .../src/Audit/EventList/EventTypeCell.tsx | 3 + .../teleport/src/Audit/fixtures/index.ts | 46 +++++++++++ .../teleport/src/services/audit/makeEvent.ts | 28 +++++++ .../teleport/src/services/audit/types.ts | 14 ++++ 10 files changed, 188 insertions(+), 3 deletions(-) diff --git a/docs/pages/reference/audit-events.mdx b/docs/pages/reference/audit-events.mdx index 0655ccb77639..cd34f752cf5a 100644 --- a/docs/pages/reference/audit-events.mdx +++ b/docs/pages/reference/audit-events.mdx @@ -3534,6 +3534,83 @@ Example: } ``` +## device.enroll_pairing.approve + +There are multiple events with the `device.enroll_pairing.approve` type. + +### TV011I + +Device Enroll Pairing Approved + +Example: + +```json +{ + "cluster_name": "im-a-cluster-name", + "code": "TV011I", + "device": { + "asset_tag": "M2CQVQV64R", + "device_id": "", + "os_type": 4 + }, + "ei": 0, + "event": "device.enroll_pairing.approve", + "success": true, + "time": "2024-04-08T19:39:48.1Z", + "uid": "b8c4f0a2-5e6d-7f8a-0123-abc456789012", + "user": "llama" +} +``` + +### TV011W + +Device Enroll Pairing Approval Failed + +Example: + +```json +{ + "cluster_name": "im-a-cluster-name", + "code": "TV011W", + "device": { + "asset_tag": "M2CQVQV64R", + "device_id": "", + "os_type": 4 + }, + "ei": 0, + "error": "enroll pairing is not awaiting approval", + "event": "device.enroll_pairing.approve", + "success": false, + "time": "2024-04-08T19:40:48.1Z", + "uid": "b8c4f0a2-5e6d-7f8a-0123-abc456789013", + "user": "llama" +} +``` + +## device.enroll_pairing.deny + +Device Enroll Pairing Denied + +Example: + +```json +{ + "cluster_name": "im-a-cluster-name", + "code": "TV012W", + "device": { + "asset_tag": "M2CQVQV64R", + "device_id": "", + "os_type": 4 + }, + "ei": 0, + "event": "device.enroll_pairing.deny", + "success": false, + "time": "2024-04-08T19:41:48.1Z", + "uid": "c9d5a1b3-6f7e-8a9b-2345-bcd567890123", + "user": "llama" +} +``` + ## device.enroll_pairing.request There are multiple events with the `device.enroll_pairing.request` type. diff --git a/lib/events/api.go b/lib/events/api.go index 50a09a9b33ea..b3e8792a9cb6 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -712,6 +712,14 @@ const ( // awaiting approval. It is also emitted on failure (e.g. an invalid token), // before any transition, in which case there is no associated user. DeviceEnrollPairingRequestEvent = "device.enroll_pairing.request" + // DeviceEnrollPairingApproveEvent is emitted when the user approves an enroll + // pairing in the Web UI, which lets the device retrieve its enrollment token. + // It is also emitted on a failed approval attempt, for example one arriving + // after the pairing expired or was already approved. + DeviceEnrollPairingApproveEvent = "device.enroll_pairing.approve" + // DeviceEnrollPairingDenyEvent is emitted when the user denies an enroll + // pairing in the Web UI. + DeviceEnrollPairingDenyEvent = "device.enroll_pairing.deny" // BotJoinEvent is emitted when a bot joins BotJoinEvent = "bot.join" diff --git a/lib/events/codes.go b/lib/events/codes.go index a5ab44204ec9..41c50ec13d2d 100644 --- a/lib/events/codes.go +++ b/lib/events/codes.go @@ -530,6 +530,13 @@ const ( // DeviceEnrollPairingRequestFailureCode is the device enroll pairing request // failure code. DeviceEnrollPairingRequestFailureCode = "TV010W" + // DeviceEnrollPairingApproveCode is the device enroll pairing approve code. + DeviceEnrollPairingApproveCode = "TV011I" + // DeviceEnrollPairingApproveFailureCode is the device enroll pairing approve + // failure code. + DeviceEnrollPairingApproveFailureCode = "TV011W" + // DeviceEnrollPairingDenyCode is the device enroll pairing deny code. + DeviceEnrollPairingDenyCode = "TV012W" // LoginRuleCreateCode is the login rule create code. LoginRuleCreateCode = "TLR00I" diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 362ece251d42..23d2b3532a69 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -297,7 +297,9 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) { DeviceEnrollTokenCreateEvent, DeviceWebTokenCreateEvent, DeviceAuthenticateConfirmEvent, - DeviceEnrollPairingRequestEvent: + DeviceEnrollPairingRequestEvent, + DeviceEnrollPairingApproveEvent, + DeviceEnrollPairingDenyEvent: e = &events.DeviceEvent2{} case LockCreatedEvent: e = &events.LockCreate{} diff --git a/lib/events/events_test.go b/lib/events/events_test.go index 3e53fa28516b..5e19a5127a0e 100644 --- a/lib/events/events_test.go +++ b/lib/events/events_test.go @@ -156,6 +156,8 @@ var eventsMap = map[string]apievents.AuditEvent{ DeviceWebTokenCreateEvent: &apievents.DeviceEvent2{}, DeviceAuthenticateConfirmEvent: &apievents.DeviceEvent2{}, DeviceEnrollPairingRequestEvent: &apievents.DeviceEvent2{}, + DeviceEnrollPairingApproveEvent: &apievents.DeviceEvent2{}, + DeviceEnrollPairingDenyEvent: &apievents.DeviceEvent2{}, LockCreatedEvent: &apievents.LockCreate{}, LockDeletedEvent: &apievents.LockDelete{}, RecoveryCodeGeneratedEvent: &apievents.RecoveryCodeGenerate{}, diff --git a/lib/services/local/enroll_pairing.go b/lib/services/local/enroll_pairing.go index 994dcc96dbf9..133f4c1b4271 100644 --- a/lib/services/local/enroll_pairing.go +++ b/lib/services/local/enroll_pairing.go @@ -208,8 +208,6 @@ func (s *EnrollPairingService) RequestEnrollPairingApproval(ctx context.Context, } const errNotAwaitingDevice = "enroll pairing is not awaiting a device" - // TODO(ravicious): Make the call retryable from the same device, as described - // in the Interrputibility section of RFD 32e. if pairing.GetStatus().GetState() != devicepb.EnrollPairingState_ENROLL_PAIRING_STATE_AWAITING_DEVICE { return nil, trace.CompareFailed(errNotAwaitingDevice) } diff --git a/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx b/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx index 60bfbe8c79ff..3a94784a0f42 100644 --- a/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx +++ b/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx @@ -208,6 +208,9 @@ export const EventIconMap: Record = { [eventCodes.DEVICE_AUTHENTICATE_CONFIRM]: Icons.Info, [eventCodes.DEVICE_ENROLL_PAIRING_REQUEST]: Icons.Info, [eventCodes.DEVICE_ENROLL_PAIRING_REQUEST_FAILURE]: Icons.Info, + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: Icons.Info, + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: Icons.Info, + [eventCodes.DEVICE_ENROLL_PAIRING_DENY]: Icons.Info, [eventCodes.MFA_DEVICE_ADD]: Icons.Info, [eventCodes.MFA_DEVICE_DELETE]: Icons.Info, [eventCodes.BILLING_CARD_CREATE]: Icons.CreditCard, diff --git a/web/packages/teleport/src/Audit/fixtures/index.ts b/web/packages/teleport/src/Audit/fixtures/index.ts index 5a7d66ea0329..9854ed543abf 100644 --- a/web/packages/teleport/src/Audit/fixtures/index.ts +++ b/web/packages/teleport/src/Audit/fixtures/index.ts @@ -3321,6 +3321,52 @@ export const events = [ time: '2024-04-08T19:38:48.1Z', uid: 'a7b3e9f1-4d5c-6a8b-9012-def345678902', }, + { + cluster_name: 'im-a-cluster-name', + code: 'TV011I', + device: { + asset_tag: 'M2CQVQV64R', + device_id: '', + os_type: 4, + }, + ei: 0, + event: 'device.enroll_pairing.approve', + success: true, + time: '2024-04-08T19:39:48.1Z', + uid: 'b8c4f0a2-5e6d-7f8a-0123-abc456789012', + user: 'llama', + }, + { + cluster_name: 'im-a-cluster-name', + code: 'TV011W', + device: { + asset_tag: 'M2CQVQV64R', + device_id: '', + os_type: 4, + }, + ei: 0, + error: 'enroll pairing is not awaiting approval', + event: 'device.enroll_pairing.approve', + success: false, + time: '2024-04-08T19:40:48.1Z', + uid: 'b8c4f0a2-5e6d-7f8a-0123-abc456789013', + user: 'llama', + }, + { + cluster_name: 'im-a-cluster-name', + code: 'TV012W', + device: { + asset_tag: 'M2CQVQV64R', + device_id: '', + os_type: 4, + }, + ei: 0, + event: 'device.enroll_pairing.deny', + success: false, + time: '2024-04-08T19:41:48.1Z', + uid: 'c9d5a1b3-6f7e-8a9b-2345-bcd567890123', + user: 'llama', + }, { cluster_name: 'im-a-cluster-name', code: 'TLR00I', diff --git a/web/packages/teleport/src/services/audit/makeEvent.ts b/web/packages/teleport/src/services/audit/makeEvent.ts index 8c38ee3a4598..142d33101ab7 100644 --- a/web/packages/teleport/src/services/audit/makeEvent.ts +++ b/web/packages/teleport/src/services/audit/makeEvent.ts @@ -1507,6 +1507,34 @@ export const formatters: Formatters = { ? `Device enrollment request failed${formatDevice(device)}: ${error}` : `Device enrollment request failed${formatDevice(device)}`, }, + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: { + type: 'device.enroll_pairing.approve', + desc: 'Device Enroll Pairing Approved', + format: ({ user, device }) => + `User [${user}] approved the device enrollment request${formatDevice(device)}`, + }, + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: { + type: 'device.enroll_pairing.approve', + desc: 'Device Enroll Pairing Approval Failed', + format: ({ user, device, error }) => { + let msg = `User [${user}] failed to approve the device enrollment request${formatDevice(device)}`; + if (error) { + msg += `: ${error}`; + } + return msg; + }, + }, + [eventCodes.DEVICE_ENROLL_PAIRING_DENY]: { + type: 'device.enroll_pairing.deny', + desc: 'Device Enroll Pairing Denied', + format: ({ user, device, error }) => { + let msg = `Device enrollment request for user [${user}] was denied${formatDevice(device)}`; + if (error) { + msg += `: ${error}`; + } + return msg; + }, + }, [eventCodes.X11_FORWARD]: { type: 'x11-forward', desc: 'X11 Forwarding Requested', diff --git a/web/packages/teleport/src/services/audit/types.ts b/web/packages/teleport/src/services/audit/types.ts index 6b8a30880f2c..9209bb70eef6 100644 --- a/web/packages/teleport/src/services/audit/types.ts +++ b/web/packages/teleport/src/services/audit/types.ts @@ -123,6 +123,9 @@ export const eventCodes = { DEVICE_AUTHENTICATE_CONFIRM: 'TV009I', DEVICE_ENROLL_PAIRING_REQUEST: 'TV010I', DEVICE_ENROLL_PAIRING_REQUEST_FAILURE: 'TV010W', + DEVICE_ENROLL_PAIRING_APPROVE: 'TV011I', + DEVICE_ENROLL_PAIRING_APPROVE_FAILURE: 'TV011W', + DEVICE_ENROLL_PAIRING_DENY: 'TV012W', EXEC_FAILURE: 'T3002E', EXEC: 'T3002I', GITHUB_CONNECTOR_CREATED: 'T8000I', @@ -1434,6 +1437,17 @@ export type RawEvents = { typeof eventCodes.DEVICE_ENROLL_PAIRING_REQUEST_FAILURE, Merge >; + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: RawDeviceEvent< + typeof eventCodes.DEVICE_ENROLL_PAIRING_APPROVE + >; + [eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: RawEvent< + typeof eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE, + Merge + >; + [eventCodes.DEVICE_ENROLL_PAIRING_DENY]: RawEvent< + typeof eventCodes.DEVICE_ENROLL_PAIRING_DENY, + Merge + >; [eventCodes.UNKNOWN]: RawEvent< typeof eventCodes.UNKNOWN, { From aaa380c338b3e04689eab8bbd08dede0c02c0cde Mon Sep 17 00:00:00 2001 From: Dustin Specker Date: Wed, 12 Aug 2026 15:32:21 +0000 Subject: [PATCH 268/347] docs(upgrading/cloud-cluster): mention email notifications (#69579) * docs(upgrading/cloud-cluster): mention email notifications Teleport Cloud has recently started supporting sending email notifications to Teleport Cloud users running Teleport v17 and v18. This adds a blurb about email notifications and link to adding new business and security contacts. * fix(docs/cloud-cluster-upgrade): use active voice * Revert "fix(docs/cloud-cluster-upgrade): use active voice" This reverts commit a85f2efcdd2d469858294513495c8b01f844d72a. --- docs/pages/upgrading/cloud-cluster-updates.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/pages/upgrading/cloud-cluster-updates.mdx b/docs/pages/upgrading/cloud-cluster-updates.mdx index 7126c47e25a7..75293088bcb5 100644 --- a/docs/pages/upgrading/cloud-cluster-updates.mdx +++ b/docs/pages/upgrading/cloud-cluster-updates.mdx @@ -23,6 +23,8 @@ release of a new major version. Minor version updates and patches occur more reg ## Notifications +### Web UI + Teleport Enterprise (Cloud) users will receive notification in the Web UI when a cluster update is scheduled. The notification will include the version and maintenance window during which the update will take place. @@ -35,6 +37,13 @@ indicating that the update has finished. ![notification-complete](../../img/cloud/notification-complete.png) +### Email + +Teleport Enterprise (Cloud) users will receive email notifications for their +verified [security and business contacts](../cloud-faq.mdx#how-do-i-update-my-security-and-business-contacts). +Emails will be sent when a Teleport upgrade is planned or in the scenario +that a Teleport upgrade is cancelled. + ## Expected impact When an update occurs, some active sessions through Teleport Cloud may be terminated. From e92b6482d74df10daaea09da65ce49df6483d1c8 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Wed, 12 Aug 2026 15:33:29 +0000 Subject: [PATCH 269/347] Remove client invocations/backend implementations of deprecated GetTokens RPC (#69576) * Remove client invocations of deprecated GetTokens RPC * slightly tidy instantiation of all tokens slices in GetAllTokens --- api/client/client.go | 16 ------- lib/auth/authclient/clt.go | 5 -- lib/auth/tls_test.go | 26 ++++++++--- lib/cache/tokens.go | 26 ----------- lib/services/local/provisioning.go | 24 ---------- lib/services/provisioning.go | 5 -- lib/web/join_tokens.go | 71 ++++++++++------------------- tool/tctl/common/resources/token.go | 34 ++------------ 8 files changed, 48 insertions(+), 159 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 6f0ab6664eac..804a1de77266 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -2667,22 +2667,6 @@ func (c *Client) GetToken(ctx context.Context, name string) (types.ProvisionToke return resp, nil } -// GetTokens returns a list of active provision tokens for nodes and users. -// Deprecated: Use [ListProvisionTokens], [GetStaticTokens], and [ListResetPasswordTokens] instead. -// TODO(hugoShaka): DELETE IN 19.0.0 -func (c *Client) GetTokens(ctx context.Context) ([]types.ProvisionToken, error) { - resp, err := c.grpc.GetTokens(ctx, &emptypb.Empty{}) //nolint:staticcheck // Provides backward compatibility, will be removed later. - if err != nil { - return nil, trace.Wrap(err) - } - - tokens := make([]types.ProvisionToken, len(resp.ProvisionTokens)) - for i, token := range resp.ProvisionTokens { - tokens[i] = token - } - return tokens, nil -} - // GetStaticTokens returns the cluster static tokens. func (c *Client) GetStaticTokens(ctx context.Context) (types.StaticTokens, error) { tokens, err := c.grpc.GetStaticTokens(ctx, &emptypb.Empty{}) diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 5c49e80a0c7f..216e4021243d 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -1227,11 +1227,6 @@ type IdentityService interface { // ProvisioningService is a service in control // of adding new nodes, auth servers and proxies to the cluster type ProvisioningService interface { - // GetTokens returns a list of active invitation tokens for nodes and users - // Deprecated: use [ListProvisionTokens] istead. - // TODO(hugoShaka): DELETE IN 19.0.0 - GetTokens(ctx context.Context) (tokens []types.ProvisionToken, err error) - // GetToken returns provisioning token GetToken(ctx context.Context, token string) (types.ProvisionToken, error) diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index c5d590c06007..2bdd44696e6c 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -46,6 +46,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/emptypb" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/breaker" @@ -6045,10 +6046,24 @@ func TestGRPCServer_GetTokens(t *testing.T) { ) require.NoError(t, err) - t.Run("no extra tokens", func(t *testing.T) { - client, err := testSrv.NewClient(authtest.TestUser(privilegedUser.GetName())) + // The deprecated GetTokens RPC no longer has an api client wrapper, so + // exercise the gRPC stub directly. DELETE IN 21.0.0 alongside the RPC. + getTokens := func(t *testing.T, identity authtest.TestIdentity) ([]types.ProvisionToken, error) { + clt, err := testSrv.NewClient(identity) require.NoError(t, err) - toks, err := client.GetTokens(ctx) + resp, err := proto.NewAuthServiceClient(clt.GetConnection()).GetTokens(t.Context(), &emptypb.Empty{}) //nolint:staticcheck // SA1019. Deprecated RPC, kept until v21. + if err != nil { + return nil, err + } + tokens := make([]types.ProvisionToken, len(resp.ProvisionTokens)) + for i, tok := range resp.ProvisionTokens { + tokens[i] = tok + } + return tokens, nil + } + + t.Run("no extra tokens", func(t *testing.T) { + toks, err := getTokens(t, authtest.TestUser(privilegedUser.GetName())) require.NoError(t, err) require.Len(t, toks, 1) // only a single static token exists }) @@ -6102,10 +6117,7 @@ func TestGRPCServer_GetTokens(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - client, err := testSrv.NewClient(tt.identity) - require.NoError(t, err) - - tokens, err := client.GetTokens(ctx) + tokens, err := getTokens(t, tt.identity) tt.requireError(t, err) if tt.requireResponse { diff --git a/lib/cache/tokens.go b/lib/cache/tokens.go index 240914f1c987..aa03e196cd67 100644 --- a/lib/cache/tokens.go +++ b/lib/cache/tokens.go @@ -130,32 +130,6 @@ func newProvisionTokensCollection(p services.Provisioner, w types.WatchKind) (*c }, nil } -// GetTokens returns all active (non-expired) provisioning tokens -// Deprecated: use [ListProvisionTokens] istead. -// TODO(hugoShaka): DELETE IN 19.0.0 -func (c *Cache) GetTokens(ctx context.Context) ([]types.ProvisionToken, error) { - ctx, span := c.Tracer.Start(ctx, "cache/GetTokens") - defer span.End() - - rg, err := acquireReadGuard(c, c.collections.provisionTokens) - if err != nil { - return nil, trace.Wrap(err) - } - defer rg.Release() - - if !rg.ReadCache() { - tokens, err := c.Config.Provisioner.GetTokens(ctx) - return tokens, trace.Wrap(err) - } - - tokens := make([]types.ProvisionToken, 0, rg.store.len()) - for t := range rg.store.resources(provisionTokenStoreNameIndex, "", "") { - tokens = append(tokens, t.Clone()) - } - - return tokens, nil -} - // GetToken finds and returns token by ID func (c *Cache) GetToken(ctx context.Context, name string) (types.ProvisionToken, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetToken") diff --git a/lib/services/local/provisioning.go b/lib/services/local/provisioning.go index 36d390e907f5..d350a0fc2cce 100644 --- a/lib/services/local/provisioning.go +++ b/lib/services/local/provisioning.go @@ -220,30 +220,6 @@ func (s *ProvisioningService) DeleteToken(ctx context.Context, token string) err return trace.Wrap(err) } -// GetTokens returns all active (non-expired) provisioning tokens -// Deprecated: use [ListProvisionTokens] instead. -// TODO(hugoShaka): DELETE IN 19.0.0 -func (s *ProvisioningService) GetTokens(ctx context.Context) ([]types.ProvisionToken, error) { - startKey := backend.ExactKey(tokensPrefix) - result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit) - if err != nil { - return nil, trace.Wrap(err) - } - tokens := make([]types.ProvisionToken, len(result.Items)) - for i, item := range result.Items { - t, err := services.UnmarshalProvisionToken( - item.Value, - services.WithExpires(item.Expires), - services.WithRevision(item.Revision), - ) - if err != nil { - return nil, trace.Wrap(err, "unmarshaling token (key: %q)", item.Key) - } - tokens[i] = t - } - return tokens, nil -} - // ListProvisionTokens returns a paginated list of provision tokens. Items can // be filtered by role and bot name. Tokens with ANY of the provided roles are // returned. If a bot name is provided, only tokens having a role of Bot are diff --git a/lib/services/provisioning.go b/lib/services/provisioning.go index 0bf51a205a98..852376e4e42a 100644 --- a/lib/services/provisioning.go +++ b/lib/services/provisioning.go @@ -46,11 +46,6 @@ type Provisioner interface { // Imlementations must guarantee that this returns trace.NotFound error if the token doesn't exist DeleteToken(ctx context.Context, token string) error - // GetTokens returns all non-expired tokens - // Deprecated: use [ListProvisionTokens] instead. - // TODO(hugoShaka): DELETE IN 19.0.0 - GetTokens(ctx context.Context) ([]types.ProvisionToken, error) - // PatchToken performs a conditional update on the named token using // `updateFn`, retrying internally if a comparison failure occurs. PatchToken( diff --git a/lib/web/join_tokens.go b/lib/web/join_tokens.go index ac4bfe083d2d..d703fde80914 100644 --- a/lib/web/join_tokens.go +++ b/lib/web/join_tokens.go @@ -102,60 +102,37 @@ func (h *Handler) getTokens(w http.ResponseWriter, r *http.Request, params httpr return nil, trace.Wrap(err) } - // There are 3 tokens types: - // - provision tokens - // - static tokens - // - user tokens - // This endpoint returns all 3 for compatibility reasons. - // Before, all 3 tokens were returned by the same "GetTokens" RPC, now we are using - // separate RPCs, with pagination. However, we don't know if the auth we are talking - // to supports the new RPCs. As the static token one got introduced last, we - // try to use it.If it works, we consume the two other RPCs. If it doesn't, - // we fallback to the legacy all-in-one RPC. - var tokens []types.ProvisionToken - - // Trying to get static tokens + // This endpoint returns provision, static, and user tokens together for + // compatibility with the legacy all-in-one "GetTokens" RPC. staticTokens, err := clt.GetStaticTokens(r.Context()) - if err != nil && !trace.IsNotImplemented(err) { + if err != nil { return nil, trace.Wrap(err, "getting static tokens") } + tokens := staticTokens.GetStaticTokens() - // TODO(hugoShaka): DELETE IN 19.0.0 - if trace.IsNotImplemented(err) { - // We are connected to an old auth, that doesn't support the per-token type RPCs - // so we fallback to the legacy all-in-one RPC. - tokens, err = clt.GetTokens(r.Context()) - if err != nil { - return nil, trace.Wrap(err, "getting all tokens through the legacy RPC") - } - } else { - // We are connected to a modern auth, we must collect all 3 tokens types. - // Getting the provision tokens. - provisionTokens, err := stream.Collect(clientutils.Resources(r.Context(), - func(ctx context.Context, pageSize int, pageKey string) ([]types.ProvisionToken, string, error) { - return clt.ListProvisionTokens(ctx, pageSize, pageKey, nil, "") - }, - )) - if err != nil { - return nil, trace.Wrap(err, "getting provision tokens") - } - tokens = append(staticTokens.GetStaticTokens(), provisionTokens...) + provisionTokens, err := stream.Collect(clientutils.Resources(r.Context(), + func(ctx context.Context, pageSize int, pageKey string) ([]types.ProvisionToken, string, error) { + return clt.ListProvisionTokens(ctx, pageSize, pageKey, nil, "") + }, + )) + if err != nil { + return nil, trace.Wrap(err, "getting provision tokens") + } + tokens = append(tokens, provisionTokens...) - // Getting the user tokens. - userTokens, err := stream.Collect(clientutils.Resources(r.Context(), clt.ListResetPasswordTokens)) + userTokens, err := stream.Collect(clientutils.Resources(r.Context(), clt.ListResetPasswordTokens)) + if err != nil { + return nil, trace.Wrap(err, "getting user tokens") + } + // Converting the user tokens as provision tokens for presentation and + // backward compatibility. + for _, t := range userTokens { + roles := types.SystemRoles{types.RoleSignup} + tok, err := types.NewProvisionToken(t.GetName(), roles, t.Expiry()) if err != nil { - return nil, trace.Wrap(err, "getting user tokens") - } - // Converting the user tokens as provision tokens for presentation and - // backward compatibility. - for _, t := range userTokens { - roles := types.SystemRoles{types.RoleSignup} - tok, err := types.NewProvisionToken(t.GetName(), roles, t.Expiry()) - if err != nil { - return nil, trace.Wrap(err, "converting user token as a provision token") - } - tokens = append(tokens, tok) + return nil, trace.Wrap(err, "converting user token as a provision token") } + tokens = append(tokens, tok) } uiTokens, err := webui.MakeJoinTokens(tokens) diff --git a/tool/tctl/common/resources/token.go b/tool/tctl/common/resources/token.go index ec9493416c6d..b8520f130ec3 100644 --- a/tool/tctl/common/resources/token.go +++ b/tool/tctl/common/resources/token.go @@ -83,34 +83,14 @@ func getToken(ctx context.Context, client *authclient.Client, ref services.Ref, // The MFA ceremony cannot be done in this function because we don't know if // the caller already attempted one (e.g. tctl get all) func GetAllTokens(ctx context.Context, clt *authclient.Client) ([]types.ProvisionToken, error) { - // There are 3 tokens types: - // - provision tokens - // - static tokens - // - user tokens - // This endpoint returns all 3 for compatibility reasons. - // Before, all 3 tokens were returned by the same "GetTokens" RPC, now we are using - // separate RPCs, with pagination. However, we don't know if the auth we are talking - // to supports the new RPCs. As the static token one got introduced last, we - // try to use it.If it works, we consume the two other RPCs. If it doesn't, - // we fallback to the legacy all-in-one RPC. - var tokens []types.ProvisionToken - - // Trying to get static tokens + // Provision, static, and user tokens are returned together for + // compatibility with the legacy all-in-one "GetTokens" RPC. staticTokens, err := clt.GetStaticTokens(ctx) - if err != nil && !trace.IsNotImplemented(err) { + if err != nil { return nil, trace.Wrap(err, "getting static tokens") } + tokens := staticTokens.GetStaticTokens() - // TODO(hugoShaka): DELETE IN 19.0.0 - if trace.IsNotImplemented(err) { - // We are connected to an old auth, that doesn't support the per-token type RPCs - // so we fallback to the legacy all-in-one RPC. - tokens, err := clt.GetTokens(ctx) - return tokens, trace.Wrap(err, "getting all tokens through the legacy RPC") - } - - // We are connected to a modern auth, we must collect all 3 tokens types. - // Getting the provision tokens. provisionTokens, err := stream.Collect(clientutils.Resources(ctx, func(ctx context.Context, pageSize int, pageKey string) ([]types.ProvisionToken, string, error) { return clt.ListProvisionTokens(ctx, pageSize, pageKey, nil, "") @@ -119,13 +99,9 @@ func GetAllTokens(ctx context.Context, clt *authclient.Client) ([]types.Provisio if err != nil { return nil, trace.Wrap(err, "getting provision tokens") } - tokens = append(staticTokens.GetStaticTokens(), provisionTokens...) + tokens = append(tokens, provisionTokens...) - // Getting the user tokens. userTokens, err := stream.Collect(clientutils.Resources(ctx, clt.ListResetPasswordTokens)) - if err != nil && !trace.IsNotImplemented(err) { - return nil, trace.Wrap(err) - } if err != nil { return nil, trace.Wrap(err, "getting user tokens") } From f62ac7c8106489dff52f32b4b790b739c1843f7b Mon Sep 17 00:00:00 2001 From: Ghassan Gedeon Achi Date: Wed, 12 Aug 2026 17:06:18 +0000 Subject: [PATCH 270/347] Fix `teleport-investigate` and `teleport-access-review` skill inaccuracies (#69309) * docs: fix skill inaccuracies from v18 bp testing * fix: additional updates after another round of testing --- skills/teleport-access-review/SKILL.md | 4 +++ .../teleport-access-review/evals/evals.json | 12 ++++--- .../evals/files/alice-standing.json | 12 +++---- .../evals/files/injection.json | 4 +-- .../evals/files/multi-grantor.json | 24 +++++++++---- .../evals/files/no-activity.json | 4 +-- .../evals/files/who-can-access.json | 12 +++---- .../references/SCHEMA.md | 34 ++++++++++++------- .../references/EXPERIENCE.md | 31 +++++++++++------ .../teleport-investigate/references/SCHEMA.md | 2 +- .../accessgraph/access_review_command_test.go | 4 +-- 11 files changed, 90 insertions(+), 53 deletions(-) diff --git a/skills/teleport-access-review/SKILL.md b/skills/teleport-access-review/SKILL.md index 2602f0f7f11a..a4bfb81df81c 100644 --- a/skills/teleport-access-review/SKILL.md +++ b/skills/teleport-access-review/SKILL.md @@ -98,6 +98,10 @@ see [QUERY.md](references/QUERY.md). - **Level** is `standing`, `impersonate`, `request`, or `denied`, resolved by priority `denied > standing > impersonate > request`. A trailing `*` marks **temporary** access (granted by an access request; self-expiring). +- **A level is a grant, not connectability.** Access Graph does not currently + resolve traits, so an identity that holds a grant but lacks the principals to + authenticate (`logins`, `db_users`, …) still shows `level: standing`, with no + field marking the gap. Report holding a grant, not the ability to exercise it. - **Results are scoped to your query.** A query through an access list shows only paths that flow through that list — not every path each member has. To see a user's _complete_ access to a resource, scope by the user and resource diff --git a/skills/teleport-access-review/evals/evals.json b/skills/teleport-access-review/evals/evals.json index cc0c1c69c3ee..3e666645de4b 100644 --- a/skills/teleport-access-review/evals/evals.json +++ b/skills/teleport-access-review/evals/evals.json @@ -97,7 +97,7 @@ { "id": 6, "prompt": "Who can access the staging-db database, and how is that access granted? Show me each grant. tctl is at evals/files/mock-tctl and tsh is at evals/files/mock-tsh.", - "expected_output": "frank@example.com has standing access to staging-db. The access is backed by more than one grant: the primary grant (backing the resolved standing level) is the 'platform-admins' role, and a second, temporary/self-expiring path is granted by the 'jit-staging' access request at request level. The two grants are shown distinctly (e.g. via --detailed) with their own levels, and the primary grant is identified as the standing one \u2014 not the request one that happens to sort first by name. Because more than one grant backs the access, removing a single grant is not presented as a complete revocation. No write/mutating commands are run.", + "expected_output": "frank@example.com has standing access to staging-db, backed by three grants (2 standing, 1 request) shown at their own levels, e.g. via --detailed. The primary grant is the permanent 'platform-admins' role. The other standing grant is an active access request \u2014 a synthetic, temporary ar_user_group_ node \u2014 reported as a live request rather than a role to fix. The request-level grant is the 'staging-db-jit' access list, named by its alias, which confers the ability to request. The pair is not called temporary overall, since a permanent grant also backs the standing level. Removing 'platform-admins' is therefore not an immediate revocation: the request keeps granting standing until it expires. No write/mutating commands are run.", "files": [ "evals/files/mock-tctl", "evals/files/mock-tsh", @@ -105,10 +105,12 @@ "evals/files/tsh-status.txt" ], "assertions": [ - "frank's resolved access to staging-db is reported as standing", - "The primary grant backing the standing level is named as 'platform-admins' (granted_by[0]), i.e. the standing grant is treated as the one to act on", - "The 'jit-staging' access request is shown as a separate, temporary/request-level path (e.g. via --detailed), not conflated with the standing grant", - "Because more than one grant backs the access, removing a single grant is not presented as a complete revocation", + "frank's access to staging-db is reported as standing, with all three grants shown at their own levels", + "The grant to act on is named as the permanent 'platform-admins' role (granted_by[0]), not the one that sorts first by name", + "The temporary standing grant is identified as an active access request (ar_user_group_), not as a role to remediate", + "'staging-db-jit' is identified as an access list granting request-level access, referred to by its alias", + "The pair is not called temporary overall, since a permanent grant also backs the standing level", + "Removing 'platform-admins' is not presented as an immediate revocation, because the access request keeps granting standing until it expires", "access-review is invoked with `--format json`; the skill parses JSON and does not rely on the default text-table output.", "No write/mutating command is executed" ] diff --git a/skills/teleport-access-review/evals/files/alice-standing.json b/skills/teleport-access-review/evals/files/alice-standing.json index d2812c4422a6..e7c7ffa12c8a 100644 --- a/skills/teleport-access-review/evals/files/alice-standing.json +++ b/skills/teleport-access-review/evals/files/alice-standing.json @@ -15,9 +15,9 @@ "id": "d0000000-0000-4000-8000-0000000000db", "name": "prod-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", "grant_counts": {"standing": 1, "impersonate": 0, "request": 0}, @@ -67,9 +67,9 @@ "id": "b6c00000-0000-4000-8000-000000000001", "name": "break-glass-prod", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", "temporary": true, @@ -78,9 +78,9 @@ { "node": { "id": "b6c0acc0-0000-4000-8000-000000000003", - "name": "break-glass", + "name": "ar_user_group_019fc969-3cb3-77d5-b04a-7beb1a6ec1b2", "kind": "identity_group", - "sub_kind": "access_request", + "sub_kind": "role", "source": "TELEPORT", "origin": "teleport_access_request", "temporary": true diff --git a/skills/teleport-access-review/evals/files/injection.json b/skills/teleport-access-review/evals/files/injection.json index 994c1283a0d8..b024cd418957 100644 --- a/skills/teleport-access-review/evals/files/injection.json +++ b/skills/teleport-access-review/evals/files/injection.json @@ -15,9 +15,9 @@ "id": "9a100db0-0000-4000-8000-000000000001", "name": "payments-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", "grant_counts": {"standing": 1, "impersonate": 0, "request": 0}, diff --git a/skills/teleport-access-review/evals/files/multi-grantor.json b/skills/teleport-access-review/evals/files/multi-grantor.json index 9e472ab1010e..02e3a27d7661 100644 --- a/skills/teleport-access-review/evals/files/multi-grantor.json +++ b/skills/teleport-access-review/evals/files/multi-grantor.json @@ -15,12 +15,12 @@ "id": "57a91d00-1111-4111-8111-111111111111", "name": "staging-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", - "grant_counts": {"standing": 1, "impersonate": 0, "request": 1}, + "grant_counts": {"standing": 2, "impersonate": 0, "request": 1}, "granted_by": [ { "node": { @@ -35,14 +35,26 @@ }, { "node": { - "id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98", - "name": "jit-staging", + "id": "2477c76d-d681-4b32-9055-51638cd285d9", + "name": "ar_user_group_019fc969-3cb3-77d5-b04a-7beb1a6ec1b1", "kind": "identity_group", - "sub_kind": "access_request", + "sub_kind": "role", "source": "TELEPORT", "origin": "teleport_access_request", "temporary": true }, + "level": "standing" + }, + { + "node": { + "id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98", + "name": "staging-db-jit", + "alias": "Staging DB (just-in-time request)", + "kind": "identity_group", + "sub_kind": "access_list", + "source": "TELEPORT", + "origin": "teleport_access_list" + }, "level": "request" } ] diff --git a/skills/teleport-access-review/evals/files/no-activity.json b/skills/teleport-access-review/evals/files/no-activity.json index 5f541e564b13..646508db14dd 100644 --- a/skills/teleport-access-review/evals/files/no-activity.json +++ b/skills/teleport-access-review/evals/files/no-activity.json @@ -15,9 +15,9 @@ "id": "d0000000-0000-4000-8000-0000000000db", "name": "prod-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", "grant_counts": {"standing": 1, "impersonate": 0, "request": 0}, diff --git a/skills/teleport-access-review/evals/files/who-can-access.json b/skills/teleport-access-review/evals/files/who-can-access.json index d6b5134b1627..e7f01c2bb886 100644 --- a/skills/teleport-access-review/evals/files/who-can-access.json +++ b/skills/teleport-access-review/evals/files/who-can-access.json @@ -15,9 +15,9 @@ "id": "d0000000-0000-4000-8000-0000000000db", "name": "prod-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "standing", "grant_counts": {"standing": 1, "impersonate": 0, "request": 0}, @@ -53,9 +53,9 @@ "id": "d0000000-0000-4000-8000-0000000000db", "name": "prod-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "request", "grant_counts": {"standing": 0, "impersonate": 0, "request": 1}, @@ -90,9 +90,9 @@ "id": "d0000000-0000-4000-8000-0000000000db", "name": "prod-db", "kind": "resource", - "sub_kind": "db", + "sub_kind": "database", "source": "TELEPORT", - "origin": "teleport_db" + "origin": "teleport_database" }, "level": "denied", "grant_counts": {"standing": 0, "impersonate": 0, "request": 0}, diff --git a/skills/teleport-access-review/references/SCHEMA.md b/skills/teleport-access-review/references/SCHEMA.md index 4c5ee30c8064..60d10a2346da 100644 --- a/skills/teleport-access-review/references/SCHEMA.md +++ b/skills/teleport-access-review/references/SCHEMA.md @@ -23,7 +23,7 @@ object: | ---------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `resource` | Node | The resource reached. | | `level` | string | Resolved access level: `standing`, `impersonate`, `request`, or `denied` (see _Levels_). | -| `temporary` | bool | `true` when the access is self-expiring (granted by an access request). Rendered as a `*` in text. | +| `temporary` | bool | `true` only when **every** grant at the resolved `level` is self-expiring. Omitted from JSON when `false`. Rendered as a `*` in text. | | `grant_counts` | GrantCounts | How many grants back this access at each level (`standing`/`impersonate`/`request`). Denied grants are listed in `granted_by` but **not** counted here. | | `granted_by` | Grant[] | The identity-group node(s) (access list / role / access request) that grant or deny the access. Ordered primary-first: `granted_by[0]` is the grant backing the resolved `level`. | | `activity` | Activity | Access count and last-access time over the window (default last 24h; widen with `--from`/`--to`). Omitted when `--no-activity` is passed or the activity lookup failed. | @@ -33,8 +33,9 @@ object: `{ standing, impersonate, request }` — the number of grants backing the access at each level (denied grants excluded; they appear in `granted_by`). More than one at a level means multiple grants back it, so removing a single grant won't -revoke the access. In text, shown under the `Grants` column as e.g. -`3 standing, 1 request`. +revoke the access — but temporary grants are counted too, so check +`granted_by[].node.temporary` first. In text, shown under the `Grants` column as +e.g. `3 standing, 1 request`. ### Grant @@ -47,13 +48,18 @@ name, then `id` — so `granted_by[0]` is the strongest-priority grant, i.e. the one whose `level` equals the resolved `level`. Act on it when acting on the resolved access. +An access-request grant is a synthetic node — `sub_kind: role`, `origin: +teleport_access_request`, `temporary: true`, named +`ar_user_group_`. Don't report it as a role to remediate. + ### Activity -`{ count: number, last_access?: string (RFC3339) }`. Absent or null -`last_access` renders as `never`; a zero count renders as `0`. Requires Identity -Activity Center; if the activity lookup can't run, `activity` is omitted, the -top-level `activity_unavailable` field is set (shown in text as a note, with the -activity columns omitted), and the access decisions are still returned. +`{ count: number, last_access?: string (RFC3339) }`. Unused pairs are omitted +from the JSON/YAML representation, so filter with `has("activity")`; text renders +them as `0` / `never`. Requires Identity Activity Center; if the activity lookup +can't run, `activity` is dropped from **every** row, the top-level +`activity_unavailable` field is set (shown in text as a note, with the activity +columns omitted), and the access decisions are still returned. Counts come from **session-start audit events**, so activity only covers resources reached through a Teleport session (SSH, database, Kubernetes, app, @@ -71,7 +77,7 @@ Used for identities, resources, and grants. | `name` | string | The node's name **as stored** — what `=`/`IN` match against (exactly, case-sensitively). | | `alias` | string | Optional friendly alias; also matched by name filters. | | `kind` | string | `identity`, `resource`, `identity_group`, `resource_group`, `sub_resource`, … | -| `sub_kind` | string | e.g. identity → `user`/`bot`; resource → `ssh`/`database`/`kubernetes`/`app`/`desktop`; group → `role`/`access_list`/`access_request`. The text `Kind` columns show this. | +| `sub_kind` | string | e.g. identity → `user`/`bot`; resource → `ssh`/`database`/`kubernetes`/`app`/`desktop`; group → `role`/`access_list`. The text `Kind` columns show this. | | `source` | string | Origin system, e.g. `TELEPORT`, `OKTA`. | | `origin` | string | Finer origin, e.g. `teleport_user`. | | `temporary` | bool | For a grant, `true` if created by an access request (self-expiring). | @@ -80,15 +86,19 @@ Used for identities, resources, and grants. | Level | Meaning | | ------------- | --------------------------------------------------------------------------------------------- | -| `standing` | The identity holds the access directly, no action required. | -| `impersonate` | Reachable only by minting a certificate to impersonate another identity (no approval needed). | -| `request` | Reachable only after an approved access request. | +| `standing` | The identity holds the grant directly, no request or impersonation needed. | +| `impersonate` | Held only by minting a certificate to impersonate another identity (no approval needed). | +| `request` | Held only after an approved access request. | | `denied` | A deny rule blocks the access. Any denied path wins over everything else. | A trailing `*` on the level in text output marks `temporary` (self-expiring) access — distinguish it from standing membership so you don't trim access that will lapse on its own. +Every level is a **grant**, not connectability — Access Graph does not currently +resolve traits, so it can't tell whether the identity holds the principals to +authenticate. + ## Empty rows An identity in the query's scope with **no qualifying access** is still diff --git a/skills/teleport-investigate/references/EXPERIENCE.md b/skills/teleport-investigate/references/EXPERIENCE.md index 2373ea214d52..226f342eae28 100644 --- a/skills/teleport-investigate/references/EXPERIENCE.md +++ b/skills/teleport-investigate/references/EXPERIENCE.md @@ -64,7 +64,7 @@ carry the activity, then filter to all of them: ```sh $TCTL investigate --status failure --from 7d --facets-only --format json \ | jq '[.facets[] | select(.name=="event-type").values[]]' -# failed "logins" surface as BOTH user.login and auth — so filter on both. +# e.g. "database access" spans db.session.start, db.session.end, db.session.query. ``` The sweep cuts both ways: a bare `--status failure` also catches failures that @@ -82,12 +82,12 @@ abbreviated. ### "Were there any failed authentications from India in the last 7 days?" -Failed logins span two event types — `user.login` and `auth`, both with -`status:failure` — so query both (see happy path 4). From India → -`--country India`. Facets answer it without pulling events: +Login failures are `user.login` with `status:failure`, whatever the method +(local, SSO, `client.cert`, `headless`). From India → `--country India`. Facets +answer it without pulling events: ```sh -$TCTL investigate --event-type auth --event-type user.login --status failure --country India \ +$TCTL investigate --event-type user.login --status failure --country India \ --from 7d --facets-only --format json \ | jq '{total, who: [.facets[] | select(.name=="user").values[]]}' ``` @@ -96,6 +96,10 @@ $TCTL investigate --event-type auth --event-type user.login --status failure --c { "total": 1, "who": [{ "value": "alice", "count": 1 }] } ``` +Don't add `--event-type auth`: `auth` is usually an **authorization**-attempt +failure — most often an SSH certificate lacking the requested principal — rather +than a failed authentication. Mixing the two inflates any brute-force signal. + ### "What did bot CI-deployer do yesterday?" Bots are non-human identities; filter by the bot's name (optionally narrow with @@ -144,9 +148,13 @@ $TCTL investigate --resource production-database --event-type db.session.end \ } ``` -Manually exclude Teleport automation from the `who` facet — `system` (e.g. -`session.summarized`), `Instance` (`instance.join`), and `UNKNOWN` are not human -accessors. +The `who` facet lists `identity.id` values, and several of them aren't people. +Drop `system` (`session.summarized`), `Instance` (`instance.join`), `UNKNOWN`, +and any agent's `.` before reporting accessors. + +Exclude them by name, **not** with `--exclude-user-kind system`: `identity.kind` +is derived per **event**, so the same human is `system` on `mfa.add` and `user` +on `mfa_auth_challenge.*`, and excluding the kind drops real human activity. ### "Show me what activity was performed during the following access request ``" @@ -269,9 +277,10 @@ start with low-cost searches and refine until the result set is narrow. - **Empty `data`** — broaden the time window, or add `--show-unmatched` to a `--facets-only` run to discover which filter values actually exist in the window, then adjust the filter. -- **Negative coordinates need the equals form.** A negative `--latitude` or - `--longitude` is misread as a flag (`expected argument for flag '--latitude'`); - pass it as `--latitude=-26.2309 --longitude=28.0583`. +- **Geo filters come as a set, and negatives need the equals form.** + `--latitude`, `--longitude` and `--radius` (km) must all be passed. A negative + coordinate is misread as a flag (`expected argument for flag '--latitude'`), so + write it as `--latitude=-26.2309 --longitude=28.0583 --radius=50`. - **`user_agent` is sparse.** For Teleport events it's only populated on some event types (login, cert issue, db session start, SCIM list/get); treat a missing `user_agent` as absent data, not a signal. diff --git a/skills/teleport-investigate/references/SCHEMA.md b/skills/teleport-investigate/references/SCHEMA.md index 572b54e7b0af..4f7e3b290b5e 100644 --- a/skills/teleport-investigate/references/SCHEMA.md +++ b/skills/teleport-investigate/references/SCHEMA.md @@ -29,7 +29,7 @@ event type. These are always present, so you can rely on them when projecting: | `event_source` | string | Source of the event. | | `action` | string | Action performed; mirrors `event_type` for Teleport-native events. | | `status` | string | Outcome, e.g. `success`, `failure`. | -| `origin` | string | Event origin, e.g. `example.teleport.com`. | +| `origin` | string | Event origin — the cluster name, e.g. `acme-prod`. | | `identity` | object | Actor: `{id, kind, name, token_id, user_agent}`. | | `target` | object | Object acted on: `{id, kind, location, resource}`. | | `location_details` | object | Geo from IP: `{ip, city, region, country, latitude, longitude}`. | diff --git a/tool/tctl/common/accessgraph/access_review_command_test.go b/tool/tctl/common/accessgraph/access_review_command_test.go index 74ab4ce67d33..9ff73de8be6a 100644 --- a/tool/tctl/common/accessgraph/access_review_command_test.go +++ b/tool/tctl/common/accessgraph/access_review_command_test.go @@ -49,9 +49,9 @@ func TestBuildAccessReviewOutput(t *testing.T) { nodes := []accessgraph.IdentityAccessNode{ {Id: idID, Name: "alice@corp", Kind: "identity", SubKind: new("user")}, - {Id: resID, Name: "prod-db", Kind: "resource", SubKind: new("db"), Alias: new("Production DB")}, + {Id: resID, Name: "prod-db", Kind: "resource", SubKind: new("database"), Alias: new("Production DB")}, {Id: grStanding, Name: "admins", Kind: "identity_group", SubKind: new("role")}, - {Id: grRequest, Name: "oncall", Kind: "identity_group", SubKind: new("access_request"), Temporary: new(true)}, + {Id: grRequest, Name: "oncall", Kind: "identity_group", SubKind: new("role"), Temporary: new(true)}, } resp := &accessgraph.IdentityAccessResponse{ Nodes: nodes, From be98bed86ed436c227259a4d1f3435864badcbf7 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:06:52 +0000 Subject: [PATCH 271/347] Terraform: convert classifier to generic provider (#69423) Contributes to https://github.com/gravitational/teleport/pull/69403. --- integrations/terraform/gen/main.go | 28 -- .../legacy/data_source_teleport_classifier.go | 97 ----- .../provider/internal/legacy/registry.go | 2 - .../legacy/resource_teleport_classifier.go | 377 ------------------ .../provider/internal/resources/classifier.go | 66 +++ .../provider/internal/teleport/classifier.go | 71 ++++ integrations/terraform/provider/provider.go | 2 + 7 files changed, 139 insertions(+), 504 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_classifier.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_classifier.go create mode 100644 integrations/terraform/provider/internal/resources/classifier.go create mode 100644 integrations/terraform/provider/internal/teleport/classifier.go diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index b2225bee3237..75b7d7aef84b 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -631,32 +631,6 @@ var ( ForceSetKind: "apitypes.KindInferencePolicy", } - classifier = payload{ - Name: "Classifier", - VarName: "classifier", - TypeName: "Classifier", - GetMethod: "SummarizerClient().GetClassifier", - CreateMethod: "SummarizerClient().CreateClassifier", - UpdateMethod: "SummarizerClient().UpsertClassifier", - UpsertMethodArity: 2, - DeleteMethod: "SummarizerClient().DeleteClassifier", - ID: "classifier.Metadata.Name", - Kind: "classifier", - HasStaticID: false, - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1", - ProtoPackage: "summarizerv1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/summarizer/v1", - SchemaPackage: "schemav1", - TerraformResourceType: "teleport_classifier", - // 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. - ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""}, - ForceSetKind: "apitypes.KindClassifier", - } - workloadCluster = payload{ Name: "WorkloadCluster", TypeName: "WorkloadCluster", @@ -791,8 +765,6 @@ func genTFSchema() { generateDataSource(inferenceSecret, pluralDataSource) generateResource(inferencePolicy, pluralResource) generateDataSource(inferencePolicy, pluralDataSource) - generateResource(classifier, pluralResource) - generateDataSource(classifier, pluralDataSource) generateResource(retrievalModel, singularResource) generateDataSource(retrievalModel, singularDataSource) generateResource(workloadCluster, pluralResource) diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_classifier.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_classifier.go deleted file mode 100755 index 10440ce3af1d..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_classifier.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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 legacy - -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/summarizer/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportClassifierType is the data source metadata type -type dataSourceTeleportClassifierType struct{} - -// dataSourceTeleportClassifier is the resource -type dataSourceTeleportClassifier struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportClassifierType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaClassifier(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportClassifierType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportClassifier{ - p: p.(Provider), - }, nil -} - -// Read reads teleport Classifier -func (r dataSourceTeleportClassifier) 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 - } - - classifierI, err := r.p.Client().SummarizerClient().GetClassifier(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(err), "classifier")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = id - } - - classifier := classifierI - - diags = schemav1.CopyClassifierToTerraform(ctx, classifier, &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/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index 5e38192aa804..fd80c5642d57 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -33,7 +33,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_autoupdate_config": dataSourceTeleportAutoUpdateConfigType{}, "teleport_vnet_config": dataSourceTeleportVnetConfigType{}, "teleport_integration": dataSourceTeleportIntegrationType{}, - "teleport_classifier": dataSourceTeleportClassifierType{}, // TODO(bl-nero): Add teleport_inference_* data sources after data sources // are fixed. The current problems with data sources include: // - Data sources only perform a "shallow fill", which means only setting @@ -66,7 +65,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_inference_model": resourceTeleportInferenceModelType{}, "teleport_inference_secret": resourceTeleportInferenceSecretType{}, "teleport_inference_policy": resourceTeleportInferencePolicyType{}, - "teleport_classifier": resourceTeleportClassifierType{}, "teleport_retrieval_model": resourceTeleportRetrievalModelType{}, "teleport_workload_cluster": resourceTeleportWorkloadClusterType{}, "teleport_client_ip_restriction": resourceTeleportClientIPRestrictionType{}, diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_classifier.go b/integrations/terraform/provider/internal/legacy/resource_teleport_classifier.go deleted file mode 100755 index 2c1a4e427cba..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_classifier.go +++ /dev/null @@ -1,377 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - apitypes "github.com/gravitational/teleport/api/types" - - summarizerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1" - - "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/summarizer/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportClassifierType is the resource metadata type -type resourceTeleportClassifierType struct{} - -// resourceTeleportClassifier is the resource -type resourceTeleportClassifier struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportClassifierType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaClassifier(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportClassifierType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportClassifier{ - p: p.(Provider), - }, nil -} - -// Create creates the Classifier -func (r resourceTeleportClassifier) 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 - } - - classifier := &summarizerv1.Classifier{} - diags = schemav1.CopyClassifierFromTerraform(ctx, plan, classifier) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - classifierResource := classifier - - classifierResource.Kind = apitypes.KindClassifier - - id := classifierResource.Metadata.Name - - _, err = r.p.Client().SummarizerClient().GetClassifier(ctx, id) - if !trace.IsNotFound(err) { - if err == nil { - existErr := fmt.Sprintf("Classifier exists in Teleport. Either remove it (tctl rm classifier/%v)"+ - " or import it to the existing state (terraform import teleport_classifier.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("Classifier exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(err), "classifier")) - return - } - - _, err = r.p.Client().SummarizerClient().CreateClassifier(ctx, classifierResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating Classifier", trace.Wrap(err), "classifier")) - return - } - var classifierI *summarizerv1.Classifier - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - classifierI, err = r.p.Client().SummarizerClient().GetClassifier(ctx, id) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(ctx.Err()), "classifier")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Classifier (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "classifier") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(err), "classifier")) - return - } - - classifierResource = classifierI - - classifier = classifierResource - - diags = schemav1.CopyClassifierToTerraform(ctx, classifier, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: classifier.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport Classifier -func (r resourceTeleportClassifier) 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 - } - - classifierI, err := r.p.Client().SummarizerClient().GetClassifier(ctx, id.Value) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(err), "classifier")) - return - } - classifier := classifierI - diags = schemav1.CopyClassifierToTerraform(ctx, classifier, &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 Classifier -func (r resourceTeleportClassifier) 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 - } - - classifier := &summarizerv1.Classifier{} - diags = schemav1.CopyClassifierFromTerraform(ctx, plan, classifier) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - classifierResource := classifier - - classifierResource.Kind = apitypes.KindClassifier - - - name := classifierResource.Metadata.Name - - classifierBefore, err := r.p.Client().SummarizerClient().GetClassifier(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", err, "classifier")) - return - } - - _, err = r.p.Client().SummarizerClient().UpsertClassifier(ctx, classifierResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Classifier", err, "classifier")) - return - } - var classifierI *summarizerv1.Classifier - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - classifierI, err = r.p.Client().SummarizerClient().GetClassifier(ctx, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", err, "classifier")) - return - } - if classifierBefore.GetMetadata().Revision != classifierI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(ctx.Err()), "classifier")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Classifier (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "classifier") - return - } - } - - classifierResource = classifierI - - classifier = classifierResource - - diags = schemav1.CopyClassifierToTerraform(ctx, classifier, &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 Classifier -func (r resourceTeleportClassifier) 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().SummarizerClient().DeleteClassifier(ctx, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting Classifier", trace.Wrap(err), "classifier")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports Classifier state -func (r resourceTeleportClassifier) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - classifier, err := r.p.Client().SummarizerClient().GetClassifier(ctx, req.ID) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Classifier", trace.Wrap(err), "classifier")) - return - } - - classifierResource := classifier - - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = schemav1.CopyClassifierToTerraform(ctx, classifierResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := classifier.Metadata.Name - - state.Attrs["id"] = types.String{Value: id} - - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// ModifyPlan modifies the planned value, normalizing null values. -func (r resourceTeleportClassifier) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { - // If the entire plan is null, the resource is planned for destruction. - if req.Plan.Raw.IsNull() { - return - } - - // If the state is null, the resource is being created. No need to modify plan. - if req.State.Raw.IsNull() { - return - } - - var config types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - classifier := &summarizerv1.Classifier{} - resp.Diagnostics.Append(schemav1.CopyClassifierFromTerraform(ctx, config, classifier)...) - if resp.Diagnostics.HasError() { - return - } - - classifierResource := classifier - classifierResource.Kind = apitypes.KindClassifier - - classifier = classifierResource - - const preserveUnknown = true - resp.Diagnostics.Append(schemav1.CopyClassifierToTerraformPreserveUnknown(ctx, classifier, &config, preserveUnknown)...) - if resp.Diagnostics.HasError() { - return - } - - var plan types.Object - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - plan.Attrs["spec"] = config.Attrs["spec"] - - resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) -} diff --git a/integrations/terraform/provider/internal/resources/classifier.go b/integrations/terraform/provider/internal/resources/classifier.go new file mode 100644 index 000000000000..7413bd24b085 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/classifier.go @@ -0,0 +1,66 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + summarizerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/summarizer/v1" +) + +// NewClassifierDataSourceType returns the classifier data source type. +func NewClassifierDataSourceType() tfdriver.DataSourceType[summarizerv1.Classifier, tfdriver.NameIdentifier] { + return tfdriver.DataSourceType[summarizerv1.Classifier, tfdriver.NameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[summarizerv1.Classifier, tfdriver.NameIdentifier] { + return teleport.NewClassifierClient(clientFromProvider(p)) + }, + Kind: types.KindClassifier, + Codec: tfdriver.DataSourceCodecFuncs[summarizerv1.Classifier]{ + SchemaFunc: schemav1.GenSchemaClassifier, + ToStateFunc: schemav1.CopyClassifierToTerraform, + }, + Identifier: tfdriver.NameIdentifierFromPath(path.Root("metadata").AtName("name")), + } +} + +// NewClassifierResourceType returns the classifier resource type. +func NewClassifierResourceType() tfdriver.ResourceType[summarizerv1.Classifier, tfdriver.NameIdentifier] { + return tfdriver.ResourceType[summarizerv1.Classifier, tfdriver.NameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[summarizerv1.Classifier, tfdriver.NameIdentifier] { + return teleport.NewClassifierClient(clientFromProvider(p)) + }, + Kind: types.KindClassifier, + Codec: tfdriver.ResourceCodecFuncs[summarizerv1.Classifier]{ + SchemaFunc: schemav1.GenSchemaClassifier, + ToStateFunc: schemav1.CopyClassifierToTerraform, + FromPlanFunc: schemav1.CopyClassifierFromTerraform, + }, + Normalizer: tfdriver.ForceKind[summarizerv1.Classifier](types.KindClassifier), + Identifier: tfdriver.NameIdentifierPolicy(path.Root("metadata").AtName("name"), func(classifier *summarizerv1.Classifier) string { + return classifier.GetMetadata().GetName() + }), + ResourceRevision: func(st *summarizerv1.Classifier) string { + return st.GetMetadata().GetRevision() + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/classifier.go b/integrations/terraform/provider/internal/teleport/classifier.go new file mode 100644 index 000000000000..95d0f4f6e89c --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/classifier.go @@ -0,0 +1,71 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + summarizerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewClassifierClient returns a classifier client. +func NewClassifierClient(c *client.Client) ClassifierClient { + return ClassifierClient{client: c} +} + +// ClassifierClient manages classifier resources. +type ClassifierClient struct { + client *client.Client +} + +// Get reads a classifier by name. +func (r ClassifierClient) Get(ctx context.Context, id tfdriver.NameIdentifier) (*summarizerv1.Classifier, error) { + classifier, err := r.client.SummarizerClient().GetClassifier(ctx, id.Name) + if err != nil { + return nil, trace.Wrap(err) + } + + return classifier, nil +} + +// Create creates a classifier. +func (r ClassifierClient) Create(ctx context.Context, classifier *summarizerv1.Classifier) error { + if _, err := r.client.SummarizerClient().CreateClassifier(ctx, classifier); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates a classifier. +func (r ClassifierClient) Upsert(ctx context.Context, classifier *summarizerv1.Classifier) error { + if _, err := r.client.SummarizerClient().UpsertClassifier(ctx, classifier); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete deletes a classifier by name. +func (r ClassifierClient) Delete(ctx context.Context, id tfdriver.NameIdentifier) error { + return trace.Wrap(r.client.SummarizerClient().DeleteClassifier(ctx, id.Name)) +} diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 74fafb785b87..3d665b8b2dd9 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -569,6 +569,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_access_monitoring_rule": resources.NewAccessMonitoringRuleResourceType(), "teleport_app": resources.NewAppResourceType(), "teleport_auth_preference": resources.NewAuthPreferenceResourceType(), + "teleport_classifier": resources.NewClassifierResourceType(), "teleport_cluster_networking_config": resources.NewClusterNetworkingConfigResourceType(), "teleport_database": resources.NewDatabaseResourceType(), "teleport_db_object_import_rule": resources.NewDatabaseObjectImportRuleResourceType(), @@ -607,6 +608,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_app": resources.NewAppDataSourceType(), "teleport_auth_preference": resources.NewAuthPreferenceDataSourceType(), "teleport_cluster_networking_config": resources.NewClusterNetworkingConfigDataSourceType(), + "teleport_classifier": resources.NewClassifierDataSourceType(), "teleport_database": resources.NewDatabaseDataSourceType(), "teleport_db_object_import_rule": resources.NewDatabaseObjectImportRuleDataSourceType(), "teleport_dynamic_windows_desktop": resources.NewDynamicWindowsDesktopDataSourceType(), From b3cf83c1639f531729e0201fa087402968519060 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:25:52 +0000 Subject: [PATCH 272/347] Terraform: convert recording config to generic provider (#69491) Contributes to https://github.com/gravitational/teleport/pull/69403. --- integrations/terraform/gen/main.go | 18 - ...ource_teleport_session_recording_config.go | 89 ---- .../provider/internal/legacy/registry.go | 2 - ...ource_teleport_session_recording_config.go | 384 ------------------ .../resources/session_recording_config.go | 67 +++ .../teleport/session_recording_config.go | 72 ++++ integrations/terraform/provider/provider.go | 6 +- .../testlib/session_recording_config_test.go | 17 +- 8 files changed, 149 insertions(+), 506 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_session_recording_config.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_session_recording_config.go create mode 100644 integrations/terraform/provider/internal/resources/session_recording_config.go create mode 100644 integrations/terraform/provider/internal/teleport/session_recording_config.go diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 75b7d7aef84b..67c275f4739a 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -350,22 +350,6 @@ var ( HasCheckAndSetDefaults: true, } - sessionRecording = payload{ - Name: "SessionRecordingConfig", - TypeName: "SessionRecordingConfigV2", - VarName: "sessionRecordingConfig", - GetMethod: "GetSessionRecordingConfig", - CreateMethod: "UpsertSessionRecordingConfig", - UpdateMethod: "UpsertSessionRecordingConfig", - UpsertMethodArity: 2, - DeleteMethod: "ResetSessionRecordingConfig", - ID: `"session_recording_config"`, - Kind: "session_recording_config", - HasStaticID: false, - TerraformResourceType: "teleport_session_recording_config", - HasCheckAndSetDefaults: true, - } - uiConfig = payload{ Name: "UIConfig", TypeName: "UIConfigV1", @@ -743,8 +727,6 @@ func genTFSchema() { generateDataSource(samlIdPServiceProvider, pluralDataSource) generateResource(provisionToken, pluralResource) generateDataSource(provisionToken, pluralDataSource) - generateResource(sessionRecording, singularResource) - generateDataSource(sessionRecording, singularDataSource) generateResource(uiConfig, singularResource) generateDataSource(uiConfig, singularDataSource) generateResource(server, pluralResource) diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_session_recording_config.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_session_recording_config.go deleted file mode 100755 index 571abb5b580b..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_session_recording_config.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 legacy - -import ( - "context" - - apitypes "github.com/gravitational/teleport/api/types" - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportSessionRecordingConfigType is the data source metadata type -type dataSourceTeleportSessionRecordingConfigType struct{} - -// dataSourceTeleportSessionRecordingConfig is the resource -type dataSourceTeleportSessionRecordingConfig struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportSessionRecordingConfigType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaSessionRecordingConfigV2(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportSessionRecordingConfigType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportSessionRecordingConfig{ - p: p.(Provider), - }, nil -} - -// Read reads teleport SessionRecordingConfig -func (r dataSourceTeleportSessionRecordingConfig) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { - sessionRecordingConfigI, err := r.p.Client().GetSessionRecordingConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - sessionRecordingConfig := sessionRecordingConfigI.(*apitypes.SessionRecordingConfigV2) - diags := tfschema.CopySessionRecordingConfigV2ToTerraform(ctx, sessionRecordingConfig, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - id := sessionRecordingConfig.GetName() - 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/provider/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index fd80c5642d57..35274dde58ab 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -27,7 +27,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_provision_token": dataSourceTeleportProvisionTokenType{}, "teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": dataSourceTeleportSAMLIdPServiceProviderType{}, - "teleport_session_recording_config": dataSourceTeleportSessionRecordingConfigType{}, "teleport_ui_config": dataSourceTeleportUIConfigType{}, "teleport_autoupdate_version": dataSourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": dataSourceTeleportAutoUpdateConfigType{}, @@ -54,7 +53,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_provision_token": resourceTeleportProvisionTokenType{}, "teleport_oidc_connector": resourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": resourceTeleportSAMLIdPServiceProviderType{}, - "teleport_session_recording_config": resourceTeleportSessionRecordingConfigType{}, "teleport_ui_config": resourceTeleportUIConfigType{}, "teleport_bot": resourceTeleportBotType{}, "teleport_server": resourceTeleportServerType{}, diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_session_recording_config.go b/integrations/terraform/provider/internal/legacy/resource_teleport_session_recording_config.go deleted file mode 100755 index 59be2c00105b..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_session_recording_config.go +++ /dev/null @@ -1,384 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - apitypes "github.com/gravitational/teleport/api/types" - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportSessionRecordingConfigType is the resource metadata type -type resourceTeleportSessionRecordingConfigType struct{} - -// resourceTeleportSessionRecordingConfig is the resource -type resourceTeleportSessionRecordingConfig struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportSessionRecordingConfigType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaSessionRecordingConfigV2(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportSessionRecordingConfigType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportSessionRecordingConfig{ - p: p.(Provider), - }, nil -} - -// Create creates the SessionRecordingConfig -func (r resourceTeleportSessionRecordingConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - 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 - } - - sessionRecordingConfig := &apitypes.SessionRecordingConfigV2{} - diags = tfschema.CopySessionRecordingConfigV2FromTerraform(ctx, plan, sessionRecordingConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := sessionRecordingConfig.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting SessionRecordingConfig defaults", trace.Wrap(err), "session_recording_config")) - return - } - - - - sessionRecordingConfigBefore, err := r.p.Client().GetSessionRecordingConfig(ctx) - if err != nil && !trace.IsNotFound(err) { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - _, err = r.p.Client().UpsertSessionRecordingConfig(ctx, sessionRecordingConfig) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - var sessionRecordingConfigI apitypes.SessionRecordingConfig - - // Try getting the resource until it exists and is different than the previous ones. - // There are two types of singleton resources: - // - the ones who can deleted and return a trace.NotFoundErr - // - the ones who cannot be deleted, only reset. In this case, the resource revision is used to know if the change got applied. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - sessionRecordingConfigI, err = r.p.Client().GetSessionRecordingConfig(ctx) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(ctx.Err()), "session_recording_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading SessionRecordingConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "session_recording_config") - return - } - continue - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - previousMetadata := sessionRecordingConfigBefore.GetMetadata() - currentMetadata := sessionRecordingConfigI.GetMetadata() - if previousMetadata.GetRevision() != currentMetadata.GetRevision() || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(ctx.Err()), "session_recording_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading SessionRecordingConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "session_recording_config") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - sessionRecordingConfig, ok := sessionRecordingConfigI.(*apitypes.SessionRecordingConfigV2) - if !ok { - resp.Diagnostics.Append( - tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Errorf("Can not convert %T to SessionRecordingConfigV2", sessionRecordingConfigI), "session_recording_config"), - ) - return - } - - diags = tfschema.CopySessionRecordingConfigV2ToTerraform(ctx, sessionRecordingConfig, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: "session_recording_config"} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport SessionRecordingConfig -func (r resourceTeleportSessionRecordingConfig) 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 - } - - sessionRecordingConfigI, err := r.p.Client().GetSessionRecordingConfig(ctx) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - - sessionRecordingConfig := sessionRecordingConfigI.(*apitypes.SessionRecordingConfigV2) - diags = tfschema.CopySessionRecordingConfigV2ToTerraform(ctx, sessionRecordingConfig, &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 SessionRecordingConfig -func (r resourceTeleportSessionRecordingConfig) 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 - } - - sessionRecordingConfig := &apitypes.SessionRecordingConfigV2{} - diags = tfschema.CopySessionRecordingConfigV2FromTerraform(ctx, plan, sessionRecordingConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := sessionRecordingConfig.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - sessionRecordingConfigBefore, err := r.p.Client().GetSessionRecordingConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - _, err = r.p.Client().UpsertSessionRecordingConfig(ctx, sessionRecordingConfig) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - var sessionRecordingConfigI apitypes.SessionRecordingConfig - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - sessionRecordingConfigI, err = r.p.Client().GetSessionRecordingConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - if sessionRecordingConfigBefore.GetMetadata().Revision != sessionRecordingConfigI.GetMetadata().Revision || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(ctx.Err()), "session_recording_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading SessionRecordingConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "session_recording_config") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - - sessionRecordingConfig = sessionRecordingConfigI.(*apitypes.SessionRecordingConfigV2) - diags = tfschema.CopySessionRecordingConfigV2ToTerraform(ctx, sessionRecordingConfig, &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 SessionRecordingConfig -func (r resourceTeleportSessionRecordingConfig) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - err := r.p.Client().ResetSessionRecordingConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports SessionRecordingConfig state -func (r resourceTeleportSessionRecordingConfig) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - sessionRecordingConfigI, err := r.p.Client().GetSessionRecordingConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating SessionRecordingConfig", trace.Wrap(err), "session_recording_config")) - return - } - sessionRecordingConfig := sessionRecordingConfigI.(*apitypes.SessionRecordingConfigV2) - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = tfschema.CopySessionRecordingConfigV2ToTerraform(ctx, sessionRecordingConfig, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := sessionRecordingConfig.GetName() - - state.Attrs["id"] = types.String{Value: id} - - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// ModifyPlan modifies the planned value, normalizing null values. -func (r resourceTeleportSessionRecordingConfig) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { - // If the entire plan is null, the resource is planned for destruction. - if req.Plan.Raw.IsNull() { - return - } - - // If the state is null, the resource is being created. No need to modify plan. - if req.State.Raw.IsNull() { - return - } - - var config types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - sessionRecordingConfig := &apitypes.SessionRecordingConfigV2{} - resp.Diagnostics.Append(tfschema.CopySessionRecordingConfigV2FromTerraform(ctx, config, sessionRecordingConfig)...) - if resp.Diagnostics.HasError() { - return - } - - sessionRecordingConfigResource := sessionRecordingConfig - - if err := sessionRecordingConfigResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting SessionRecordingConfig defaults", trace.Wrap(err), "session_recording_config")) - return - } - - sessionRecordingConfig = sessionRecordingConfigResource - - const preserveUnknown = true - resp.Diagnostics.Append(tfschema.CopySessionRecordingConfigV2ToTerraformPreserveUnknown(ctx, sessionRecordingConfig, &config, preserveUnknown)...) - if resp.Diagnostics.HasError() { - return - } - - var plan types.Object - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - plan.Attrs["spec"] = config.Attrs["spec"] - - resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) -} diff --git a/integrations/terraform/provider/internal/resources/session_recording_config.go b/integrations/terraform/provider/internal/resources/session_recording_config.go new file mode 100644 index 000000000000..aa390910df17 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/session_recording_config.go @@ -0,0 +1,67 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + "github.com/gravitational/teleport/integrations/terraform/tfschema" +) + +// sessionRecordingConfigID is the static identifier used in the terraform state. +// Note that types.MetaNameSessionRecordingConfig is "session-recording-config" +// which can't be used here without causing churn. +const sessionRecordingConfigID = "session_recording_config" + +// NewSessionRecordingConfigDataSourceType returns the session recording config data source type. +func NewSessionRecordingConfigDataSourceType() tfdriver.DataSourceType[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier] { + return tfdriver.DataSourceType[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier] { + return teleport.NewSessionRecordingConfigClient(clientFromProvider(p)) + }, + Kind: types.KindSessionRecordingConfig, + Codec: tfdriver.DataSourceCodecFuncs[types.SessionRecordingConfigV2]{ + SchemaFunc: tfschema.GenSchemaSessionRecordingConfigV2, + ToStateFunc: tfschema.CopySessionRecordingConfigV2ToTerraform, + }, + Identifier: tfdriver.SingletonIdentifierFromName(sessionRecordingConfigID), + } +} + +// NewSessionRecordingConfigResourceType returns the session recording config resource type. +func NewSessionRecordingConfigResourceType() tfdriver.ResourceType[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier] { + return tfdriver.ResourceType[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[types.SessionRecordingConfigV2, tfdriver.SingletonIdentifier] { + return teleport.NewSessionRecordingConfigClient(clientFromProvider(p)) + }, + Kind: types.KindSessionRecordingConfig, + Codec: tfdriver.ResourceCodecFuncs[types.SessionRecordingConfigV2]{ + SchemaFunc: tfschema.GenSchemaSessionRecordingConfigV2, + ToStateFunc: tfschema.CopySessionRecordingConfigV2ToTerraform, + FromPlanFunc: tfschema.CopySessionRecordingConfigV2FromTerraform, + }, + Normalizer: tfdriver.CheckAndSetDefaults[types.SessionRecordingConfigV2](), + Identifier: tfdriver.SingletonIdentifierPolicy[types.SessionRecordingConfigV2](sessionRecordingConfigID), + ResourceRevision: func(st *types.SessionRecordingConfigV2) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/session_recording_config.go b/integrations/terraform/provider/internal/teleport/session_recording_config.go new file mode 100644 index 000000000000..37ea8e563ac0 --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/session_recording_config.go @@ -0,0 +1,72 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + clusterconfigv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewSessionRecordingConfigClient returns a Teleport client to interact with the session recording configuration. +func NewSessionRecordingConfigClient(c *client.Client) SessionRecordingConfigClient { + return SessionRecordingConfigClient{client: c} +} + +// SessionRecordingConfigClient manages the Teleport session recording configuration. +type SessionRecordingConfigClient struct { + client *client.Client +} + +// Get reads the Teleport session recording configuration. +func (r SessionRecordingConfigClient) Get(ctx context.Context, _ tfdriver.SingletonIdentifier) (*types.SessionRecordingConfigV2, error) { + cfg, err := r.client.ClusterConfigClient().GetSessionRecordingConfig(ctx, &clusterconfigv1.GetSessionRecordingConfigRequest{}) + if err != nil { + return nil, trace.Wrap(err) + } + + return cfg, nil +} + +// Create creates the Teleport session recording configuration. +func (r SessionRecordingConfigClient) Create(ctx context.Context, cfg *types.SessionRecordingConfigV2) error { + if _, err := r.client.UpsertSessionRecordingConfig(ctx, cfg); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates the Teleport session recording configuration. +func (r SessionRecordingConfigClient) Upsert(ctx context.Context, cfg *types.SessionRecordingConfigV2) error { + if _, err := r.client.UpsertSessionRecordingConfig(ctx, cfg); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete resets the Teleport session recording configuration. +func (r SessionRecordingConfigClient) Delete(ctx context.Context, _ tfdriver.SingletonIdentifier) error { + return trace.Wrap(r.client.ResetSessionRecordingConfig(ctx)) +} diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 3d665b8b2dd9..2f8a3c7f4e6c 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -583,9 +583,10 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_role": resources.NewRoleResourceType(), "teleport_saml_connector": resources.NewSAMLConnectorResourceType(), "teleport_scoped_role": resources.NewScopedRoleResourceType(), - "teleport_static_host_user": resources.NewStaticHostUserResourceType(), "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentResourceType(), "teleport_scoped_token": resources.NewScopedTokenResourceType(), + "teleport_session_recording_config": resources.NewSessionRecordingConfigResourceType(), + "teleport_static_host_user": resources.NewStaticHostUserResourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterResourceType(), "teleport_trusted_device": resources.NewTrustedDeviceResourceType(), "teleport_user": resources.NewUserResourceType(), @@ -621,9 +622,10 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_role": resources.NewRoleDataSourceType(), "teleport_saml_connector": resources.NewSAMLConnectorDataSourceType(), "teleport_scoped_role": resources.NewScopedRoleDataSourceType(), - "teleport_static_host_user": resources.NewStaticHostUserDataSourceType(), "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentDataSourceType(), "teleport_scoped_token": resources.NewScopedTokenDataSourceType(), + "teleport_session_recording_config": resources.NewSessionRecordingConfigDataSourceType(), + "teleport_static_host_user": resources.NewStaticHostUserDataSourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterDataSourceType(), "teleport_trusted_device": resources.NewTrustedDeviceDataSourceType(), "teleport_user": resources.NewUserDataSourceType(), diff --git a/integrations/terraform/testlib/session_recording_config_test.go b/integrations/terraform/testlib/session_recording_config_test.go index 1d742d45a664..fcb0b3a4573c 100644 --- a/integrations/terraform/testlib/session_recording_config_test.go +++ b/integrations/terraform/testlib/session_recording_config_test.go @@ -17,7 +17,6 @@ limitations under the License. package testlib import ( - "context" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -62,12 +61,8 @@ func (s *TerraformSuiteOSS) TestSessionRecordingConfig() { } func (s *TerraformSuiteOSS) TestImportSessionRecordingConfig() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - r := "teleport_session_recording_config" - id := "test_import" - name := r + "." + id + name := r + "." + "session_recording_config" sessionrRecordingConfig := &types.SessionRecordingConfigV2{ Metadata: types.Metadata{}, @@ -78,16 +73,16 @@ func (s *TerraformSuiteOSS) TestImportSessionRecordingConfig() { err := sessionrRecordingConfig.CheckAndSetDefaults() require.NoError(s.T(), err) - recordingConfigBefore, err := s.client.GetSessionRecordingConfig(ctx) + recordingConfigBefore, err := s.client.GetSessionRecordingConfig(s.T().Context()) require.NoError(s.T(), err) - _, err = s.client.ClusterConfigClient().UpsertSessionRecordingConfig(ctx, &clusterconfigv1.UpsertSessionRecordingConfigRequest{ + _, err = s.client.ClusterConfigClient().UpsertSessionRecordingConfig(s.T().Context(), &clusterconfigv1.UpsertSessionRecordingConfigRequest{ SessionRecordingConfig: sessionrRecordingConfig, }) require.NoError(s.T(), err) require.Eventually(s.T(), func() bool { - recordingConfigCurrent, err := s.client.GetSessionRecordingConfig(ctx) + recordingConfigCurrent, err := s.client.GetSessionRecordingConfig(s.T().Context()) require.NoError(s.T(), err) return recordingConfigBefore.GetMetadata().Revision != recordingConfigCurrent.GetMetadata().Revision @@ -97,10 +92,10 @@ func (s *TerraformSuiteOSS) TestImportSessionRecordingConfig() { ProtoV6ProviderFactories: s.terraformProviders, Steps: []resource.TestStep{ { - Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`, + Config: s.terraformConfig + "\n" + `resource "` + r + `" "session_recording_config" { }`, ResourceName: name, ImportState: true, - ImportStateId: id, + ImportStateId: "session_recording_config", ImportStateCheck: func(state []*terraform.InstanceState) error { require.Equal(s.T(), "session_recording_config", state[0].Attributes["kind"]) require.Equal(s.T(), "off", state[0].Attributes["spec.mode"]) From 3ff50a057af36540a1c001601c8765cb5ceaff3c Mon Sep 17 00:00:00 2001 From: Alan Parra <12500300+codingllama@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:34:22 +0000 Subject: [PATCH 273/347] Fix improper userca.pem concatenation by kinitProvider (#69582) * Test improper concatenation of userca.pem * Fix improper PEM concatenation * Use string-writing methods --- lib/srv/db/common/kerberos/kinit/kinit.go | 17 +- .../db/common/kerberos/kinit/kinit_test.go | 145 ++++++++++++++++++ 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/lib/srv/db/common/kerberos/kinit/kinit.go b/lib/srv/db/common/kerberos/kinit/kinit.go index ff5d1316bb0c..16eee55517c1 100644 --- a/lib/srv/db/common/kerberos/kinit/kinit.go +++ b/lib/srv/db/common/kerberos/kinit/kinit.go @@ -156,7 +156,7 @@ type dbCertGetter struct { type getCertificateResult struct { certPEM []byte keyPEM []byte - caCert []byte + caCerts [][]byte sidLookupError error } @@ -189,7 +189,7 @@ func (d *dbCertGetter) getCertificate(ctx context.Context, username string) (*ge return &getCertificateResult{ certPEM: certPEM, keyPEM: keyPEM, - caCert: bytes.Join(caCerts, []byte("\n")), + caCerts: caCerts, sidLookupError: sidLookupError, }, nil } @@ -232,7 +232,7 @@ func (k *kinitProvider) CreateClient(ctx context.Context, username string) (*cli return nil, trace.Wrap(err) } - err = os.WriteFile(userCAPath, k.buildAnchorsFileContents(certResult.caCert), 0600) + err = os.WriteFile(userCAPath, k.buildAnchorsFileContents(certResult.caCerts), 0600) if err != nil { return nil, trace.Wrap(err) } @@ -281,6 +281,13 @@ func (k *kinitProvider) CreateClient(ctx context.Context, username string) (*cli // buildAnchorsFileContents generates the contents of the anchors file (pkinit). // The file must contain the Teleport DB CA and the KDB/LDAP CA, otherwise the // connections will fail. -func (k *kinitProvider) buildAnchorsFileContents(caBytes []byte) []byte { - return append(caBytes, []byte(k.ldapCertificatePEM)...) +func (k *kinitProvider) buildAnchorsFileContents(caCerts [][]byte) []byte { + var buf bytes.Buffer + for _, pem := range caCerts { + buf.Write(bytes.TrimSpace(pem)) + buf.WriteRune('\n') + } + buf.WriteString(strings.TrimSpace(k.ldapCertificatePEM)) + buf.WriteRune('\n') + return buf.Bytes() } diff --git a/lib/srv/db/common/kerberos/kinit/kinit_test.go b/lib/srv/db/common/kerberos/kinit/kinit_test.go index bbb94c18e347..ff8d38c5a608 100644 --- a/lib/srv/db/common/kerberos/kinit/kinit_test.go +++ b/lib/srv/db/common/kerberos/kinit/kinit_test.go @@ -19,19 +19,28 @@ package kinit import ( + "bytes" "context" _ "embed" + "encoding/pem" + "flag" + "fmt" "log/slog" "os" + "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/gravitational/trace" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/tlsutils" "github.com/gravitational/teleport/lib/fixtures" + subcaenv "github.com/gravitational/teleport/lib/subca/testenv" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils/log/logtest" "github.com/gravitational/teleport/lib/winpki" ) @@ -137,7 +146,143 @@ func TestUseOrCreateCredentials(t *testing.T) { } }) } +} + +// TestKinitProvider_CreateClient_multipleUserCAs tests a bug where having +// multiple caCerts would generate an invalid userca.pem file. +func TestKinitProvider_CreateClient_multipleUserCAs(t *testing.T) { + t.Parallel() + + const chainLength = 3 + caChain, err := subcaenv.MakeCAChain(chainLength, nil) + require.NoError(t, err) + + wantCADERs := make([][]byte, 0, chainLength) // intermediates + ldapCert + caCerts := make([][]byte, 0, chainLength-1) + for _, ca := range caChain[1:] { + caCerts = append(caCerts, bytes.TrimSpace(ca.CertPEM)) + wantCADERs = append(wantCADERs, ca.Cert.Raw) + } + + ldapCertPEM := fixtures.TLSCACertPEM + ldapCert, err := tlsutils.ParseCertificatePEM([]byte(ldapCertPEM)) + require.NoError(t, err) + wantCADERs = append(wantCADERs, ldapCert.Raw) + + auth := struct{ winpki.AuthInterface }{} + provider, err := newKinitProvider( + logtest.NewLogger(), + auth, // Auth is skipped by the faked certGetter. + types.AD{ + // LDAPCert must be present (and valid) for the test to work. + // Other values just need to satisfy newKinitProvider. + Domain: "example.com", + KDCHostName: "host.example.com", + LDAPCert: ldapCertPEM, + LDAPServiceAccountName: "DOMAIN\\test-user", + LDAPServiceAccountSID: "S-1-5-21-2191801808-3167526388-2669316733-1104", + }) + require.NoError(t, err) + provider.certGetter = &fakeCertGetter{ + result: &getCertificateResult{ + certPEM: []byte(`insert cert pem here`), + keyPEM: []byte(`insert key pem here`), + caCerts: caCerts, + }, + } + runner := &parseUserCAsRunner{} + provider.runner = runner + + const username = "alice" + _, err = provider.CreateClient(context.Background(), username) + require.NoError(t, err) + + // Verify anchors. + if diff := cmp.Diff(wantCADERs, runner.anchorsDER); diff != "" { + t.Errorf("Method mismatch (-want +got)\n%s", diff) + } +} + +type fakeCertGetter struct { + result *getCertificateResult + err error +} + +func (f *fakeCertGetter) getCertificate(ctx context.Context, username string) (*getCertificateResult, error) { + return f.result, f.err +} + +type stringsValue []string + +func (s *stringsValue) Set(val string) error { + *s = append(*s, val) + return nil +} + +func (s *stringsValue) String() string { + return strings.Join(*s, " ") +} + +type parseUserCAsRunner struct { + anchorsDER [][]byte +} + +func (r *parseUserCAsRunner) runCommand( + ctx context.Context, + env map[string]string, + command string, + args ...string, +) (string, error) { + var xFlag stringsValue + fs := flag.NewFlagSet("", flag.ContinueOnError) + cachePath := fs.String("c", "", "") + fs.Var(&xFlag, "X", "") + + if err := fs.Parse(args); err != nil { + return "", fmt.Errorf("parse args: %w", err) + } + + const anchorsPrefix = "X509_anchors=FILE:" + var anchorsFile string + for _, x := range xFlag { + if strings.HasPrefix(x, anchorsPrefix) { + anchorsFile = x[len(anchorsPrefix):] + break + } + } + if anchorsFile == "" { + return "", fmt.Errorf("anchors not informed (-X %spath", anchorsPrefix) + } + anchorsPEM, err := os.ReadFile(anchorsFile) + if err != nil { + return "", fmt.Errorf("read anchors: %w", err) + } + + // Do an explicit check for the improper concatenation. + const badLine = "-----END CERTIFICATE----------BEGIN CERTIFICATE-----\n" + for line := range bytes.Lines(anchorsPEM) { + if string(line) == badLine { + return "", fmt.Errorf("found poorly concatenated PEMs in anchors file, data=[%s]", anchorsPEM) + } + } + // Parse anchors PEMs. + for pems := anchorsPEM; true; { + block, rest := pem.Decode(pems) + if block == nil { + return "", fmt.Errorf("failed to decode PEM, data=[%s]", rest) + } + r.anchorsDER = append(r.anchorsDER, block.Bytes) + pems = rest + if len(pems) == 0 { + break + } + } + + if err := os.WriteFile(*cachePath, validCacheData, 0600); err != nil { + return "", fmt.Errorf("write cache data: %w", err) + } + return "", nil } const ( From 8c22cf3db28c24a35f42cdbe9ff3783bf525ce2b Mon Sep 17 00:00:00 2001 From: Angel Dionisio Date: Wed, 12 Aug 2026 18:51:55 +0000 Subject: [PATCH 274/347] mark bloat binary builds as dev builds so they do not require keys (#69609) --- .github/workflows/bloat.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bloat.yaml b/.github/workflows/bloat.yaml index 85749e8f3759..bd1aa618ae51 100644 --- a/.github/workflows/bloat.yaml +++ b/.github/workflows/bloat.yaml @@ -82,7 +82,7 @@ jobs: name: Build base id: build_base run: | - make WEBASSETS_SKIP_BUILD=1 BUILDDIR=base_build binaries + TELEPORT_UPDATE_DEV_BUILD=1 make WEBASSETS_SKIP_BUILD=1 BUILDDIR=base_build binaries cd .github/shared-workflows/bot && go run main.go -workflow=binary-sizes --artifacts="tbot,tctl,teleport,tsh,teleport-update,fdpass-teleport,sessionhelper" --builddir="../../../base_build" -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ env.REVIEWERS }}" > ~/teleport_base_build_stats echo "base_stats_file=~/teleport_base_build_stats" >> $GITHUB_OUTPUT echo "base_stats=$(cat ~/teleport_base_build_stats)" >> $GITHUB_ENV @@ -117,7 +117,7 @@ jobs: - name: Build Binaries id: build_branch run: | - BUILD_SECRET=FAKE_SECRET make WEBASSETS_SKIP_BUILD=1 binaries + BUILD_SECRET=FAKE_SECRET TELEPORT_UPDATE_DEV_BUILD=1 make WEBASSETS_SKIP_BUILD=1 binaries - name: Check for Environment Leak id: check_branch_env_leak From b8792db7787b845addaf7a4b38fec3739d7bcefa Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Wed, 12 Aug 2026 19:12:01 +0000 Subject: [PATCH 275/347] tfgen: support generating top-level access list scope field (#69337) --- ...tGenerate_AccessListMemberWithScope.golden | 19 ++++++++ .../TestGenerate_AccessListWithScope.golden | 31 +++++++++++++ lib/tfgen/tfgen.go | 8 ++++ lib/tfgen/tfgen_test.go | 46 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 lib/tfgen/testdata/TestGenerate_AccessListMemberWithScope.golden create mode 100644 lib/tfgen/testdata/TestGenerate_AccessListWithScope.golden diff --git a/lib/tfgen/testdata/TestGenerate_AccessListMemberWithScope.golden b/lib/tfgen/testdata/TestGenerate_AccessListMemberWithScope.golden new file mode 100644 index 000000000000..fe05fa08c492 --- /dev/null +++ b/lib/tfgen/testdata/TestGenerate_AccessListMemberWithScope.golden @@ -0,0 +1,19 @@ +resource "teleport_access_list_member" "some-member" { + scope = "/some/member-scope" + header = { + kind = "access_list_member" + version = "v1" + metadata = { + name = "some-member" + } + } + + spec = { + access_list = "some-id" + name = "some-member" + reason = "some reason" + added_by = "admin" + ineligible_status = "0" + membership_kind = "1" + } +} diff --git a/lib/tfgen/testdata/TestGenerate_AccessListWithScope.golden b/lib/tfgen/testdata/TestGenerate_AccessListWithScope.golden new file mode 100644 index 000000000000..51a17fff87e2 --- /dev/null +++ b/lib/tfgen/testdata/TestGenerate_AccessListWithScope.golden @@ -0,0 +1,31 @@ +resource "teleport_access_list" "some-access-list" { + scope = "/some/scope" + header = { + kind = "access_list" + version = "v1" + metadata = { + name = "some-access-list" + } + } + + spec = { + description = "An example scoped access list" + owners = [{ + description = "some description" + ineligible_status = "0" + membership_kind = "1" + name = "llama" + }] + audit = { + next_audit_date = "2023-02-02T00:00:00Z" + recurrence = { + frequency = "3" + day_of_month = "1" + } + notifications = { + start = "336h0m0s" + } + } + title = "My Access List" + } +} diff --git a/lib/tfgen/tfgen.go b/lib/tfgen/tfgen.go index 563ae6a8e75f..e234b8a8f112 100644 --- a/lib/tfgen/tfgen.go +++ b/lib/tfgen/tfgen.go @@ -208,6 +208,14 @@ func generateResource( // a header field, while other resources expect these fields as top-level fields. _, hasHeaderField := resource.(*headerResourceWrapper) if hasHeaderField { + // Access list resources have top level scope field: + scope := msg.AttributeNamed("scope") + if scope != nil && !opts.fieldsToOmit["scope"] { + tokens := valueToTokens(fieldPath{"scope"}, scope.Value, opts, false /* emitZeroVal */) + if tokens != nil { + resourceBlock.Body().SetAttributeRaw("scope", tokens) + } + } if header := msg.AttributeNamed("header"); header != nil && !opts.fieldsToOmit["header"] { tokens := messageToTokens(fieldPath{"header"}, header.Value, opts) if tokens != nil { diff --git a/lib/tfgen/tfgen_test.go b/lib/tfgen/tfgen_test.go index 879c89529890..2114de45eebc 100644 --- a/lib/tfgen/tfgen_test.go +++ b/lib/tfgen/tfgen_test.go @@ -499,3 +499,49 @@ func goldenTest(t *testing.T, resource tfgen.Resource, opts ...tfgen.GenerateOpt ), ) } + +func TestGenerate_AccessListWithScope(t *testing.T) { + t.Parallel() + + al, err := accesslist.NewAccessListWithScope( + header.Metadata{Name: "some-access-list"}, + accesslist.Spec{ + Title: "My Access List", + Description: "An example scoped access list", + Owners: []accesslist.Owner{ + {Name: "llama", Description: "some description"}, + }, + Audit: accesslist.Audit{ + NextAuditDate: time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), + Recurrence: accesslist.Recurrence{ + Frequency: accesslist.ThreeMonths, + DayOfMonth: accesslist.FirstDayOfMonth, + }, + }, + }, + "/some/scope", + ) + require.NoError(t, err) + + alProto := tfgen.WrapHeaderResource(accesslistconv.ToProto(al)) + goldenTest(t, alProto) +} + +func TestGenerate_AccessListMemberWithScope(t *testing.T) { + t.Parallel() + + member, err := accesslist.NewAccessListMemberWithScope( + header.Metadata{Name: "some-member"}, + accesslist.AccessListMemberSpec{ + AccessList: "some-id", + Name: "some-member", + Reason: "some reason", + AddedBy: "admin", + }, + "/some/member-scope", + ) + require.NoError(t, err) + + memProto := tfgen.WrapHeaderResource(accesslistconv.ToMemberProto(member)) + goldenTest(t, memProto) +} From 9c1fc271b5778e78451d9f8d6e70924511a36e67 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Wed, 12 Aug 2026 21:24:08 +0000 Subject: [PATCH 276/347] Remove Firestore key migration (#69590) In preparation for 19.0.0 this removes all references to legacy or broken record keys. The background key conversion will have already converted all legacy or broken keys thus allowing the removal of the migration and the key types. --- lib/backend/firestore/firestorebk.go | 160 ++-------------- lib/backend/firestore/firestorebk_test.go | 212 ---------------------- lib/backend/firestore/migration.go | 166 ----------------- 3 files changed, 11 insertions(+), 527 deletions(-) delete mode 100644 lib/backend/firestore/migration.go diff --git a/lib/backend/firestore/firestorebk.go b/lib/backend/firestore/firestorebk.go index f40f51471feb..ae984c1f4191 100644 --- a/lib/backend/firestore/firestorebk.go +++ b/lib/backend/firestore/firestorebk.go @@ -49,8 +49,6 @@ import ( "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/itertools/stream" - "github.com/gravitational/teleport/lib/utils/interval" ) func init() { @@ -155,34 +153,6 @@ func (r *record) updates() []firestore.Update { } } -// legacyRecord is an older version of record used to marshal backend.Items. -// The only difference is the Value field: string (legacy) vs []byte (new). -// -// Firestore encoder enforces string fields to be valid UTF-8, which Go does -// not. Some data we store have binary values. -// Firestore decoder will not transparently unmarshal string records into -// []byte fields for us, so we have to do it manually. -// See newRecordFromDoc below. -type legacyRecord struct { - Key string `firestore:"key,omitempty"` - Timestamp int64 `firestore:"timestamp,omitempty"` - Expires int64 `firestore:"expires,omitempty"` - Value string `firestore:"value,omitempty"` -} - -type brokenKey []byte - -// brokenRecord is an incorrect version of record used to marshal backend.Items. -// The Key type was inadvertently changed from a []byte to backend.Key which -// causes problems reading existing data prior to the conversion. -type brokenRecord struct { - Key brokenKey `firestore:"key,omitempty"` - Timestamp int64 `firestore:"timestamp,omitempty"` - Expires int64 `firestore:"expires,omitempty"` - Value []byte `firestore:"value,omitempty"` - RevisionV2 string `firestore:"revision,omitempty"` -} - func newRecord(from backend.Item, clock clockwork.Clock) record { r := record{ Key: []byte(from.Key.String()), @@ -202,46 +172,10 @@ func newRecord(from backend.Item, clock clockwork.Clock) record { return r } -// TODO(tigrato|rosstimothy): Simplify this function by removing the brokenRecord and legacyRecord struct in 19.0.0 func newRecordFromDoc(doc *firestore.DocumentSnapshot) (*record, error) { - k, err := doc.DataAt(keyDocProperty) - if err != nil { - return nil, ConvertGRPCError(err) - } - var r record - switch k.(type) { - case []any: - // If the key is a slice of any, then the key was mistakenly persisted - // as a backend.Key directly. - var br brokenRecord - if err := doc.DataTo(&br); err != nil { - return nil, ConvertGRPCError(err) - } - - r = record{ - Key: br.Key, - Value: br.Value, - Timestamp: br.Timestamp, - Expires: br.Expires, - RevisionV2: br.RevisionV2, - } - default: - if err := doc.DataTo(&r); err != nil { - // If unmarshal failed, try using the old format of records, where - // Value was a string. This document could've been written by an older - // version of our code. - var rl legacyRecord - if legacyErr := doc.DataTo(&rl); legacyErr != nil { - return nil, trace.NewAggregate(ConvertGRPCError(err), ConvertGRPCError(legacyErr)) - } - r = record{ - Key: []byte(rl.Key), - Value: []byte(rl.Value), - Timestamp: rl.Timestamp, - Expires: rl.Expires, - } - } + if err := doc.DataTo(&r); err != nil { + return nil, ConvertGRPCError(err) } r.snapShot = doc @@ -439,26 +373,6 @@ func New(ctx context.Context, params backend.Params, options Options) (*Backend, go RetryingAsyncFunctionRunner(b.clientContext, linearConfig, b.logger.With("task_name", "purged_expired_documents"), b.purgeExpiredDocuments) } - // Migrate incorrect key types to the correct type. - // TODO(tigrato|rosstimothy): DELETE in 19.0.0 - go func() { - migrationInterval := interval.New(interval.Config{ - Duration: time.Hour * 12, - FirstDuration: retryutils.FullJitter(time.Minute * 5), - Jitter: retryutils.SeventhJitter, - Clock: b.clock, - }) - defer migrationInterval.Stop() - for { - select { - case <-migrationInterval.Next(): - b.migrateIncorrectKeyTypes() - case <-b.clientContext.Done(): - return - } - } - }() - l.InfoContext(b.clientContext, "Backend created.") return b, nil @@ -525,28 +439,11 @@ func (b *Backend) getRangeDocs(ctx context.Context, startKey, endKey backend.Key if err != nil { return nil, trace.Wrap(err) } - legacyDocs, err := b.svc.Collection(b.CollectionName). - Where(keyDocProperty, ">=", startKey.String()). - Where(keyDocProperty, "<=", endKey.String()). - Limit(limit). - Documents(ctx).GetAll() - if err != nil { - return nil, trace.Wrap(err) - } - brokenDocs, err := b.svc.Collection(b.CollectionName). - Where(keyDocProperty, ">=", brokenKey(startKey.String())). - Where(keyDocProperty, "<=", brokenKey(endKey.String())). - Limit(limit). - Documents(ctx).GetAll() - if err != nil { - return nil, trace.Wrap(err) - } - allDocs := append(append(docs, legacyDocs...), brokenDocs...) - if len(allDocs) >= backend.DefaultRangeLimit { + if len(docs) >= backend.DefaultRangeLimit { b.logger.WarnContext(ctx, "Range query hit backend limit. (this is a bug!)", "start_key", startKey, "limit", backend.DefaultRangeLimit) } - return allDocs, nil + return docs, nil } func (b *Backend) Items(ctx context.Context, params backend.ItemsParams) iter.Seq2[backend.Item, error] { @@ -577,7 +474,13 @@ func (b *Backend) Items(ctx context.Context, params backend.ItemsParams) iter.Se } }() - for r, err := range b.mergedRecords(ctx, params.StartKey.String(), params.EndKey.String(), limit, sort) { + snaps := records(b.svc.Collection(b.CollectionName). + Where(keyDocProperty, ">=", []byte(params.StartKey.String())). + Where(keyDocProperty, "<=", []byte(params.EndKey.String())). + Limit(limit). + OrderBy(keyDocProperty, sort). + Documents(ctx)) + for r, err := range snaps { if err != nil { yield(backend.Item{}, trace.Wrap(err)) return @@ -619,47 +522,6 @@ func (b *Backend) Items(ctx context.Context, params backend.ItemsParams) iter.Se } } -// mergedRecords returns an iterator that aggregates all items in the collection -// in the desired order. This is required because over the years the key has been -// stored in three formats. In order to iterate over all keys in the collection in the -// correct order, documents with keys of all formats need to be considered. -// -// TODO(tross|tigrato): DELETE IN V19.0.0 with the background migration -func (b *Backend) mergedRecords(ctx context.Context, startKey, endKey string, limit int, sort firestore.Direction) iter.Seq2[*record, error] { - return func(yield func(*record, error) bool) { - snaps := records(b.svc.Collection(b.CollectionName). - Where(keyDocProperty, ">=", []byte(startKey)). - Where(keyDocProperty, "<=", []byte(endKey)). - Limit(limit). - OrderBy(keyDocProperty, sort). - Documents(ctx)) - - legacySnaps := records(b.svc.Collection(b.CollectionName). - Where(keyDocProperty, ">=", startKey). - Where(keyDocProperty, "<=", endKey). - Limit(limit). - OrderBy(keyDocProperty, sort). - Documents(ctx)) - - brokenSnaps := records(b.svc.Collection(b.CollectionName). - Where(keyDocProperty, ">=", brokenKey(startKey)). - Where(keyDocProperty, "<=", brokenKey(endKey)). - Limit(limit). - OrderBy(keyDocProperty, sort). - Documents(ctx)) - - less := func(a, b *record) bool { - return bytes.Compare(a.Key, b.Key) < 0 - } - - for snap, err := range stream.MergeStreams(stream.MergeStreams(brokenSnaps, legacySnaps, less), snaps, less) { - if !yield(snap, err) || err != nil { - return - } - } - } -} - func records(iter *firestore.DocumentIterator) iter.Seq2[*record, error] { return func(yield func(*record, error) bool) { defer iter.Stop() diff --git a/lib/backend/firestore/firestorebk_test.go b/lib/backend/firestore/firestorebk_test.go index d1400f198b85..fd7953411035 100644 --- a/lib/backend/firestore/firestorebk_test.go +++ b/lib/backend/firestore/firestorebk_test.go @@ -20,7 +20,6 @@ package firestore import ( "context" - "encoding/base64" "errors" "fmt" "log/slog" @@ -38,7 +37,6 @@ import ( "cloud.google.com/go/firestore/apiv1/firestorepb" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/api/option" "google.golang.org/genproto/googleapis/rpc/code" @@ -166,155 +164,6 @@ func TestFirestoreDB(t *testing.T) { test.RunBackendComplianceSuite(t, newBackend) } -// newBackend creates a self-closing firestore backend -func newBackend(t *testing.T, cfg map[string]any) *Backend { - clock := clockwork.NewFakeClock() - - uut, err := New(context.Background(), cfg, Options{Clock: clock}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, uut.Close()) }) - - return uut -} - -func TestReadLegacyRecord(t *testing.T) { - cfg := firestoreParams() - ensureTestsEnabled(t) - ensureEmulatorRunning(t, cfg) - - uut := newBackend(t, cfg) - - item := backend.Item{ - Key: backend.NewKey("legacy-record"), - Value: []byte("foo"), - Expires: uut.clock.Now().Add(time.Minute).Round(time.Second).UTC(), - } - - // Write using legacy record format, emulating data written by an older - // version of this backend. - ctx := context.Background() - rl := legacyRecord{ - Key: item.Key.String(), - Value: string(item.Value), - Expires: item.Expires.UTC().Unix(), - Timestamp: uut.clock.Now().UTC().Unix(), - } - _, err := uut.svc.Collection(uut.CollectionName).Doc(uut.keyToDocumentID(item.Key)).Set(ctx, rl) - require.NoError(t, err) - - // Read the data back and make sure it matches the original item. - got, err := uut.Get(ctx, item.Key) - require.NoError(t, err) - require.Equal(t, item.Key, got.Key) - require.Equal(t, item.Value, got.Value) - require.Equal(t, item.Expires, got.Expires) - - // Read the data back using a range query too. - gotRange, err := uut.GetRange(ctx, item.Key, item.Key, 1) - require.NoError(t, err) - require.Len(t, gotRange.Items, 1) - - got = &gotRange.Items[0] - require.Equal(t, item.Key, got.Key) - require.Equal(t, item.Value, got.Value) - require.Equal(t, item.Expires, got.Expires) -} - -func TestReadBrokenRecord(t *testing.T) { - cfg := firestoreParams() - ensureTestsEnabled(t) - ensureEmulatorRunning(t, cfg) - - uut := newBackend(t, cfg) - - ctx := context.Background() - - prefix := test.MakePrefix() - - // Create a valid record with the correct key type. - item := backend.Item{ - Key: prefix("valid-record"), - Value: []byte("llamas"), - } - _, err := uut.Put(ctx, item) - require.NoError(t, err) - - // Create a legacy record with a string key type. - lr := legacyRecord{ - Key: prefix("legacy-record").String(), - Value: "sheep", - } - _, err = uut.svc.Collection(uut.CollectionName).Doc(uut.keyToDocumentID(backend.KeyFromString(lr.Key))).Set(ctx, lr) - require.NoError(t, err) - - // Create a broken record with a backend.Key key type. - brokenItem := backend.Item{ - Key: prefix("broken-record"), - Value: []byte("foo"), - Expires: uut.clock.Now().Add(time.Minute).Round(time.Second).UTC(), - } - - // Write using broken record format, emulating data written by an older - // version of this backend. - br := brokenRecord{ - Key: brokenKey(brokenItem.Key.String()), - Value: brokenItem.Value, - Expires: brokenItem.Expires.UTC().Unix(), - Timestamp: uut.clock.Now().UTC().Unix(), - } - _, err = uut.svc.Collection(uut.CollectionName).Doc(uut.keyToDocumentID(brokenItem.Key)).Set(ctx, br) - require.NoError(t, err) - - // Read the data back and make sure it matches the original item. - got, err := uut.Get(ctx, brokenItem.Key) - require.NoError(t, err) - require.Equal(t, brokenItem.Key, got.Key) - require.Equal(t, brokenItem.Value, got.Value) - require.Equal(t, brokenItem.Expires, got.Expires) - - // Read the data back using a range query too. - gotRange, err := uut.GetRange(ctx, brokenItem.Key, brokenItem.Key, 1) - require.NoError(t, err) - require.Len(t, gotRange.Items, 1) - - got = &gotRange.Items[0] - require.Equal(t, brokenItem.Key, got.Key) - require.Equal(t, brokenItem.Value, got.Value) - require.Equal(t, brokenItem.Expires, got.Expires) - - // Retrieve the entire key range to validate that there are no duplicate records - results, err := uut.GetRange(ctx, prefix(""), backend.RangeEnd(prefix("")), 5) - require.NoError(t, err) - - require.Len(t, results.Items, 3) - - for _, result := range results.Items { - switch r := result.Key.String(); r { - case item.Key.String(): - assert.Equal(t, item.Value, result.Value) - case string(br.Key): - assert.Equal(t, br.Value, result.Value) - case lr.Key: - assert.Equal(t, lr.Value, string(result.Value)) - default: - t.Errorf("GetRange returned unexpected item key %s", r) - } - } - - // Update the value and ensure that it's set to the correct key value - item.Value = []byte("llama") - _, err = uut.Update(ctx, item) - require.NoError(t, err) - - doc, err := uut.svc.Collection(uut.CollectionName).Doc(uut.keyToDocumentID(item.Key)).Get(ctx) - require.NoError(t, err) - - var r record - require.NoError(t, doc.DataTo(&r)) - require.Equal(t, []byte(item.Key.String()), r.Key) - require.Equal(t, item.Value, r.Value) -} - type mockFirestoreServer struct { // Embed for forward compatibility. // Tests will keep working if more methods are added @@ -461,64 +310,3 @@ func TestDeleteDocuments(t *testing.T) { } } - -// TestFirestoreMigration tests the migration of incorrect key types in Firestore. -// TODO(tigrato|rosstimothy): DELETE In 19.0.0: Remove this migration in 19.0.0. -func TestFirestoreMigration(t *testing.T) { - cfg := firestoreParams() - ensureTestsEnabled(t) - ensureEmulatorRunning(t, cfg) - - clock := clockwork.NewRealClock() - - uut, err := New(context.Background(), cfg, Options{Clock: clock}) - require.NoError(t, err) - - // Empty the collection to make sure previous tests don't interfere - snapshot, err := uut.svc.Collection(uut.CollectionName).Documents(context.Background()).GetAll() - require.NoError(t, err) - require.NoError(t, uut.deleteDocuments(snapshot)) - - type byteAlias []byte - type badRecord struct { - Key byteAlias `firestore:"key,omitempty"` - Timestamp int64 `firestore:"timestamp,omitempty"` - Expires int64 `firestore:"expires,omitempty"` - Value []byte `firestore:"value,omitempty"` - RevisionV2 string `firestore:"revision,omitempty"` - RevisionV1 string `firestore:"-"` - } - - for i := range 301 { - key := fmt.Appendf(nil, "test-%d", i) - _, err = uut.svc.Collection(uut.CollectionName). - Doc(base64.URLEncoding.EncodeToString(key)). - Set(context.Background(), &badRecord{ - Key: key, - Timestamp: clock.Now().UTC().Unix(), - Expires: clock.Now().Add(time.Minute).UTC().Unix(), - Value: key, - RevisionV2: "v2", - }) - require.NoError(t, err) - } - - // Migrate the collection - uut.migrateIncorrectKeyTypes() - - // Ensure that all incorrect key types have been migrated - docs, err := uut.svc.Collection(uut.CollectionName). - Where(keyDocProperty, ">", byteAlias("/")). - Limit(100). - Documents(context.Background()).GetAll() - require.NoError(t, err) - - require.Empty(t, docs, "expected all incorrect key types to be migrated") - - // Ensure that all incorrect key types have been migrated to the correct key type []byte - docs, err = uut.svc.Collection(uut.CollectionName). - Where(keyDocProperty, ">", []byte("/")). - Documents(context.Background()).GetAll() - require.NoError(t, err) - require.Len(t, docs, 301) -} diff --git a/lib/backend/firestore/migration.go b/lib/backend/firestore/migration.go deleted file mode 100644 index f1569c014331..000000000000 --- a/lib/backend/firestore/migration.go +++ /dev/null @@ -1,166 +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 firestore - -import ( - "context" - "log/slog" - "time" - - "cloud.google.com/go/firestore" - "github.com/gravitational/trace" - - "github.com/gravitational/teleport/lib/backend" -) - -// migrateIncorrectKeyTypes migrates incorrect key types (backend.Key and string) to the correct type (bytes) -// in the backend. This is necessary because the backend was incorrectly storing keys as strings and backend.Key -// types and Firestore clients mapped them to different database types. This forces calling ReadRange 3 times. -// This migration will fix the issue by converting all keys to the correct type (bytes). -// TODO(tigrato|rosstimothy): DELETE In 19.0.0: Remove this migration in 19.0.0. -func (b *Backend) migrateIncorrectKeyTypes() { - var ( - numberOfDocsMigrated int - duration time.Duration - ) - err := backend.RunWhileLocked( - b.clientContext, - backend.RunWhileLockedConfig{ - LockConfiguration: backend.LockConfiguration{ - LockNameComponents: []string{"firestore_migrate_incorrect_key_types"}, - Backend: b, - TTL: 5 * time.Minute, - RetryInterval: time.Minute, - }, - ReleaseCtxTimeout: 10 * time.Second, - RefreshLockInterval: time.Minute, - }, - func(ctx context.Context) error { - start := time.Now() - defer func() { - duration = time.Since(start) - }() - // backend.Key is converted to array of ints when sending to the db. - toArray := func(key []byte) []any { - arrKey := make([]any, len(key)) - for i, b := range key { - arrKey[i] = int(b) - } - return arrKey - } - nDocs, err := migrateKeyType[[]any](ctx, b, toArray) - numberOfDocsMigrated += nDocs - if err != nil { - return trace.Wrap(err, "failed to migrate backend key") - } - - stringKey := func(key []byte) string { - return string(key) - } - nDocs, err = migrateKeyType[string](ctx, b, stringKey) - numberOfDocsMigrated += nDocs - if err != nil { - return trace.Wrap(err, "failed to migrate legacy key") - } - return nil - }) - - entry := b.logger.With( - slog.Duration("duration", duration), - slog.Int("migrated", numberOfDocsMigrated), - ) - if err != nil { - entry.ErrorContext(b.clientContext, "Failed to migrate incorrect key types.", "error", err) - return - } - entry.InfoContext(b.clientContext, "Successfully migrated incorrect key types.") -} - -func migrateKeyType[T any](ctx context.Context, b *Backend, newKey func([]byte) T) (int, error) { - limit := 300 - startKey := newKey([]byte("/")) - - bulkWriter := b.svc.BulkWriter(b.clientContext) - defer bulkWriter.End() - - nDocs := 0 - // handle the migration in batches of 300 documents per second - t := time.NewTimer(time.Second) - defer t.Stop() - for { - - select { - case <-ctx.Done(): - return 0, ctx.Err() - case <-t.C: - } - - docs, err := b.svc.Collection(b.CollectionName). - // passing the key type here forces the client to map the key to the underlying type - // and return all the keys in that share the same underlying type. - // backend.Key is mapped to Array in Firestore. - // []byte is mapped to Bytes in Firestore. - // string is mapped to String in Firestore. - // Searching for keys with the same underlying type will return all keys with the same type. - Where(keyDocProperty, ">", startKey). - Limit(limit). - Documents(ctx).GetAll() - if err != nil { - return nDocs, trace.Wrap(err) - } - - jobs := make([]*firestore.BulkWriterJob, len(docs)) - for i, dbDoc := range docs { - newDoc, err := newRecordFromDoc(dbDoc) - if err != nil { - return nDocs, trace.Wrap(err, "failed to convert document") - } - - // use conditional update to ensure that the document has not been updated since the read - jobs[i], err = bulkWriter.Update( - b.svc.Collection(b.CollectionName). - Doc(b.keyToDocumentID(newDoc.backendItem().Key)), - newDoc.updates(), - firestore.LastUpdateTime(dbDoc.UpdateTime), - ) - if err != nil { - return nDocs, trace.Wrap(err, "failed stream bulk action") - } - - startKey = newKey(newDoc.Key) // update start key - } - - bulkWriter.Flush() // flush the buffer - - for _, job := range jobs { - if _, err := job.Results(); err != nil { - // log the error and continue - b.logger.ErrorContext(ctx, "failed to write bulk action", "error", err) - } - } - - nDocs += len(docs) - if len(docs) < limit { - break - } - - t.Reset(time.Second) - } - return nDocs, nil -} From 3ef4934487d3c9ff88900868e2505491db0ae6b5 Mon Sep 17 00:00:00 2001 From: williamong-tel Date: Wed, 12 Aug 2026 22:10:04 +0000 Subject: [PATCH 277/347] IAC SSH Server Namespacing support (#69542) Update provider.go --- .../data-sources/data-sources.mdx | 1 + .../data-sources/server.mdx | 111 +++++ integrations/terraform/gen/main.go | 20 - .../protoc-gen-terraform-teleport.yaml | 2 + .../legacy/data_source_teleport_server.go | 98 ----- .../provider/internal/legacy/registry.go | 1 - .../legacy/resource_teleport_server.go | 401 ------------------ .../provider/internal/resources/server.go | 83 ++++ .../provider/internal/teleport/server.go | 77 ++++ .../provider/internal/tfdriver/normalize.go | 19 +- .../internal/tfdriver/normalize_test.go | 12 + integrations/terraform/provider/provider.go | 2 + .../server_openssh_scoped_and_unscoped.tf | 26 ++ integrations/terraform/testlib/server_test.go | 102 ++++- .../terraform/tfschema/types_terraform.go | 2 +- 15 files changed, 412 insertions(+), 545 deletions(-) create mode 100644 docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/server.mdx delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_server.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_server.go create mode 100644 integrations/terraform/provider/internal/resources/server.go create mode 100644 integrations/terraform/provider/internal/teleport/server.go create mode 100644 integrations/terraform/testlib/fixtures/server_openssh_scoped_and_unscoped.tf diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx index e22836883b30..c26e16f3c54c 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx @@ -45,6 +45,7 @@ The Teleport Terraform provider supports the following data-sources: - [`teleport_scoped_role`](./scoped_role.mdx) - [`teleport_scoped_role_assignment`](./scoped_role_assignment.mdx) - [`teleport_scoped_token`](./scoped_token.mdx) + - [`teleport_server`](./server.mdx) - [`teleport_session_recording_config`](./session_recording_config.mdx) - [`teleport_static_host_user`](./static_host_user.mdx) - [`teleport_trusted_cluster`](./trusted_cluster.mdx) diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/server.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/server.mdx new file mode 100644 index 000000000000..cfcd71e9bd11 --- /dev/null +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/server.mdx @@ -0,0 +1,111 @@ +--- +title: Reference for the teleport_server Terraform data-source +sidebar_label: server +description: This page lists the supported values of the teleport_server data-source of the Teleport Terraform provider. +page_type: reference +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + +{/* Disable the outdated name check since data source fields occasionally need +to refer to these. */} +{/* vale 3rd-party-products.former-names = NO */} + +This page lists the supported values of the `teleport_server` data source of the +Teleport Terraform provider. + + + + + +{/* schema generated by tfplugindocs */} +## Schema + +### Required + +- `sub_kind` (String) SubKind is an optional resource sub kind, used in some resources +- `version` (String) Version is version + +### Optional + +- `metadata` (Attributes) Metadata is resource metadata (see [below for nested schema](#nested-schema-for-metadata)) +- `scope` (String) The advertized scope of the server which can not change once assigned. +- `spec` (Attributes) Spec is a server spec (see [below for nested schema](#nested-schema-for-spec)) + +### 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: + +- `addr` (String) Addr is a host:port address where this server can be reached. +- `cloud_metadata` (Attributes) CloudMetadata contains info about the cloud instance the server is running on, if any. (see [below for nested schema](#nested-schema-for-speccloud_metadata)) +- `github` (Attributes) GitHub contains info about GitHub proxies where each server represents a GitHub organization. (see [below for nested schema](#nested-schema-for-specgithub)) +- `hostname` (String) Hostname is server hostname +- `immutable_labels` (Map of String) The immutable labels assigned to the server when joining. The hash of these labels is expected to match the hash included in the server's certificate. +- `peer_addr` (String) PeerAddr is the address a proxy server is reachable at by its peer proxies. +- `proxy_ids` (List of String) ProxyIDs is a list of proxy IDs this server is expected to be connected to. +- `public_addrs` (List of String) PublicAddrs is a list of public addresses where this server can be reached. +- `relay_group` (String) the name of the Relay group that the server is connected to +- `relay_ids` (List of String) the list of Relay host IDs that the server is connected to +- `rotation` (Attributes) Rotation specifies server rotation (see [below for nested schema](#nested-schema-for-specrotation)) +- `use_tunnel` (Boolean) UseTunnel indicates that connections to this server should occur over a reverse tunnel. +- `version` (String) TeleportVersion is the teleport version that the server is running on + +### Nested Schema for `spec.cloud_metadata` + +Optional: + +- `aws` (Attributes) AWSInfo contains attributes to match to an EC2 instance. (see [below for nested schema](#nested-schema-for-speccloud_metadataaws)) + +### Nested Schema for `spec.cloud_metadata.aws` + +Optional: + +- `account_id` (String) AccountID is an AWS account ID. +- `instance_id` (String) InstanceID is an EC2 instance ID. +- `integration` (String) Integration is the integration name that added this Node. When connecting to it, it will use this integration to issue AWS API calls in order to set up the connection. This includes sending an SSH Key and then opening a tunnel (EC2 Instance Connect Endpoint) so Teleport can connect to it. +- `region` (String) Region is the AWS EC2 Instance Region. +- `subnet_id` (String) SubnetID is the Subnet ID in use by the instance. +- `vpc_id` (String) VPCID is the AWS VPC ID where the Instance is running. + + + +### Nested Schema for `spec.github` + +Optional: + +- `integration` (String) Integration is the integration that is associated with this Server. +- `organization` (String) Organization specifies the name of the organization for the GitHub integration. + + +### Nested Schema for `spec.rotation` + +Optional: + +- `current_id` (String) CurrentID is the ID of the rotation operation to differentiate between rotation attempts. +- `grace_period` (String) GracePeriod is a period during which old and new CA are valid for checking purposes, but only new CA is issuing certificates. +- `last_rotated` (String) LastRotated specifies the last time of the completed rotation. +- `mode` (String) Mode sets manual or automatic rotation mode. +- `phase` (String) Phase is the current rotation phase. +- `schedule` (Attributes) Schedule is a rotation schedule - used in automatic mode to switch between phases. (see [below for nested schema](#nested-schema-for-specrotationschedule)) +- `started` (String) Started is set to the time when rotation has been started in case if the state of the rotation is "in_progress". +- `state` (String) State could be one of "init" or "in_progress". + +### Nested Schema for `spec.rotation.schedule` + +Optional: + +- `standby` (String) Standby specifies time to switch to the "Standby" phase. +- `update_clients` (String) UpdateClients specifies time to switch to the "Update clients" phase +- `update_servers` (String) UpdateServers specifies time to switch to the "Update servers" phase. + diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 67c275f4739a..403c1f022a50 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -367,24 +367,6 @@ var ( GetCanReturnNil: true, } - server = payload{ - Name: "Server", - TypeName: "ServerV2", - VarName: "server", - GetMethod: "GetNode", - CreateMethod: "UpsertNode", - UpdateMethod: "UpsertNode", - UpsertMethodArity: 2, - DeleteMethod: "DeleteNode", - ID: "server.Metadata.Name", - Kind: "node", - HasStaticID: false, - TerraformResourceType: "teleport_server", - HasCheckAndSetDefaults: true, - Namespaced: true, - ForceSetKind: "apitypes.KindNode", - } - autoUpdateVersion = payload{ Name: "AutoUpdateVersion", TypeName: "AutoUpdateVersion", @@ -729,8 +711,6 @@ func genTFSchema() { generateDataSource(provisionToken, pluralDataSource) generateResource(uiConfig, singularResource) generateDataSource(uiConfig, singularDataSource) - generateResource(server, pluralResource) - generateDataSource(server, pluralDataSource) generateResource(autoUpdateVersion, singularResource) generateDataSource(autoUpdateVersion, singularDataSource) generateResource(autoUpdateConfig, singularResource) diff --git a/integrations/terraform/protoc-gen-terraform-teleport.yaml b/integrations/terraform/protoc-gen-terraform-teleport.yaml index d3b8600cafee..8c8727d6cccb 100644 --- a/integrations/terraform/protoc-gen-terraform-teleport.yaml +++ b/integrations/terraform/protoc-gen-terraform-teleport.yaml @@ -533,6 +533,8 @@ plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" KubernetesClusterV3.scope: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + ServerV2.scope: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" ServerV2.Metadata.Name: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_server.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_server.go deleted file mode 100755 index bd75e64f57d6..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_server.go +++ /dev/null @@ -1,98 +0,0 @@ -// 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 legacy - -import ( - "context" - - apitypes "github.com/gravitational/teleport/api/types" - "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/gravitational/teleport/api/defaults" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportServerType is the data source metadata type -type dataSourceTeleportServerType struct{} - -// dataSourceTeleportServer is the resource -type dataSourceTeleportServer struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportServerType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaServerV2(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportServerType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportServer{ - p: p.(Provider), - }, nil -} - -// Read reads teleport Server -func (r dataSourceTeleportServer) 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 - } - - serverI, err := r.p.Client().GetNode(ctx, defaults.Namespace, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - state.Attrs["id"] = id - } - - - server := serverI.(*apitypes.ServerV2) - diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index 35274dde58ab..dfd9de86ee15 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -55,7 +55,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_saml_idp_service_provider": resourceTeleportSAMLIdPServiceProviderType{}, "teleport_ui_config": resourceTeleportUIConfigType{}, "teleport_bot": resourceTeleportBotType{}, - "teleport_server": resourceTeleportServerType{}, "teleport_autoupdate_version": resourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": resourceTeleportAutoUpdateConfigType{}, "teleport_vnet_config": resourceTeleportVnetConfigType{}, diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_server.go b/integrations/terraform/provider/internal/legacy/resource_teleport_server.go deleted file mode 100755 index 89e4977a7331..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_server.go +++ /dev/null @@ -1,401 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - apitypes "github.com/gravitational/teleport/api/types" - - "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/gravitational/teleport/api/defaults" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportServerType is the resource metadata type -type resourceTeleportServerType struct{} - -// resourceTeleportServer is the resource -type resourceTeleportServer struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportServerType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaServerV2(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportServerType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportServer{ - p: p.(Provider), - }, nil -} - -// Create creates the Server -func (r resourceTeleportServer) 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 - } - - server := &apitypes.ServerV2{} - diags = tfschema.CopyServerV2FromTerraform(ctx, plan, server) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - serverResource := server - - serverResource.Kind = apitypes.KindNode - err = serverResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) - return - } - - id := serverResource.Metadata.Name - - _, err = r.p.Client().GetNode(ctx, defaults.Namespace, id) - if !trace.IsNotFound(err) { - if err == nil { - existErr := fmt.Sprintf("Server exists in Teleport. Either remove it (tctl rm node/%v)"+ - " or import it to the existing state (terraform import teleport_server.%v %v)", id, id, id) - - resp.Diagnostics.Append(tfdiag.DiagFromErr("Server exists in Teleport", trace.Errorf(existErr))) - return - } - - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) - return - } - - _, err = r.p.Client().UpsertNode(ctx, serverResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating Server", trace.Wrap(err), "node")) - return - } - - // Not really an inferface, just using the same name for easier templating. - var serverI apitypes.Server - // Try getting the resource until it exists. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - serverI, err = r.p.Client().GetNode(ctx, defaults.Namespace, id) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(ctx.Err()), "node")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Server (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "node") - return - } - continue - } - break - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) - return - } - - serverResource, ok := serverI.(*apitypes.ServerV2) - if !ok { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Errorf("Can not convert %T to ServerV2", serverI), "node")) - return - } - server = serverResource - - diags = tfschema.CopyServerV2ToTerraform(ctx, server, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: server.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport Server -func (r resourceTeleportServer) 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 - } - - serverI, err := r.p.Client().GetNode(ctx, defaults.Namespace, id.Value) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) - return - } - - server := serverI.(*apitypes.ServerV2) - diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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 Server -func (r resourceTeleportServer) 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 - } - - server := &apitypes.ServerV2{} - diags = tfschema.CopyServerV2FromTerraform(ctx, plan, server) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - serverResource := server - - serverResource.Kind = apitypes.KindNode - - if err := serverResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Server", err, "node")) - return - } - name := serverResource.Metadata.Name - - serverBefore, err := r.p.Client().GetNode(ctx, defaults.Namespace, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", err, "node")) - return - } - - _, err = r.p.Client().UpsertNode(ctx, serverResource) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Server", err, "node")) - return - } - - // Not really an inferface, just using the same name for easier templating. - var serverI apitypes.Server - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - serverI, err = r.p.Client().GetNode(ctx, defaults.Namespace, name) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", err, "node")) - return - } - if serverBefore.GetMetadata().Revision != serverI.GetMetadata().Revision || false { - break - } - - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(ctx.Err()), "node")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading Server (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "node") - return - } - } - - serverResource, ok := serverI.(*apitypes.ServerV2) - if !ok { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Errorf("Can not convert %T to ServerV2", serverI), "node")) - return - } - server = serverResource - - diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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 Server -func (r resourceTeleportServer) 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().DeleteNode(ctx, defaults.Namespace, id.Value) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting ServerV2", trace.Wrap(err), "node")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports Server state -func (r resourceTeleportServer) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - server, err := r.p.Client().GetNode(ctx, defaults.Namespace, req.ID) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) - return - } - - - serverResource := server.(*apitypes.ServerV2) - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = tfschema.CopyServerV2ToTerraform(ctx, serverResource, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := serverResource.GetName() - - state.Attrs["id"] = types.String{Value: id} - - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// ModifyPlan modifies the planned value, normalizing null values. -func (r resourceTeleportServer) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { - // If the entire plan is null, the resource is planned for destruction. - if req.Plan.Raw.IsNull() { - return - } - - // If the state is null, the resource is being created. No need to modify plan. - if req.State.Raw.IsNull() { - return - } - - var config types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - server := &apitypes.ServerV2{} - resp.Diagnostics.Append(tfschema.CopyServerV2FromTerraform(ctx, config, server)...) - if resp.Diagnostics.HasError() { - return - } - - serverResource := server - serverResource.Kind = apitypes.KindNode - - if err := serverResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) - return - } - - server = serverResource - - const preserveUnknown = true - resp.Diagnostics.Append(tfschema.CopyServerV2ToTerraformPreserveUnknown(ctx, server, &config, preserveUnknown)...) - if resp.Diagnostics.HasError() { - return - } - - var plan types.Object - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - plan.Attrs["spec"] = config.Attrs["spec"] - - resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) -} diff --git a/integrations/terraform/provider/internal/resources/server.go b/integrations/terraform/provider/internal/resources/server.go new file mode 100644 index 000000000000..29e5a2e60992 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/server.go @@ -0,0 +1,83 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + apitypes "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/scopes" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + "github.com/gravitational/teleport/integrations/terraform/tfschema" +) + +// NewSSHServerDataSourceType returns the SSH server data source type. +func NewSSHServerDataSourceType() tfdriver.DataSourceType[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.DataSourceType[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewServerClient(clientFromProvider(p)) + }, + Kind: apitypes.KindNode, + Codec: tfdriver.DataSourceCodecFuncs[apitypes.ServerV2]{ + SchemaFunc: tfschema.GenSchemaServerV2, + ToStateFunc: tfschema.CopyServerV2ToTerraform, + }, + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath( + tfdriver.ScopeQualifiedPath{ + Name: path.Root("metadata").AtName("name"), + Scope: path.Root("scope"), + }, + ), + } +} + +// NewSSHServerResourceType returns the SSH server resource type. +func NewSSHServerResourceType() tfdriver.ResourceType[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier] { + return tfdriver.ResourceType[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[apitypes.ServerV2, tfdriver.ScopeQualifiedNameIdentifier] { + return teleport.NewServerClient(clientFromProvider(p)) + }, + Kind: apitypes.KindNode, + Codec: tfdriver.ResourceCodecFuncs[apitypes.ServerV2]{ + SchemaFunc: tfschema.GenSchemaServerV2, + ToStateFunc: tfschema.CopyServerV2ToTerraform, + FromPlanFunc: tfschema.CopyServerV2FromTerraform, + }, + Normalizer: tfdriver.ResourceNormalizers[apitypes.ServerV2]{ + tfdriver.ForceKindFunc(func(server *apitypes.ServerV2) { + server.Kind = apitypes.KindNode + }), + tfdriver.CheckAndSetDefaults[apitypes.ServerV2](), + }, + Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy( + tfdriver.ScopeQualifiedPath{ + Name: path.Root("metadata").AtName("name"), + Scope: path.Root("scope"), + }, + func(sv *apitypes.ServerV2) scopes.QualifiedName { + return scopes.QualifiedName{ + Name: sv.GetMetadata().Name, + Scope: sv.GetScope()} + }), + ResourceRevision: func(st *apitypes.ServerV2) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/server.go b/integrations/terraform/provider/internal/teleport/server.go new file mode 100644 index 000000000000..45ce929f4a75 --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/server.go @@ -0,0 +1,77 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewServerClient returns a Teleport client to interact with SSH server resources. +func NewServerClient(c *client.Client) ServerClient { + return ServerClient{client: c} +} + +// ServerClient manages SSH server resources in Teleport. +type ServerClient struct { + client *client.Client +} + +// Get reads an SSH server by scope-qualified name. +func (r ServerClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*types.ServerV2, error) { + server, err := r.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{ + Name: id.Name, + Scope: id.Scope, + }.Build()) + if err != nil { + return nil, trace.Wrap(err) + } + + serverV2, ok := server.(*types.ServerV2) + if !ok { + return nil, trace.BadParameter("unexpected SSH server type: %T", server) + } + + return serverV2, nil +} + +// Create creates an SSH server resource in Teleport. +func (r ServerClient) Create(ctx context.Context, server *types.ServerV2) error { + _, err := r.client.UpsertNode(ctx, server) + return trace.Wrap(err) +} + +// Upsert updates an SSH server resource in Teleport. +func (r ServerClient) Upsert(ctx context.Context, server *types.ServerV2) error { + _, err := r.client.UpsertNode(ctx, server) + return trace.Wrap(err) +} + +// Delete deletes an SSH server resource from Teleport by scope-qualified name. +func (r ServerClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error { + return trace.Wrap(r.client.DeleteSSHServer(ctx, presencev1.DeleteSSHServerRequest_builder{ + Name: id.Name, + Scope: id.Scope, + }.Build())) +} diff --git a/integrations/terraform/provider/internal/tfdriver/normalize.go b/integrations/terraform/provider/internal/tfdriver/normalize.go index da4f7f3a13bb..ba43e2d21ba6 100644 --- a/integrations/terraform/provider/internal/tfdriver/normalize.go +++ b/integrations/terraform/provider/internal/tfdriver/normalize.go @@ -95,7 +95,8 @@ func CheckAndSetDefaults[T any]() ResourceNormalizer[T] { } } -// ForceKind sets a Teleport resource kind on create and update. +// ForceKind sets a Teleport resource kind on create and update using the +// resource's SetKind method. func ForceKind[T any](kind string) ResourceNormalizer[T] { return ResourceNormalizerFuncs[T]{ Create: func(_ context.Context, resource *T) error { @@ -106,7 +107,7 @@ func ForceKind[T any](kind string) ResourceNormalizer[T] { setter.SetKind(kind) return nil }, - Update: func(ctx context.Context, resource *T) error { + Update: func(_ context.Context, resource *T) error { setter, ok := any(resource).(interface{ SetKind(string) }) if !ok { return trace.BadParameter("%T does not implement SetKind", resource) @@ -116,3 +117,17 @@ func ForceKind[T any](kind string) ResourceNormalizer[T] { }, } } + +// ForceKindFunc sets a Teleport resource kind on create and update using a +// caller-supplied setter. +func ForceKindFunc[T any](setKind func(*T)) ResourceNormalizer[T] { + setKindFunc := func(_ context.Context, resource *T) error { + setKind(resource) + return nil + } + + return ResourceNormalizerFuncs[T]{ + Create: setKindFunc, + Update: setKindFunc, + } +} diff --git a/integrations/terraform/provider/internal/tfdriver/normalize_test.go b/integrations/terraform/provider/internal/tfdriver/normalize_test.go index d573382bec9c..b2e0a8be352d 100644 --- a/integrations/terraform/provider/internal/tfdriver/normalize_test.go +++ b/integrations/terraform/provider/internal/tfdriver/normalize_test.go @@ -70,6 +70,18 @@ func TestResourceNormalizersChainsNormalizers(t *testing.T) { require.True(t, resource.defaulted) } +func TestForceKindFuncUsesCallerSuppliedSetter(t *testing.T) { + var resource normalizerTestResource + normalizer := ForceKindFunc[normalizerTestResource](func(resource *normalizerTestResource) { + resource.kind = "test_kind" + }) + require.NoError(t, normalizer.NormalizeCreate(t.Context(), &resource)) + require.Equal(t, "test_kind", resource.kind) + + require.NoError(t, normalizer.NormalizeUpdate(t.Context(), &resource)) + require.Equal(t, "test_kind", resource.kind) +} + func TestResourceNormalizersStopsOnError(t *testing.T) { sentinel := errors.New("stop") normalizers := ResourceNormalizers[normalizerTestResource]{ diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 2f8a3c7f4e6c..22435ce40562 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -585,6 +585,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_scoped_role": resources.NewScopedRoleResourceType(), "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentResourceType(), "teleport_scoped_token": resources.NewScopedTokenResourceType(), + "teleport_server": resources.NewSSHServerResourceType(), "teleport_session_recording_config": resources.NewSessionRecordingConfigResourceType(), "teleport_static_host_user": resources.NewStaticHostUserResourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterResourceType(), @@ -624,6 +625,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_scoped_role": resources.NewScopedRoleDataSourceType(), "teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentDataSourceType(), "teleport_scoped_token": resources.NewScopedTokenDataSourceType(), + "teleport_server": resources.NewSSHServerDataSourceType(), "teleport_session_recording_config": resources.NewSessionRecordingConfigDataSourceType(), "teleport_static_host_user": resources.NewStaticHostUserDataSourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterDataSourceType(), diff --git a/integrations/terraform/testlib/fixtures/server_openssh_scoped_and_unscoped.tf b/integrations/terraform/testlib/fixtures/server_openssh_scoped_and_unscoped.tf new file mode 100644 index 000000000000..90b077165d18 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/server_openssh_scoped_and_unscoped.tf @@ -0,0 +1,26 @@ +resource "teleport_server" "unscoped" { + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test-unscoped" + } + spec = { + addr = "127.0.0.1:22" + hostname = "unscoped.local" + } +} + +resource "teleport_server" "scoped" { + depends_on = [teleport_server.unscoped] + + version = "v2" + sub_kind = "openssh" + scope = "/foo/bar" + metadata = { + name = "test-scoped" + } + spec = { + addr = "127.0.0.1:2222" + hostname = "scoped.local" + } +} diff --git a/integrations/terraform/testlib/server_test.go b/integrations/terraform/testlib/server_test.go index 71c39dc03251..6a9685e68b0c 100644 --- a/integrations/terraform/testlib/server_test.go +++ b/integrations/terraform/testlib/server_test.go @@ -25,16 +25,38 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/client" presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1" "github.com/gravitational/teleport/api/types" ) -func (s *TerraformSuiteOSS) TestOpenSSHServer() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) +func checkSSHServerExists(ctx context.Context, clt *client.Client, scope, name, hostname string) resource.TestCheckFunc { + return func(state *terraform.State) error { + server, err := clt.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{ + Name: name, + Scope: scope, + }.Build()) + if err != nil { + return trace.Wrap(err) + } + serverV2, ok := server.(*types.ServerV2) + if !ok { + return trace.BadParameter("unexpected SSH server type: %T", server) + } + if got := serverV2.GetScope(); got != scope { + return trace.CompareFailed("SSH server scope mismatch: got %q, want %q", got, scope) + } + if got := serverV2.GetHostname(); got != hostname { + return trace.CompareFailed("SSH server hostname mismatch: got %q, want %q", got, hostname) + } + return nil + } +} + +func (s *TerraformSuiteOSS) TestOpenSSHServer() { checkServerDestroyed := func(state *terraform.State) error { - _, err := s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{Name: "test"}.Build()) + _, err := s.client.GetSSHServer(s.T().Context(), presencev1.GetSSHServerRequest_builder{Name: "test"}.Build()) if trace.IsNotFound(err) { return nil } @@ -81,12 +103,9 @@ func (s *TerraformSuiteOSS) TestOpenSSHServer() { } func (s *TerraformSuiteOSS) TestOpenSSHServerNameless() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - checkServerDestroyed := func(state *terraform.State) error { // The name is a UUID but we can lookup by hostname as well. - _, err := s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{Name: "test.local"}.Build()) + _, err := s.client.GetSSHServer(s.T().Context(), presencev1.GetSSHServerRequest_builder{Name: "test.local"}.Build()) if trace.IsNotFound(err) { return nil } @@ -132,10 +151,55 @@ func (s *TerraformSuiteOSS) TestOpenSSHServerNameless() { }) } -func (s *TerraformSuiteOSS) TestImportOpenSSHServer() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) +func (s *TerraformSuiteOSSScopedResources) TestOpenSSHServerScopedAndUnscoped() { + ctx := s.T().Context() + + checkServerDestroyed := func(state *terraform.State) error { + for _, tc := range []struct { + scope string + name string + }{ + {name: "test-unscoped"}, + {scope: "/foo/bar", name: "test-scoped"}, + } { + _, err := s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{ + Name: tc.name, + Scope: tc.scope, + }.Build()) + if err != nil && !trace.IsNotFound(err) { + return trace.Wrap(err) + } + } + return nil + } + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkServerDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("server_openssh_scoped_and_unscoped.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("teleport_server.unscoped", "metadata.name", "test-unscoped"), + resource.TestCheckResourceAttr("teleport_server.unscoped", "id", "test-unscoped"), + resource.TestCheckResourceAttr("teleport_server.unscoped", "spec.addr", "127.0.0.1:22"), + resource.TestCheckResourceAttr("teleport_server.scoped", "metadata.name", "test-scoped"), + resource.TestCheckResourceAttr("teleport_server.scoped", "scope", "/foo/bar"), + resource.TestCheckResourceAttr("teleport_server.scoped", "id", "/foo/bar::test-scoped"), + resource.TestCheckResourceAttr("teleport_server.scoped", "spec.addr", "127.0.0.1:2222"), + checkSSHServerExists(ctx, s.client, "", "test-unscoped", "unscoped.local"), + checkSSHServerExists(ctx, s.client, "/foo/bar", "test-scoped", "scoped.local"), + ), + }, + { + Config: s.getFixture("server_openssh_scoped_and_unscoped.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuiteOSS) TestImportOpenSSHServer() { r := "teleport_server" id := "test_import" name := r + "." + id @@ -155,11 +219,11 @@ func (s *TerraformSuiteOSS) TestImportOpenSSHServer() { err := server.CheckAndSetDefaults() require.NoError(s.T(), err) - _, err = s.client.UpsertNode(ctx, server) + _, err = s.client.UpsertNode(s.T().Context(), server) require.NoError(s.T(), err) require.Eventually(s.T(), func() bool { - _, err = s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{Name: server.GetName(), Scope: server.GetScope()}.Build()) + _, err = s.client.GetSSHServer(s.T().Context(), presencev1.GetSSHServerRequest_builder{Name: server.GetName(), Scope: server.GetScope()}.Build()) if trace.IsNotFound(err) { return false } @@ -189,11 +253,8 @@ func (s *TerraformSuiteOSS) TestImportOpenSSHServer() { } func (s *TerraformSuiteOSS) TestOpenSSHEICEServer() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - checkServerDestroyed := func(state *terraform.State) error { - _, err := s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{Name: "test"}.Build()) + _, err := s.client.GetSSHServer(s.T().Context(), presencev1.GetSSHServerRequest_builder{Name: "test"}.Build()) if trace.IsNotFound(err) { return nil } @@ -252,9 +313,6 @@ func (s *TerraformSuiteOSS) TestOpenSSHEICEServer() { } func (s *TerraformSuiteOSS) TestImportOpenSSHEICEServer() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - r := "teleport_server" id := "test_import" name := r + "." + id @@ -284,11 +342,11 @@ func (s *TerraformSuiteOSS) TestImportOpenSSHEICEServer() { err := server.CheckAndSetDefaults() require.NoError(s.T(), err) - _, err = s.client.UpsertNode(ctx, server) + _, err = s.client.UpsertNode(s.T().Context(), server) require.NoError(s.T(), err) require.Eventually(s.T(), func() bool { - _, err = s.client.GetSSHServer(ctx, presencev1.GetSSHServerRequest_builder{Name: server.GetName(), Scope: server.GetScope()}.Build()) + _, err = s.client.GetSSHServer(s.T().Context(), presencev1.GetSSHServerRequest_builder{Name: server.GetName(), Scope: server.GetScope()}.Build()) if trace.IsNotFound(err) { return false } diff --git a/integrations/terraform/tfschema/types_terraform.go b/integrations/terraform/tfschema/types_terraform.go index 1d8a30a56662..f73220982c5d 100644 --- a/integrations/terraform/tfschema/types_terraform.go +++ b/integrations/terraform/tfschema/types_terraform.go @@ -843,7 +843,7 @@ func GenSchemaServerV2(ctx context.Context) (github_com_hashicorp_terraform_plug Computed: true, Description: "The advertized scope of the server which can not change once assigned.", Optional: true, - PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + 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, }, "spec": { From db19a7f551ff6f57b488cdbd545ac612b61291d8 Mon Sep 17 00:00:00 2001 From: Kyle Kloberdanz Date: Wed, 12 Aug 2026 22:23:22 +0000 Subject: [PATCH 278/347] Implement tctl observability (#67904) This PR adds audit queue observability to tctl. It allows you to view the audit queue depth and also sort records based on number of events in each queue. --- api/client/inventory.go | 6 + api/client/proto/inventory_protohybrid.pb.go | 280 +- api/client/proto/inventory_protoopaque.pb.go | 273 +- api/client/proto/types.go | 2 + .../legacy/client/proto/inventory.proto | 10 + api/proto/teleport/legacy/types/types.proto | 22 + api/types/instance.go | 11 + api/types/types.pb.go | 6587 +++++++++-------- docs/pages/reference/cli/tctl.mdx | 1 + lib/auth/auth.go | 1 + lib/events/auditqueue/auditqueue.go | 34 +- lib/events/auditqueue/metrics.go | 30 + lib/events/auditqueue/sqlite.go | 86 +- lib/events/auditqueue/sqlite_disk.go | 10 +- lib/events/auditqueue/sqlite_memory.go | 7 + lib/events/auditqueue/sqlite_test.go | 140 + lib/events/emitter.go | 47 +- lib/events/fallback_emitter.go | 39 +- lib/events/fallback_emitter_test.go | 104 +- lib/inventory/controller.go | 6 +- lib/inventory/controller_test.go | 119 + lib/inventory/inventory.go | 128 +- lib/service/auditqueue_status_test.go | 98 + lib/service/service.go | 140 +- tool/tctl/common/inventory_command.go | 80 +- tool/tctl/common/inventory_command_test.go | 105 + 26 files changed, 5016 insertions(+), 3350 deletions(-) create mode 100644 lib/service/auditqueue_status_test.go create mode 100644 tool/tctl/common/inventory_command_test.go diff --git a/api/client/inventory.go b/api/client/inventory.go index c20720a017f1..bf8f28b63014 100644 --- a/api/client/inventory.go +++ b/api/client/inventory.go @@ -357,6 +357,10 @@ func (i *downstreamICS) runSendLoop(stream proto.AuthService_InventoryControlStr oneOf.Msg = &proto.UpstreamInventoryOneOf_StopHeartbeat{ StopHeartbeat: msg, } + case *proto.InstanceStatus: + oneOf.Msg = &proto.UpstreamInventoryOneOf_InstanceStatus{ + InstanceStatus: msg, + } default: sendMsg.errC <- trace.BadParameter("cannot send unexpected upstream msg type: %T", msg) continue @@ -500,6 +504,8 @@ func (i *upstreamICS) runRecvLoop(stream proto.AuthService_InventoryControlStrea msg = oneOf.GetGoodbye() case oneOf.GetStopHeartbeat() != nil: msg = oneOf.GetStopHeartbeat() + case oneOf.GetInstanceStatus() != nil: + msg = oneOf.GetInstanceStatus() default: slog.WarnContext(stream.Context(), "received unknown upstream message", "message", oneOf) continue diff --git a/api/client/proto/inventory_protohybrid.pb.go b/api/client/proto/inventory_protohybrid.pb.go index 4f65d1cee150..0abf3031aa0f 100644 --- a/api/client/proto/inventory_protohybrid.pb.go +++ b/api/client/proto/inventory_protohybrid.pb.go @@ -149,6 +149,7 @@ type UpstreamInventoryOneOf struct { // *UpstreamInventoryOneOf_AgentMetadata // *UpstreamInventoryOneOf_Goodbye // *UpstreamInventoryOneOf_StopHeartbeat + // *UpstreamInventoryOneOf_InstanceStatus Msg isUpstreamInventoryOneOf_Msg `protobuf_oneof:"Msg"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -240,6 +241,15 @@ func (x *UpstreamInventoryOneOf) GetStopHeartbeat() *UpstreamInventoryStopHeartb return nil } +func (x *UpstreamInventoryOneOf) GetInstanceStatus() *InstanceStatus { + if x != nil { + if x, ok := x.Msg.(*UpstreamInventoryOneOf_InstanceStatus); ok { + return x.InstanceStatus + } + } + return nil +} + func (x *UpstreamInventoryOneOf) SetHello(v *UpstreamInventoryHello) { if v == nil { x.Msg = nil @@ -288,6 +298,14 @@ func (x *UpstreamInventoryOneOf) SetStopHeartbeat(v *UpstreamInventoryStopHeartb x.Msg = &UpstreamInventoryOneOf_StopHeartbeat{v} } +func (x *UpstreamInventoryOneOf) SetInstanceStatus(v *InstanceStatus) { + if v == nil { + x.Msg = nil + return + } + x.Msg = &UpstreamInventoryOneOf_InstanceStatus{v} +} + func (x *UpstreamInventoryOneOf) HasMsg() bool { if x == nil { return false @@ -343,6 +361,14 @@ func (x *UpstreamInventoryOneOf) HasStopHeartbeat() bool { return ok } +func (x *UpstreamInventoryOneOf) HasInstanceStatus() bool { + if x == nil { + return false + } + _, ok := x.Msg.(*UpstreamInventoryOneOf_InstanceStatus) + return ok +} + func (x *UpstreamInventoryOneOf) ClearMsg() { x.Msg = nil } @@ -383,6 +409,12 @@ func (x *UpstreamInventoryOneOf) ClearStopHeartbeat() { } } +func (x *UpstreamInventoryOneOf) ClearInstanceStatus() { + if _, ok := x.Msg.(*UpstreamInventoryOneOf_InstanceStatus); ok { + x.Msg = nil + } +} + const UpstreamInventoryOneOf_Msg_not_set_case case_UpstreamInventoryOneOf_Msg = 0 const UpstreamInventoryOneOf_Hello_case case_UpstreamInventoryOneOf_Msg = 1 const UpstreamInventoryOneOf_Heartbeat_case case_UpstreamInventoryOneOf_Msg = 2 @@ -390,6 +422,7 @@ const UpstreamInventoryOneOf_Pong_case case_UpstreamInventoryOneOf_Msg = 3 const UpstreamInventoryOneOf_AgentMetadata_case case_UpstreamInventoryOneOf_Msg = 4 const UpstreamInventoryOneOf_Goodbye_case case_UpstreamInventoryOneOf_Msg = 5 const UpstreamInventoryOneOf_StopHeartbeat_case case_UpstreamInventoryOneOf_Msg = 6 +const UpstreamInventoryOneOf_InstanceStatus_case case_UpstreamInventoryOneOf_Msg = 7 func (x *UpstreamInventoryOneOf) WhichMsg() case_UpstreamInventoryOneOf_Msg { if x == nil { @@ -408,6 +441,8 @@ func (x *UpstreamInventoryOneOf) WhichMsg() case_UpstreamInventoryOneOf_Msg { return UpstreamInventoryOneOf_Goodbye_case case *UpstreamInventoryOneOf_StopHeartbeat: return UpstreamInventoryOneOf_StopHeartbeat_case + case *UpstreamInventoryOneOf_InstanceStatus: + return UpstreamInventoryOneOf_InstanceStatus_case default: return UpstreamInventoryOneOf_Msg_not_set_case } @@ -432,6 +467,8 @@ type UpstreamInventoryOneOf_builder struct { // UpstreamInventoryStopHeartbeat informs the upstream service that a // heartbeat is stopping. StopHeartbeat *UpstreamInventoryStopHeartbeat + // InstanceStatus reports periodic status about the instance. + InstanceStatus *InstanceStatus // -- end of Msg } @@ -457,6 +494,9 @@ func (b0 UpstreamInventoryOneOf_builder) Build() *UpstreamInventoryOneOf { if b.StopHeartbeat != nil { x.Msg = &UpstreamInventoryOneOf_StopHeartbeat{b.StopHeartbeat} } + if b.InstanceStatus != nil { + x.Msg = &UpstreamInventoryOneOf_InstanceStatus{b.InstanceStatus} + } return m0 } @@ -505,6 +545,11 @@ type UpstreamInventoryOneOf_StopHeartbeat struct { StopHeartbeat *UpstreamInventoryStopHeartbeat `protobuf:"bytes,6,opt,name=stop_heartbeat,json=stopHeartbeat,proto3,oneof"` } +type UpstreamInventoryOneOf_InstanceStatus struct { + // InstanceStatus reports periodic status about the instance. + InstanceStatus *InstanceStatus `protobuf:"bytes,7,opt,name=instance_status,json=instanceStatus,proto3,oneof"` +} + func (*UpstreamInventoryOneOf_Hello) isUpstreamInventoryOneOf_Msg() {} func (*UpstreamInventoryOneOf_Heartbeat) isUpstreamInventoryOneOf_Msg() {} @@ -517,6 +562,8 @@ func (*UpstreamInventoryOneOf_Goodbye) isUpstreamInventoryOneOf_Msg() {} func (*UpstreamInventoryOneOf_StopHeartbeat) isUpstreamInventoryOneOf_Msg() {} +func (*UpstreamInventoryOneOf_InstanceStatus) isUpstreamInventoryOneOf_Msg() {} + // DownstreamInventoryOneOf is the downstream message for the inventory control stream, // sent from auth servers to teleport instances. type DownstreamInventoryOneOf struct { @@ -1790,6 +1837,77 @@ func (b0 InventoryHeartbeat_builder) Build() *InventoryHeartbeat { return m0 } +// InstanceStatus reports periodic status about an instance. +type InstanceStatus struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // AuditQueue reports the depth of the instance's audit-log queue. + AuditQueue *types.AuditQueueStatus `protobuf:"bytes,1,opt,name=audit_queue,json=auditQueue,proto3" json:"audit_queue,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstanceStatus) Reset() { + *x = InstanceStatus{} + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstanceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstanceStatus) ProtoMessage() {} + +func (x *InstanceStatus) ProtoReflect() protoreflect.Message { + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *InstanceStatus) GetAuditQueue() *types.AuditQueueStatus { + if x != nil { + return x.AuditQueue + } + return nil +} + +func (x *InstanceStatus) SetAuditQueue(v *types.AuditQueueStatus) { + x.AuditQueue = v +} + +func (x *InstanceStatus) HasAuditQueue() bool { + if x == nil { + return false + } + return x.AuditQueue != nil +} + +func (x *InstanceStatus) ClearAuditQueue() { + x.AuditQueue = nil +} + +type InstanceStatus_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // AuditQueue reports the depth of the instance's audit-log queue. + AuditQueue *types.AuditQueueStatus +} + +func (b0 InstanceStatus_builder) Build() *InstanceStatus { + m0 := &InstanceStatus{} + b, x := &b0, m0 + _, _ = b, x + x.AuditQueue = b.AuditQueue + return m0 +} + // UpstreamInventoryGoodbye informs the upstream service that instance // is terminating type UpstreamInventoryGoodbye struct { @@ -1806,7 +1924,7 @@ type UpstreamInventoryGoodbye struct { func (x *UpstreamInventoryGoodbye) Reset() { *x = UpstreamInventoryGoodbye{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1818,7 +1936,7 @@ func (x *UpstreamInventoryGoodbye) String() string { func (*UpstreamInventoryGoodbye) ProtoMessage() {} func (x *UpstreamInventoryGoodbye) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1883,7 +2001,7 @@ type InventoryStatusRequest struct { func (x *InventoryStatusRequest) Reset() { *x = InventoryStatusRequest{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1895,7 +2013,7 @@ func (x *InventoryStatusRequest) String() string { func (*InventoryStatusRequest) ProtoMessage() {} func (x *InventoryStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1953,7 +2071,7 @@ type InventoryStatusSummary struct { func (x *InventoryStatusSummary) Reset() { *x = InventoryStatusSummary{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1965,7 +2083,7 @@ func (x *InventoryStatusSummary) String() string { func (*InventoryStatusSummary) ProtoMessage() {} func (x *InventoryStatusSummary) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2073,7 +2191,7 @@ type UpstreamInventoryStopHeartbeat struct { func (x *UpstreamInventoryStopHeartbeat) Reset() { *x = UpstreamInventoryStopHeartbeat{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2085,7 +2203,7 @@ func (x *UpstreamInventoryStopHeartbeat) String() string { func (*UpstreamInventoryStopHeartbeat) ProtoMessage() {} func (x *UpstreamInventoryStopHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2185,13 +2303,15 @@ type DownstreamInventoryHello_SupportedCapabilities struct { LinuxDesktopHeartbeats bool `protobuf:"varint,21,opt,name=linux_desktop_heartbeats,json=linuxDesktopHeartbeats,proto3" json:"linux_desktop_heartbeats,omitempty"` // LinuxDesktopCleanup indicates the ICS supports deleting linux desktops when UpstreamInventoryGoodbye.DeleteResources is set. LinuxDesktopCleanup bool `protobuf:"varint,22,opt,name=linux_desktop_cleanup,json=linuxDesktopCleanup,proto3" json:"linux_desktop_cleanup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // InstanceStatus indicates the ICS supports the InstanceStatus message. + InstanceStatus bool `protobuf:"varint,23,opt,name=instance_status,json=instanceStatus,proto3" json:"instance_status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DownstreamInventoryHello_SupportedCapabilities) Reset() { *x = DownstreamInventoryHello_SupportedCapabilities{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2203,7 +2323,7 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) String() string { func (*DownstreamInventoryHello_SupportedCapabilities) ProtoMessage() {} func (x *DownstreamInventoryHello_SupportedCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2368,6 +2488,13 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) GetLinuxDesktopCleanup( return false } +func (x *DownstreamInventoryHello_SupportedCapabilities) GetInstanceStatus() bool { + if x != nil { + return x.InstanceStatus + } + return false +} + func (x *DownstreamInventoryHello_SupportedCapabilities) SetProxyHeartbeats(v bool) { x.ProxyHeartbeats = v } @@ -2456,6 +2583,10 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) SetLinuxDesktopCleanup( x.LinuxDesktopCleanup = v } +func (x *DownstreamInventoryHello_SupportedCapabilities) SetInstanceStatus(v bool) { + x.InstanceStatus = v +} + type DownstreamInventoryHello_SupportedCapabilities_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -2503,6 +2634,8 @@ type DownstreamInventoryHello_SupportedCapabilities_builder struct { LinuxDesktopHeartbeats bool // LinuxDesktopCleanup indicates the ICS supports deleting linux desktops when UpstreamInventoryGoodbye.DeleteResources is set. LinuxDesktopCleanup bool + // InstanceStatus indicates the ICS supports the InstanceStatus message. + InstanceStatus bool } func (b0 DownstreamInventoryHello_SupportedCapabilities_builder) Build() *DownstreamInventoryHello_SupportedCapabilities { @@ -2531,6 +2664,7 @@ func (b0 DownstreamInventoryHello_SupportedCapabilities_builder) Build() *Downst x.DatabaseHeartbeatGracefulStop = b.DatabaseHeartbeatGracefulStop x.LinuxDesktopHeartbeats = b.LinuxDesktopHeartbeats x.LinuxDesktopCleanup = b.LinuxDesktopCleanup + x.InstanceStatus = b.InstanceStatus return m0 } @@ -2538,14 +2672,15 @@ var File_teleport_legacy_client_proto_inventory_proto protoreflect.FileDescripto const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\n" + - ",teleport/legacy/client/proto/inventory.proto\x12\x05proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/legacy/types/types.proto\x1a,teleport/linuxdesktop/v1/linux_desktop.proto\x1a'teleport/presence/v1/relay_server.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xa1\x03\n" + + ",teleport/legacy/client/proto/inventory.proto\x12\x05proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/legacy/types/types.proto\x1a,teleport/linuxdesktop/v1/linux_desktop.proto\x1a'teleport/presence/v1/relay_server.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xe3\x03\n" + "\x16UpstreamInventoryOneOf\x125\n" + "\x05Hello\x18\x01 \x01(\v2\x1d.proto.UpstreamInventoryHelloH\x00R\x05Hello\x129\n" + "\tHeartbeat\x18\x02 \x01(\v2\x19.proto.InventoryHeartbeatH\x00R\tHeartbeat\x122\n" + "\x04Pong\x18\x03 \x01(\v2\x1c.proto.UpstreamInventoryPongH\x00R\x04Pong\x12M\n" + "\rAgentMetadata\x18\x04 \x01(\v2%.proto.UpstreamInventoryAgentMetadataH\x00R\rAgentMetadata\x12;\n" + "\aGoodbye\x18\x05 \x01(\v2\x1f.proto.UpstreamInventoryGoodbyeH\x00R\aGoodbye\x12N\n" + - "\x0estop_heartbeat\x18\x06 \x01(\v2%.proto.UpstreamInventoryStopHeartbeatH\x00R\rstopHeartbeatB\x05\n" + + "\x0estop_heartbeat\x18\x06 \x01(\v2%.proto.UpstreamInventoryStopHeartbeatH\x00R\rstopHeartbeat\x12@\n" + + "\x0finstance_status\x18\a \x01(\v2\x15.proto.InstanceStatusH\x00R\x0einstanceStatusB\x05\n" + "\x03Msg\"\xde\x01\n" + "\x18DownstreamInventoryOneOf\x127\n" + "\x05Hello\x18\x01 \x01(\v2\x1f.proto.DownstreamInventoryHelloH\x00R\x05Hello\x124\n" + @@ -2576,12 +2711,12 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x0eInstallMethods\x18\x05 \x03(\tR\x0eInstallMethods\x12*\n" + "\x10ContainerRuntime\x18\x06 \x01(\tR\x10ContainerRuntime\x124\n" + "\x15ContainerOrchestrator\x18\a \x01(\tR\x15ContainerOrchestrator\x12*\n" + - "\x10CloudEnvironment\x18\b \x01(\tR\x10CloudEnvironment\"\x9d\n" + + "\x10CloudEnvironment\x18\b \x01(\tR\x10CloudEnvironment\"\xc6\n" + "\n" + "\x18DownstreamInventoryHello\x12\x18\n" + "\aVersion\x18\x01 \x01(\tR\aVersion\x12\x1a\n" + "\bServerID\x18\x02 \x01(\tR\bServerID\x12Y\n" + - "\fCapabilities\x18\x03 \x01(\v25.proto.DownstreamInventoryHello.SupportedCapabilitiesR\fCapabilities\x1a\xef\b\n" + + "\fCapabilities\x18\x03 \x01(\v25.proto.DownstreamInventoryHello.SupportedCapabilitiesR\fCapabilities\x1a\x98\t\n" + "\x15SupportedCapabilities\x12(\n" + "\x0fProxyHeartbeats\x18\x01 \x01(\bR\x0fProxyHeartbeats\x12\"\n" + "\fProxyCleanup\x18\x02 \x01(\bR\fProxyCleanup\x12&\n" + @@ -2607,7 +2742,8 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x1frelay_server_heartbeats_cleanup\x18\x13 \x01(\bR\x1crelayServerHeartbeatsCleanup\x12G\n" + " database_heartbeat_graceful_stop\x18\x14 \x01(\bR\x1ddatabaseHeartbeatGracefulStop\x128\n" + "\x18linux_desktop_heartbeats\x18\x15 \x01(\bR\x16linuxDesktopHeartbeats\x122\n" + - "\x15linux_desktop_cleanup\x18\x16 \x01(\bR\x13linuxDesktopCleanup\"\xea\x01\n" + + "\x15linux_desktop_cleanup\x18\x16 \x01(\bR\x13linuxDesktopCleanup\x12'\n" + + "\x0finstance_status\x18\x17 \x01(\bR\x0einstanceStatus\"\xea\x01\n" + "\x1cInventoryUpdateLabelsRequest\x12\x1a\n" + "\bServerID\x18\x01 \x01(\tR\bServerID\x12*\n" + "\x04Kind\x18\x02 \x01(\x0e2\x16.proto.LabelUpdateKindR\x04Kind\x12G\n" + @@ -2627,7 +2763,10 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x0eDatabaseServer\x18\x03 \x01(\v2\x17.types.DatabaseServerV3R\x0eDatabaseServer\x12E\n" + "\x10KubernetesServer\x18\x04 \x01(\v2\x19.types.KubernetesServerV3R\x10KubernetesServer\x12D\n" + "\frelay_server\x18\x05 \x01(\v2!.teleport.presence.v1.RelayServerR\vrelayServer\x12K\n" + - "\rlinux_desktop\x18\x06 \x01(\v2&.teleport.linuxdesktop.v1.LinuxDesktopR\flinuxDesktop\"d\n" + + "\rlinux_desktop\x18\x06 \x01(\v2&.teleport.linuxdesktop.v1.LinuxDesktopR\flinuxDesktop\"J\n" + + "\x0eInstanceStatus\x128\n" + + "\vaudit_queue\x18\x01 \x01(\v2\x17.types.AuditQueueStatusR\n" + + "auditQueue\"d\n" + "\x18UpstreamInventoryGoodbye\x12(\n" + "\x0fDeleteResources\x18\x01 \x01(\bR\x0fDeleteResources\x12\x1e\n" + "\n" + @@ -2661,7 +2800,7 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "#STOP_HEARTBEAT_KIND_DATABASE_SERVER\x10\x01B4Z2github.com/gravitational/teleport/api/client/protob\x06proto3" var file_teleport_legacy_client_proto_inventory_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_teleport_legacy_client_proto_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_teleport_legacy_client_proto_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_teleport_legacy_client_proto_inventory_proto_goTypes = []any{ (LabelUpdateKind)(0), // 0: proto.LabelUpdateKind (StopHeartbeatKind)(0), // 1: proto.StopHeartbeatKind @@ -2675,60 +2814,64 @@ var file_teleport_legacy_client_proto_inventory_proto_goTypes = []any{ (*InventoryUpdateLabelsRequest)(nil), // 9: proto.InventoryUpdateLabelsRequest (*DownstreamInventoryUpdateLabels)(nil), // 10: proto.DownstreamInventoryUpdateLabels (*InventoryHeartbeat)(nil), // 11: proto.InventoryHeartbeat - (*UpstreamInventoryGoodbye)(nil), // 12: proto.UpstreamInventoryGoodbye - (*InventoryStatusRequest)(nil), // 13: proto.InventoryStatusRequest - (*InventoryStatusSummary)(nil), // 14: proto.InventoryStatusSummary - (*UpstreamInventoryStopHeartbeat)(nil), // 15: proto.UpstreamInventoryStopHeartbeat - (*DownstreamInventoryHello_SupportedCapabilities)(nil), // 16: proto.DownstreamInventoryHello.SupportedCapabilities - nil, // 17: proto.InventoryUpdateLabelsRequest.LabelsEntry - nil, // 18: proto.DownstreamInventoryUpdateLabels.LabelsEntry - nil, // 19: proto.InventoryStatusSummary.VersionCountsEntry - nil, // 20: proto.InventoryStatusSummary.UpgraderCountsEntry - nil, // 21: proto.InventoryStatusSummary.ServiceCountsEntry - (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp - (*types.UpdaterV2Info)(nil), // 23: types.UpdaterV2Info - (*v1.ImmutableLabels)(nil), // 24: teleport.scopes.joining.v1.ImmutableLabels - (*types.ServerV2)(nil), // 25: types.ServerV2 - (*types.AppServerV3)(nil), // 26: types.AppServerV3 - (*types.DatabaseServerV3)(nil), // 27: types.DatabaseServerV3 - (*types.KubernetesServerV3)(nil), // 28: types.KubernetesServerV3 - (*v11.RelayServer)(nil), // 29: teleport.presence.v1.RelayServer - (*v12.LinuxDesktop)(nil), // 30: teleport.linuxdesktop.v1.LinuxDesktop + (*InstanceStatus)(nil), // 12: proto.InstanceStatus + (*UpstreamInventoryGoodbye)(nil), // 13: proto.UpstreamInventoryGoodbye + (*InventoryStatusRequest)(nil), // 14: proto.InventoryStatusRequest + (*InventoryStatusSummary)(nil), // 15: proto.InventoryStatusSummary + (*UpstreamInventoryStopHeartbeat)(nil), // 16: proto.UpstreamInventoryStopHeartbeat + (*DownstreamInventoryHello_SupportedCapabilities)(nil), // 17: proto.DownstreamInventoryHello.SupportedCapabilities + nil, // 18: proto.InventoryUpdateLabelsRequest.LabelsEntry + nil, // 19: proto.DownstreamInventoryUpdateLabels.LabelsEntry + nil, // 20: proto.InventoryStatusSummary.VersionCountsEntry + nil, // 21: proto.InventoryStatusSummary.UpgraderCountsEntry + nil, // 22: proto.InventoryStatusSummary.ServiceCountsEntry + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*types.UpdaterV2Info)(nil), // 24: types.UpdaterV2Info + (*v1.ImmutableLabels)(nil), // 25: teleport.scopes.joining.v1.ImmutableLabels + (*types.ServerV2)(nil), // 26: types.ServerV2 + (*types.AppServerV3)(nil), // 27: types.AppServerV3 + (*types.DatabaseServerV3)(nil), // 28: types.DatabaseServerV3 + (*types.KubernetesServerV3)(nil), // 29: types.KubernetesServerV3 + (*v11.RelayServer)(nil), // 30: teleport.presence.v1.RelayServer + (*v12.LinuxDesktop)(nil), // 31: teleport.linuxdesktop.v1.LinuxDesktop + (*types.AuditQueueStatus)(nil), // 32: types.AuditQueueStatus } var file_teleport_legacy_client_proto_inventory_proto_depIdxs = []int32{ 6, // 0: proto.UpstreamInventoryOneOf.Hello:type_name -> proto.UpstreamInventoryHello 11, // 1: proto.UpstreamInventoryOneOf.Heartbeat:type_name -> proto.InventoryHeartbeat 5, // 2: proto.UpstreamInventoryOneOf.Pong:type_name -> proto.UpstreamInventoryPong 7, // 3: proto.UpstreamInventoryOneOf.AgentMetadata:type_name -> proto.UpstreamInventoryAgentMetadata - 12, // 4: proto.UpstreamInventoryOneOf.Goodbye:type_name -> proto.UpstreamInventoryGoodbye - 15, // 5: proto.UpstreamInventoryOneOf.stop_heartbeat:type_name -> proto.UpstreamInventoryStopHeartbeat - 8, // 6: proto.DownstreamInventoryOneOf.Hello:type_name -> proto.DownstreamInventoryHello - 4, // 7: proto.DownstreamInventoryOneOf.Ping:type_name -> proto.DownstreamInventoryPing - 10, // 8: proto.DownstreamInventoryOneOf.UpdateLabels:type_name -> proto.DownstreamInventoryUpdateLabels - 22, // 9: proto.UpstreamInventoryPong.SystemClock:type_name -> google.protobuf.Timestamp - 23, // 10: proto.UpstreamInventoryHello.UpdaterInfo:type_name -> types.UpdaterV2Info - 24, // 11: proto.UpstreamInventoryHello.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels - 16, // 12: proto.DownstreamInventoryHello.Capabilities:type_name -> proto.DownstreamInventoryHello.SupportedCapabilities - 0, // 13: proto.InventoryUpdateLabelsRequest.Kind:type_name -> proto.LabelUpdateKind - 17, // 14: proto.InventoryUpdateLabelsRequest.Labels:type_name -> proto.InventoryUpdateLabelsRequest.LabelsEntry - 0, // 15: proto.DownstreamInventoryUpdateLabels.Kind:type_name -> proto.LabelUpdateKind - 18, // 16: proto.DownstreamInventoryUpdateLabels.Labels:type_name -> proto.DownstreamInventoryUpdateLabels.LabelsEntry - 25, // 17: proto.InventoryHeartbeat.SSHServer:type_name -> types.ServerV2 - 26, // 18: proto.InventoryHeartbeat.AppServer:type_name -> types.AppServerV3 - 27, // 19: proto.InventoryHeartbeat.DatabaseServer:type_name -> types.DatabaseServerV3 - 28, // 20: proto.InventoryHeartbeat.KubernetesServer:type_name -> types.KubernetesServerV3 - 29, // 21: proto.InventoryHeartbeat.relay_server:type_name -> teleport.presence.v1.RelayServer - 30, // 22: proto.InventoryHeartbeat.linux_desktop:type_name -> teleport.linuxdesktop.v1.LinuxDesktop - 6, // 23: proto.InventoryStatusSummary.Connected:type_name -> proto.UpstreamInventoryHello - 19, // 24: proto.InventoryStatusSummary.VersionCounts:type_name -> proto.InventoryStatusSummary.VersionCountsEntry - 20, // 25: proto.InventoryStatusSummary.UpgraderCounts:type_name -> proto.InventoryStatusSummary.UpgraderCountsEntry - 21, // 26: proto.InventoryStatusSummary.ServiceCounts:type_name -> proto.InventoryStatusSummary.ServiceCountsEntry - 1, // 27: proto.UpstreamInventoryStopHeartbeat.kind:type_name -> proto.StopHeartbeatKind - 28, // [28:28] is the sub-list for method output_type - 28, // [28:28] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name + 13, // 4: proto.UpstreamInventoryOneOf.Goodbye:type_name -> proto.UpstreamInventoryGoodbye + 16, // 5: proto.UpstreamInventoryOneOf.stop_heartbeat:type_name -> proto.UpstreamInventoryStopHeartbeat + 12, // 6: proto.UpstreamInventoryOneOf.instance_status:type_name -> proto.InstanceStatus + 8, // 7: proto.DownstreamInventoryOneOf.Hello:type_name -> proto.DownstreamInventoryHello + 4, // 8: proto.DownstreamInventoryOneOf.Ping:type_name -> proto.DownstreamInventoryPing + 10, // 9: proto.DownstreamInventoryOneOf.UpdateLabels:type_name -> proto.DownstreamInventoryUpdateLabels + 23, // 10: proto.UpstreamInventoryPong.SystemClock:type_name -> google.protobuf.Timestamp + 24, // 11: proto.UpstreamInventoryHello.UpdaterInfo:type_name -> types.UpdaterV2Info + 25, // 12: proto.UpstreamInventoryHello.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels + 17, // 13: proto.DownstreamInventoryHello.Capabilities:type_name -> proto.DownstreamInventoryHello.SupportedCapabilities + 0, // 14: proto.InventoryUpdateLabelsRequest.Kind:type_name -> proto.LabelUpdateKind + 18, // 15: proto.InventoryUpdateLabelsRequest.Labels:type_name -> proto.InventoryUpdateLabelsRequest.LabelsEntry + 0, // 16: proto.DownstreamInventoryUpdateLabels.Kind:type_name -> proto.LabelUpdateKind + 19, // 17: proto.DownstreamInventoryUpdateLabels.Labels:type_name -> proto.DownstreamInventoryUpdateLabels.LabelsEntry + 26, // 18: proto.InventoryHeartbeat.SSHServer:type_name -> types.ServerV2 + 27, // 19: proto.InventoryHeartbeat.AppServer:type_name -> types.AppServerV3 + 28, // 20: proto.InventoryHeartbeat.DatabaseServer:type_name -> types.DatabaseServerV3 + 29, // 21: proto.InventoryHeartbeat.KubernetesServer:type_name -> types.KubernetesServerV3 + 30, // 22: proto.InventoryHeartbeat.relay_server:type_name -> teleport.presence.v1.RelayServer + 31, // 23: proto.InventoryHeartbeat.linux_desktop:type_name -> teleport.linuxdesktop.v1.LinuxDesktop + 32, // 24: proto.InstanceStatus.audit_queue:type_name -> types.AuditQueueStatus + 6, // 25: proto.InventoryStatusSummary.Connected:type_name -> proto.UpstreamInventoryHello + 20, // 26: proto.InventoryStatusSummary.VersionCounts:type_name -> proto.InventoryStatusSummary.VersionCountsEntry + 21, // 27: proto.InventoryStatusSummary.UpgraderCounts:type_name -> proto.InventoryStatusSummary.UpgraderCountsEntry + 22, // 28: proto.InventoryStatusSummary.ServiceCounts:type_name -> proto.InventoryStatusSummary.ServiceCountsEntry + 1, // 29: proto.UpstreamInventoryStopHeartbeat.kind:type_name -> proto.StopHeartbeatKind + 30, // [30:30] is the sub-list for method output_type + 30, // [30:30] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_teleport_legacy_client_proto_inventory_proto_init() } @@ -2743,6 +2886,7 @@ func file_teleport_legacy_client_proto_inventory_proto_init() { (*UpstreamInventoryOneOf_AgentMetadata)(nil), (*UpstreamInventoryOneOf_Goodbye)(nil), (*UpstreamInventoryOneOf_StopHeartbeat)(nil), + (*UpstreamInventoryOneOf_InstanceStatus)(nil), } file_teleport_legacy_client_proto_inventory_proto_msgTypes[1].OneofWrappers = []any{ (*DownstreamInventoryOneOf_Hello)(nil), @@ -2755,7 +2899,7 @@ func file_teleport_legacy_client_proto_inventory_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_legacy_client_proto_inventory_proto_rawDesc), len(file_teleport_legacy_client_proto_inventory_proto_rawDesc)), NumEnums: 2, - NumMessages: 20, + NumMessages: 21, NumExtensions: 0, NumServices: 0, }, diff --git a/api/client/proto/inventory_protoopaque.pb.go b/api/client/proto/inventory_protoopaque.pb.go index 7b9156b0890e..80dea53c6b3c 100644 --- a/api/client/proto/inventory_protoopaque.pb.go +++ b/api/client/proto/inventory_protoopaque.pb.go @@ -223,6 +223,15 @@ func (x *UpstreamInventoryOneOf) GetStopHeartbeat() *UpstreamInventoryStopHeartb return nil } +func (x *UpstreamInventoryOneOf) GetInstanceStatus() *InstanceStatus { + if x != nil { + if x, ok := x.xxx_hidden_Msg.(*upstreamInventoryOneOf_InstanceStatus); ok { + return x.InstanceStatus + } + } + return nil +} + func (x *UpstreamInventoryOneOf) SetHello(v *UpstreamInventoryHello) { if v == nil { x.xxx_hidden_Msg = nil @@ -271,6 +280,14 @@ func (x *UpstreamInventoryOneOf) SetStopHeartbeat(v *UpstreamInventoryStopHeartb x.xxx_hidden_Msg = &upstreamInventoryOneOf_StopHeartbeat{v} } +func (x *UpstreamInventoryOneOf) SetInstanceStatus(v *InstanceStatus) { + if v == nil { + x.xxx_hidden_Msg = nil + return + } + x.xxx_hidden_Msg = &upstreamInventoryOneOf_InstanceStatus{v} +} + func (x *UpstreamInventoryOneOf) HasMsg() bool { if x == nil { return false @@ -326,6 +343,14 @@ func (x *UpstreamInventoryOneOf) HasStopHeartbeat() bool { return ok } +func (x *UpstreamInventoryOneOf) HasInstanceStatus() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_Msg.(*upstreamInventoryOneOf_InstanceStatus) + return ok +} + func (x *UpstreamInventoryOneOf) ClearMsg() { x.xxx_hidden_Msg = nil } @@ -366,6 +391,12 @@ func (x *UpstreamInventoryOneOf) ClearStopHeartbeat() { } } +func (x *UpstreamInventoryOneOf) ClearInstanceStatus() { + if _, ok := x.xxx_hidden_Msg.(*upstreamInventoryOneOf_InstanceStatus); ok { + x.xxx_hidden_Msg = nil + } +} + const UpstreamInventoryOneOf_Msg_not_set_case case_UpstreamInventoryOneOf_Msg = 0 const UpstreamInventoryOneOf_Hello_case case_UpstreamInventoryOneOf_Msg = 1 const UpstreamInventoryOneOf_Heartbeat_case case_UpstreamInventoryOneOf_Msg = 2 @@ -373,6 +404,7 @@ const UpstreamInventoryOneOf_Pong_case case_UpstreamInventoryOneOf_Msg = 3 const UpstreamInventoryOneOf_AgentMetadata_case case_UpstreamInventoryOneOf_Msg = 4 const UpstreamInventoryOneOf_Goodbye_case case_UpstreamInventoryOneOf_Msg = 5 const UpstreamInventoryOneOf_StopHeartbeat_case case_UpstreamInventoryOneOf_Msg = 6 +const UpstreamInventoryOneOf_InstanceStatus_case case_UpstreamInventoryOneOf_Msg = 7 func (x *UpstreamInventoryOneOf) WhichMsg() case_UpstreamInventoryOneOf_Msg { if x == nil { @@ -391,6 +423,8 @@ func (x *UpstreamInventoryOneOf) WhichMsg() case_UpstreamInventoryOneOf_Msg { return UpstreamInventoryOneOf_Goodbye_case case *upstreamInventoryOneOf_StopHeartbeat: return UpstreamInventoryOneOf_StopHeartbeat_case + case *upstreamInventoryOneOf_InstanceStatus: + return UpstreamInventoryOneOf_InstanceStatus_case default: return UpstreamInventoryOneOf_Msg_not_set_case } @@ -415,6 +449,8 @@ type UpstreamInventoryOneOf_builder struct { // UpstreamInventoryStopHeartbeat informs the upstream service that a // heartbeat is stopping. StopHeartbeat *UpstreamInventoryStopHeartbeat + // InstanceStatus reports periodic status about the instance. + InstanceStatus *InstanceStatus // -- end of xxx_hidden_Msg } @@ -440,6 +476,9 @@ func (b0 UpstreamInventoryOneOf_builder) Build() *UpstreamInventoryOneOf { if b.StopHeartbeat != nil { x.xxx_hidden_Msg = &upstreamInventoryOneOf_StopHeartbeat{b.StopHeartbeat} } + if b.InstanceStatus != nil { + x.xxx_hidden_Msg = &upstreamInventoryOneOf_InstanceStatus{b.InstanceStatus} + } return m0 } @@ -488,6 +527,11 @@ type upstreamInventoryOneOf_StopHeartbeat struct { StopHeartbeat *UpstreamInventoryStopHeartbeat `protobuf:"bytes,6,opt,name=stop_heartbeat,json=stopHeartbeat,proto3,oneof"` } +type upstreamInventoryOneOf_InstanceStatus struct { + // InstanceStatus reports periodic status about the instance. + InstanceStatus *InstanceStatus `protobuf:"bytes,7,opt,name=instance_status,json=instanceStatus,proto3,oneof"` +} + func (*upstreamInventoryOneOf_Hello) isUpstreamInventoryOneOf_Msg() {} func (*upstreamInventoryOneOf_Heartbeat) isUpstreamInventoryOneOf_Msg() {} @@ -500,6 +544,8 @@ func (*upstreamInventoryOneOf_Goodbye) isUpstreamInventoryOneOf_Msg() {} func (*upstreamInventoryOneOf_StopHeartbeat) isUpstreamInventoryOneOf_Msg() {} +func (*upstreamInventoryOneOf_InstanceStatus) isUpstreamInventoryOneOf_Msg() {} + // DownstreamInventoryOneOf is the downstream message for the inventory control stream, // sent from auth servers to teleport instances. type DownstreamInventoryOneOf struct { @@ -1713,6 +1759,76 @@ func (b0 InventoryHeartbeat_builder) Build() *InventoryHeartbeat { return m0 } +// InstanceStatus reports periodic status about an instance. +type InstanceStatus struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_AuditQueue *types.AuditQueueStatus `protobuf:"bytes,1,opt,name=audit_queue,json=auditQueue,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstanceStatus) Reset() { + *x = InstanceStatus{} + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstanceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstanceStatus) ProtoMessage() {} + +func (x *InstanceStatus) ProtoReflect() protoreflect.Message { + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *InstanceStatus) GetAuditQueue() *types.AuditQueueStatus { + if x != nil { + return x.xxx_hidden_AuditQueue + } + return nil +} + +func (x *InstanceStatus) SetAuditQueue(v *types.AuditQueueStatus) { + x.xxx_hidden_AuditQueue = v +} + +func (x *InstanceStatus) HasAuditQueue() bool { + if x == nil { + return false + } + return x.xxx_hidden_AuditQueue != nil +} + +func (x *InstanceStatus) ClearAuditQueue() { + x.xxx_hidden_AuditQueue = nil +} + +type InstanceStatus_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // AuditQueue reports the depth of the instance's audit-log queue. + AuditQueue *types.AuditQueueStatus +} + +func (b0 InstanceStatus_builder) Build() *InstanceStatus { + m0 := &InstanceStatus{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_AuditQueue = b.AuditQueue + return m0 +} + // UpstreamInventoryGoodbye informs the upstream service that instance // is terminating type UpstreamInventoryGoodbye struct { @@ -1725,7 +1841,7 @@ type UpstreamInventoryGoodbye struct { func (x *UpstreamInventoryGoodbye) Reset() { *x = UpstreamInventoryGoodbye{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1737,7 +1853,7 @@ func (x *UpstreamInventoryGoodbye) String() string { func (*UpstreamInventoryGoodbye) ProtoMessage() {} func (x *UpstreamInventoryGoodbye) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[10] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1800,7 +1916,7 @@ type InventoryStatusRequest struct { func (x *InventoryStatusRequest) Reset() { *x = InventoryStatusRequest{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1812,7 +1928,7 @@ func (x *InventoryStatusRequest) String() string { func (*InventoryStatusRequest) ProtoMessage() {} func (x *InventoryStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[11] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1864,7 +1980,7 @@ type InventoryStatusSummary struct { func (x *InventoryStatusSummary) Reset() { *x = InventoryStatusSummary{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1876,7 +1992,7 @@ func (x *InventoryStatusSummary) String() string { func (*InventoryStatusSummary) ProtoMessage() {} func (x *InventoryStatusSummary) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[12] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1984,7 +2100,7 @@ type UpstreamInventoryStopHeartbeat struct { func (x *UpstreamInventoryStopHeartbeat) Reset() { *x = UpstreamInventoryStopHeartbeat{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1996,7 +2112,7 @@ func (x *UpstreamInventoryStopHeartbeat) String() string { func (*UpstreamInventoryStopHeartbeat) ProtoMessage() {} func (x *UpstreamInventoryStopHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[13] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2074,13 +2190,14 @@ type DownstreamInventoryHello_SupportedCapabilities struct { xxx_hidden_DatabaseHeartbeatGracefulStop bool `protobuf:"varint,20,opt,name=database_heartbeat_graceful_stop,json=databaseHeartbeatGracefulStop,proto3"` xxx_hidden_LinuxDesktopHeartbeats bool `protobuf:"varint,21,opt,name=linux_desktop_heartbeats,json=linuxDesktopHeartbeats,proto3"` xxx_hidden_LinuxDesktopCleanup bool `protobuf:"varint,22,opt,name=linux_desktop_cleanup,json=linuxDesktopCleanup,proto3"` + xxx_hidden_InstanceStatus bool `protobuf:"varint,23,opt,name=instance_status,json=instanceStatus,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DownstreamInventoryHello_SupportedCapabilities) Reset() { *x = DownstreamInventoryHello_SupportedCapabilities{} - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2092,7 +2209,7 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) String() string { func (*DownstreamInventoryHello_SupportedCapabilities) ProtoMessage() {} func (x *DownstreamInventoryHello_SupportedCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[14] + mi := &file_teleport_legacy_client_proto_inventory_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2257,6 +2374,13 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) GetLinuxDesktopCleanup( return false } +func (x *DownstreamInventoryHello_SupportedCapabilities) GetInstanceStatus() bool { + if x != nil { + return x.xxx_hidden_InstanceStatus + } + return false +} + func (x *DownstreamInventoryHello_SupportedCapabilities) SetProxyHeartbeats(v bool) { x.xxx_hidden_ProxyHeartbeats = v } @@ -2345,6 +2469,10 @@ func (x *DownstreamInventoryHello_SupportedCapabilities) SetLinuxDesktopCleanup( x.xxx_hidden_LinuxDesktopCleanup = v } +func (x *DownstreamInventoryHello_SupportedCapabilities) SetInstanceStatus(v bool) { + x.xxx_hidden_InstanceStatus = v +} + type DownstreamInventoryHello_SupportedCapabilities_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -2392,6 +2520,8 @@ type DownstreamInventoryHello_SupportedCapabilities_builder struct { LinuxDesktopHeartbeats bool // LinuxDesktopCleanup indicates the ICS supports deleting linux desktops when UpstreamInventoryGoodbye.DeleteResources is set. LinuxDesktopCleanup bool + // InstanceStatus indicates the ICS supports the InstanceStatus message. + InstanceStatus bool } func (b0 DownstreamInventoryHello_SupportedCapabilities_builder) Build() *DownstreamInventoryHello_SupportedCapabilities { @@ -2420,6 +2550,7 @@ func (b0 DownstreamInventoryHello_SupportedCapabilities_builder) Build() *Downst x.xxx_hidden_DatabaseHeartbeatGracefulStop = b.DatabaseHeartbeatGracefulStop x.xxx_hidden_LinuxDesktopHeartbeats = b.LinuxDesktopHeartbeats x.xxx_hidden_LinuxDesktopCleanup = b.LinuxDesktopCleanup + x.xxx_hidden_InstanceStatus = b.InstanceStatus return m0 } @@ -2427,14 +2558,15 @@ var File_teleport_legacy_client_proto_inventory_proto protoreflect.FileDescripto const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\n" + - ",teleport/legacy/client/proto/inventory.proto\x12\x05proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/legacy/types/types.proto\x1a,teleport/linuxdesktop/v1/linux_desktop.proto\x1a'teleport/presence/v1/relay_server.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xa1\x03\n" + + ",teleport/legacy/client/proto/inventory.proto\x12\x05proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!teleport/legacy/types/types.proto\x1a,teleport/linuxdesktop/v1/linux_desktop.proto\x1a'teleport/presence/v1/relay_server.proto\x1a&teleport/scopes/joining/v1/token.proto\"\xe3\x03\n" + "\x16UpstreamInventoryOneOf\x125\n" + "\x05Hello\x18\x01 \x01(\v2\x1d.proto.UpstreamInventoryHelloH\x00R\x05Hello\x129\n" + "\tHeartbeat\x18\x02 \x01(\v2\x19.proto.InventoryHeartbeatH\x00R\tHeartbeat\x122\n" + "\x04Pong\x18\x03 \x01(\v2\x1c.proto.UpstreamInventoryPongH\x00R\x04Pong\x12M\n" + "\rAgentMetadata\x18\x04 \x01(\v2%.proto.UpstreamInventoryAgentMetadataH\x00R\rAgentMetadata\x12;\n" + "\aGoodbye\x18\x05 \x01(\v2\x1f.proto.UpstreamInventoryGoodbyeH\x00R\aGoodbye\x12N\n" + - "\x0estop_heartbeat\x18\x06 \x01(\v2%.proto.UpstreamInventoryStopHeartbeatH\x00R\rstopHeartbeatB\x05\n" + + "\x0estop_heartbeat\x18\x06 \x01(\v2%.proto.UpstreamInventoryStopHeartbeatH\x00R\rstopHeartbeat\x12@\n" + + "\x0finstance_status\x18\a \x01(\v2\x15.proto.InstanceStatusH\x00R\x0einstanceStatusB\x05\n" + "\x03Msg\"\xde\x01\n" + "\x18DownstreamInventoryOneOf\x127\n" + "\x05Hello\x18\x01 \x01(\v2\x1f.proto.DownstreamInventoryHelloH\x00R\x05Hello\x124\n" + @@ -2465,12 +2597,12 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x0eInstallMethods\x18\x05 \x03(\tR\x0eInstallMethods\x12*\n" + "\x10ContainerRuntime\x18\x06 \x01(\tR\x10ContainerRuntime\x124\n" + "\x15ContainerOrchestrator\x18\a \x01(\tR\x15ContainerOrchestrator\x12*\n" + - "\x10CloudEnvironment\x18\b \x01(\tR\x10CloudEnvironment\"\x9d\n" + + "\x10CloudEnvironment\x18\b \x01(\tR\x10CloudEnvironment\"\xc6\n" + "\n" + "\x18DownstreamInventoryHello\x12\x18\n" + "\aVersion\x18\x01 \x01(\tR\aVersion\x12\x1a\n" + "\bServerID\x18\x02 \x01(\tR\bServerID\x12Y\n" + - "\fCapabilities\x18\x03 \x01(\v25.proto.DownstreamInventoryHello.SupportedCapabilitiesR\fCapabilities\x1a\xef\b\n" + + "\fCapabilities\x18\x03 \x01(\v25.proto.DownstreamInventoryHello.SupportedCapabilitiesR\fCapabilities\x1a\x98\t\n" + "\x15SupportedCapabilities\x12(\n" + "\x0fProxyHeartbeats\x18\x01 \x01(\bR\x0fProxyHeartbeats\x12\"\n" + "\fProxyCleanup\x18\x02 \x01(\bR\fProxyCleanup\x12&\n" + @@ -2496,7 +2628,8 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x1frelay_server_heartbeats_cleanup\x18\x13 \x01(\bR\x1crelayServerHeartbeatsCleanup\x12G\n" + " database_heartbeat_graceful_stop\x18\x14 \x01(\bR\x1ddatabaseHeartbeatGracefulStop\x128\n" + "\x18linux_desktop_heartbeats\x18\x15 \x01(\bR\x16linuxDesktopHeartbeats\x122\n" + - "\x15linux_desktop_cleanup\x18\x16 \x01(\bR\x13linuxDesktopCleanup\"\xea\x01\n" + + "\x15linux_desktop_cleanup\x18\x16 \x01(\bR\x13linuxDesktopCleanup\x12'\n" + + "\x0finstance_status\x18\x17 \x01(\bR\x0einstanceStatus\"\xea\x01\n" + "\x1cInventoryUpdateLabelsRequest\x12\x1a\n" + "\bServerID\x18\x01 \x01(\tR\bServerID\x12*\n" + "\x04Kind\x18\x02 \x01(\x0e2\x16.proto.LabelUpdateKindR\x04Kind\x12G\n" + @@ -2516,7 +2649,10 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "\x0eDatabaseServer\x18\x03 \x01(\v2\x17.types.DatabaseServerV3R\x0eDatabaseServer\x12E\n" + "\x10KubernetesServer\x18\x04 \x01(\v2\x19.types.KubernetesServerV3R\x10KubernetesServer\x12D\n" + "\frelay_server\x18\x05 \x01(\v2!.teleport.presence.v1.RelayServerR\vrelayServer\x12K\n" + - "\rlinux_desktop\x18\x06 \x01(\v2&.teleport.linuxdesktop.v1.LinuxDesktopR\flinuxDesktop\"d\n" + + "\rlinux_desktop\x18\x06 \x01(\v2&.teleport.linuxdesktop.v1.LinuxDesktopR\flinuxDesktop\"J\n" + + "\x0eInstanceStatus\x128\n" + + "\vaudit_queue\x18\x01 \x01(\v2\x17.types.AuditQueueStatusR\n" + + "auditQueue\"d\n" + "\x18UpstreamInventoryGoodbye\x12(\n" + "\x0fDeleteResources\x18\x01 \x01(\bR\x0fDeleteResources\x12\x1e\n" + "\n" + @@ -2550,7 +2686,7 @@ const file_teleport_legacy_client_proto_inventory_proto_rawDesc = "" + "#STOP_HEARTBEAT_KIND_DATABASE_SERVER\x10\x01B4Z2github.com/gravitational/teleport/api/client/protob\x06proto3" var file_teleport_legacy_client_proto_inventory_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_teleport_legacy_client_proto_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_teleport_legacy_client_proto_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_teleport_legacy_client_proto_inventory_proto_goTypes = []any{ (LabelUpdateKind)(0), // 0: proto.LabelUpdateKind (StopHeartbeatKind)(0), // 1: proto.StopHeartbeatKind @@ -2564,60 +2700,64 @@ var file_teleport_legacy_client_proto_inventory_proto_goTypes = []any{ (*InventoryUpdateLabelsRequest)(nil), // 9: proto.InventoryUpdateLabelsRequest (*DownstreamInventoryUpdateLabels)(nil), // 10: proto.DownstreamInventoryUpdateLabels (*InventoryHeartbeat)(nil), // 11: proto.InventoryHeartbeat - (*UpstreamInventoryGoodbye)(nil), // 12: proto.UpstreamInventoryGoodbye - (*InventoryStatusRequest)(nil), // 13: proto.InventoryStatusRequest - (*InventoryStatusSummary)(nil), // 14: proto.InventoryStatusSummary - (*UpstreamInventoryStopHeartbeat)(nil), // 15: proto.UpstreamInventoryStopHeartbeat - (*DownstreamInventoryHello_SupportedCapabilities)(nil), // 16: proto.DownstreamInventoryHello.SupportedCapabilities - nil, // 17: proto.InventoryUpdateLabelsRequest.LabelsEntry - nil, // 18: proto.DownstreamInventoryUpdateLabels.LabelsEntry - nil, // 19: proto.InventoryStatusSummary.VersionCountsEntry - nil, // 20: proto.InventoryStatusSummary.UpgraderCountsEntry - nil, // 21: proto.InventoryStatusSummary.ServiceCountsEntry - (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp - (*types.UpdaterV2Info)(nil), // 23: types.UpdaterV2Info - (*v1.ImmutableLabels)(nil), // 24: teleport.scopes.joining.v1.ImmutableLabels - (*types.ServerV2)(nil), // 25: types.ServerV2 - (*types.AppServerV3)(nil), // 26: types.AppServerV3 - (*types.DatabaseServerV3)(nil), // 27: types.DatabaseServerV3 - (*types.KubernetesServerV3)(nil), // 28: types.KubernetesServerV3 - (*v11.RelayServer)(nil), // 29: teleport.presence.v1.RelayServer - (*v12.LinuxDesktop)(nil), // 30: teleport.linuxdesktop.v1.LinuxDesktop + (*InstanceStatus)(nil), // 12: proto.InstanceStatus + (*UpstreamInventoryGoodbye)(nil), // 13: proto.UpstreamInventoryGoodbye + (*InventoryStatusRequest)(nil), // 14: proto.InventoryStatusRequest + (*InventoryStatusSummary)(nil), // 15: proto.InventoryStatusSummary + (*UpstreamInventoryStopHeartbeat)(nil), // 16: proto.UpstreamInventoryStopHeartbeat + (*DownstreamInventoryHello_SupportedCapabilities)(nil), // 17: proto.DownstreamInventoryHello.SupportedCapabilities + nil, // 18: proto.InventoryUpdateLabelsRequest.LabelsEntry + nil, // 19: proto.DownstreamInventoryUpdateLabels.LabelsEntry + nil, // 20: proto.InventoryStatusSummary.VersionCountsEntry + nil, // 21: proto.InventoryStatusSummary.UpgraderCountsEntry + nil, // 22: proto.InventoryStatusSummary.ServiceCountsEntry + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*types.UpdaterV2Info)(nil), // 24: types.UpdaterV2Info + (*v1.ImmutableLabels)(nil), // 25: teleport.scopes.joining.v1.ImmutableLabels + (*types.ServerV2)(nil), // 26: types.ServerV2 + (*types.AppServerV3)(nil), // 27: types.AppServerV3 + (*types.DatabaseServerV3)(nil), // 28: types.DatabaseServerV3 + (*types.KubernetesServerV3)(nil), // 29: types.KubernetesServerV3 + (*v11.RelayServer)(nil), // 30: teleport.presence.v1.RelayServer + (*v12.LinuxDesktop)(nil), // 31: teleport.linuxdesktop.v1.LinuxDesktop + (*types.AuditQueueStatus)(nil), // 32: types.AuditQueueStatus } var file_teleport_legacy_client_proto_inventory_proto_depIdxs = []int32{ 6, // 0: proto.UpstreamInventoryOneOf.Hello:type_name -> proto.UpstreamInventoryHello 11, // 1: proto.UpstreamInventoryOneOf.Heartbeat:type_name -> proto.InventoryHeartbeat 5, // 2: proto.UpstreamInventoryOneOf.Pong:type_name -> proto.UpstreamInventoryPong 7, // 3: proto.UpstreamInventoryOneOf.AgentMetadata:type_name -> proto.UpstreamInventoryAgentMetadata - 12, // 4: proto.UpstreamInventoryOneOf.Goodbye:type_name -> proto.UpstreamInventoryGoodbye - 15, // 5: proto.UpstreamInventoryOneOf.stop_heartbeat:type_name -> proto.UpstreamInventoryStopHeartbeat - 8, // 6: proto.DownstreamInventoryOneOf.Hello:type_name -> proto.DownstreamInventoryHello - 4, // 7: proto.DownstreamInventoryOneOf.Ping:type_name -> proto.DownstreamInventoryPing - 10, // 8: proto.DownstreamInventoryOneOf.UpdateLabels:type_name -> proto.DownstreamInventoryUpdateLabels - 22, // 9: proto.UpstreamInventoryPong.SystemClock:type_name -> google.protobuf.Timestamp - 23, // 10: proto.UpstreamInventoryHello.UpdaterInfo:type_name -> types.UpdaterV2Info - 24, // 11: proto.UpstreamInventoryHello.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels - 16, // 12: proto.DownstreamInventoryHello.Capabilities:type_name -> proto.DownstreamInventoryHello.SupportedCapabilities - 0, // 13: proto.InventoryUpdateLabelsRequest.Kind:type_name -> proto.LabelUpdateKind - 17, // 14: proto.InventoryUpdateLabelsRequest.Labels:type_name -> proto.InventoryUpdateLabelsRequest.LabelsEntry - 0, // 15: proto.DownstreamInventoryUpdateLabels.Kind:type_name -> proto.LabelUpdateKind - 18, // 16: proto.DownstreamInventoryUpdateLabels.Labels:type_name -> proto.DownstreamInventoryUpdateLabels.LabelsEntry - 25, // 17: proto.InventoryHeartbeat.SSHServer:type_name -> types.ServerV2 - 26, // 18: proto.InventoryHeartbeat.AppServer:type_name -> types.AppServerV3 - 27, // 19: proto.InventoryHeartbeat.DatabaseServer:type_name -> types.DatabaseServerV3 - 28, // 20: proto.InventoryHeartbeat.KubernetesServer:type_name -> types.KubernetesServerV3 - 29, // 21: proto.InventoryHeartbeat.relay_server:type_name -> teleport.presence.v1.RelayServer - 30, // 22: proto.InventoryHeartbeat.linux_desktop:type_name -> teleport.linuxdesktop.v1.LinuxDesktop - 6, // 23: proto.InventoryStatusSummary.Connected:type_name -> proto.UpstreamInventoryHello - 19, // 24: proto.InventoryStatusSummary.VersionCounts:type_name -> proto.InventoryStatusSummary.VersionCountsEntry - 20, // 25: proto.InventoryStatusSummary.UpgraderCounts:type_name -> proto.InventoryStatusSummary.UpgraderCountsEntry - 21, // 26: proto.InventoryStatusSummary.ServiceCounts:type_name -> proto.InventoryStatusSummary.ServiceCountsEntry - 1, // 27: proto.UpstreamInventoryStopHeartbeat.kind:type_name -> proto.StopHeartbeatKind - 28, // [28:28] is the sub-list for method output_type - 28, // [28:28] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name + 13, // 4: proto.UpstreamInventoryOneOf.Goodbye:type_name -> proto.UpstreamInventoryGoodbye + 16, // 5: proto.UpstreamInventoryOneOf.stop_heartbeat:type_name -> proto.UpstreamInventoryStopHeartbeat + 12, // 6: proto.UpstreamInventoryOneOf.instance_status:type_name -> proto.InstanceStatus + 8, // 7: proto.DownstreamInventoryOneOf.Hello:type_name -> proto.DownstreamInventoryHello + 4, // 8: proto.DownstreamInventoryOneOf.Ping:type_name -> proto.DownstreamInventoryPing + 10, // 9: proto.DownstreamInventoryOneOf.UpdateLabels:type_name -> proto.DownstreamInventoryUpdateLabels + 23, // 10: proto.UpstreamInventoryPong.SystemClock:type_name -> google.protobuf.Timestamp + 24, // 11: proto.UpstreamInventoryHello.UpdaterInfo:type_name -> types.UpdaterV2Info + 25, // 12: proto.UpstreamInventoryHello.immutable_labels:type_name -> teleport.scopes.joining.v1.ImmutableLabels + 17, // 13: proto.DownstreamInventoryHello.Capabilities:type_name -> proto.DownstreamInventoryHello.SupportedCapabilities + 0, // 14: proto.InventoryUpdateLabelsRequest.Kind:type_name -> proto.LabelUpdateKind + 18, // 15: proto.InventoryUpdateLabelsRequest.Labels:type_name -> proto.InventoryUpdateLabelsRequest.LabelsEntry + 0, // 16: proto.DownstreamInventoryUpdateLabels.Kind:type_name -> proto.LabelUpdateKind + 19, // 17: proto.DownstreamInventoryUpdateLabels.Labels:type_name -> proto.DownstreamInventoryUpdateLabels.LabelsEntry + 26, // 18: proto.InventoryHeartbeat.SSHServer:type_name -> types.ServerV2 + 27, // 19: proto.InventoryHeartbeat.AppServer:type_name -> types.AppServerV3 + 28, // 20: proto.InventoryHeartbeat.DatabaseServer:type_name -> types.DatabaseServerV3 + 29, // 21: proto.InventoryHeartbeat.KubernetesServer:type_name -> types.KubernetesServerV3 + 30, // 22: proto.InventoryHeartbeat.relay_server:type_name -> teleport.presence.v1.RelayServer + 31, // 23: proto.InventoryHeartbeat.linux_desktop:type_name -> teleport.linuxdesktop.v1.LinuxDesktop + 32, // 24: proto.InstanceStatus.audit_queue:type_name -> types.AuditQueueStatus + 6, // 25: proto.InventoryStatusSummary.Connected:type_name -> proto.UpstreamInventoryHello + 20, // 26: proto.InventoryStatusSummary.VersionCounts:type_name -> proto.InventoryStatusSummary.VersionCountsEntry + 21, // 27: proto.InventoryStatusSummary.UpgraderCounts:type_name -> proto.InventoryStatusSummary.UpgraderCountsEntry + 22, // 28: proto.InventoryStatusSummary.ServiceCounts:type_name -> proto.InventoryStatusSummary.ServiceCountsEntry + 1, // 29: proto.UpstreamInventoryStopHeartbeat.kind:type_name -> proto.StopHeartbeatKind + 30, // [30:30] is the sub-list for method output_type + 30, // [30:30] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_teleport_legacy_client_proto_inventory_proto_init() } @@ -2632,6 +2772,7 @@ func file_teleport_legacy_client_proto_inventory_proto_init() { (*upstreamInventoryOneOf_AgentMetadata)(nil), (*upstreamInventoryOneOf_Goodbye)(nil), (*upstreamInventoryOneOf_StopHeartbeat)(nil), + (*upstreamInventoryOneOf_InstanceStatus)(nil), } file_teleport_legacy_client_proto_inventory_proto_msgTypes[1].OneofWrappers = []any{ (*downstreamInventoryOneOf_Hello)(nil), @@ -2644,7 +2785,7 @@ func file_teleport_legacy_client_proto_inventory_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_legacy_client_proto_inventory_proto_rawDesc), len(file_teleport_legacy_client_proto_inventory_proto_rawDesc)), NumEnums: 2, - NumMessages: 20, + NumMessages: 21, NumExtensions: 0, NumServices: 0, }, diff --git a/api/client/proto/types.go b/api/client/proto/types.go index e7f00c436046..5cec26098f80 100644 --- a/api/client/proto/types.go +++ b/api/client/proto/types.go @@ -109,6 +109,8 @@ func (h *UpstreamInventoryGoodbye) sealedUpstreamInventoryMessage() {} func (h *UpstreamInventoryStopHeartbeat) sealedUpstreamInventoryMessage() {} +func (s *InstanceStatus) sealedUpstreamInventoryMessage() {} + // DownstreamInventoryMessage is a sealed interface representing the possible // downstream messages of the inventory controls stream after initial hello. type DownstreamInventoryMessage interface { diff --git a/api/proto/teleport/legacy/client/proto/inventory.proto b/api/proto/teleport/legacy/client/proto/inventory.proto index 0b26a1811f99..15af473f0b56 100644 --- a/api/proto/teleport/legacy/client/proto/inventory.proto +++ b/api/proto/teleport/legacy/client/proto/inventory.proto @@ -44,6 +44,8 @@ message UpstreamInventoryOneOf { // UpstreamInventoryStopHeartbeat informs the upstream service that a // heartbeat is stopping. UpstreamInventoryStopHeartbeat stop_heartbeat = 6; + // InstanceStatus reports periodic status about the instance. + InstanceStatus instance_status = 7; } } @@ -186,6 +188,8 @@ message DownstreamInventoryHello { bool linux_desktop_heartbeats = 21; // LinuxDesktopCleanup indicates the ICS supports deleting linux desktops when UpstreamInventoryGoodbye.DeleteResources is set. bool linux_desktop_cleanup = 22; + // InstanceStatus indicates the ICS supports the InstanceStatus message. + bool instance_status = 23; } // SupportedCapabilities advertises the supported features of the auth server. @@ -243,6 +247,12 @@ message InventoryHeartbeat { teleport.linuxdesktop.v1.LinuxDesktop linux_desktop = 6; } +// InstanceStatus reports periodic status about an instance. +message InstanceStatus { + // AuditQueue reports the depth of the instance's audit-log queue. + types.AuditQueueStatus audit_queue = 1; +} + // UpstreamInventoryGoodbye informs the upstream service that instance // is terminating message UpstreamInventoryGoodbye { diff --git a/api/proto/teleport/legacy/types/types.proto b/api/proto/teleport/legacy/types/types.proto index 03ca43e3adbc..10fb25e44a76 100644 --- a/api/proto/teleport/legacy/types/types.proto +++ b/api/proto/teleport/legacy/types/types.proto @@ -772,6 +772,28 @@ message InstanceSpecV1 { // UpdaterInfo stores information about how the Teleport updater is doing. UpdaterV2Info UpdaterInfo = 10; + + // AuditQueueStatus reports the depth of this instance's audit-log queue. + AuditQueueStatus audit_queue_status = 11; +} + +// AuditQueueStatus reports the depth of an instance's audit-log queue. +message AuditQueueStatus { + // PendingCount is the number of audit events waiting in the main queue. + int64 pending_count = 1; + // DeadLetterCount is the number of audit events in the dead-letter queue. + int64 dead_letter_count = 2; + // CorruptCount is the number of audit events quarantined because their + // payloads failed to deserialize. + int64 corrupt_count = 3; + // OldestPendingAgeSeconds is the age in seconds of the oldest event in the + // main queue, measured when the status was collected. Zero when the main + // queue is empty. + int64 oldest_pending_age_seconds = 4; + // OldestDeadLetterAgeSeconds is the age in seconds of the oldest event in + // the dead-letter queue, measured when the status was collected. Zero when + // the dead-letter queue is empty. + int64 oldest_dead_letter_age_seconds = 5; } // SystemClockMeasurement represents the measurement state of the systems clock difference. diff --git a/api/types/instance.go b/api/types/instance.go index 53cef8da3b48..91688df7a18f 100644 --- a/api/types/instance.go +++ b/api/types/instance.go @@ -193,6 +193,10 @@ type Instance interface { // GetUpdaterInfo returns information about the instance updater. GetUpdaterInfo() *UpdaterV2Info + // GetAuditQueueStatus returns the depth of the instance's audit-log queue, + // or nil if the instance has not reported it. + GetAuditQueueStatus() *AuditQueueStatus + // Clone performs a deep copy on this instance. Clone() Instance } @@ -321,6 +325,13 @@ func (i *InstanceV1) GetLastMeasurement() *SystemClockMeasurement { return i.Spec.LastMeasurement } +func (i *InstanceV1) GetAuditQueueStatus() *AuditQueueStatus { + if i == nil { + return nil + } + return i.Spec.AuditQueueStatus +} + // expireControlLog removes expired entries from the control log relative to the supplied // "now" value. The supplied ttl is used as the default ttl for entries that do not specify // a custom ttl value. The returned timestamp is the observed expiry that was furthest in diff --git a/api/types/types.pb.go b/api/types/types.pb.go index 22388944a77a..66ab7a450bec 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -1309,7 +1309,7 @@ func (x CertAuthoritySpecV2_SigningAlgType) String() string { } func (CertAuthoritySpecV2_SigningAlgType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{66, 0} + return fileDescriptor_9198ee693835762e, []int{67, 0} } // FIPSEndpointState represents an AWS FIPS endpoint state. @@ -1342,7 +1342,7 @@ func (x ClusterAuditConfigSpecV2_FIPSEndpointState) String() string { } func (ClusterAuditConfigSpecV2_FIPSEndpointState) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{96, 0} + return fileDescriptor_9198ee693835762e, []int{97, 0} } // TraceType is an identification of the checkpoint. @@ -1412,7 +1412,7 @@ func (x ConnectionDiagnosticTrace_TraceType) String() string { } func (ConnectionDiagnosticTrace_TraceType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{294, 0} + return fileDescriptor_9198ee693835762e, []int{295, 0} } // StatusType describes whether this was a success or a failure. @@ -1441,7 +1441,7 @@ func (x ConnectionDiagnosticTrace_StatusType) String() string { } func (ConnectionDiagnosticTrace_StatusType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{294, 1} + return fileDescriptor_9198ee693835762e, []int{295, 1} } // OktaAssignmentStatus represents the status of an Okta assignment. @@ -1481,7 +1481,7 @@ func (x OktaAssignmentSpecV1_OktaAssignmentStatus) String() string { } func (OktaAssignmentSpecV1_OktaAssignmentStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{389, 0} + return fileDescriptor_9198ee693835762e, []int{390, 0} } // OktaAssignmentTargetType is the type of Okta object that an assignment is targeting. @@ -1513,7 +1513,7 @@ func (x OktaAssignmentTargetV1_OktaAssignmentTargetType) String() string { } func (OktaAssignmentTargetV1_OktaAssignmentTargetType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{390, 0} + return fileDescriptor_9198ee693835762e, []int{391, 0} } type KeepAlive struct { @@ -3186,10 +3186,12 @@ type InstanceSpecV1 struct { // LastMeasurement stores information about the latest measurement between services. LastMeasurement *SystemClockMeasurement `protobuf:"bytes,9,opt,name=LastMeasurement,proto3" json:"LastMeasurement,omitempty"` // UpdaterInfo stores information about how the Teleport updater is doing. - UpdaterInfo *UpdaterV2Info `protobuf:"bytes,10,opt,name=UpdaterInfo,proto3" json:"UpdaterInfo,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + UpdaterInfo *UpdaterV2Info `protobuf:"bytes,10,opt,name=UpdaterInfo,proto3" json:"UpdaterInfo,omitempty"` + // AuditQueueStatus reports the depth of this instance's audit-log queue. + AuditQueueStatus *AuditQueueStatus `protobuf:"bytes,11,opt,name=audit_queue_status,json=auditQueueStatus,proto3" json:"audit_queue_status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *InstanceSpecV1) Reset() { *m = InstanceSpecV1{} } @@ -3225,6 +3227,61 @@ func (m *InstanceSpecV1) XXX_DiscardUnknown() { var xxx_messageInfo_InstanceSpecV1 proto.InternalMessageInfo +// AuditQueueStatus reports the depth of an instance's audit-log queue. +type AuditQueueStatus struct { + // PendingCount is the number of audit events waiting in the main queue. + PendingCount int64 `protobuf:"varint,1,opt,name=pending_count,json=pendingCount,proto3" json:"pending_count,omitempty"` + // DeadLetterCount is the number of audit events in the dead-letter queue. + DeadLetterCount int64 `protobuf:"varint,2,opt,name=dead_letter_count,json=deadLetterCount,proto3" json:"dead_letter_count,omitempty"` + // CorruptCount is the number of audit events quarantined because their + // payloads failed to deserialize. + CorruptCount int64 `protobuf:"varint,3,opt,name=corrupt_count,json=corruptCount,proto3" json:"corrupt_count,omitempty"` + // OldestPendingAgeSeconds is the age in seconds of the oldest event in the + // main queue, measured when the status was collected. Zero when the main + // queue is empty. + OldestPendingAgeSeconds int64 `protobuf:"varint,4,opt,name=oldest_pending_age_seconds,json=oldestPendingAgeSeconds,proto3" json:"oldest_pending_age_seconds,omitempty"` + // OldestDeadLetterAgeSeconds is the age in seconds of the oldest event in + // the dead-letter queue, measured when the status was collected. Zero when + // the dead-letter queue is empty. + OldestDeadLetterAgeSeconds int64 `protobuf:"varint,5,opt,name=oldest_dead_letter_age_seconds,json=oldestDeadLetterAgeSeconds,proto3" json:"oldest_dead_letter_age_seconds,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AuditQueueStatus) Reset() { *m = AuditQueueStatus{} } +func (m *AuditQueueStatus) String() string { return proto.CompactTextString(m) } +func (*AuditQueueStatus) ProtoMessage() {} +func (*AuditQueueStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_9198ee693835762e, []int{34} +} +func (m *AuditQueueStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AuditQueueStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AuditQueueStatus.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 *AuditQueueStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuditQueueStatus.Merge(m, src) +} +func (m *AuditQueueStatus) XXX_Size() int { + return m.Size() +} +func (m *AuditQueueStatus) XXX_DiscardUnknown() { + xxx_messageInfo_AuditQueueStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_AuditQueueStatus proto.InternalMessageInfo + // SystemClockMeasurement represents the measurement state of the systems clock difference. type SystemClockMeasurement struct { // ControllerSystemClock is the system clock of the inventory controller. @@ -3242,7 +3299,7 @@ func (m *SystemClockMeasurement) Reset() { *m = SystemClockMeasurement{} func (m *SystemClockMeasurement) String() string { return proto.CompactTextString(m) } func (*SystemClockMeasurement) ProtoMessage() {} func (*SystemClockMeasurement) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{34} + return fileDescriptor_9198ee693835762e, []int{35} } func (m *SystemClockMeasurement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3312,7 +3369,7 @@ func (m *InstanceControlLogEntry) Reset() { *m = InstanceControlLogEntry func (m *InstanceControlLogEntry) String() string { return proto.CompactTextString(m) } func (*InstanceControlLogEntry) ProtoMessage() {} func (*InstanceControlLogEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{35} + return fileDescriptor_9198ee693835762e, []int{36} } func (m *InstanceControlLogEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3366,7 +3423,7 @@ func (m *UpdaterV2Info) Reset() { *m = UpdaterV2Info{} } func (m *UpdaterV2Info) String() string { return proto.CompactTextString(m) } func (*UpdaterV2Info) ProtoMessage() {} func (*UpdaterV2Info) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{36} + return fileDescriptor_9198ee693835762e, []int{37} } func (m *UpdaterV2Info) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3424,7 +3481,7 @@ func (m *InstanceFilter) Reset() { *m = InstanceFilter{} } func (m *InstanceFilter) String() string { return proto.CompactTextString(m) } func (*InstanceFilter) ProtoMessage() {} func (*InstanceFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{37} + return fileDescriptor_9198ee693835762e, []int{38} } func (m *InstanceFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3475,7 +3532,7 @@ type ServerV2 struct { func (m *ServerV2) Reset() { *m = ServerV2{} } func (*ServerV2) ProtoMessage() {} func (*ServerV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{38} + return fileDescriptor_9198ee693835762e, []int{39} } func (m *ServerV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3549,7 +3606,7 @@ func (m *ServerSpecV2) Reset() { *m = ServerSpecV2{} } func (m *ServerSpecV2) String() string { return proto.CompactTextString(m) } func (*ServerSpecV2) ProtoMessage() {} func (*ServerSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{39} + return fileDescriptor_9198ee693835762e, []int{40} } func (m *ServerSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3603,7 +3660,7 @@ func (m *AWSInfo) Reset() { *m = AWSInfo{} } func (m *AWSInfo) String() string { return proto.CompactTextString(m) } func (*AWSInfo) ProtoMessage() {} func (*AWSInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{40} + return fileDescriptor_9198ee693835762e, []int{41} } func (m *AWSInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3646,7 +3703,7 @@ func (m *CloudMetadata) Reset() { *m = CloudMetadata{} } func (m *CloudMetadata) String() string { return proto.CompactTextString(m) } func (*CloudMetadata) ProtoMessage() {} func (*CloudMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{41} + return fileDescriptor_9198ee693835762e, []int{42} } func (m *CloudMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3691,7 +3748,7 @@ func (m *GitHubServerMetadata) Reset() { *m = GitHubServerMetadata{} } func (m *GitHubServerMetadata) String() string { return proto.CompactTextString(m) } func (*GitHubServerMetadata) ProtoMessage() {} func (*GitHubServerMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{42} + return fileDescriptor_9198ee693835762e, []int{43} } func (m *GitHubServerMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3742,7 +3799,7 @@ type AppServerV3 struct { func (m *AppServerV3) Reset() { *m = AppServerV3{} } func (*AppServerV3) ProtoMessage() {} func (*AppServerV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{43} + return fileDescriptor_9198ee693835762e, []int{44} } func (m *AppServerV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3800,7 +3857,7 @@ func (m *AppServerSpecV3) Reset() { *m = AppServerSpecV3{} } func (m *AppServerSpecV3) String() string { return proto.CompactTextString(m) } func (*AppServerSpecV3) ProtoMessage() {} func (*AppServerSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{44} + return fileDescriptor_9198ee693835762e, []int{45} } func (m *AppServerSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3842,7 +3899,7 @@ func (m *AppV3List) Reset() { *m = AppV3List{} } func (m *AppV3List) String() string { return proto.CompactTextString(m) } func (*AppV3List) ProtoMessage() {} func (*AppV3List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{45} + return fileDescriptor_9198ee693835762e, []int{46} } func (m *AppV3List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3894,7 +3951,7 @@ type AppV3 struct { func (m *AppV3) Reset() { *m = AppV3{} } func (*AppV3) ProtoMessage() {} func (*AppV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{46} + return fileDescriptor_9198ee693835762e, []int{47} } func (m *AppV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3946,7 +4003,7 @@ func (m *CORSPolicy) Reset() { *m = CORSPolicy{} } func (m *CORSPolicy) String() string { return proto.CompactTextString(m) } func (*CORSPolicy) ProtoMessage() {} func (*CORSPolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{47} + return fileDescriptor_9198ee693835762e, []int{48} } func (m *CORSPolicy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3994,7 +4051,7 @@ func (m *IdentityCenterPermissionSet) Reset() { *m = IdentityCenterPermi func (m *IdentityCenterPermissionSet) String() string { return proto.CompactTextString(m) } func (*IdentityCenterPermissionSet) ProtoMessage() {} func (*IdentityCenterPermissionSet) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{48} + return fileDescriptor_9198ee693835762e, []int{49} } func (m *IdentityCenterPermissionSet) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4039,7 +4096,7 @@ func (m *AppIdentityCenter) Reset() { *m = AppIdentityCenter{} } func (m *AppIdentityCenter) String() string { return proto.CompactTextString(m) } func (*AppIdentityCenter) ProtoMessage() {} func (*AppIdentityCenter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{49} + return fileDescriptor_9198ee693835762e, []int{50} } func (m *AppIdentityCenter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4123,7 +4180,7 @@ func (m *AppSpecV3) Reset() { *m = AppSpecV3{} } func (m *AppSpecV3) String() string { return proto.CompactTextString(m) } func (*AppSpecV3) ProtoMessage() {} func (*AppSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{50} + return fileDescriptor_9198ee693835762e, []int{51} } func (m *AppSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4170,7 +4227,7 @@ func (m *MCP) Reset() { *m = MCP{} } func (m *MCP) String() string { return proto.CompactTextString(m) } func (*MCP) ProtoMessage() {} func (*MCP) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{51} + return fileDescriptor_9198ee693835762e, []int{52} } func (m *MCP) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4218,7 +4275,7 @@ func (m *Rewrite) Reset() { *m = Rewrite{} } func (m *Rewrite) String() string { return proto.CompactTextString(m) } func (*Rewrite) ProtoMessage() {} func (*Rewrite) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{52} + return fileDescriptor_9198ee693835762e, []int{53} } func (m *Rewrite) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4262,7 +4319,7 @@ func (m *Header) Reset() { *m = Header{} } func (m *Header) String() string { return proto.CompactTextString(m) } func (*Header) ProtoMessage() {} func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{53} + return fileDescriptor_9198ee693835762e, []int{54} } func (m *Header) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4309,7 +4366,7 @@ type PortRange struct { func (m *PortRange) Reset() { *m = PortRange{} } func (*PortRange) ProtoMessage() {} func (*PortRange) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{54} + return fileDescriptor_9198ee693835762e, []int{55} } func (m *PortRange) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4359,7 +4416,7 @@ func (m *LLM) Reset() { *m = LLM{} } func (m *LLM) String() string { return proto.CompactTextString(m) } func (*LLM) ProtoMessage() {} func (*LLM) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{55} + return fileDescriptor_9198ee693835762e, []int{56} } func (m *LLM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4403,7 +4460,7 @@ func (m *LLM_Model) Reset() { *m = LLM_Model{} } func (m *LLM_Model) String() string { return proto.CompactTextString(m) } func (*LLM_Model) ProtoMessage() {} func (*LLM_Model) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{55, 0} + return fileDescriptor_9198ee693835762e, []int{56, 0} } func (m *LLM_Model) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4468,7 +4525,7 @@ func (m *AppTLS) Reset() { *m = AppTLS{} } func (m *AppTLS) String() string { return proto.CompactTextString(m) } func (*AppTLS) ProtoMessage() {} func (*AppTLS) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{56} + return fileDescriptor_9198ee693835762e, []int{57} } func (m *AppTLS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4515,7 +4572,7 @@ func (m *CommandLabelV2) Reset() { *m = CommandLabelV2{} } func (m *CommandLabelV2) String() string { return proto.CompactTextString(m) } func (*CommandLabelV2) ProtoMessage() {} func (*CommandLabelV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{57} + return fileDescriptor_9198ee693835762e, []int{58} } func (m *CommandLabelV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4563,7 +4620,7 @@ func (m *AppAWS) Reset() { *m = AppAWS{} } func (m *AppAWS) String() string { return proto.CompactTextString(m) } func (*AppAWS) ProtoMessage() {} func (*AppAWS) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{58} + return fileDescriptor_9198ee693835762e, []int{59} } func (m *AppAWS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4611,7 +4668,7 @@ func (m *AppAWSRolesAnywhereProfile) Reset() { *m = AppAWSRolesAnywhereP func (m *AppAWSRolesAnywhereProfile) String() string { return proto.CompactTextString(m) } func (*AppAWSRolesAnywhereProfile) ProtoMessage() {} func (*AppAWSRolesAnywhereProfile) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{59} + return fileDescriptor_9198ee693835762e, []int{60} } func (m *AppAWSRolesAnywhereProfile) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4657,7 +4714,7 @@ func (m *SSHKeyPair) Reset() { *m = SSHKeyPair{} } func (m *SSHKeyPair) String() string { return proto.CompactTextString(m) } func (*SSHKeyPair) ProtoMessage() {} func (*SSHKeyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{60} + return fileDescriptor_9198ee693835762e, []int{61} } func (m *SSHKeyPair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4705,7 +4762,7 @@ func (m *TLSKeyPair) Reset() { *m = TLSKeyPair{} } func (m *TLSKeyPair) String() string { return proto.CompactTextString(m) } func (*TLSKeyPair) ProtoMessage() {} func (*TLSKeyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{61} + return fileDescriptor_9198ee693835762e, []int{62} } func (m *TLSKeyPair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4751,7 +4808,7 @@ func (m *JWTKeyPair) Reset() { *m = JWTKeyPair{} } func (m *JWTKeyPair) String() string { return proto.CompactTextString(m) } func (*JWTKeyPair) ProtoMessage() {} func (*JWTKeyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{62} + return fileDescriptor_9198ee693835762e, []int{63} } func (m *JWTKeyPair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4800,7 +4857,7 @@ func (m *EncryptionKeyPair) Reset() { *m = EncryptionKeyPair{} } func (m *EncryptionKeyPair) String() string { return proto.CompactTextString(m) } func (*EncryptionKeyPair) ProtoMessage() {} func (*EncryptionKeyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{63} + return fileDescriptor_9198ee693835762e, []int{64} } func (m *EncryptionKeyPair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4842,7 +4899,7 @@ func (m *AgeEncryptionKey) Reset() { *m = AgeEncryptionKey{} } func (m *AgeEncryptionKey) String() string { return proto.CompactTextString(m) } func (*AgeEncryptionKey) ProtoMessage() {} func (*AgeEncryptionKey) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{64} + return fileDescriptor_9198ee693835762e, []int{65} } func (m *AgeEncryptionKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4891,7 +4948,7 @@ type CertAuthorityV2 struct { func (m *CertAuthorityV2) Reset() { *m = CertAuthorityV2{} } func (*CertAuthorityV2) ProtoMessage() {} func (*CertAuthorityV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{65} + return fileDescriptor_9198ee693835762e, []int{66} } func (m *CertAuthorityV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4952,7 +5009,7 @@ func (m *CertAuthoritySpecV2) Reset() { *m = CertAuthoritySpecV2{} } func (m *CertAuthoritySpecV2) String() string { return proto.CompactTextString(m) } func (*CertAuthoritySpecV2) ProtoMessage() {} func (*CertAuthoritySpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{66} + return fileDescriptor_9198ee693835762e, []int{67} } func (m *CertAuthoritySpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4998,7 +5055,7 @@ func (m *CAKeySet) Reset() { *m = CAKeySet{} } func (m *CAKeySet) String() string { return proto.CompactTextString(m) } func (*CAKeySet) ProtoMessage() {} func (*CAKeySet) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{67} + return fileDescriptor_9198ee693835762e, []int{68} } func (m *CAKeySet) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5043,7 +5100,7 @@ func (m *RoleMapping) Reset() { *m = RoleMapping{} } func (m *RoleMapping) String() string { return proto.CompactTextString(m) } func (*RoleMapping) ProtoMessage() {} func (*RoleMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{68} + return fileDescriptor_9198ee693835762e, []int{69} } func (m *RoleMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5091,7 +5148,7 @@ type ProvisionTokenV1 struct { func (m *ProvisionTokenV1) Reset() { *m = ProvisionTokenV1{} } func (*ProvisionTokenV1) ProtoMessage() {} func (*ProvisionTokenV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{69} + return fileDescriptor_9198ee693835762e, []int{70} } func (m *ProvisionTokenV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5144,7 +5201,7 @@ type ProvisionTokenV2 struct { func (m *ProvisionTokenV2) Reset() { *m = ProvisionTokenV2{} } func (*ProvisionTokenV2) ProtoMessage() {} func (*ProvisionTokenV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{70} + return fileDescriptor_9198ee693835762e, []int{71} } func (m *ProvisionTokenV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5186,7 +5243,7 @@ func (m *ProvisionTokenV2List) Reset() { *m = ProvisionTokenV2List{} } func (m *ProvisionTokenV2List) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenV2List) ProtoMessage() {} func (*ProvisionTokenV2List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{71} + return fileDescriptor_9198ee693835762e, []int{72} } func (m *ProvisionTokenV2List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5245,7 +5302,7 @@ func (m *TokenRule) Reset() { *m = TokenRule{} } func (m *TokenRule) String() string { return proto.CompactTextString(m) } func (*TokenRule) ProtoMessage() {} func (*TokenRule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{72} + return fileDescriptor_9198ee693835762e, []int{73} } func (m *TokenRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5342,7 +5399,7 @@ func (m *ProvisionTokenSpecV2) Reset() { *m = ProvisionTokenSpecV2{} } func (m *ProvisionTokenSpecV2) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2) ProtoMessage() {} func (*ProvisionTokenSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{73} + return fileDescriptor_9198ee693835762e, []int{74} } func (m *ProvisionTokenSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5391,7 +5448,7 @@ func (m *ProvisionTokenSpecV2AzureDevops) Reset() { *m = ProvisionTokenS func (m *ProvisionTokenSpecV2AzureDevops) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2AzureDevops) ProtoMessage() {} func (*ProvisionTokenSpecV2AzureDevops) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{74} + return fileDescriptor_9198ee693835762e, []int{75} } func (m *ProvisionTokenSpecV2AzureDevops) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5463,7 +5520,7 @@ func (m *ProvisionTokenSpecV2AzureDevops_Rule) Reset() { *m = ProvisionT func (m *ProvisionTokenSpecV2AzureDevops_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2AzureDevops_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2AzureDevops_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{74, 0} + return fileDescriptor_9198ee693835762e, []int{75, 0} } func (m *ProvisionTokenSpecV2AzureDevops_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5515,7 +5572,7 @@ func (m *ProvisionTokenSpecV2TPM) Reset() { *m = ProvisionTokenSpecV2TPM func (m *ProvisionTokenSpecV2TPM) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2TPM) ProtoMessage() {} func (*ProvisionTokenSpecV2TPM) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{75} + return fileDescriptor_9198ee693835762e, []int{76} } func (m *ProvisionTokenSpecV2TPM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5571,7 +5628,7 @@ func (m *ProvisionTokenSpecV2TPM_Rule) Reset() { *m = ProvisionTokenSpec func (m *ProvisionTokenSpecV2TPM_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2TPM_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2TPM_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{75, 0} + return fileDescriptor_9198ee693835762e, []int{76, 0} } func (m *ProvisionTokenSpecV2TPM_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5640,7 +5697,7 @@ func (m *ProvisionTokenSpecV2GitHub) Reset() { *m = ProvisionTokenSpecV2 func (m *ProvisionTokenSpecV2GitHub) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GitHub) ProtoMessage() {} func (*ProvisionTokenSpecV2GitHub) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{76} + return fileDescriptor_9198ee693835762e, []int{77} } func (m *ProvisionTokenSpecV2GitHub) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5734,7 +5791,7 @@ func (m *ProvisionTokenSpecV2GitHub_Rule) Reset() { *m = ProvisionTokenS func (m *ProvisionTokenSpecV2GitHub_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GitHub_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2GitHub_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{76, 0} + return fileDescriptor_9198ee693835762e, []int{77, 0} } func (m *ProvisionTokenSpecV2GitHub_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5787,7 +5844,7 @@ func (m *ProvisionTokenSpecV2GitLab) Reset() { *m = ProvisionTokenSpecV2 func (m *ProvisionTokenSpecV2GitLab) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GitLab) ProtoMessage() {} func (*ProvisionTokenSpecV2GitLab) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{77} + return fileDescriptor_9198ee693835762e, []int{78} } func (m *ProvisionTokenSpecV2GitLab) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5889,7 +5946,7 @@ func (m *ProvisionTokenSpecV2GitLab_Rule) Reset() { *m = ProvisionTokenS func (m *ProvisionTokenSpecV2GitLab_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GitLab_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2GitLab_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{77, 0} + return fileDescriptor_9198ee693835762e, []int{78, 0} } func (m *ProvisionTokenSpecV2GitLab_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5934,7 +5991,7 @@ func (m *ProvisionTokenSpecV2CircleCI) Reset() { *m = ProvisionTokenSpec func (m *ProvisionTokenSpecV2CircleCI) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2CircleCI) ProtoMessage() {} func (*ProvisionTokenSpecV2CircleCI) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{78} + return fileDescriptor_9198ee693835762e, []int{79} } func (m *ProvisionTokenSpecV2CircleCI) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5975,7 +6032,7 @@ func (m *ProvisionTokenSpecV2CircleCI_Rule) Reset() { *m = ProvisionToke func (m *ProvisionTokenSpecV2CircleCI_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2CircleCI_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2CircleCI_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{78, 0} + return fileDescriptor_9198ee693835762e, []int{79, 0} } func (m *ProvisionTokenSpecV2CircleCI_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6025,7 +6082,7 @@ func (m *ProvisionTokenSpecV2Spacelift) Reset() { *m = ProvisionTokenSpe func (m *ProvisionTokenSpecV2Spacelift) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Spacelift) ProtoMessage() {} func (*ProvisionTokenSpecV2Spacelift) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{79} + return fileDescriptor_9198ee693835762e, []int{80} } func (m *ProvisionTokenSpecV2Spacelift) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6084,7 +6141,7 @@ func (m *ProvisionTokenSpecV2Spacelift_Rule) Reset() { *m = ProvisionTok func (m *ProvisionTokenSpecV2Spacelift_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Spacelift_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Spacelift_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{79, 0} + return fileDescriptor_9198ee693835762e, []int{80, 0} } func (m *ProvisionTokenSpecV2Spacelift_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6139,7 +6196,7 @@ func (m *ProvisionTokenSpecV2Kubernetes) Reset() { *m = ProvisionTokenSp func (m *ProvisionTokenSpecV2Kubernetes) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Kubernetes) ProtoMessage() {} func (*ProvisionTokenSpecV2Kubernetes) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{80} + return fileDescriptor_9198ee693835762e, []int{81} } func (m *ProvisionTokenSpecV2Kubernetes) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6186,7 +6243,7 @@ func (m *ProvisionTokenSpecV2Kubernetes_StaticJWKSConfig) String() string { } func (*ProvisionTokenSpecV2Kubernetes_StaticJWKSConfig) ProtoMessage() {} func (*ProvisionTokenSpecV2Kubernetes_StaticJWKSConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{80, 0} + return fileDescriptor_9198ee693835762e, []int{81, 0} } func (m *ProvisionTokenSpecV2Kubernetes_StaticJWKSConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6238,7 +6295,7 @@ func (m *ProvisionTokenSpecV2Kubernetes_OIDCConfig) String() string { } func (*ProvisionTokenSpecV2Kubernetes_OIDCConfig) ProtoMessage() {} func (*ProvisionTokenSpecV2Kubernetes_OIDCConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{80, 1} + return fileDescriptor_9198ee693835762e, []int{81, 1} } func (m *ProvisionTokenSpecV2Kubernetes_OIDCConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6298,7 +6355,7 @@ func (m *ProvisionTokenSpecV2Kubernetes_Rule) Reset() { *m = ProvisionTo func (m *ProvisionTokenSpecV2Kubernetes_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Kubernetes_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Kubernetes_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{80, 2} + return fileDescriptor_9198ee693835762e, []int{81, 2} } func (m *ProvisionTokenSpecV2Kubernetes_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6342,7 +6399,7 @@ func (m *ProvisionTokenSpecV2Azure) Reset() { *m = ProvisionTokenSpecV2A func (m *ProvisionTokenSpecV2Azure) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Azure) ProtoMessage() {} func (*ProvisionTokenSpecV2Azure) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{81} + return fileDescriptor_9198ee693835762e, []int{82} } func (m *ProvisionTokenSpecV2Azure) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6390,7 +6447,7 @@ func (m *ProvisionTokenSpecV2Azure_Rule) Reset() { *m = ProvisionTokenSp func (m *ProvisionTokenSpecV2Azure_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Azure_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Azure_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{81, 0} + return fileDescriptor_9198ee693835762e, []int{82, 0} } func (m *ProvisionTokenSpecV2Azure_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6434,7 +6491,7 @@ func (m *ProvisionTokenSpecV2GCP) Reset() { *m = ProvisionTokenSpecV2GCP func (m *ProvisionTokenSpecV2GCP) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GCP) ProtoMessage() {} func (*ProvisionTokenSpecV2GCP) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{82} + return fileDescriptor_9198ee693835762e, []int{83} } func (m *ProvisionTokenSpecV2GCP) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6483,7 +6540,7 @@ func (m *ProvisionTokenSpecV2GCP_Rule) Reset() { *m = ProvisionTokenSpec func (m *ProvisionTokenSpecV2GCP_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GCP_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2GCP_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{82, 0} + return fileDescriptor_9198ee693835762e, []int{83, 0} } func (m *ProvisionTokenSpecV2GCP_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6540,7 +6597,7 @@ func (m *ProvisionTokenSpecV2TerraformCloud) Reset() { *m = ProvisionTok func (m *ProvisionTokenSpecV2TerraformCloud) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2TerraformCloud) ProtoMessage() {} func (*ProvisionTokenSpecV2TerraformCloud) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{83} + return fileDescriptor_9198ee693835762e, []int{84} } func (m *ProvisionTokenSpecV2TerraformCloud) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6605,7 +6662,7 @@ func (m *ProvisionTokenSpecV2TerraformCloud_Rule) Reset() { func (m *ProvisionTokenSpecV2TerraformCloud_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2TerraformCloud_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2TerraformCloud_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{83, 0} + return fileDescriptor_9198ee693835762e, []int{84, 0} } func (m *ProvisionTokenSpecV2TerraformCloud_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6657,7 +6714,7 @@ func (m *ProvisionTokenSpecV2Bitbucket) Reset() { *m = ProvisionTokenSpe func (m *ProvisionTokenSpecV2Bitbucket) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Bitbucket) ProtoMessage() {} func (*ProvisionTokenSpecV2Bitbucket) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{84} + return fileDescriptor_9198ee693835762e, []int{85} } func (m *ProvisionTokenSpecV2Bitbucket) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6715,7 +6772,7 @@ func (m *ProvisionTokenSpecV2Bitbucket_Rule) Reset() { *m = ProvisionTok func (m *ProvisionTokenSpecV2Bitbucket_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Bitbucket_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Bitbucket_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{84, 0} + return fileDescriptor_9198ee693835762e, []int{85, 0} } func (m *ProvisionTokenSpecV2Bitbucket_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6759,7 +6816,7 @@ func (m *ProvisionTokenSpecV2Oracle) Reset() { *m = ProvisionTokenSpecV2 func (m *ProvisionTokenSpecV2Oracle) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Oracle) ProtoMessage() {} func (*ProvisionTokenSpecV2Oracle) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{85} + return fileDescriptor_9198ee693835762e, []int{86} } func (m *ProvisionTokenSpecV2Oracle) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6814,7 +6871,7 @@ func (m *ProvisionTokenSpecV2Oracle_Rule) Reset() { *m = ProvisionTokenS func (m *ProvisionTokenSpecV2Oracle_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Oracle_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Oracle_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{85, 0} + return fileDescriptor_9198ee693835762e, []int{86, 0} } func (m *ProvisionTokenSpecV2Oracle_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6858,7 +6915,7 @@ func (m *ProvisionTokenSpecV2Env0) Reset() { *m = ProvisionTokenSpecV2En func (m *ProvisionTokenSpecV2Env0) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Env0) ProtoMessage() {} func (*ProvisionTokenSpecV2Env0) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{86} + return fileDescriptor_9198ee693835762e, []int{87} } func (m *ProvisionTokenSpecV2Env0) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6932,7 +6989,7 @@ func (m *ProvisionTokenSpecV2Env0_Rule) Reset() { *m = ProvisionTokenSpe func (m *ProvisionTokenSpecV2Env0_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2Env0_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2Env0_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{86, 0} + return fileDescriptor_9198ee693835762e, []int{87, 0} } func (m *ProvisionTokenSpecV2Env0_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7037,7 +7094,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC) Reset() { *m = ProvisionTokenS func (m *ProvisionTokenSpecV2GenericOIDC) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GenericOIDC) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87} + return fileDescriptor_9198ee693835762e, []int{88} } func (m *ProvisionTokenSpecV2GenericOIDC) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7083,7 +7140,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_ConditionEq) String() string { } func (*ProvisionTokenSpecV2GenericOIDC_ConditionEq) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_ConditionEq) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 0} + return fileDescriptor_9198ee693835762e, []int{88, 0} } func (m *ProvisionTokenSpecV2GenericOIDC_ConditionEq) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7129,7 +7186,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_ConditionNotEq) String() string { } func (*ProvisionTokenSpecV2GenericOIDC_ConditionNotEq) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_ConditionNotEq) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 1} + return fileDescriptor_9198ee693835762e, []int{88, 1} } func (m *ProvisionTokenSpecV2GenericOIDC_ConditionNotEq) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7175,7 +7232,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_ConditionIn) String() string { } func (*ProvisionTokenSpecV2GenericOIDC_ConditionIn) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_ConditionIn) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 2} + return fileDescriptor_9198ee693835762e, []int{88, 2} } func (m *ProvisionTokenSpecV2GenericOIDC_ConditionIn) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7221,7 +7278,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_ConditionNotIn) String() string { } func (*ProvisionTokenSpecV2GenericOIDC_ConditionNotIn) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_ConditionNotIn) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 3} + return fileDescriptor_9198ee693835762e, []int{88, 3} } func (m *ProvisionTokenSpecV2GenericOIDC_ConditionNotIn) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7276,7 +7333,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_Condition) String() string { } func (*ProvisionTokenSpecV2GenericOIDC_Condition) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_Condition) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 4} + return fileDescriptor_9198ee693835762e, []int{88, 4} } func (m *ProvisionTokenSpecV2GenericOIDC_Condition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7326,7 +7383,7 @@ func (m *ProvisionTokenSpecV2GenericOIDC_Rule) Reset() { *m = ProvisionT func (m *ProvisionTokenSpecV2GenericOIDC_Rule) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2GenericOIDC_Rule) ProtoMessage() {} func (*ProvisionTokenSpecV2GenericOIDC_Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{87, 5} + return fileDescriptor_9198ee693835762e, []int{88, 5} } func (m *ProvisionTokenSpecV2GenericOIDC_Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7378,7 +7435,7 @@ func (m *ProvisionTokenSpecV2BoundKeypair) Reset() { *m = ProvisionToken func (m *ProvisionTokenSpecV2BoundKeypair) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenSpecV2BoundKeypair) ProtoMessage() {} func (*ProvisionTokenSpecV2BoundKeypair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{88} + return fileDescriptor_9198ee693835762e, []int{89} } func (m *ProvisionTokenSpecV2BoundKeypair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7444,7 +7501,7 @@ func (m *ProvisionTokenSpecV2BoundKeypair_OnboardingSpec) String() string { } func (*ProvisionTokenSpecV2BoundKeypair_OnboardingSpec) ProtoMessage() {} func (*ProvisionTokenSpecV2BoundKeypair_OnboardingSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{88, 0} + return fileDescriptor_9198ee693835762e, []int{89, 0} } func (m *ProvisionTokenSpecV2BoundKeypair_OnboardingSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7512,7 +7569,7 @@ func (m *ProvisionTokenSpecV2BoundKeypair_RecoverySpec) String() string { } func (*ProvisionTokenSpecV2BoundKeypair_RecoverySpec) ProtoMessage() {} func (*ProvisionTokenSpecV2BoundKeypair_RecoverySpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{88, 1} + return fileDescriptor_9198ee693835762e, []int{89, 1} } func (m *ProvisionTokenSpecV2BoundKeypair_RecoverySpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7556,7 +7613,7 @@ func (m *ProvisionTokenStatusV2) Reset() { *m = ProvisionTokenStatusV2{} func (m *ProvisionTokenStatusV2) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenStatusV2) ProtoMessage() {} func (*ProvisionTokenStatusV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{89} + return fileDescriptor_9198ee693835762e, []int{90} } func (m *ProvisionTokenStatusV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7635,7 +7692,7 @@ func (m *ProvisionTokenStatusV2BoundKeypair) Reset() { *m = ProvisionTok func (m *ProvisionTokenStatusV2BoundKeypair) String() string { return proto.CompactTextString(m) } func (*ProvisionTokenStatusV2BoundKeypair) ProtoMessage() {} func (*ProvisionTokenStatusV2BoundKeypair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{90} + return fileDescriptor_9198ee693835762e, []int{91} } func (m *ProvisionTokenStatusV2BoundKeypair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7684,7 +7741,7 @@ type StaticTokensV2 struct { func (m *StaticTokensV2) Reset() { *m = StaticTokensV2{} } func (*StaticTokensV2) ProtoMessage() {} func (*StaticTokensV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{91} + return fileDescriptor_9198ee693835762e, []int{92} } func (m *StaticTokensV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7727,7 +7784,7 @@ func (m *StaticTokensSpecV2) Reset() { *m = StaticTokensSpecV2{} } func (m *StaticTokensSpecV2) String() string { return proto.CompactTextString(m) } func (*StaticTokensSpecV2) ProtoMessage() {} func (*StaticTokensSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{92} + return fileDescriptor_9198ee693835762e, []int{93} } func (m *StaticTokensSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7776,7 +7833,7 @@ type ClusterNameV2 struct { func (m *ClusterNameV2) Reset() { *m = ClusterNameV2{} } func (*ClusterNameV2) ProtoMessage() {} func (*ClusterNameV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{93} + return fileDescriptor_9198ee693835762e, []int{94} } func (m *ClusterNameV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7822,7 +7879,7 @@ func (m *ClusterNameSpecV2) Reset() { *m = ClusterNameSpecV2{} } func (m *ClusterNameSpecV2) String() string { return proto.CompactTextString(m) } func (*ClusterNameSpecV2) ProtoMessage() {} func (*ClusterNameSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{94} + return fileDescriptor_9198ee693835762e, []int{95} } func (m *ClusterNameSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7872,7 +7929,7 @@ func (m *ClusterAuditConfigV2) Reset() { *m = ClusterAuditConfigV2{} } func (m *ClusterAuditConfigV2) String() string { return proto.CompactTextString(m) } func (*ClusterAuditConfigV2) ProtoMessage() {} func (*ClusterAuditConfigV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{95} + return fileDescriptor_9198ee693835762e, []int{96} } func (m *ClusterAuditConfigV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7942,7 +7999,7 @@ func (m *ClusterAuditConfigSpecV2) Reset() { *m = ClusterAuditConfigSpec func (m *ClusterAuditConfigSpecV2) String() string { return proto.CompactTextString(m) } func (*ClusterAuditConfigSpecV2) ProtoMessage() {} func (*ClusterAuditConfigSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{96} + return fileDescriptor_9198ee693835762e, []int{97} } func (m *ClusterAuditConfigSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -7993,7 +8050,7 @@ func (m *ClusterNetworkingConfigV2) Reset() { *m = ClusterNetworkingConf func (m *ClusterNetworkingConfigV2) String() string { return proto.CompactTextString(m) } func (*ClusterNetworkingConfigV2) ProtoMessage() {} func (*ClusterNetworkingConfigV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{97} + return fileDescriptor_9198ee693835762e, []int{98} } func (m *ClusterNetworkingConfigV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8072,7 +8129,7 @@ func (m *ClusterNetworkingConfigSpecV2) Reset() { *m = ClusterNetworking func (m *ClusterNetworkingConfigSpecV2) String() string { return proto.CompactTextString(m) } func (*ClusterNetworkingConfigSpecV2) ProtoMessage() {} func (*ClusterNetworkingConfigSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{98} + return fileDescriptor_9198ee693835762e, []int{99} } func (m *ClusterNetworkingConfigSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8116,7 +8173,7 @@ func (m *TunnelStrategyV1) Reset() { *m = TunnelStrategyV1{} } func (m *TunnelStrategyV1) String() string { return proto.CompactTextString(m) } func (*TunnelStrategyV1) ProtoMessage() {} func (*TunnelStrategyV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{99} + return fileDescriptor_9198ee693835762e, []int{100} } func (m *TunnelStrategyV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8201,7 +8258,7 @@ func (m *AgentMeshTunnelStrategy) Reset() { *m = AgentMeshTunnelStrategy func (m *AgentMeshTunnelStrategy) String() string { return proto.CompactTextString(m) } func (*AgentMeshTunnelStrategy) ProtoMessage() {} func (*AgentMeshTunnelStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{100} + return fileDescriptor_9198ee693835762e, []int{101} } func (m *AgentMeshTunnelStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8245,7 +8302,7 @@ func (m *ProxyPeeringTunnelStrategy) Reset() { *m = ProxyPeeringTunnelSt func (m *ProxyPeeringTunnelStrategy) String() string { return proto.CompactTextString(m) } func (*ProxyPeeringTunnelStrategy) ProtoMessage() {} func (*ProxyPeeringTunnelStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{101} + return fileDescriptor_9198ee693835762e, []int{102} } func (m *ProxyPeeringTunnelStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8298,7 +8355,7 @@ func (m *SessionRecordingConfigV2) Reset() { *m = SessionRecordingConfig func (m *SessionRecordingConfigV2) String() string { return proto.CompactTextString(m) } func (*SessionRecordingConfigV2) ProtoMessage() {} func (*SessionRecordingConfigV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{102} + return fileDescriptor_9198ee693835762e, []int{103} } func (m *SessionRecordingConfigV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8343,7 +8400,7 @@ func (m *KeyLabel) Reset() { *m = KeyLabel{} } func (m *KeyLabel) String() string { return proto.CompactTextString(m) } func (*KeyLabel) ProtoMessage() {} func (*KeyLabel) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{103} + return fileDescriptor_9198ee693835762e, []int{104} } func (m *KeyLabel) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8390,7 +8447,7 @@ func (m *ManualKeyManagementConfig) Reset() { *m = ManualKeyManagementCo func (m *ManualKeyManagementConfig) String() string { return proto.CompactTextString(m) } func (*ManualKeyManagementConfig) ProtoMessage() {} func (*ManualKeyManagementConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{104} + return fileDescriptor_9198ee693835762e, []int{105} } func (m *ManualKeyManagementConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8436,7 +8493,7 @@ func (m *SessionRecordingEncryptionConfig) Reset() { *m = SessionRecordi func (m *SessionRecordingEncryptionConfig) String() string { return proto.CompactTextString(m) } func (*SessionRecordingEncryptionConfig) ProtoMessage() {} func (*SessionRecordingEncryptionConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{105} + return fileDescriptor_9198ee693835762e, []int{106} } func (m *SessionRecordingEncryptionConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8484,7 +8541,7 @@ func (m *SessionRecordingConfigSpecV2) Reset() { *m = SessionRecordingCo func (m *SessionRecordingConfigSpecV2) String() string { return proto.CompactTextString(m) } func (*SessionRecordingConfigSpecV2) ProtoMessage() {} func (*SessionRecordingConfigSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{106} + return fileDescriptor_9198ee693835762e, []int{107} } func (m *SessionRecordingConfigSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8528,7 +8585,7 @@ func (m *SessionRecordingConfigStatus) Reset() { *m = SessionRecordingCo func (m *SessionRecordingConfigStatus) String() string { return proto.CompactTextString(m) } func (*SessionRecordingConfigStatus) ProtoMessage() {} func (*SessionRecordingConfigStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{107} + return fileDescriptor_9198ee693835762e, []int{108} } func (m *SessionRecordingConfigStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8578,7 +8635,7 @@ type AuthPreferenceV2 struct { func (m *AuthPreferenceV2) Reset() { *m = AuthPreferenceV2{} } func (*AuthPreferenceV2) ProtoMessage() {} func (*AuthPreferenceV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{108} + return fileDescriptor_9198ee693835762e, []int{109} } func (m *AuthPreferenceV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8683,7 +8740,7 @@ func (m *AuthPreferenceSpecV2) Reset() { *m = AuthPreferenceSpecV2{} } func (m *AuthPreferenceSpecV2) String() string { return proto.CompactTextString(m) } func (*AuthPreferenceSpecV2) ProtoMessage() {} func (*AuthPreferenceSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{109} + return fileDescriptor_9198ee693835762e, []int{110} } func (m *AuthPreferenceSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8734,7 +8791,7 @@ func (m *StableUNIXUserConfig) Reset() { *m = StableUNIXUserConfig{} } func (m *StableUNIXUserConfig) String() string { return proto.CompactTextString(m) } func (*StableUNIXUserConfig) ProtoMessage() {} func (*StableUNIXUserConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{110} + return fileDescriptor_9198ee693835762e, []int{111} } func (m *StableUNIXUserConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8785,7 +8842,7 @@ func (m *U2F) Reset() { *m = U2F{} } func (m *U2F) String() string { return proto.CompactTextString(m) } func (*U2F) ProtoMessage() {} func (*U2F) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{111} + return fileDescriptor_9198ee693835762e, []int{112} } func (m *U2F) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8853,7 +8910,7 @@ func (m *Webauthn) Reset() { *m = Webauthn{} } func (m *Webauthn) String() string { return proto.CompactTextString(m) } func (*Webauthn) ProtoMessage() {} func (*Webauthn) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{112} + return fileDescriptor_9198ee693835762e, []int{113} } func (m *Webauthn) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8926,7 +8983,7 @@ func (m *DeviceTrust) Reset() { *m = DeviceTrust{} } func (m *DeviceTrust) String() string { return proto.CompactTextString(m) } func (*DeviceTrust) ProtoMessage() {} func (*DeviceTrust) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{113} + return fileDescriptor_9198ee693835762e, []int{114} } func (m *DeviceTrust) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8976,7 +9033,7 @@ func (m *HardwareKey) Reset() { *m = HardwareKey{} } func (m *HardwareKey) String() string { return proto.CompactTextString(m) } func (*HardwareKey) ProtoMessage() {} func (*HardwareKey) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{114} + return fileDescriptor_9198ee693835762e, []int{115} } func (m *HardwareKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9023,7 +9080,7 @@ func (m *HardwareKeySerialNumberValidation) Reset() { *m = HardwareKeySe func (m *HardwareKeySerialNumberValidation) String() string { return proto.CompactTextString(m) } func (*HardwareKeySerialNumberValidation) ProtoMessage() {} func (*HardwareKeySerialNumberValidation) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{115} + return fileDescriptor_9198ee693835762e, []int{116} } func (m *HardwareKeySerialNumberValidation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9073,7 +9130,7 @@ func (m *Namespace) Reset() { *m = Namespace{} } func (m *Namespace) String() string { return proto.CompactTextString(m) } func (*Namespace) ProtoMessage() {} func (*Namespace) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{116} + return fileDescriptor_9198ee693835762e, []int{117} } func (m *Namespace) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9113,7 +9170,7 @@ func (m *NamespaceSpec) Reset() { *m = NamespaceSpec{} } func (m *NamespaceSpec) String() string { return proto.CompactTextString(m) } func (*NamespaceSpec) ProtoMessage() {} func (*NamespaceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{117} + return fileDescriptor_9198ee693835762e, []int{118} } func (m *NamespaceSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9161,7 +9218,7 @@ type UserTokenV3 struct { func (m *UserTokenV3) Reset() { *m = UserTokenV3{} } func (*UserTokenV3) ProtoMessage() {} func (*UserTokenV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{118} + return fileDescriptor_9198ee693835762e, []int{119} } func (m *UserTokenV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9208,7 +9265,7 @@ func (m *UserTokenSpecV3) Reset() { *m = UserTokenSpecV3{} } func (m *UserTokenSpecV3) String() string { return proto.CompactTextString(m) } func (*UserTokenSpecV3) ProtoMessage() {} func (*UserTokenSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{119} + return fileDescriptor_9198ee693835762e, []int{120} } func (m *UserTokenSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9256,7 +9313,7 @@ type UserTokenSecretsV3 struct { func (m *UserTokenSecretsV3) Reset() { *m = UserTokenSecretsV3{} } func (*UserTokenSecretsV3) ProtoMessage() {} func (*UserTokenSecretsV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{120} + return fileDescriptor_9198ee693835762e, []int{121} } func (m *UserTokenSecretsV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9301,7 +9358,7 @@ func (m *UserTokenSecretsSpecV3) Reset() { *m = UserTokenSecretsSpecV3{} func (m *UserTokenSecretsSpecV3) String() string { return proto.CompactTextString(m) } func (*UserTokenSecretsSpecV3) ProtoMessage() {} func (*UserTokenSecretsSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{121} + return fileDescriptor_9198ee693835762e, []int{122} } func (m *UserTokenSecretsSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9350,7 +9407,7 @@ type AccessRequestV3 struct { func (m *AccessRequestV3) Reset() { *m = AccessRequestV3{} } func (*AccessRequestV3) ProtoMessage() {} func (*AccessRequestV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{122} + return fileDescriptor_9198ee693835762e, []int{123} } func (m *AccessRequestV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9402,7 +9459,7 @@ func (m *AccessReviewThreshold) Reset() { *m = AccessReviewThreshold{} } func (m *AccessReviewThreshold) String() string { return proto.CompactTextString(m) } func (*AccessReviewThreshold) ProtoMessage() {} func (*AccessReviewThreshold) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{123} + return fileDescriptor_9198ee693835762e, []int{124} } func (m *AccessReviewThreshold) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9447,7 +9504,7 @@ func (m *PromotedAccessList) Reset() { *m = PromotedAccessList{} } func (m *PromotedAccessList) String() string { return proto.CompactTextString(m) } func (*PromotedAccessList) ProtoMessage() {} func (*PromotedAccessList) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{124} + return fileDescriptor_9198ee693835762e, []int{125} } func (m *PromotedAccessList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9495,7 +9552,7 @@ func (m *AccessRequestDryRunEnrichment) Reset() { *m = AccessRequestDryR func (m *AccessRequestDryRunEnrichment) String() string { return proto.CompactTextString(m) } func (*AccessRequestDryRunEnrichment) ProtoMessage() {} func (*AccessRequestDryRunEnrichment) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{125} + return fileDescriptor_9198ee693835762e, []int{126} } func (m *AccessRequestDryRunEnrichment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9542,7 +9599,7 @@ func (m *AccessRequestUserDisplay) Reset() { *m = AccessRequestUserDispl func (m *AccessRequestUserDisplay) String() string { return proto.CompactTextString(m) } func (*AccessRequestUserDisplay) ProtoMessage() {} func (*AccessRequestUserDisplay) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{126} + return fileDescriptor_9198ee693835762e, []int{127} } func (m *AccessRequestUserDisplay) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9610,7 +9667,7 @@ func (m *AccessReview) Reset() { *m = AccessReview{} } func (m *AccessReview) String() string { return proto.CompactTextString(m) } func (*AccessReview) ProtoMessage() {} func (*AccessReview) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{127} + return fileDescriptor_9198ee693835762e, []int{128} } func (m *AccessReview) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9655,7 +9712,7 @@ func (m *AccessReviewSubmission) Reset() { *m = AccessReviewSubmission{} func (m *AccessReviewSubmission) String() string { return proto.CompactTextString(m) } func (*AccessReviewSubmission) ProtoMessage() {} func (*AccessReviewSubmission) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{128} + return fileDescriptor_9198ee693835762e, []int{129} } func (m *AccessReviewSubmission) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9698,7 +9755,7 @@ func (m *ThresholdIndexSet) Reset() { *m = ThresholdIndexSet{} } func (m *ThresholdIndexSet) String() string { return proto.CompactTextString(m) } func (*ThresholdIndexSet) ProtoMessage() {} func (*ThresholdIndexSet) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{129} + return fileDescriptor_9198ee693835762e, []int{130} } func (m *ThresholdIndexSet) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9741,7 +9798,7 @@ func (m *ThresholdIndexSets) Reset() { *m = ThresholdIndexSets{} } func (m *ThresholdIndexSets) String() string { return proto.CompactTextString(m) } func (*ThresholdIndexSets) ProtoMessage() {} func (*ThresholdIndexSets) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{130} + return fileDescriptor_9198ee693835762e, []int{131} } func (m *ThresholdIndexSets) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9860,7 +9917,7 @@ func (m *AccessRequestSpecV3) Reset() { *m = AccessRequestSpecV3{} } func (m *AccessRequestSpecV3) String() string { return proto.CompactTextString(m) } func (*AccessRequestSpecV3) ProtoMessage() {} func (*AccessRequestSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{131} + return fileDescriptor_9198ee693835762e, []int{132} } func (m *AccessRequestSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9917,7 +9974,7 @@ func (m *AccessRequestFilter) Reset() { *m = AccessRequestFilter{} } func (m *AccessRequestFilter) String() string { return proto.CompactTextString(m) } func (*AccessRequestFilter) ProtoMessage() {} func (*AccessRequestFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{132} + return fileDescriptor_9198ee693835762e, []int{133} } func (m *AccessRequestFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9973,7 +10030,7 @@ func (m *AccessCapabilities) Reset() { *m = AccessCapabilities{} } func (m *AccessCapabilities) String() string { return proto.CompactTextString(m) } func (*AccessCapabilities) ProtoMessage() {} func (*AccessCapabilities) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{133} + return fileDescriptor_9198ee693835762e, []int{134} } func (m *AccessCapabilities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10036,7 +10093,7 @@ func (m *AccessCapabilitiesRequest) Reset() { *m = AccessCapabilitiesReq func (m *AccessCapabilitiesRequest) String() string { return proto.CompactTextString(m) } func (*AccessCapabilitiesRequest) ProtoMessage() {} func (*AccessCapabilitiesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{134} + return fileDescriptor_9198ee693835762e, []int{135} } func (m *AccessCapabilitiesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10082,7 +10139,7 @@ func (m *RemoteAccessCapabilities) Reset() { *m = RemoteAccessCapabiliti func (m *RemoteAccessCapabilities) String() string { return proto.CompactTextString(m) } func (*RemoteAccessCapabilities) ProtoMessage() {} func (*RemoteAccessCapabilities) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{135} + return fileDescriptor_9198ee693835762e, []int{136} } func (m *RemoteAccessCapabilities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10136,7 +10193,7 @@ func (m *RemoteAccessCapabilitiesRequest) Reset() { *m = RemoteAccessCap func (m *RemoteAccessCapabilitiesRequest) String() string { return proto.CompactTextString(m) } func (*RemoteAccessCapabilitiesRequest) ProtoMessage() {} func (*RemoteAccessCapabilitiesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{136} + return fileDescriptor_9198ee693835762e, []int{137} } func (m *RemoteAccessCapabilitiesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10182,7 +10239,7 @@ func (m *RequestKubernetesResource) Reset() { *m = RequestKubernetesReso func (m *RequestKubernetesResource) String() string { return proto.CompactTextString(m) } func (*RequestKubernetesResource) ProtoMessage() {} func (*RequestKubernetesResource) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{137} + return fileDescriptor_9198ee693835762e, []int{138} } func (m *RequestKubernetesResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10231,7 +10288,7 @@ type PluginDataV3 struct { func (m *PluginDataV3) Reset() { *m = PluginDataV3{} } func (*PluginDataV3) ProtoMessage() {} func (*PluginDataV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{138} + return fileDescriptor_9198ee693835762e, []int{139} } func (m *PluginDataV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10274,7 +10331,7 @@ func (m *PluginDataEntry) Reset() { *m = PluginDataEntry{} } func (m *PluginDataEntry) String() string { return proto.CompactTextString(m) } func (*PluginDataEntry) ProtoMessage() {} func (*PluginDataEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{139} + return fileDescriptor_9198ee693835762e, []int{140} } func (m *PluginDataEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10316,7 +10373,7 @@ func (m *PluginDataSpecV3) Reset() { *m = PluginDataSpecV3{} } func (m *PluginDataSpecV3) String() string { return proto.CompactTextString(m) } func (*PluginDataSpecV3) ProtoMessage() {} func (*PluginDataSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{140} + return fileDescriptor_9198ee693835762e, []int{141} } func (m *PluginDataSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10363,7 +10420,7 @@ func (m *PluginDataFilter) Reset() { *m = PluginDataFilter{} } func (m *PluginDataFilter) String() string { return proto.CompactTextString(m) } func (*PluginDataFilter) ProtoMessage() {} func (*PluginDataFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{141} + return fileDescriptor_9198ee693835762e, []int{142} } func (m *PluginDataFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10414,7 +10471,7 @@ func (m *PluginDataUpdateParams) Reset() { *m = PluginDataUpdateParams{} func (m *PluginDataUpdateParams) String() string { return proto.CompactTextString(m) } func (*PluginDataUpdateParams) ProtoMessage() {} func (*PluginDataUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{142} + return fileDescriptor_9198ee693835762e, []int{143} } func (m *PluginDataUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10458,7 +10515,7 @@ func (m *RoleFilter) Reset() { *m = RoleFilter{} } func (m *RoleFilter) String() string { return proto.CompactTextString(m) } func (*RoleFilter) ProtoMessage() {} func (*RoleFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{143} + return fileDescriptor_9198ee693835762e, []int{144} } func (m *RoleFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10508,7 +10565,7 @@ type RoleV6 struct { func (m *RoleV6) Reset() { *m = RoleV6{} } func (*RoleV6) ProtoMessage() {} func (*RoleV6) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{144} + return fileDescriptor_9198ee693835762e, []int{145} } func (m *RoleV6) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10555,7 +10612,7 @@ func (m *RoleSpecV6) Reset() { *m = RoleSpecV6{} } func (m *RoleSpecV6) String() string { return proto.CompactTextString(m) } func (*RoleSpecV6) ProtoMessage() {} func (*RoleSpecV6) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{145} + return fileDescriptor_9198ee693835762e, []int{146} } func (m *RoleSpecV6) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10596,7 +10653,7 @@ func (m *SSHLocalPortForwarding) Reset() { *m = SSHLocalPortForwarding{} func (m *SSHLocalPortForwarding) String() string { return proto.CompactTextString(m) } func (*SSHLocalPortForwarding) ProtoMessage() {} func (*SSHLocalPortForwarding) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{146} + return fileDescriptor_9198ee693835762e, []int{147} } func (m *SSHLocalPortForwarding) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10637,7 +10694,7 @@ func (m *SSHRemotePortForwarding) Reset() { *m = SSHRemotePortForwarding func (m *SSHRemotePortForwarding) String() string { return proto.CompactTextString(m) } func (*SSHRemotePortForwarding) ProtoMessage() {} func (*SSHRemotePortForwarding) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{147} + return fileDescriptor_9198ee693835762e, []int{148} } func (m *SSHRemotePortForwarding) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10681,7 +10738,7 @@ func (m *SSHPortForwarding) Reset() { *m = SSHPortForwarding{} } func (m *SSHPortForwarding) String() string { return proto.CompactTextString(m) } func (*SSHPortForwarding) ProtoMessage() {} func (*SSHPortForwarding) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{148} + return fileDescriptor_9198ee693835762e, []int{149} } func (m *SSHPortForwarding) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10817,7 +10874,7 @@ func (m *RoleOptions) Reset() { *m = RoleOptions{} } func (m *RoleOptions) String() string { return proto.CompactTextString(m) } func (*RoleOptions) ProtoMessage() {} func (*RoleOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{149} + return fileDescriptor_9198ee693835762e, []int{150} } func (m *RoleOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10863,7 +10920,7 @@ func (m *RecordSession) Reset() { *m = RecordSession{} } func (m *RecordSession) String() string { return proto.CompactTextString(m) } func (*RecordSession) ProtoMessage() {} func (*RecordSession) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{150} + return fileDescriptor_9198ee693835762e, []int{151} } func (m *RecordSession) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10915,7 +10972,7 @@ func (m *CertExtension) Reset() { *m = CertExtension{} } func (m *CertExtension) String() string { return proto.CompactTextString(m) } func (*CertExtension) ProtoMessage() {} func (*CertExtension) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{151} + return fileDescriptor_9198ee693835762e, []int{152} } func (m *CertExtension) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11091,7 +11148,7 @@ func (m *RoleConditions) Reset() { *m = RoleConditions{} } func (m *RoleConditions) String() string { return proto.CompactTextString(m) } func (*RoleConditions) ProtoMessage() {} func (*RoleConditions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{152} + return fileDescriptor_9198ee693835762e, []int{153} } func (m *RoleConditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11193,7 +11250,7 @@ func (m *AppResource) Reset() { *m = AppResource{} } func (m *AppResource) String() string { return proto.CompactTextString(m) } func (*AppResource) ProtoMessage() {} func (*AppResource) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{153} + return fileDescriptor_9198ee693835762e, []int{154} } func (m *AppResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11236,7 +11293,7 @@ func (m *IdentityCenterAccountAssignment) Reset() { *m = IdentityCenterA func (m *IdentityCenterAccountAssignment) String() string { return proto.CompactTextString(m) } func (*IdentityCenterAccountAssignment) ProtoMessage() {} func (*IdentityCenterAccountAssignment) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{154} + return fileDescriptor_9198ee693835762e, []int{155} } func (m *IdentityCenterAccountAssignment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11277,7 +11334,7 @@ func (m *GitHubPermission) Reset() { *m = GitHubPermission{} } func (m *GitHubPermission) String() string { return proto.CompactTextString(m) } func (*GitHubPermission) ProtoMessage() {} func (*GitHubPermission) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{155} + return fileDescriptor_9198ee693835762e, []int{156} } func (m *GitHubPermission) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11322,7 +11379,7 @@ func (m *MCPPermissions) Reset() { *m = MCPPermissions{} } func (m *MCPPermissions) String() string { return proto.CompactTextString(m) } func (*MCPPermissions) ProtoMessage() {} func (*MCPPermissions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{156} + return fileDescriptor_9198ee693835762e, []int{157} } func (m *MCPPermissions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11398,7 +11455,7 @@ func (m *SPIFFERoleCondition) Reset() { *m = SPIFFERoleCondition{} } func (m *SPIFFERoleCondition) String() string { return proto.CompactTextString(m) } func (*SPIFFERoleCondition) ProtoMessage() {} func (*SPIFFERoleCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{157} + return fileDescriptor_9198ee693835762e, []int{158} } func (m *SPIFFERoleCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11442,7 +11499,7 @@ func (m *DatabasePermission) Reset() { *m = DatabasePermission{} } func (m *DatabasePermission) String() string { return proto.CompactTextString(m) } func (*DatabasePermission) ProtoMessage() {} func (*DatabasePermission) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{158} + return fileDescriptor_9198ee693835762e, []int{159} } func (m *DatabasePermission) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11495,7 +11552,7 @@ func (m *KubernetesResource) Reset() { *m = KubernetesResource{} } func (m *KubernetesResource) String() string { return proto.CompactTextString(m) } func (*KubernetesResource) ProtoMessage() {} func (*KubernetesResource) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{159} + return fileDescriptor_9198ee693835762e, []int{160} } func (m *KubernetesResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11548,7 +11605,7 @@ func (m *SessionRequirePolicy) Reset() { *m = SessionRequirePolicy{} } func (m *SessionRequirePolicy) String() string { return proto.CompactTextString(m) } func (*SessionRequirePolicy) ProtoMessage() {} func (*SessionRequirePolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{160} + return fileDescriptor_9198ee693835762e, []int{161} } func (m *SessionRequirePolicy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11596,7 +11653,7 @@ func (m *SessionJoinPolicy) Reset() { *m = SessionJoinPolicy{} } func (m *SessionJoinPolicy) String() string { return proto.CompactTextString(m) } func (*SessionJoinPolicy) ProtoMessage() {} func (*SessionJoinPolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{161} + return fileDescriptor_9198ee693835762e, []int{162} } func (m *SessionJoinPolicy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11672,7 +11729,7 @@ func (m *AccessRequestConditions) Reset() { *m = AccessRequestConditions func (m *AccessRequestConditions) String() string { return proto.CompactTextString(m) } func (*AccessRequestConditions) ProtoMessage() {} func (*AccessRequestConditions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{162} + return fileDescriptor_9198ee693835762e, []int{163} } func (m *AccessRequestConditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11721,7 +11778,7 @@ func (m *AccessRequestConditionsReason) Reset() { *m = AccessRequestCond func (m *AccessRequestConditionsReason) String() string { return proto.CompactTextString(m) } func (*AccessRequestConditionsReason) ProtoMessage() {} func (*AccessRequestConditionsReason) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{163} + return fileDescriptor_9198ee693835762e, []int{164} } func (m *AccessRequestConditionsReason) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11779,7 +11836,7 @@ func (m *AccessReviewConditions) Reset() { *m = AccessReviewConditions{} func (m *AccessReviewConditions) String() string { return proto.CompactTextString(m) } func (*AccessReviewConditions) ProtoMessage() {} func (*AccessReviewConditions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{164} + return fileDescriptor_9198ee693835762e, []int{165} } func (m *AccessReviewConditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11821,7 +11878,7 @@ func (m *AccessRequestAllowedPromotion) Reset() { *m = AccessRequestAllo func (m *AccessRequestAllowedPromotion) String() string { return proto.CompactTextString(m) } func (*AccessRequestAllowedPromotion) ProtoMessage() {} func (*AccessRequestAllowedPromotion) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{165} + return fileDescriptor_9198ee693835762e, []int{166} } func (m *AccessRequestAllowedPromotion) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11864,7 +11921,7 @@ func (m *AccessRequestAllowedPromotions) Reset() { *m = AccessRequestAll func (m *AccessRequestAllowedPromotions) String() string { return proto.CompactTextString(m) } func (*AccessRequestAllowedPromotions) ProtoMessage() {} func (*AccessRequestAllowedPromotions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{166} + return fileDescriptor_9198ee693835762e, []int{167} } func (m *AccessRequestAllowedPromotions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11905,7 +11962,7 @@ func (m *ResourceIDList) Reset() { *m = ResourceIDList{} } func (m *ResourceIDList) String() string { return proto.CompactTextString(m) } func (*ResourceIDList) ProtoMessage() {} func (*ResourceIDList) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{167} + return fileDescriptor_9198ee693835762e, []int{168} } func (m *ResourceIDList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11958,7 +12015,7 @@ func (m *LongTermResourceGrouping) Reset() { *m = LongTermResourceGroupi func (m *LongTermResourceGrouping) String() string { return proto.CompactTextString(m) } func (*LongTermResourceGrouping) ProtoMessage() {} func (*LongTermResourceGrouping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{168} + return fileDescriptor_9198ee693835762e, []int{169} } func (m *LongTermResourceGrouping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12004,7 +12061,7 @@ func (m *ClaimMapping) Reset() { *m = ClaimMapping{} } func (m *ClaimMapping) String() string { return proto.CompactTextString(m) } func (*ClaimMapping) ProtoMessage() {} func (*ClaimMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{169} + return fileDescriptor_9198ee693835762e, []int{170} } func (m *ClaimMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12050,7 +12107,7 @@ func (m *TraitMapping) Reset() { *m = TraitMapping{} } func (m *TraitMapping) String() string { return proto.CompactTextString(m) } func (*TraitMapping) ProtoMessage() {} func (*TraitMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{170} + return fileDescriptor_9198ee693835762e, []int{171} } func (m *TraitMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12099,7 +12156,7 @@ func (m *Rule) Reset() { *m = Rule{} } func (m *Rule) String() string { return proto.CompactTextString(m) } func (*Rule) ProtoMessage() {} func (*Rule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{171} + return fileDescriptor_9198ee693835762e, []int{172} } func (m *Rule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12147,7 +12204,7 @@ func (m *ImpersonateConditions) Reset() { *m = ImpersonateConditions{} } func (m *ImpersonateConditions) String() string { return proto.CompactTextString(m) } func (*ImpersonateConditions) ProtoMessage() {} func (*ImpersonateConditions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{172} + return fileDescriptor_9198ee693835762e, []int{173} } func (m *ImpersonateConditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12189,7 +12246,7 @@ func (m *BoolValue) Reset() { *m = BoolValue{} } func (m *BoolValue) String() string { return proto.CompactTextString(m) } func (*BoolValue) ProtoMessage() {} func (*BoolValue) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{173} + return fileDescriptor_9198ee693835762e, []int{174} } func (m *BoolValue) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12235,7 +12292,7 @@ func (m *UserFilter) Reset() { *m = UserFilter{} } func (m *UserFilter) String() string { return proto.CompactTextString(m) } func (*UserFilter) ProtoMessage() {} func (*UserFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{174} + return fileDescriptor_9198ee693835762e, []int{175} } func (m *UserFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12286,7 +12343,7 @@ type UserV2 struct { func (m *UserV2) Reset() { *m = UserV2{} } func (*UserV2) ProtoMessage() {} func (*UserV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{175} + return fileDescriptor_9198ee693835762e, []int{176} } func (m *UserV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12335,7 +12392,7 @@ func (m *UserStatusV2) Reset() { *m = UserStatusV2{} } func (m *UserStatusV2) String() string { return proto.CompactTextString(m) } func (*UserStatusV2) ProtoMessage() {} func (*UserStatusV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{176} + return fileDescriptor_9198ee693835762e, []int{177} } func (m *UserStatusV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12409,7 +12466,7 @@ func (m *UserSpecV2) Reset() { *m = UserSpecV2{} } func (m *UserSpecV2) String() string { return proto.CompactTextString(m) } func (*UserSpecV2) ProtoMessage() {} func (*UserSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{177} + return fileDescriptor_9198ee693835762e, []int{178} } func (m *UserSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12459,7 +12516,7 @@ type ExternalIdentity struct { func (m *ExternalIdentity) Reset() { *m = ExternalIdentity{} } func (*ExternalIdentity) ProtoMessage() {} func (*ExternalIdentity) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{178} + return fileDescriptor_9198ee693835762e, []int{179} } func (m *ExternalIdentity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12507,7 +12564,7 @@ func (m *LoginStatus) Reset() { *m = LoginStatus{} } func (m *LoginStatus) String() string { return proto.CompactTextString(m) } func (*LoginStatus) ProtoMessage() {} func (*LoginStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{179} + return fileDescriptor_9198ee693835762e, []int{180} } func (m *LoginStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12552,7 +12609,7 @@ type CreatedBy struct { func (m *CreatedBy) Reset() { *m = CreatedBy{} } func (*CreatedBy) ProtoMessage() {} func (*CreatedBy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{180} + return fileDescriptor_9198ee693835762e, []int{181} } func (m *CreatedBy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12601,7 +12658,7 @@ func (m *LocalAuthSecrets) Reset() { *m = LocalAuthSecrets{} } func (m *LocalAuthSecrets) String() string { return proto.CompactTextString(m) } func (*LocalAuthSecrets) ProtoMessage() {} func (*LocalAuthSecrets) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{181} + return fileDescriptor_9198ee693835762e, []int{182} } func (m *LocalAuthSecrets) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12644,7 +12701,7 @@ func (m *WebauthnLocalAuth) Reset() { *m = WebauthnLocalAuth{} } func (m *WebauthnLocalAuth) String() string { return proto.CompactTextString(m) } func (*WebauthnLocalAuth) ProtoMessage() {} func (*WebauthnLocalAuth) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{182} + return fileDescriptor_9198ee693835762e, []int{183} } func (m *WebauthnLocalAuth) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12690,7 +12747,7 @@ func (m *ConnectorRef) Reset() { *m = ConnectorRef{} } func (m *ConnectorRef) String() string { return proto.CompactTextString(m) } func (*ConnectorRef) ProtoMessage() {} func (*ConnectorRef) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{183} + return fileDescriptor_9198ee693835762e, []int{184} } func (m *ConnectorRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12732,7 +12789,7 @@ func (m *UserRef) Reset() { *m = UserRef{} } func (m *UserRef) String() string { return proto.CompactTextString(m) } func (*UserRef) ProtoMessage() {} func (*UserRef) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{184} + return fileDescriptor_9198ee693835762e, []int{185} } func (m *UserRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12782,7 +12839,7 @@ func (m *ReverseTunnelV2) Reset() { *m = ReverseTunnelV2{} } func (m *ReverseTunnelV2) String() string { return proto.CompactTextString(m) } func (*ReverseTunnelV2) ProtoMessage() {} func (*ReverseTunnelV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{185} + return fileDescriptor_9198ee693835762e, []int{186} } func (m *ReverseTunnelV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12829,7 +12886,7 @@ func (m *ReverseTunnelSpecV2) Reset() { *m = ReverseTunnelSpecV2{} } func (m *ReverseTunnelSpecV2) String() string { return proto.CompactTextString(m) } func (*ReverseTunnelSpecV2) ProtoMessage() {} func (*ReverseTunnelSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{186} + return fileDescriptor_9198ee693835762e, []int{187} } func (m *ReverseTunnelSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12878,7 +12935,7 @@ type TunnelConnectionV2 struct { func (m *TunnelConnectionV2) Reset() { *m = TunnelConnectionV2{} } func (*TunnelConnectionV2) ProtoMessage() {} func (*TunnelConnectionV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{187} + return fileDescriptor_9198ee693835762e, []int{188} } func (m *TunnelConnectionV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12926,7 +12983,7 @@ func (m *TunnelConnectionSpecV2) Reset() { *m = TunnelConnectionSpecV2{} func (m *TunnelConnectionSpecV2) String() string { return proto.CompactTextString(m) } func (*TunnelConnectionSpecV2) ProtoMessage() {} func (*TunnelConnectionSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{188} + return fileDescriptor_9198ee693835762e, []int{189} } func (m *TunnelConnectionSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12974,7 +13031,7 @@ func (m *SemaphoreFilter) Reset() { *m = SemaphoreFilter{} } func (m *SemaphoreFilter) String() string { return proto.CompactTextString(m) } func (*SemaphoreFilter) ProtoMessage() {} func (*SemaphoreFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{189} + return fileDescriptor_9198ee693835762e, []int{190} } func (m *SemaphoreFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13025,7 +13082,7 @@ func (m *AcquireSemaphoreRequest) Reset() { *m = AcquireSemaphoreRequest func (m *AcquireSemaphoreRequest) String() string { return proto.CompactTextString(m) } func (*AcquireSemaphoreRequest) ProtoMessage() {} func (*AcquireSemaphoreRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{190} + return fileDescriptor_9198ee693835762e, []int{191} } func (m *AcquireSemaphoreRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13073,7 +13130,7 @@ func (m *SemaphoreLease) Reset() { *m = SemaphoreLease{} } func (m *SemaphoreLease) String() string { return proto.CompactTextString(m) } func (*SemaphoreLease) ProtoMessage() {} func (*SemaphoreLease) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{191} + return fileDescriptor_9198ee693835762e, []int{192} } func (m *SemaphoreLease) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13119,7 +13176,7 @@ func (m *SemaphoreLeaseRef) Reset() { *m = SemaphoreLeaseRef{} } func (m *SemaphoreLeaseRef) String() string { return proto.CompactTextString(m) } func (*SemaphoreLeaseRef) ProtoMessage() {} func (*SemaphoreLeaseRef) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{192} + return fileDescriptor_9198ee693835762e, []int{193} } func (m *SemaphoreLeaseRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13168,7 +13225,7 @@ type SemaphoreV3 struct { func (m *SemaphoreV3) Reset() { *m = SemaphoreV3{} } func (*SemaphoreV3) ProtoMessage() {} func (*SemaphoreV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{193} + return fileDescriptor_9198ee693835762e, []int{194} } func (m *SemaphoreV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13210,7 +13267,7 @@ func (m *SemaphoreSpecV3) Reset() { *m = SemaphoreSpecV3{} } func (m *SemaphoreSpecV3) String() string { return proto.CompactTextString(m) } func (*SemaphoreSpecV3) ProtoMessage() {} func (*SemaphoreSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{194} + return fileDescriptor_9198ee693835762e, []int{195} } func (m *SemaphoreSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13259,7 +13316,7 @@ type WebSessionV2 struct { func (m *WebSessionV2) Reset() { *m = WebSessionV2{} } func (*WebSessionV2) ProtoMessage() {} func (*WebSessionV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{195} + return fileDescriptor_9198ee693835762e, []int{196} } func (m *WebSessionV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13350,7 +13407,7 @@ func (m *WebSessionSpecV2) Reset() { *m = WebSessionSpecV2{} } func (m *WebSessionSpecV2) String() string { return proto.CompactTextString(m) } func (*WebSessionSpecV2) ProtoMessage() {} func (*WebSessionSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{196} + return fileDescriptor_9198ee693835762e, []int{197} } func (m *WebSessionSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13395,7 +13452,7 @@ func (m *DeviceWebToken) Reset() { *m = DeviceWebToken{} } func (m *DeviceWebToken) String() string { return proto.CompactTextString(m) } func (*DeviceWebToken) ProtoMessage() {} func (*DeviceWebToken) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{197} + return fileDescriptor_9198ee693835762e, []int{198} } func (m *DeviceWebToken) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13437,7 +13494,7 @@ func (m *WebSessionFilter) Reset() { *m = WebSessionFilter{} } func (m *WebSessionFilter) String() string { return proto.CompactTextString(m) } func (*WebSessionFilter) ProtoMessage() {} func (*WebSessionFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{198} + return fileDescriptor_9198ee693835762e, []int{199} } func (m *WebSessionFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13508,7 +13565,7 @@ func (m *SAMLSessionData) Reset() { *m = SAMLSessionData{} } func (m *SAMLSessionData) String() string { return proto.CompactTextString(m) } func (*SAMLSessionData) ProtoMessage() {} func (*SAMLSessionData) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{199} + return fileDescriptor_9198ee693835762e, []int{200} } func (m *SAMLSessionData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13557,7 +13614,7 @@ func (m *SAMLAttribute) Reset() { *m = SAMLAttribute{} } func (m *SAMLAttribute) String() string { return proto.CompactTextString(m) } func (*SAMLAttribute) ProtoMessage() {} func (*SAMLAttribute) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{200} + return fileDescriptor_9198ee693835762e, []int{201} } func (m *SAMLAttribute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13604,7 +13661,7 @@ func (m *SAMLAttributeValue) Reset() { *m = SAMLAttributeValue{} } func (m *SAMLAttributeValue) String() string { return proto.CompactTextString(m) } func (*SAMLAttributeValue) ProtoMessage() {} func (*SAMLAttributeValue) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{201} + return fileDescriptor_9198ee693835762e, []int{202} } func (m *SAMLAttributeValue) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13655,7 +13712,7 @@ func (m *SAMLNameID) Reset() { *m = SAMLNameID{} } func (m *SAMLNameID) String() string { return proto.CompactTextString(m) } func (*SAMLNameID) ProtoMessage() {} func (*SAMLNameID) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{202} + return fileDescriptor_9198ee693835762e, []int{203} } func (m *SAMLNameID) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13704,7 +13761,7 @@ type RemoteClusterV3 struct { func (m *RemoteClusterV3) Reset() { *m = RemoteClusterV3{} } func (*RemoteClusterV3) ProtoMessage() {} func (*RemoteClusterV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{203} + return fileDescriptor_9198ee693835762e, []int{204} } func (m *RemoteClusterV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13748,7 +13805,7 @@ func (m *RemoteClusterStatusV3) Reset() { *m = RemoteClusterStatusV3{} } func (m *RemoteClusterStatusV3) String() string { return proto.CompactTextString(m) } func (*RemoteClusterStatusV3) ProtoMessage() {} func (*RemoteClusterStatusV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{204} + return fileDescriptor_9198ee693835762e, []int{205} } func (m *RemoteClusterStatusV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13798,7 +13855,7 @@ func (m *KubernetesCluster) Reset() { *m = KubernetesCluster{} } func (m *KubernetesCluster) String() string { return proto.CompactTextString(m) } func (*KubernetesCluster) ProtoMessage() {} func (*KubernetesCluster) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{205} + return fileDescriptor_9198ee693835762e, []int{206} } func (m *KubernetesCluster) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13851,7 +13908,7 @@ type KubernetesClusterV3 struct { func (m *KubernetesClusterV3) Reset() { *m = KubernetesClusterV3{} } func (*KubernetesClusterV3) ProtoMessage() {} func (*KubernetesClusterV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{206} + return fileDescriptor_9198ee693835762e, []int{207} } func (m *KubernetesClusterV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13902,7 +13959,7 @@ func (m *KubernetesClusterSpecV3) Reset() { *m = KubernetesClusterSpecV3 func (m *KubernetesClusterSpecV3) String() string { return proto.CompactTextString(m) } func (*KubernetesClusterSpecV3) ProtoMessage() {} func (*KubernetesClusterSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{207} + return fileDescriptor_9198ee693835762e, []int{208} } func (m *KubernetesClusterSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13950,7 +14007,7 @@ func (m *KubeAzure) Reset() { *m = KubeAzure{} } func (m *KubeAzure) String() string { return proto.CompactTextString(m) } func (*KubeAzure) ProtoMessage() {} func (*KubeAzure) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{208} + return fileDescriptor_9198ee693835762e, []int{209} } func (m *KubeAzure) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13996,7 +14053,7 @@ func (m *KubeAWS) Reset() { *m = KubeAWS{} } func (m *KubeAWS) String() string { return proto.CompactTextString(m) } func (*KubeAWS) ProtoMessage() {} func (*KubeAWS) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{209} + return fileDescriptor_9198ee693835762e, []int{210} } func (m *KubeAWS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14042,7 +14099,7 @@ func (m *KubeGCP) Reset() { *m = KubeGCP{} } func (m *KubeGCP) String() string { return proto.CompactTextString(m) } func (*KubeGCP) ProtoMessage() {} func (*KubeGCP) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{210} + return fileDescriptor_9198ee693835762e, []int{211} } func (m *KubeGCP) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14083,7 +14140,7 @@ func (m *KubernetesClusterStatus) Reset() { *m = KubernetesClusterStatus func (m *KubernetesClusterStatus) String() string { return proto.CompactTextString(m) } func (*KubernetesClusterStatus) ProtoMessage() {} func (*KubernetesClusterStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{211} + return fileDescriptor_9198ee693835762e, []int{212} } func (m *KubernetesClusterStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14127,7 +14184,7 @@ func (m *KubernetesClusterDiscoveryStatus) Reset() { *m = KubernetesClus func (m *KubernetesClusterDiscoveryStatus) String() string { return proto.CompactTextString(m) } func (*KubernetesClusterDiscoveryStatus) ProtoMessage() {} func (*KubernetesClusterDiscoveryStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{212} + return fileDescriptor_9198ee693835762e, []int{213} } func (m *KubernetesClusterDiscoveryStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14180,7 +14237,7 @@ func (m *KubernetesClusterAWSStatus) Reset() { *m = KubernetesClusterAWS func (m *KubernetesClusterAWSStatus) String() string { return proto.CompactTextString(m) } func (*KubernetesClusterAWSStatus) ProtoMessage() {} func (*KubernetesClusterAWSStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{213} + return fileDescriptor_9198ee693835762e, []int{214} } func (m *KubernetesClusterAWSStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14222,7 +14279,7 @@ func (m *KubernetesClusterV3List) Reset() { *m = KubernetesClusterV3List func (m *KubernetesClusterV3List) String() string { return proto.CompactTextString(m) } func (*KubernetesClusterV3List) ProtoMessage() {} func (*KubernetesClusterV3List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{214} + return fileDescriptor_9198ee693835762e, []int{215} } func (m *KubernetesClusterV3List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14275,7 +14332,7 @@ type KubernetesServerV3 struct { func (m *KubernetesServerV3) Reset() { *m = KubernetesServerV3{} } func (*KubernetesServerV3) ProtoMessage() {} func (*KubernetesServerV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{215} + return fileDescriptor_9198ee693835762e, []int{216} } func (m *KubernetesServerV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14331,7 +14388,7 @@ func (m *KubernetesServerSpecV3) Reset() { *m = KubernetesServerSpecV3{} func (m *KubernetesServerSpecV3) String() string { return proto.CompactTextString(m) } func (*KubernetesServerSpecV3) ProtoMessage() {} func (*KubernetesServerSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{216} + return fileDescriptor_9198ee693835762e, []int{217} } func (m *KubernetesServerSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14374,7 +14431,7 @@ func (m *KubernetesServerStatusV3) Reset() { *m = KubernetesServerStatus func (m *KubernetesServerStatusV3) String() string { return proto.CompactTextString(m) } func (*KubernetesServerStatusV3) ProtoMessage() {} func (*KubernetesServerStatusV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{217} + return fileDescriptor_9198ee693835762e, []int{218} } func (m *KubernetesServerStatusV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14426,7 +14483,7 @@ type WebTokenV3 struct { func (m *WebTokenV3) Reset() { *m = WebTokenV3{} } func (*WebTokenV3) ProtoMessage() {} func (*WebTokenV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{218} + return fileDescriptor_9198ee693835762e, []int{219} } func (m *WebTokenV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14470,7 +14527,7 @@ func (m *WebTokenSpecV3) Reset() { *m = WebTokenSpecV3{} } func (m *WebTokenSpecV3) String() string { return proto.CompactTextString(m) } func (*WebTokenSpecV3) ProtoMessage() {} func (*WebTokenSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{219} + return fileDescriptor_9198ee693835762e, []int{220} } func (m *WebTokenSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14514,7 +14571,7 @@ func (m *GetWebSessionRequest) Reset() { *m = GetWebSessionRequest{} } func (m *GetWebSessionRequest) String() string { return proto.CompactTextString(m) } func (*GetWebSessionRequest) ProtoMessage() {} func (*GetWebSessionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{220} + return fileDescriptor_9198ee693835762e, []int{221} } func (m *GetWebSessionRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14558,7 +14615,7 @@ func (m *DeleteWebSessionRequest) Reset() { *m = DeleteWebSessionRequest func (m *DeleteWebSessionRequest) String() string { return proto.CompactTextString(m) } func (*DeleteWebSessionRequest) ProtoMessage() {} func (*DeleteWebSessionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{221} + return fileDescriptor_9198ee693835762e, []int{222} } func (m *DeleteWebSessionRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14602,7 +14659,7 @@ func (m *GetWebTokenRequest) Reset() { *m = GetWebTokenRequest{} } func (m *GetWebTokenRequest) String() string { return proto.CompactTextString(m) } func (*GetWebTokenRequest) ProtoMessage() {} func (*GetWebTokenRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{222} + return fileDescriptor_9198ee693835762e, []int{223} } func (m *GetWebTokenRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14646,7 +14703,7 @@ func (m *DeleteWebTokenRequest) Reset() { *m = DeleteWebTokenRequest{} } func (m *DeleteWebTokenRequest) String() string { return proto.CompactTextString(m) } func (*DeleteWebTokenRequest) ProtoMessage() {} func (*DeleteWebTokenRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{223} + return fileDescriptor_9198ee693835762e, []int{224} } func (m *DeleteWebTokenRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14688,7 +14745,7 @@ func (m *ResourceRequest) Reset() { *m = ResourceRequest{} } func (m *ResourceRequest) String() string { return proto.CompactTextString(m) } func (*ResourceRequest) ProtoMessage() {} func (*ResourceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{224} + return fileDescriptor_9198ee693835762e, []int{225} } func (m *ResourceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14737,7 +14794,7 @@ func (m *ResourceWithSecretsRequest) Reset() { *m = ResourceWithSecretsR func (m *ResourceWithSecretsRequest) String() string { return proto.CompactTextString(m) } func (*ResourceWithSecretsRequest) ProtoMessage() {} func (*ResourceWithSecretsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{225} + return fileDescriptor_9198ee693835762e, []int{226} } func (m *ResourceWithSecretsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14784,7 +14841,7 @@ func (m *ResourcesWithSecretsRequest) Reset() { *m = ResourcesWithSecret func (m *ResourcesWithSecretsRequest) String() string { return proto.CompactTextString(m) } func (*ResourcesWithSecretsRequest) ProtoMessage() {} func (*ResourcesWithSecretsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{226} + return fileDescriptor_9198ee693835762e, []int{227} } func (m *ResourcesWithSecretsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14828,7 +14885,7 @@ func (m *ResourceInNamespaceRequest) Reset() { *m = ResourceInNamespaceR func (m *ResourceInNamespaceRequest) String() string { return proto.CompactTextString(m) } func (*ResourceInNamespaceRequest) ProtoMessage() {} func (*ResourceInNamespaceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{227} + return fileDescriptor_9198ee693835762e, []int{228} } func (m *ResourceInNamespaceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14870,7 +14927,7 @@ func (m *ResourcesInNamespaceRequest) Reset() { *m = ResourcesInNamespac func (m *ResourcesInNamespaceRequest) String() string { return proto.CompactTextString(m) } func (*ResourcesInNamespaceRequest) ProtoMessage() {} func (*ResourcesInNamespaceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{228} + return fileDescriptor_9198ee693835762e, []int{229} } func (m *ResourcesInNamespaceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14921,7 +14978,7 @@ func (m *OIDCConnectorV3) Reset() { *m = OIDCConnectorV3{} } func (m *OIDCConnectorV3) String() string { return proto.CompactTextString(m) } func (*OIDCConnectorV3) ProtoMessage() {} func (*OIDCConnectorV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{229} + return fileDescriptor_9198ee693835762e, []int{230} } func (m *OIDCConnectorV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14963,7 +15020,7 @@ func (m *OIDCConnectorV3List) Reset() { *m = OIDCConnectorV3List{} } func (m *OIDCConnectorV3List) String() string { return proto.CompactTextString(m) } func (*OIDCConnectorV3List) ProtoMessage() {} func (*OIDCConnectorV3List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{230} + return fileDescriptor_9198ee693835762e, []int{231} } func (m *OIDCConnectorV3List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15064,7 +15121,7 @@ func (m *OIDCConnectorSpecV3) Reset() { *m = OIDCConnectorSpecV3{} } func (m *OIDCConnectorSpecV3) String() string { return proto.CompactTextString(m) } func (*OIDCConnectorSpecV3) ProtoMessage() {} func (*OIDCConnectorSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{231} + return fileDescriptor_9198ee693835762e, []int{232} } func (m *OIDCConnectorSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15126,7 +15183,7 @@ func (m *EntraIDGroupsProvider) Reset() { *m = EntraIDGroupsProvider{} } func (m *EntraIDGroupsProvider) String() string { return proto.CompactTextString(m) } func (*EntraIDGroupsProvider) ProtoMessage() {} func (*EntraIDGroupsProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{232} + return fileDescriptor_9198ee693835762e, []int{233} } func (m *EntraIDGroupsProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15168,7 +15225,7 @@ func (m *MaxAge) Reset() { *m = MaxAge{} } func (m *MaxAge) String() string { return proto.CompactTextString(m) } func (*MaxAge) ProtoMessage() {} func (*MaxAge) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{233} + return fileDescriptor_9198ee693835762e, []int{234} } func (m *MaxAge) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15213,7 +15270,7 @@ func (m *SSOClientRedirectSettings) Reset() { *m = SSOClientRedirectSett func (m *SSOClientRedirectSettings) String() string { return proto.CompactTextString(m) } func (*SSOClientRedirectSettings) ProtoMessage() {} func (*SSOClientRedirectSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{234} + return fileDescriptor_9198ee693835762e, []int{235} } func (m *SSOClientRedirectSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15276,7 +15333,7 @@ func (m *OIDCConnectorMFASettings) Reset() { *m = OIDCConnectorMFASettin func (m *OIDCConnectorMFASettings) String() string { return proto.CompactTextString(m) } func (*OIDCConnectorMFASettings) ProtoMessage() {} func (*OIDCConnectorMFASettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{235} + return fileDescriptor_9198ee693835762e, []int{236} } func (m *OIDCConnectorMFASettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15378,7 +15435,7 @@ func (m *OIDCAuthRequest) Reset() { *m = OIDCAuthRequest{} } func (m *OIDCAuthRequest) String() string { return proto.CompactTextString(m) } func (*OIDCAuthRequest) ProtoMessage() {} func (*OIDCAuthRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{236} + return fileDescriptor_9198ee693835762e, []int{237} } func (m *OIDCAuthRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15429,7 +15486,7 @@ func (m *SAMLConnectorV2) Reset() { *m = SAMLConnectorV2{} } func (m *SAMLConnectorV2) String() string { return proto.CompactTextString(m) } func (*SAMLConnectorV2) ProtoMessage() {} func (*SAMLConnectorV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{237} + return fileDescriptor_9198ee693835762e, []int{238} } func (m *SAMLConnectorV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15471,7 +15528,7 @@ func (m *SAMLConnectorV2List) Reset() { *m = SAMLConnectorV2List{} } func (m *SAMLConnectorV2List) String() string { return proto.CompactTextString(m) } func (*SAMLConnectorV2List) ProtoMessage() {} func (*SAMLConnectorV2List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{238} + return fileDescriptor_9198ee693835762e, []int{239} } func (m *SAMLConnectorV2List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15577,7 +15634,7 @@ func (m *SAMLConnectorSpecV2) Reset() { *m = SAMLConnectorSpecV2{} } func (m *SAMLConnectorSpecV2) String() string { return proto.CompactTextString(m) } func (*SAMLConnectorSpecV2) ProtoMessage() {} func (*SAMLConnectorSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{239} + return fileDescriptor_9198ee693835762e, []int{240} } func (m *SAMLConnectorSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15620,7 +15677,7 @@ func (m *SAMLConnectorCredentials) Reset() { *m = SAMLConnectorCredentia func (m *SAMLConnectorCredentials) String() string { return proto.CompactTextString(m) } func (*SAMLConnectorCredentials) ProtoMessage() {} func (*SAMLConnectorCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{240} + return fileDescriptor_9198ee693835762e, []int{241} } func (m *SAMLConnectorCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15664,7 +15721,7 @@ func (m *OAuthClientCredentials) Reset() { *m = OAuthClientCredentials{} func (m *OAuthClientCredentials) String() string { return proto.CompactTextString(m) } func (*OAuthClientCredentials) ProtoMessage() {} func (*OAuthClientCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{241} + return fileDescriptor_9198ee693835762e, []int{242} } func (m *OAuthClientCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15723,7 +15780,7 @@ func (m *SAMLConnectorMFASettings) Reset() { *m = SAMLConnectorMFASettin func (m *SAMLConnectorMFASettings) String() string { return proto.CompactTextString(m) } func (*SAMLConnectorMFASettings) ProtoMessage() {} func (*SAMLConnectorMFASettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{242} + return fileDescriptor_9198ee693835762e, []int{243} } func (m *SAMLConnectorMFASettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15823,7 +15880,7 @@ func (m *SAMLAuthRequest) Reset() { *m = SAMLAuthRequest{} } func (m *SAMLAuthRequest) String() string { return proto.CompactTextString(m) } func (*SAMLAuthRequest) ProtoMessage() {} func (*SAMLAuthRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{243} + return fileDescriptor_9198ee693835762e, []int{244} } func (m *SAMLAuthRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15869,7 +15926,7 @@ func (m *AttributeMapping) Reset() { *m = AttributeMapping{} } func (m *AttributeMapping) String() string { return proto.CompactTextString(m) } func (*AttributeMapping) ProtoMessage() {} func (*AttributeMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{244} + return fileDescriptor_9198ee693835762e, []int{245} } func (m *AttributeMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15914,7 +15971,7 @@ func (m *AsymmetricKeyPair) Reset() { *m = AsymmetricKeyPair{} } func (m *AsymmetricKeyPair) String() string { return proto.CompactTextString(m) } func (*AsymmetricKeyPair) ProtoMessage() {} func (*AsymmetricKeyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{245} + return fileDescriptor_9198ee693835762e, []int{246} } func (m *AsymmetricKeyPair) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15965,7 +16022,7 @@ func (m *GithubConnectorV3) Reset() { *m = GithubConnectorV3{} } func (m *GithubConnectorV3) String() string { return proto.CompactTextString(m) } func (*GithubConnectorV3) ProtoMessage() {} func (*GithubConnectorV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{246} + return fileDescriptor_9198ee693835762e, []int{247} } func (m *GithubConnectorV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16007,7 +16064,7 @@ func (m *GithubConnectorV3List) Reset() { *m = GithubConnectorV3List{} } func (m *GithubConnectorV3List) String() string { return proto.CompactTextString(m) } func (*GithubConnectorV3List) ProtoMessage() {} func (*GithubConnectorV3List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{247} + return fileDescriptor_9198ee693835762e, []int{248} } func (m *GithubConnectorV3List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16073,7 +16130,7 @@ func (m *GithubConnectorSpecV3) Reset() { *m = GithubConnectorSpecV3{} } func (m *GithubConnectorSpecV3) String() string { return proto.CompactTextString(m) } func (*GithubConnectorSpecV3) ProtoMessage() {} func (*GithubConnectorSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{248} + return fileDescriptor_9198ee693835762e, []int{249} } func (m *GithubConnectorSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16166,7 +16223,7 @@ func (m *GithubAuthRequest) Reset() { *m = GithubAuthRequest{} } func (m *GithubAuthRequest) String() string { return proto.CompactTextString(m) } func (*GithubAuthRequest) ProtoMessage() {} func (*GithubAuthRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{249} + return fileDescriptor_9198ee693835762e, []int{250} } func (m *GithubAuthRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16210,7 +16267,7 @@ func (m *SSOWarnings) Reset() { *m = SSOWarnings{} } func (m *SSOWarnings) String() string { return proto.CompactTextString(m) } func (*SSOWarnings) ProtoMessage() {} func (*SSOWarnings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{250} + return fileDescriptor_9198ee693835762e, []int{251} } func (m *SSOWarnings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16266,7 +16323,7 @@ func (m *CreateUserParams) Reset() { *m = CreateUserParams{} } func (m *CreateUserParams) String() string { return proto.CompactTextString(m) } func (*CreateUserParams) ProtoMessage() {} func (*CreateUserParams) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{251} + return fileDescriptor_9198ee693835762e, []int{252} } func (m *CreateUserParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16351,7 +16408,7 @@ func (m *SSODiagnosticInfo) Reset() { *m = SSODiagnosticInfo{} } func (m *SSODiagnosticInfo) String() string { return proto.CompactTextString(m) } func (*SSODiagnosticInfo) ProtoMessage() {} func (*SSODiagnosticInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{252} + return fileDescriptor_9198ee693835762e, []int{253} } func (m *SSODiagnosticInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16395,7 +16452,7 @@ func (m *GithubTokenInfo) Reset() { *m = GithubTokenInfo{} } func (m *GithubTokenInfo) String() string { return proto.CompactTextString(m) } func (*GithubTokenInfo) ProtoMessage() {} func (*GithubTokenInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{253} + return fileDescriptor_9198ee693835762e, []int{254} } func (m *GithubTokenInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16446,7 +16503,7 @@ func (m *GithubClaims) Reset() { *m = GithubClaims{} } func (m *GithubClaims) String() string { return proto.CompactTextString(m) } func (*GithubClaims) ProtoMessage() {} func (*GithubClaims) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{254} + return fileDescriptor_9198ee693835762e, []int{255} } func (m *GithubClaims) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16498,7 +16555,7 @@ func (m *TeamMapping) Reset() { *m = TeamMapping{} } func (m *TeamMapping) String() string { return proto.CompactTextString(m) } func (*TeamMapping) ProtoMessage() {} func (*TeamMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{255} + return fileDescriptor_9198ee693835762e, []int{256} } func (m *TeamMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16544,7 +16601,7 @@ func (m *TeamRolesMapping) Reset() { *m = TeamRolesMapping{} } func (m *TeamRolesMapping) String() string { return proto.CompactTextString(m) } func (*TeamRolesMapping) ProtoMessage() {} func (*TeamRolesMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{256} + return fileDescriptor_9198ee693835762e, []int{257} } func (m *TeamRolesMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16594,7 +16651,7 @@ type TrustedClusterV2 struct { func (m *TrustedClusterV2) Reset() { *m = TrustedClusterV2{} } func (*TrustedClusterV2) ProtoMessage() {} func (*TrustedClusterV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{257} + return fileDescriptor_9198ee693835762e, []int{258} } func (m *TrustedClusterV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16636,7 +16693,7 @@ func (m *TrustedClusterV2List) Reset() { *m = TrustedClusterV2List{} } func (m *TrustedClusterV2List) String() string { return proto.CompactTextString(m) } func (*TrustedClusterV2List) ProtoMessage() {} func (*TrustedClusterV2List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{258} + return fileDescriptor_9198ee693835762e, []int{259} } func (m *TrustedClusterV2List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16692,7 +16749,7 @@ func (m *TrustedClusterSpecV2) Reset() { *m = TrustedClusterSpecV2{} } func (m *TrustedClusterSpecV2) String() string { return proto.CompactTextString(m) } func (*TrustedClusterSpecV2) ProtoMessage() {} func (*TrustedClusterSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{259} + return fileDescriptor_9198ee693835762e, []int{260} } func (m *TrustedClusterSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16746,7 +16803,7 @@ func (m *LockV2) Reset() { *m = LockV2{} } func (m *LockV2) String() string { return proto.CompactTextString(m) } func (*LockV2) ProtoMessage() {} func (*LockV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{260} + return fileDescriptor_9198ee693835762e, []int{261} } func (m *LockV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16796,7 +16853,7 @@ func (m *LockSpecV2) Reset() { *m = LockSpecV2{} } func (m *LockSpecV2) String() string { return proto.CompactTextString(m) } func (*LockSpecV2) ProtoMessage() {} func (*LockSpecV2) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{261} + return fileDescriptor_9198ee693835762e, []int{262} } func (m *LockSpecV2) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16866,7 +16923,7 @@ type LockTarget struct { func (m *LockTarget) Reset() { *m = LockTarget{} } func (*LockTarget) ProtoMessage() {} func (*LockTarget) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{262} + return fileDescriptor_9198ee693835762e, []int{263} } func (m *LockTarget) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16911,7 +16968,7 @@ func (m *LockFilter) Reset() { *m = LockFilter{} } func (m *LockFilter) String() string { return proto.CompactTextString(m) } func (*LockFilter) ProtoMessage() {} func (*LockFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{263} + return fileDescriptor_9198ee693835762e, []int{264} } func (m *LockFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16955,7 +17012,7 @@ func (m *AddressCondition) Reset() { *m = AddressCondition{} } func (m *AddressCondition) String() string { return proto.CompactTextString(m) } func (*AddressCondition) ProtoMessage() {} func (*AddressCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{264} + return fileDescriptor_9198ee693835762e, []int{265} } func (m *AddressCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16998,7 +17055,7 @@ func (m *NetworkRestrictionsSpecV4) Reset() { *m = NetworkRestrictionsSp func (m *NetworkRestrictionsSpecV4) String() string { return proto.CompactTextString(m) } func (*NetworkRestrictionsSpecV4) ProtoMessage() {} func (*NetworkRestrictionsSpecV4) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{265} + return fileDescriptor_9198ee693835762e, []int{266} } func (m *NetworkRestrictionsSpecV4) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17051,7 +17108,7 @@ func (m *NetworkRestrictionsV4) Reset() { *m = NetworkRestrictionsV4{} } func (m *NetworkRestrictionsV4) String() string { return proto.CompactTextString(m) } func (*NetworkRestrictionsV4) ProtoMessage() {} func (*NetworkRestrictionsV4) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{266} + return fileDescriptor_9198ee693835762e, []int{267} } func (m *NetworkRestrictionsV4) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17095,7 +17152,7 @@ func (m *WindowsDesktopServiceV3) Reset() { *m = WindowsDesktopServiceV3 func (m *WindowsDesktopServiceV3) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopServiceV3) ProtoMessage() {} func (*WindowsDesktopServiceV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{267} + return fileDescriptor_9198ee693835762e, []int{268} } func (m *WindowsDesktopServiceV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17147,7 +17204,7 @@ func (m *WindowsDesktopServiceSpecV3) Reset() { *m = WindowsDesktopServi func (m *WindowsDesktopServiceSpecV3) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopServiceSpecV3) ProtoMessage() {} func (*WindowsDesktopServiceSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{268} + return fileDescriptor_9198ee693835762e, []int{269} } func (m *WindowsDesktopServiceSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17193,7 +17250,7 @@ func (m *WindowsDesktopFilter) Reset() { *m = WindowsDesktopFilter{} } func (m *WindowsDesktopFilter) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopFilter) ProtoMessage() {} func (*WindowsDesktopFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{269} + return fileDescriptor_9198ee693835762e, []int{270} } func (m *WindowsDesktopFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17238,7 +17295,7 @@ func (m *WindowsDesktopV3) Reset() { *m = WindowsDesktopV3{} } func (m *WindowsDesktopV3) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopV3) ProtoMessage() {} func (*WindowsDesktopV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{270} + return fileDescriptor_9198ee693835762e, []int{271} } func (m *WindowsDesktopV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17280,7 +17337,7 @@ func (m *WindowsDesktopStatus) Reset() { *m = WindowsDesktopStatus{} } func (m *WindowsDesktopStatus) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopStatus) ProtoMessage() {} func (*WindowsDesktopStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{271} + return fileDescriptor_9198ee693835762e, []int{272} } func (m *WindowsDesktopStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17333,7 +17390,7 @@ func (m *WindowsDesktopSpecV3) Reset() { *m = WindowsDesktopSpecV3{} } func (m *WindowsDesktopSpecV3) String() string { return proto.CompactTextString(m) } func (*WindowsDesktopSpecV3) ProtoMessage() {} func (*WindowsDesktopSpecV3) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{272} + return fileDescriptor_9198ee693835762e, []int{273} } func (m *WindowsDesktopSpecV3) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17377,7 +17434,7 @@ func (m *DynamicWindowsDesktopV1) Reset() { *m = DynamicWindowsDesktopV1 func (m *DynamicWindowsDesktopV1) String() string { return proto.CompactTextString(m) } func (*DynamicWindowsDesktopV1) ProtoMessage() {} func (*DynamicWindowsDesktopV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{273} + return fileDescriptor_9198ee693835762e, []int{274} } func (m *DynamicWindowsDesktopV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17428,7 +17485,7 @@ func (m *DynamicWindowsDesktopSpecV1) Reset() { *m = DynamicWindowsDeskt func (m *DynamicWindowsDesktopSpecV1) String() string { return proto.CompactTextString(m) } func (*DynamicWindowsDesktopSpecV1) ProtoMessage() {} func (*DynamicWindowsDesktopSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{274} + return fileDescriptor_9198ee693835762e, []int{275} } func (m *DynamicWindowsDesktopSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17469,7 +17526,7 @@ func (m *Resolution) Reset() { *m = Resolution{} } func (m *Resolution) String() string { return proto.CompactTextString(m) } func (*Resolution) ProtoMessage() {} func (*Resolution) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{275} + return fileDescriptor_9198ee693835762e, []int{276} } func (m *Resolution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17556,7 +17613,7 @@ func (m *RegisterUsingTokenRequest) Reset() { *m = RegisterUsingTokenReq func (m *RegisterUsingTokenRequest) String() string { return proto.CompactTextString(m) } func (*RegisterUsingTokenRequest) ProtoMessage() {} func (*RegisterUsingTokenRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{276} + return fileDescriptor_9198ee693835762e, []int{277} } func (m *RegisterUsingTokenRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17610,7 +17667,7 @@ func (m *RecoveryCodesV1) Reset() { *m = RecoveryCodesV1{} } func (m *RecoveryCodesV1) String() string { return proto.CompactTextString(m) } func (*RecoveryCodesV1) ProtoMessage() {} func (*RecoveryCodesV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{277} + return fileDescriptor_9198ee693835762e, []int{278} } func (m *RecoveryCodesV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17655,7 +17712,7 @@ func (m *RecoveryCodesSpecV1) Reset() { *m = RecoveryCodesSpecV1{} } func (m *RecoveryCodesSpecV1) String() string { return proto.CompactTextString(m) } func (*RecoveryCodesSpecV1) ProtoMessage() {} func (*RecoveryCodesSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{278} + return fileDescriptor_9198ee693835762e, []int{279} } func (m *RecoveryCodesSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17699,7 +17756,7 @@ func (m *RecoveryCode) Reset() { *m = RecoveryCode{} } func (m *RecoveryCode) String() string { return proto.CompactTextString(m) } func (*RecoveryCode) ProtoMessage() {} func (*RecoveryCode) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{279} + return fileDescriptor_9198ee693835762e, []int{280} } func (m *RecoveryCode) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17739,7 +17796,7 @@ func (m *NullableSessionState) Reset() { *m = NullableSessionState{} } func (m *NullableSessionState) String() string { return proto.CompactTextString(m) } func (*NullableSessionState) ProtoMessage() {} func (*NullableSessionState) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{280} + return fileDescriptor_9198ee693835762e, []int{281} } func (m *NullableSessionState) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17785,7 +17842,7 @@ func (m *SessionTrackerFilter) Reset() { *m = SessionTrackerFilter{} } func (m *SessionTrackerFilter) String() string { return proto.CompactTextString(m) } func (*SessionTrackerFilter) ProtoMessage() {} func (*SessionTrackerFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{281} + return fileDescriptor_9198ee693835762e, []int{282} } func (m *SessionTrackerFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17829,7 +17886,7 @@ func (m *SessionTrackerV1) Reset() { *m = SessionTrackerV1{} } func (m *SessionTrackerV1) String() string { return proto.CompactTextString(m) } func (*SessionTrackerV1) ProtoMessage() {} func (*SessionTrackerV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{282} + return fileDescriptor_9198ee693835762e, []int{283} } func (m *SessionTrackerV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17927,7 +17984,7 @@ func (m *SessionTrackerSpecV1) Reset() { *m = SessionTrackerSpecV1{} } func (m *SessionTrackerSpecV1) String() string { return proto.CompactTextString(m) } func (*SessionTrackerSpecV1) ProtoMessage() {} func (*SessionTrackerSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{283} + return fileDescriptor_9198ee693835762e, []int{284} } func (m *SessionTrackerSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17974,7 +18031,7 @@ func (m *SessionTrackerPolicySet) Reset() { *m = SessionTrackerPolicySet func (m *SessionTrackerPolicySet) String() string { return proto.CompactTextString(m) } func (*SessionTrackerPolicySet) ProtoMessage() {} func (*SessionTrackerPolicySet) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{284} + return fileDescriptor_9198ee693835762e, []int{285} } func (m *SessionTrackerPolicySet) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18024,7 +18081,7 @@ func (m *Participant) Reset() { *m = Participant{} } func (m *Participant) String() string { return proto.CompactTextString(m) } func (*Participant) ProtoMessage() {} func (*Participant) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{285} + return fileDescriptor_9198ee693835762e, []int{286} } func (m *Participant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18068,7 +18125,7 @@ func (m *UIConfigV1) Reset() { *m = UIConfigV1{} } func (m *UIConfigV1) String() string { return proto.CompactTextString(m) } func (*UIConfigV1) ProtoMessage() {} func (*UIConfigV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{286} + return fileDescriptor_9198ee693835762e, []int{287} } func (m *UIConfigV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18114,7 +18171,7 @@ func (m *UIConfigSpecV1) Reset() { *m = UIConfigSpecV1{} } func (m *UIConfigSpecV1) String() string { return proto.CompactTextString(m) } func (*UIConfigSpecV1) ProtoMessage() {} func (*UIConfigSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{287} + return fileDescriptor_9198ee693835762e, []int{288} } func (m *UIConfigSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18165,7 +18222,7 @@ func (m *InstallerV1) Reset() { *m = InstallerV1{} } func (m *InstallerV1) String() string { return proto.CompactTextString(m) } func (*InstallerV1) ProtoMessage() {} func (*InstallerV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{288} + return fileDescriptor_9198ee693835762e, []int{289} } func (m *InstallerV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18207,7 +18264,7 @@ func (m *InstallerSpecV1) Reset() { *m = InstallerSpecV1{} } func (m *InstallerSpecV1) String() string { return proto.CompactTextString(m) } func (*InstallerSpecV1) ProtoMessage() {} func (*InstallerSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{289} + return fileDescriptor_9198ee693835762e, []int{290} } func (m *InstallerSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18249,7 +18306,7 @@ func (m *InstallerV1List) Reset() { *m = InstallerV1List{} } func (m *InstallerV1List) String() string { return proto.CompactTextString(m) } func (*InstallerV1List) ProtoMessage() {} func (*InstallerV1List) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{290} + return fileDescriptor_9198ee693835762e, []int{291} } func (m *InstallerV1List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18293,7 +18350,7 @@ func (m *SortBy) Reset() { *m = SortBy{} } func (m *SortBy) String() string { return proto.CompactTextString(m) } func (*SortBy) ProtoMessage() {} func (*SortBy) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{291} + return fileDescriptor_9198ee693835762e, []int{292} } func (m *SortBy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18339,7 +18396,7 @@ func (m *ConnectionDiagnosticV1) Reset() { *m = ConnectionDiagnosticV1{} func (m *ConnectionDiagnosticV1) String() string { return proto.CompactTextString(m) } func (*ConnectionDiagnosticV1) ProtoMessage() {} func (*ConnectionDiagnosticV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{292} + return fileDescriptor_9198ee693835762e, []int{293} } func (m *ConnectionDiagnosticV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18389,7 +18446,7 @@ func (m *ConnectionDiagnosticSpecV1) Reset() { *m = ConnectionDiagnostic func (m *ConnectionDiagnosticSpecV1) String() string { return proto.CompactTextString(m) } func (*ConnectionDiagnosticSpecV1) ProtoMessage() {} func (*ConnectionDiagnosticSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{293} + return fileDescriptor_9198ee693835762e, []int{294} } func (m *ConnectionDiagnosticSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18435,7 +18492,7 @@ func (m *ConnectionDiagnosticTrace) Reset() { *m = ConnectionDiagnosticT func (m *ConnectionDiagnosticTrace) String() string { return proto.CompactTextString(m) } func (*ConnectionDiagnosticTrace) ProtoMessage() {} func (*ConnectionDiagnosticTrace) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{294} + return fileDescriptor_9198ee693835762e, []int{295} } func (m *ConnectionDiagnosticTrace) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18478,7 +18535,7 @@ func (m *DatabaseServiceV1) Reset() { *m = DatabaseServiceV1{} } func (m *DatabaseServiceV1) String() string { return proto.CompactTextString(m) } func (*DatabaseServiceV1) ProtoMessage() {} func (*DatabaseServiceV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{295} + return fileDescriptor_9198ee693835762e, []int{296} } func (m *DatabaseServiceV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18522,7 +18579,7 @@ func (m *DatabaseServiceSpecV1) Reset() { *m = DatabaseServiceSpecV1{} } func (m *DatabaseServiceSpecV1) String() string { return proto.CompactTextString(m) } func (*DatabaseServiceSpecV1) ProtoMessage() {} func (*DatabaseServiceSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{296} + return fileDescriptor_9198ee693835762e, []int{297} } func (m *DatabaseServiceSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18564,7 +18621,7 @@ func (m *DatabaseResourceMatcher) Reset() { *m = DatabaseResourceMatcher func (m *DatabaseResourceMatcher) String() string { return proto.CompactTextString(m) } func (*DatabaseResourceMatcher) ProtoMessage() {} func (*DatabaseResourceMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{297} + return fileDescriptor_9198ee693835762e, []int{298} } func (m *DatabaseResourceMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18608,7 +18665,7 @@ func (m *ResourceMatcherAWS) Reset() { *m = ResourceMatcherAWS{} } func (m *ResourceMatcherAWS) String() string { return proto.CompactTextString(m) } func (*ResourceMatcherAWS) ProtoMessage() {} func (*ResourceMatcherAWS) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{298} + return fileDescriptor_9198ee693835762e, []int{299} } func (m *ResourceMatcherAWS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18650,7 +18707,7 @@ func (m *ClusterAlert) Reset() { *m = ClusterAlert{} } func (m *ClusterAlert) String() string { return proto.CompactTextString(m) } func (*ClusterAlert) ProtoMessage() {} func (*ClusterAlert) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{299} + return fileDescriptor_9198ee693835762e, []int{300} } func (m *ClusterAlert) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18696,7 +18753,7 @@ func (m *ClusterAlertSpec) Reset() { *m = ClusterAlertSpec{} } func (m *ClusterAlertSpec) String() string { return proto.CompactTextString(m) } func (*ClusterAlertSpec) ProtoMessage() {} func (*ClusterAlertSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{300} + return fileDescriptor_9198ee693835762e, []int{301} } func (m *ClusterAlertSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18749,7 +18806,7 @@ func (m *GetClusterAlertsRequest) Reset() { *m = GetClusterAlertsRequest func (m *GetClusterAlertsRequest) String() string { return proto.CompactTextString(m) } func (*GetClusterAlertsRequest) ProtoMessage() {} func (*GetClusterAlertsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{301} + return fileDescriptor_9198ee693835762e, []int{302} } func (m *GetClusterAlertsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18799,7 +18856,7 @@ func (m *AlertAcknowledgement) Reset() { *m = AlertAcknowledgement{} } func (m *AlertAcknowledgement) String() string { return proto.CompactTextString(m) } func (*AlertAcknowledgement) ProtoMessage() {} func (*AlertAcknowledgement) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{302} + return fileDescriptor_9198ee693835762e, []int{303} } func (m *AlertAcknowledgement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18851,7 +18908,7 @@ func (m *Release) Reset() { *m = Release{} } func (m *Release) String() string { return proto.CompactTextString(m) } func (*Release) ProtoMessage() {} func (*Release) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{303} + return fileDescriptor_9198ee693835762e, []int{304} } func (m *Release) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18909,7 +18966,7 @@ func (m *Asset) Reset() { *m = Asset{} } func (m *Asset) String() string { return proto.CompactTextString(m) } func (*Asset) ProtoMessage() {} func (*Asset) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{304} + return fileDescriptor_9198ee693835762e, []int{305} } func (m *Asset) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18962,7 +19019,7 @@ func (m *PluginV1) Reset() { *m = PluginV1{} } func (m *PluginV1) String() string { return proto.CompactTextString(m) } func (*PluginV1) ProtoMessage() {} func (*PluginV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{305} + return fileDescriptor_9198ee693835762e, []int{306} } func (m *PluginV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19031,7 +19088,7 @@ func (m *PluginSpecV1) Reset() { *m = PluginSpecV1{} } func (m *PluginSpecV1) String() string { return proto.CompactTextString(m) } func (*PluginSpecV1) ProtoMessage() {} func (*PluginSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{306} + return fileDescriptor_9198ee693835762e, []int{307} } func (m *PluginSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19347,7 +19404,7 @@ func (m *PluginGithubSettings) Reset() { *m = PluginGithubSettings{} } func (m *PluginGithubSettings) String() string { return proto.CompactTextString(m) } func (*PluginGithubSettings) ProtoMessage() {} func (*PluginGithubSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{307} + return fileDescriptor_9198ee693835762e, []int{308} } func (m *PluginGithubSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19387,7 +19444,7 @@ func (m *PluginSlackAccessSettings) Reset() { *m = PluginSlackAccessSett func (m *PluginSlackAccessSettings) String() string { return proto.CompactTextString(m) } func (*PluginSlackAccessSettings) ProtoMessage() {} func (*PluginSlackAccessSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{308} + return fileDescriptor_9198ee693835762e, []int{309} } func (m *PluginSlackAccessSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19428,7 +19485,7 @@ func (m *PluginGitlabSettings) Reset() { *m = PluginGitlabSettings{} } func (m *PluginGitlabSettings) String() string { return proto.CompactTextString(m) } func (*PluginGitlabSettings) ProtoMessage() {} func (*PluginGitlabSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{309} + return fileDescriptor_9198ee693835762e, []int{310} } func (m *PluginGitlabSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19475,7 +19532,7 @@ func (m *PluginOpsgenieAccessSettings) Reset() { *m = PluginOpsgenieAcce func (m *PluginOpsgenieAccessSettings) String() string { return proto.CompactTextString(m) } func (*PluginOpsgenieAccessSettings) ProtoMessage() {} func (*PluginOpsgenieAccessSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{310} + return fileDescriptor_9198ee693835762e, []int{311} } func (m *PluginOpsgenieAccessSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19523,7 +19580,7 @@ func (m *PluginServiceNowSettings) Reset() { *m = PluginServiceNowSettin func (m *PluginServiceNowSettings) String() string { return proto.CompactTextString(m) } func (*PluginServiceNowSettings) ProtoMessage() {} func (*PluginServiceNowSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{311} + return fileDescriptor_9198ee693835762e, []int{312} } func (m *PluginServiceNowSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19569,7 +19626,7 @@ func (m *PluginPagerDutySettings) Reset() { *m = PluginPagerDutySettings func (m *PluginPagerDutySettings) String() string { return proto.CompactTextString(m) } func (*PluginPagerDutySettings) ProtoMessage() {} func (*PluginPagerDutySettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{312} + return fileDescriptor_9198ee693835762e, []int{313} } func (m *PluginPagerDutySettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19615,7 +19672,7 @@ func (m *PluginJiraSettings) Reset() { *m = PluginJiraSettings{} } func (m *PluginJiraSettings) String() string { return proto.CompactTextString(m) } func (*PluginJiraSettings) ProtoMessage() {} func (*PluginJiraSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{313} + return fileDescriptor_9198ee693835762e, []int{314} } func (m *PluginJiraSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19655,7 +19712,7 @@ func (m *PluginOpenAISettings) Reset() { *m = PluginOpenAISettings{} } func (m *PluginOpenAISettings) String() string { return proto.CompactTextString(m) } func (*PluginOpenAISettings) ProtoMessage() {} func (*PluginOpenAISettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{314} + return fileDescriptor_9198ee693835762e, []int{315} } func (m *PluginOpenAISettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19706,7 +19763,7 @@ func (m *PluginMattermostSettings) Reset() { *m = PluginMattermostSettin func (m *PluginMattermostSettings) String() string { return proto.CompactTextString(m) } func (*PluginMattermostSettings) ProtoMessage() {} func (*PluginMattermostSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{315} + return fileDescriptor_9198ee693835762e, []int{316} } func (m *PluginMattermostSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19748,7 +19805,7 @@ func (m *PluginJamfSettings) Reset() { *m = PluginJamfSettings{} } func (m *PluginJamfSettings) String() string { return proto.CompactTextString(m) } func (*PluginJamfSettings) ProtoMessage() {} func (*PluginJamfSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{316} + return fileDescriptor_9198ee693835762e, []int{317} } func (m *PluginJamfSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19803,7 +19860,7 @@ func (m *PluginIntuneSettings) Reset() { *m = PluginIntuneSettings{} } func (m *PluginIntuneSettings) String() string { return proto.CompactTextString(m) } func (*PluginIntuneSettings) ProtoMessage() {} func (*PluginIntuneSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{317} + return fileDescriptor_9198ee693835762e, []int{318} } func (m *PluginIntuneSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19855,7 +19912,7 @@ func (m *PluginOktaSettings) Reset() { *m = PluginOktaSettings{} } func (m *PluginOktaSettings) String() string { return proto.CompactTextString(m) } func (*PluginOktaSettings) ProtoMessage() {} func (*PluginOktaSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{318} + return fileDescriptor_9198ee693835762e, []int{319} } func (m *PluginOktaSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19902,7 +19959,7 @@ func (m *PluginOktaCredentialsInfo) Reset() { *m = PluginOktaCredentials func (m *PluginOktaCredentialsInfo) String() string { return proto.CompactTextString(m) } func (*PluginOktaCredentialsInfo) ProtoMessage() {} func (*PluginOktaCredentialsInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{319} + return fileDescriptor_9198ee693835762e, []int{320} } func (m *PluginOktaCredentialsInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20026,7 +20083,7 @@ func (m *PluginOktaSyncSettings) Reset() { *m = PluginOktaSyncSettings{} func (m *PluginOktaSyncSettings) String() string { return proto.CompactTextString(m) } func (*PluginOktaSyncSettings) ProtoMessage() {} func (*PluginOktaSyncSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{320} + return fileDescriptor_9198ee693835762e, []int{321} } func (m *PluginOktaSyncSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20067,7 +20124,7 @@ func (m *DiscordChannels) Reset() { *m = DiscordChannels{} } func (m *DiscordChannels) String() string { return proto.CompactTextString(m) } func (*DiscordChannels) ProtoMessage() {} func (*DiscordChannels) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{321} + return fileDescriptor_9198ee693835762e, []int{322} } func (m *DiscordChannels) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20111,7 +20168,7 @@ func (m *PluginDiscordSettings) Reset() { *m = PluginDiscordSettings{} } func (m *PluginDiscordSettings) String() string { return proto.CompactTextString(m) } func (*PluginDiscordSettings) ProtoMessage() {} func (*PluginDiscordSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{322} + return fileDescriptor_9198ee693835762e, []int{323} } func (m *PluginDiscordSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20156,7 +20213,7 @@ func (m *PluginEntraIDSettings) Reset() { *m = PluginEntraIDSettings{} } func (m *PluginEntraIDSettings) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDSettings) ProtoMessage() {} func (*PluginEntraIDSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{323} + return fileDescriptor_9198ee693835762e, []int{324} } func (m *PluginEntraIDSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20216,7 +20273,7 @@ func (m *PluginEntraIDSyncSettings) Reset() { *m = PluginEntraIDSyncSett func (m *PluginEntraIDSyncSettings) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDSyncSettings) ProtoMessage() {} func (*PluginEntraIDSyncSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{324} + return fileDescriptor_9198ee693835762e, []int{325} } func (m *PluginEntraIDSyncSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20263,7 +20320,7 @@ func (m *PluginEntraIDSyncIntervals) Reset() { *m = PluginEntraIDSyncInt func (m *PluginEntraIDSyncIntervals) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDSyncIntervals) ProtoMessage() {} func (*PluginEntraIDSyncIntervals) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{325} + return fileDescriptor_9198ee693835762e, []int{326} } func (m *PluginEntraIDSyncIntervals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20317,7 +20374,7 @@ func (m *PluginSyncFilter) Reset() { *m = PluginSyncFilter{} } func (m *PluginSyncFilter) String() string { return proto.CompactTextString(m) } func (*PluginSyncFilter) ProtoMessage() {} func (*PluginSyncFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{326} + return fileDescriptor_9198ee693835762e, []int{327} } func (m *PluginSyncFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20444,7 +20501,7 @@ func (m *PluginEntraIDAccessGraphSettings) Reset() { *m = PluginEntraIDA func (m *PluginEntraIDAccessGraphSettings) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDAccessGraphSettings) ProtoMessage() {} func (*PluginEntraIDAccessGraphSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{327} + return fileDescriptor_9198ee693835762e, []int{328} } func (m *PluginEntraIDAccessGraphSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20490,7 +20547,7 @@ func (m *PluginEntraIDAppSSOSettings) Reset() { *m = PluginEntraIDAppSSO func (m *PluginEntraIDAppSSOSettings) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDAppSSOSettings) ProtoMessage() {} func (*PluginEntraIDAppSSOSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{328} + return fileDescriptor_9198ee693835762e, []int{329} } func (m *PluginEntraIDAppSSOSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20545,7 +20602,7 @@ func (m *PluginSCIMSettings) Reset() { *m = PluginSCIMSettings{} } func (m *PluginSCIMSettings) String() string { return proto.CompactTextString(m) } func (*PluginSCIMSettings) ProtoMessage() {} func (*PluginSCIMSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{329} + return fileDescriptor_9198ee693835762e, []int{330} } func (m *PluginSCIMSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20589,7 +20646,7 @@ func (m *PluginSCIMSettings_ConnectorInfo) Reset() { *m = PluginSCIMSett func (m *PluginSCIMSettings_ConnectorInfo) String() string { return proto.CompactTextString(m) } func (*PluginSCIMSettings_ConnectorInfo) ProtoMessage() {} func (*PluginSCIMSettings_ConnectorInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{329, 0} + return fileDescriptor_9198ee693835762e, []int{330, 0} } func (m *PluginSCIMSettings_ConnectorInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20640,7 +20697,7 @@ func (m *PluginSCIMRateLimit) Reset() { *m = PluginSCIMRateLimit{} } func (m *PluginSCIMRateLimit) String() string { return proto.CompactTextString(m) } func (*PluginSCIMRateLimit) ProtoMessage() {} func (*PluginSCIMRateLimit) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{330} + return fileDescriptor_9198ee693835762e, []int{331} } func (m *PluginSCIMRateLimit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20684,7 +20741,7 @@ func (m *PluginDatadogAccessSettings) Reset() { *m = PluginDatadogAccess func (m *PluginDatadogAccessSettings) String() string { return proto.CompactTextString(m) } func (*PluginDatadogAccessSettings) ProtoMessage() {} func (*PluginDatadogAccessSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{331} + return fileDescriptor_9198ee693835762e, []int{332} } func (m *PluginDatadogAccessSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20786,7 +20843,7 @@ func (m *PluginAWSICSettings) Reset() { *m = PluginAWSICSettings{} } func (m *PluginAWSICSettings) String() string { return proto.CompactTextString(m) } func (*PluginAWSICSettings) ProtoMessage() {} func (*PluginAWSICSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{332} + return fileDescriptor_9198ee693835762e, []int{333} } func (m *PluginAWSICSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20831,7 +20888,7 @@ func (m *AWSICCredentials) Reset() { *m = AWSICCredentials{} } func (m *AWSICCredentials) String() string { return proto.CompactTextString(m) } func (*AWSICCredentials) ProtoMessage() {} func (*AWSICCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{333} + return fileDescriptor_9198ee693835762e, []int{334} } func (m *AWSICCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20921,7 +20978,7 @@ func (m *AWSICCredentialSourceSystem) Reset() { *m = AWSICCredentialSour func (m *AWSICCredentialSourceSystem) String() string { return proto.CompactTextString(m) } func (*AWSICCredentialSourceSystem) ProtoMessage() {} func (*AWSICCredentialSourceSystem) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{334} + return fileDescriptor_9198ee693835762e, []int{335} } func (m *AWSICCredentialSourceSystem) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20965,7 +21022,7 @@ func (m *AWSICCredentialSourceOIDC) Reset() { *m = AWSICCredentialSource func (m *AWSICCredentialSourceOIDC) String() string { return proto.CompactTextString(m) } func (*AWSICCredentialSourceOIDC) ProtoMessage() {} func (*AWSICCredentialSourceOIDC) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{335} + return fileDescriptor_9198ee693835762e, []int{336} } func (m *AWSICCredentialSourceOIDC) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21021,7 +21078,7 @@ func (m *AWSICResourceFilter) Reset() { *m = AWSICResourceFilter{} } func (m *AWSICResourceFilter) String() string { return proto.CompactTextString(m) } func (*AWSICResourceFilter) ProtoMessage() {} func (*AWSICResourceFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{336} + return fileDescriptor_9198ee693835762e, []int{337} } func (m *AWSICResourceFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21144,7 +21201,7 @@ func (m *AWSICUserSyncFilter) Reset() { *m = AWSICUserSyncFilter{} } func (m *AWSICUserSyncFilter) String() string { return proto.CompactTextString(m) } func (*AWSICUserSyncFilter) ProtoMessage() {} func (*AWSICUserSyncFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{337} + return fileDescriptor_9198ee693835762e, []int{338} } func (m *AWSICUserSyncFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21191,7 +21248,7 @@ func (m *AWSICProvisioningSpec) Reset() { *m = AWSICProvisioningSpec{} } func (m *AWSICProvisioningSpec) String() string { return proto.CompactTextString(m) } func (*AWSICProvisioningSpec) ProtoMessage() {} func (*AWSICProvisioningSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{338} + return fileDescriptor_9198ee693835762e, []int{339} } func (m *AWSICProvisioningSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21233,7 +21290,7 @@ func (m *PluginAWSICStatusV1) Reset() { *m = PluginAWSICStatusV1{} } func (m *PluginAWSICStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginAWSICStatusV1) ProtoMessage() {} func (*PluginAWSICStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{339} + return fileDescriptor_9198ee693835762e, []int{340} } func (m *PluginAWSICStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21278,7 +21335,7 @@ func (m *AWSICGroupImportStatus) Reset() { *m = AWSICGroupImportStatus{} func (m *AWSICGroupImportStatus) String() string { return proto.CompactTextString(m) } func (*AWSICGroupImportStatus) ProtoMessage() {} func (*AWSICGroupImportStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{340} + return fileDescriptor_9198ee693835762e, []int{341} } func (m *AWSICGroupImportStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21329,7 +21386,7 @@ func (m *PluginEmailSettings) Reset() { *m = PluginEmailSettings{} } func (m *PluginEmailSettings) String() string { return proto.CompactTextString(m) } func (*PluginEmailSettings) ProtoMessage() {} func (*PluginEmailSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{341} + return fileDescriptor_9198ee693835762e, []int{342} } func (m *PluginEmailSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21417,7 +21474,7 @@ func (m *MailgunSpec) Reset() { *m = MailgunSpec{} } func (m *MailgunSpec) String() string { return proto.CompactTextString(m) } func (*MailgunSpec) ProtoMessage() {} func (*MailgunSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{342} + return fileDescriptor_9198ee693835762e, []int{343} } func (m *MailgunSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21464,7 +21521,7 @@ func (m *SMTPSpec) Reset() { *m = SMTPSpec{} } func (m *SMTPSpec) String() string { return proto.CompactTextString(m) } func (*SMTPSpec) ProtoMessage() {} func (*SMTPSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{343} + return fileDescriptor_9198ee693835762e, []int{344} } func (m *SMTPSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21514,7 +21571,7 @@ func (m *PluginMSTeamsSettings) Reset() { *m = PluginMSTeamsSettings{} } func (m *PluginMSTeamsSettings) String() string { return proto.CompactTextString(m) } func (*PluginMSTeamsSettings) ProtoMessage() {} func (*PluginMSTeamsSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{344} + return fileDescriptor_9198ee693835762e, []int{345} } func (m *PluginMSTeamsSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21561,7 +21618,7 @@ func (m *PluginNetIQSettings) Reset() { *m = PluginNetIQSettings{} } func (m *PluginNetIQSettings) String() string { return proto.CompactTextString(m) } func (*PluginNetIQSettings) ProtoMessage() {} func (*PluginNetIQSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{345} + return fileDescriptor_9198ee693835762e, []int{346} } func (m *PluginNetIQSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21606,7 +21663,7 @@ func (m *PluginBootstrapCredentialsV1) Reset() { *m = PluginBootstrapCre func (m *PluginBootstrapCredentialsV1) String() string { return proto.CompactTextString(m) } func (*PluginBootstrapCredentialsV1) ProtoMessage() {} func (*PluginBootstrapCredentialsV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{346} + return fileDescriptor_9198ee693835762e, []int{347} } func (m *PluginBootstrapCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21706,7 +21763,7 @@ func (m *PluginIdSecretCredential) Reset() { *m = PluginIdSecretCredenti func (m *PluginIdSecretCredential) String() string { return proto.CompactTextString(m) } func (*PluginIdSecretCredential) ProtoMessage() {} func (*PluginIdSecretCredential) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{347} + return fileDescriptor_9198ee693835762e, []int{348} } func (m *PluginIdSecretCredential) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21749,7 +21806,7 @@ func (m *PluginOAuth2AuthorizationCodeCredentials) Reset() { func (m *PluginOAuth2AuthorizationCodeCredentials) String() string { return proto.CompactTextString(m) } func (*PluginOAuth2AuthorizationCodeCredentials) ProtoMessage() {} func (*PluginOAuth2AuthorizationCodeCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{348} + return fileDescriptor_9198ee693835762e, []int{349} } func (m *PluginOAuth2AuthorizationCodeCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21809,7 +21866,7 @@ func (m *PluginStatusV1) Reset() { *m = PluginStatusV1{} } func (m *PluginStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginStatusV1) ProtoMessage() {} func (*PluginStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{349} + return fileDescriptor_9198ee693835762e, []int{350} } func (m *PluginStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21938,7 +21995,7 @@ func (m *PluginNetIQStatusV1) Reset() { *m = PluginNetIQStatusV1{} } func (m *PluginNetIQStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginNetIQStatusV1) ProtoMessage() {} func (*PluginNetIQStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{350} + return fileDescriptor_9198ee693835762e, []int{351} } func (m *PluginNetIQStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21984,7 +22041,7 @@ func (m *PluginGitlabStatusV1) Reset() { *m = PluginGitlabStatusV1{} } func (m *PluginGitlabStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginGitlabStatusV1) ProtoMessage() {} func (*PluginGitlabStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{351} + return fileDescriptor_9198ee693835762e, []int{352} } func (m *PluginGitlabStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22030,7 +22087,7 @@ func (m *PluginEntraIDStatusV1) Reset() { *m = PluginEntraIDStatusV1{} } func (m *PluginEntraIDStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginEntraIDStatusV1) ProtoMessage() {} func (*PluginEntraIDStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{352} + return fileDescriptor_9198ee693835762e, []int{353} } func (m *PluginEntraIDStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22087,7 +22144,7 @@ func (m *PluginOktaStatusV1) Reset() { *m = PluginOktaStatusV1{} } func (m *PluginOktaStatusV1) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusV1) ProtoMessage() {} func (*PluginOktaStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{353} + return fileDescriptor_9198ee693835762e, []int{354} } func (m *PluginOktaStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22140,7 +22197,7 @@ func (m *PluginOktaStatusDetailsSSO) Reset() { *m = PluginOktaStatusDeta func (m *PluginOktaStatusDetailsSSO) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusDetailsSSO) ProtoMessage() {} func (*PluginOktaStatusDetailsSSO) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{354} + return fileDescriptor_9198ee693835762e, []int{355} } func (m *PluginOktaStatusDetailsSSO) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22197,7 +22254,7 @@ func (m *PluginOktaStatusDetailsAppGroupSync) Reset() { *m = PluginOktaS func (m *PluginOktaStatusDetailsAppGroupSync) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusDetailsAppGroupSync) ProtoMessage() {} func (*PluginOktaStatusDetailsAppGroupSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{355} + return fileDescriptor_9198ee693835762e, []int{356} } func (m *PluginOktaStatusDetailsAppGroupSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22251,7 +22308,7 @@ func (m *PluginOktaStatusDetailsUsersSync) Reset() { *m = PluginOktaStat func (m *PluginOktaStatusDetailsUsersSync) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusDetailsUsersSync) ProtoMessage() {} func (*PluginOktaStatusDetailsUsersSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{356} + return fileDescriptor_9198ee693835762e, []int{357} } func (m *PluginOktaStatusDetailsUsersSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22294,7 +22351,7 @@ func (m *PluginOktaStatusDetailsSCIM) Reset() { *m = PluginOktaStatusDet func (m *PluginOktaStatusDetailsSCIM) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusDetailsSCIM) ProtoMessage() {} func (*PluginOktaStatusDetailsSCIM) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{357} + return fileDescriptor_9198ee693835762e, []int{358} } func (m *PluginOktaStatusDetailsSCIM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22356,7 +22413,7 @@ func (m *PluginOktaStatusDetailsAccessListsSync) Reset() { func (m *PluginOktaStatusDetailsAccessListsSync) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusDetailsAccessListsSync) ProtoMessage() {} func (*PluginOktaStatusDetailsAccessListsSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{358} + return fileDescriptor_9198ee693835762e, []int{359} } func (m *PluginOktaStatusDetailsAccessListsSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22408,7 +22465,7 @@ func (m *PluginOktaStatusSystemLogExporter) Reset() { *m = PluginOktaSta func (m *PluginOktaStatusSystemLogExporter) String() string { return proto.CompactTextString(m) } func (*PluginOktaStatusSystemLogExporter) ProtoMessage() {} func (*PluginOktaStatusSystemLogExporter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{359} + return fileDescriptor_9198ee693835762e, []int{360} } func (m *PluginOktaStatusSystemLogExporter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22456,7 +22513,7 @@ func (m *PluginCredentialsV1) Reset() { *m = PluginCredentialsV1{} } func (m *PluginCredentialsV1) String() string { return proto.CompactTextString(m) } func (*PluginCredentialsV1) ProtoMessage() {} func (*PluginCredentialsV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{360} + return fileDescriptor_9198ee693835762e, []int{361} } func (m *PluginCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22568,7 +22625,7 @@ func (m *PluginOAuth2AccessTokenCredentials) Reset() { *m = PluginOAuth2 func (m *PluginOAuth2AccessTokenCredentials) String() string { return proto.CompactTextString(m) } func (*PluginOAuth2AccessTokenCredentials) ProtoMessage() {} func (*PluginOAuth2AccessTokenCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{361} + return fileDescriptor_9198ee693835762e, []int{362} } func (m *PluginOAuth2AccessTokenCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22609,7 +22666,7 @@ func (m *PluginBearerTokenCredentials) Reset() { *m = PluginBearerTokenC func (m *PluginBearerTokenCredentials) String() string { return proto.CompactTextString(m) } func (*PluginBearerTokenCredentials) ProtoMessage() {} func (*PluginBearerTokenCredentials) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{362} + return fileDescriptor_9198ee693835762e, []int{363} } func (m *PluginBearerTokenCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22651,7 +22708,7 @@ func (m *PluginStaticCredentialsRef) Reset() { *m = PluginStaticCredenti func (m *PluginStaticCredentialsRef) String() string { return proto.CompactTextString(m) } func (*PluginStaticCredentialsRef) ProtoMessage() {} func (*PluginStaticCredentialsRef) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{363} + return fileDescriptor_9198ee693835762e, []int{364} } func (m *PluginStaticCredentialsRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22693,7 +22750,7 @@ func (m *PluginListV1) Reset() { *m = PluginListV1{} } func (m *PluginListV1) String() string { return proto.CompactTextString(m) } func (*PluginListV1) ProtoMessage() {} func (*PluginListV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{364} + return fileDescriptor_9198ee693835762e, []int{365} } func (m *PluginListV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22736,7 +22793,7 @@ type PluginStaticCredentialsV1 struct { func (m *PluginStaticCredentialsV1) Reset() { *m = PluginStaticCredentialsV1{} } func (*PluginStaticCredentialsV1) ProtoMessage() {} func (*PluginStaticCredentialsV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{365} + return fileDescriptor_9198ee693835762e, []int{366} } func (m *PluginStaticCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22784,7 +22841,7 @@ func (m *PluginStaticCredentialsSpecV1) Reset() { *m = PluginStaticCrede func (m *PluginStaticCredentialsSpecV1) String() string { return proto.CompactTextString(m) } func (*PluginStaticCredentialsSpecV1) ProtoMessage() {} func (*PluginStaticCredentialsSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{366} + return fileDescriptor_9198ee693835762e, []int{367} } func (m *PluginStaticCredentialsSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22912,7 +22969,7 @@ func (m *PluginStaticCredentialsBasicAuth) Reset() { *m = PluginStaticCr func (m *PluginStaticCredentialsBasicAuth) String() string { return proto.CompactTextString(m) } func (*PluginStaticCredentialsBasicAuth) ProtoMessage() {} func (*PluginStaticCredentialsBasicAuth) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{367} + return fileDescriptor_9198ee693835762e, []int{368} } func (m *PluginStaticCredentialsBasicAuth) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -22958,7 +23015,7 @@ func (m *PluginStaticCredentialsOAuthClientSecret) Reset() { func (m *PluginStaticCredentialsOAuthClientSecret) String() string { return proto.CompactTextString(m) } func (*PluginStaticCredentialsOAuthClientSecret) ProtoMessage() {} func (*PluginStaticCredentialsOAuthClientSecret) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{368} + return fileDescriptor_9198ee693835762e, []int{369} } func (m *PluginStaticCredentialsOAuthClientSecret) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23006,7 +23063,7 @@ func (m *PluginStaticCredentialsSSHCertAuthorities) String() string { } func (*PluginStaticCredentialsSSHCertAuthorities) ProtoMessage() {} func (*PluginStaticCredentialsSSHCertAuthorities) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{369} + return fileDescriptor_9198ee693835762e, []int{370} } func (m *PluginStaticCredentialsSSHCertAuthorities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23049,7 +23106,7 @@ type SAMLIdPServiceProviderV1 struct { func (m *SAMLIdPServiceProviderV1) Reset() { *m = SAMLIdPServiceProviderV1{} } func (*SAMLIdPServiceProviderV1) ProtoMessage() {} func (*SAMLIdPServiceProviderV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{370} + return fileDescriptor_9198ee693835762e, []int{371} } func (m *SAMLIdPServiceProviderV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23120,7 +23177,7 @@ func (m *SAMLIdPServiceProviderSpecV1) Reset() { *m = SAMLIdPServiceProv func (m *SAMLIdPServiceProviderSpecV1) String() string { return proto.CompactTextString(m) } func (*SAMLIdPServiceProviderSpecV1) ProtoMessage() {} func (*SAMLIdPServiceProviderSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{371} + return fileDescriptor_9198ee693835762e, []int{372} } func (m *SAMLIdPServiceProviderSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23167,7 +23224,7 @@ func (m *SAMLAttributeMapping) Reset() { *m = SAMLAttributeMapping{} } func (m *SAMLAttributeMapping) String() string { return proto.CompactTextString(m) } func (*SAMLAttributeMapping) ProtoMessage() {} func (*SAMLAttributeMapping) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{372} + return fileDescriptor_9198ee693835762e, []int{373} } func (m *SAMLAttributeMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23209,7 +23266,7 @@ func (m *IdPOptions) Reset() { *m = IdPOptions{} } func (m *IdPOptions) String() string { return proto.CompactTextString(m) } func (*IdPOptions) ProtoMessage() {} func (*IdPOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{373} + return fileDescriptor_9198ee693835762e, []int{374} } func (m *IdPOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23251,7 +23308,7 @@ func (m *IdPSAMLOptions) Reset() { *m = IdPSAMLOptions{} } func (m *IdPSAMLOptions) String() string { return proto.CompactTextString(m) } func (*IdPSAMLOptions) ProtoMessage() {} func (*IdPSAMLOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{374} + return fileDescriptor_9198ee693835762e, []int{375} } func (m *IdPSAMLOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23301,7 +23358,7 @@ func (m *KubernetesResourceV1) Reset() { *m = KubernetesResourceV1{} } func (m *KubernetesResourceV1) String() string { return proto.CompactTextString(m) } func (*KubernetesResourceV1) ProtoMessage() {} func (*KubernetesResourceV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{375} + return fileDescriptor_9198ee693835762e, []int{376} } func (m *KubernetesResourceV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23343,7 +23400,7 @@ func (m *KubernetesResourceSpecV1) Reset() { *m = KubernetesResourceSpec func (m *KubernetesResourceSpecV1) String() string { return proto.CompactTextString(m) } func (*KubernetesResourceSpecV1) ProtoMessage() {} func (*KubernetesResourceSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{376} + return fileDescriptor_9198ee693835762e, []int{377} } func (m *KubernetesResourceSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23389,7 +23446,7 @@ func (m *ClusterMaintenanceConfigV1) Reset() { *m = ClusterMaintenanceCo func (m *ClusterMaintenanceConfigV1) String() string { return proto.CompactTextString(m) } func (*ClusterMaintenanceConfigV1) ProtoMessage() {} func (*ClusterMaintenanceConfigV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{377} + return fileDescriptor_9198ee693835762e, []int{378} } func (m *ClusterMaintenanceConfigV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23431,7 +23488,7 @@ func (m *ClusterMaintenanceConfigSpecV1) Reset() { *m = ClusterMaintenan func (m *ClusterMaintenanceConfigSpecV1) String() string { return proto.CompactTextString(m) } func (*ClusterMaintenanceConfigSpecV1) ProtoMessage() {} func (*ClusterMaintenanceConfigSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{378} + return fileDescriptor_9198ee693835762e, []int{379} } func (m *ClusterMaintenanceConfigSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23477,7 +23534,7 @@ func (m *AgentUpgradeWindow) Reset() { *m = AgentUpgradeWindow{} } func (m *AgentUpgradeWindow) String() string { return proto.CompactTextString(m) } func (*AgentUpgradeWindow) ProtoMessage() {} func (*AgentUpgradeWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{379} + return fileDescriptor_9198ee693835762e, []int{380} } func (m *AgentUpgradeWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23524,7 +23581,7 @@ func (m *ScheduledAgentUpgradeWindow) Reset() { *m = ScheduledAgentUpgra func (m *ScheduledAgentUpgradeWindow) String() string { return proto.CompactTextString(m) } func (*ScheduledAgentUpgradeWindow) ProtoMessage() {} func (*ScheduledAgentUpgradeWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{380} + return fileDescriptor_9198ee693835762e, []int{381} } func (m *ScheduledAgentUpgradeWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23567,7 +23624,7 @@ func (m *AgentUpgradeSchedule) Reset() { *m = AgentUpgradeSchedule{} } func (m *AgentUpgradeSchedule) String() string { return proto.CompactTextString(m) } func (*AgentUpgradeSchedule) ProtoMessage() {} func (*AgentUpgradeSchedule) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{381} + return fileDescriptor_9198ee693835762e, []int{382} } func (m *AgentUpgradeSchedule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23610,7 +23667,7 @@ type UserGroupV1 struct { func (m *UserGroupV1) Reset() { *m = UserGroupV1{} } func (*UserGroupV1) ProtoMessage() {} func (*UserGroupV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{382} + return fileDescriptor_9198ee693835762e, []int{383} } func (m *UserGroupV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23652,7 +23709,7 @@ func (m *UserGroupSpecV1) Reset() { *m = UserGroupSpecV1{} } func (m *UserGroupSpecV1) String() string { return proto.CompactTextString(m) } func (*UserGroupSpecV1) ProtoMessage() {} func (*UserGroupSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{383} + return fileDescriptor_9198ee693835762e, []int{384} } func (m *UserGroupSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23696,7 +23753,7 @@ func (m *OktaImportRuleSpecV1) Reset() { *m = OktaImportRuleSpecV1{} } func (m *OktaImportRuleSpecV1) String() string { return proto.CompactTextString(m) } func (*OktaImportRuleSpecV1) ProtoMessage() {} func (*OktaImportRuleSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{384} + return fileDescriptor_9198ee693835762e, []int{385} } func (m *OktaImportRuleSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23740,7 +23797,7 @@ func (m *OktaImportRuleMappingV1) Reset() { *m = OktaImportRuleMappingV1 func (m *OktaImportRuleMappingV1) String() string { return proto.CompactTextString(m) } func (*OktaImportRuleMappingV1) ProtoMessage() {} func (*OktaImportRuleMappingV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{385} + return fileDescriptor_9198ee693835762e, []int{386} } func (m *OktaImportRuleMappingV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23783,7 +23840,7 @@ type OktaImportRuleV1 struct { func (m *OktaImportRuleV1) Reset() { *m = OktaImportRuleV1{} } func (*OktaImportRuleV1) ProtoMessage() {} func (*OktaImportRuleV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{386} + return fileDescriptor_9198ee693835762e, []int{387} } func (m *OktaImportRuleV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23831,7 +23888,7 @@ func (m *OktaImportRuleMatchV1) Reset() { *m = OktaImportRuleMatchV1{} } func (m *OktaImportRuleMatchV1) String() string { return proto.CompactTextString(m) } func (*OktaImportRuleMatchV1) ProtoMessage() {} func (*OktaImportRuleMatchV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{387} + return fileDescriptor_9198ee693835762e, []int{388} } func (m *OktaImportRuleMatchV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23874,7 +23931,7 @@ type OktaAssignmentV1 struct { func (m *OktaAssignmentV1) Reset() { *m = OktaAssignmentV1{} } func (*OktaAssignmentV1) ProtoMessage() {} func (*OktaAssignmentV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{388} + return fileDescriptor_9198ee693835762e, []int{389} } func (m *OktaAssignmentV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23928,7 +23985,7 @@ func (m *OktaAssignmentSpecV1) Reset() { *m = OktaAssignmentSpecV1{} } func (m *OktaAssignmentSpecV1) String() string { return proto.CompactTextString(m) } func (*OktaAssignmentSpecV1) ProtoMessage() {} func (*OktaAssignmentSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{389} + return fileDescriptor_9198ee693835762e, []int{390} } func (m *OktaAssignmentSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -23974,7 +24031,7 @@ func (m *OktaAssignmentTargetV1) Reset() { *m = OktaAssignmentTargetV1{} func (m *OktaAssignmentTargetV1) String() string { return proto.CompactTextString(m) } func (*OktaAssignmentTargetV1) ProtoMessage() {} func (*OktaAssignmentTargetV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{390} + return fileDescriptor_9198ee693835762e, []int{391} } func (m *OktaAssignmentTargetV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24022,7 +24079,7 @@ func (m *OktaAssignmentTargetStatus) Reset() { *m = OktaAssignmentTarget func (m *OktaAssignmentTargetStatus) String() string { return proto.CompactTextString(m) } func (*OktaAssignmentTargetStatus) ProtoMessage() {} func (*OktaAssignmentTargetStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{391} + return fileDescriptor_9198ee693835762e, []int{392} } func (m *OktaAssignmentTargetStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24069,7 +24126,7 @@ type IntegrationV1 struct { func (m *IntegrationV1) Reset() { *m = IntegrationV1{} } func (*IntegrationV1) ProtoMessage() {} func (*IntegrationV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{392} + return fileDescriptor_9198ee693835762e, []int{393} } func (m *IntegrationV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24118,7 +24175,7 @@ func (m *IntegrationSpecV1) Reset() { *m = IntegrationSpecV1{} } func (m *IntegrationSpecV1) String() string { return proto.CompactTextString(m) } func (*IntegrationSpecV1) ProtoMessage() {} func (*IntegrationSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{393} + return fileDescriptor_9198ee693835762e, []int{394} } func (m *IntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24229,7 +24286,7 @@ func (m *IntegrationStatusV1) Reset() { *m = IntegrationStatusV1{} } func (m *IntegrationStatusV1) String() string { return proto.CompactTextString(m) } func (*IntegrationStatusV1) ProtoMessage() {} func (*IntegrationStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{394} + return fileDescriptor_9198ee693835762e, []int{395} } func (m *IntegrationStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24291,7 +24348,7 @@ func (m *AWSOIDCIntegrationSpecV1) Reset() { *m = AWSOIDCIntegrationSpec func (m *AWSOIDCIntegrationSpecV1) String() string { return proto.CompactTextString(m) } func (*AWSOIDCIntegrationSpecV1) ProtoMessage() {} func (*AWSOIDCIntegrationSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{395} + return fileDescriptor_9198ee693835762e, []int{396} } func (m *AWSOIDCIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24337,7 +24394,7 @@ func (m *AzureOIDCIntegrationSpecV1) Reset() { *m = AzureOIDCIntegration func (m *AzureOIDCIntegrationSpecV1) String() string { return proto.CompactTextString(m) } func (*AzureOIDCIntegrationSpecV1) ProtoMessage() {} func (*AzureOIDCIntegrationSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{396} + return fileDescriptor_9198ee693835762e, []int{397} } func (m *AzureOIDCIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24379,7 +24436,7 @@ func (m *GitHubIntegrationSpecV1) Reset() { *m = GitHubIntegrationSpecV1 func (m *GitHubIntegrationSpecV1) String() string { return proto.CompactTextString(m) } func (*GitHubIntegrationSpecV1) ProtoMessage() {} func (*GitHubIntegrationSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{397} + return fileDescriptor_9198ee693835762e, []int{398} } func (m *GitHubIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24424,7 +24481,7 @@ func (m *AWSRAIntegrationSpecV1) Reset() { *m = AWSRAIntegrationSpecV1{} func (m *AWSRAIntegrationSpecV1) String() string { return proto.CompactTextString(m) } func (*AWSRAIntegrationSpecV1) ProtoMessage() {} func (*AWSRAIntegrationSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{398} + return fileDescriptor_9198ee693835762e, []int{399} } func (m *AWSRAIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24487,7 +24544,7 @@ func (m *AWSRolesAnywhereProfileSyncConfig) Reset() { *m = AWSRolesAnywh func (m *AWSRolesAnywhereProfileSyncConfig) String() string { return proto.CompactTextString(m) } func (*AWSRolesAnywhereProfileSyncConfig) ProtoMessage() {} func (*AWSRolesAnywhereProfileSyncConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{399} + return fileDescriptor_9198ee693835762e, []int{400} } func (m *AWSRolesAnywhereProfileSyncConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24529,7 +24586,7 @@ func (m *AWSRAIntegrationStatusV1) Reset() { *m = AWSRAIntegrationStatus func (m *AWSRAIntegrationStatusV1) String() string { return proto.CompactTextString(m) } func (*AWSRAIntegrationStatusV1) ProtoMessage() {} func (*AWSRAIntegrationStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{400} + return fileDescriptor_9198ee693835762e, []int{401} } func (m *AWSRAIntegrationStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24583,7 +24640,7 @@ func (m *AWSRolesAnywhereProfileSyncIterationSummary) String() string { } func (*AWSRolesAnywhereProfileSyncIterationSummary) ProtoMessage() {} func (*AWSRolesAnywhereProfileSyncIterationSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{401} + return fileDescriptor_9198ee693835762e, []int{402} } func (m *AWSRolesAnywhereProfileSyncIterationSummary) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24640,7 +24697,7 @@ func (m *HeadlessAuthentication) Reset() { *m = HeadlessAuthentication{} func (m *HeadlessAuthentication) String() string { return proto.CompactTextString(m) } func (*HeadlessAuthentication) ProtoMessage() {} func (*HeadlessAuthentication) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{402} + return fileDescriptor_9198ee693835762e, []int{403} } func (m *HeadlessAuthentication) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24685,7 +24742,7 @@ func (m *ScopeFilter) Reset() { *m = ScopeFilter{} } func (m *ScopeFilter) String() string { return proto.CompactTextString(m) } func (*ScopeFilter) ProtoMessage() {} func (*ScopeFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{403} + return fileDescriptor_9198ee693835762e, []int{404} } func (m *ScopeFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24746,7 +24803,7 @@ func (m *WatchKind) Reset() { *m = WatchKind{} } func (m *WatchKind) String() string { return proto.CompactTextString(m) } func (*WatchKind) ProtoMessage() {} func (*WatchKind) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{404} + return fileDescriptor_9198ee693835762e, []int{405} } func (m *WatchKind) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24796,7 +24853,7 @@ func (m *WatchStatusV1) Reset() { *m = WatchStatusV1{} } func (m *WatchStatusV1) String() string { return proto.CompactTextString(m) } func (*WatchStatusV1) ProtoMessage() {} func (*WatchStatusV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{405} + return fileDescriptor_9198ee693835762e, []int{406} } func (m *WatchStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24837,7 +24894,7 @@ func (m *WatchStatusSpecV1) Reset() { *m = WatchStatusSpecV1{} } func (m *WatchStatusSpecV1) String() string { return proto.CompactTextString(m) } func (*WatchStatusSpecV1) ProtoMessage() {} func (*WatchStatusSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{406} + return fileDescriptor_9198ee693835762e, []int{407} } func (m *WatchStatusSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24887,7 +24944,7 @@ func (m *ServerInfoV1) Reset() { *m = ServerInfoV1{} } func (m *ServerInfoV1) String() string { return proto.CompactTextString(m) } func (*ServerInfoV1) ProtoMessage() {} func (*ServerInfoV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{407} + return fileDescriptor_9198ee693835762e, []int{408} } func (m *ServerInfoV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24929,7 +24986,7 @@ func (m *ServerInfoSpecV1) Reset() { *m = ServerInfoSpecV1{} } func (m *ServerInfoSpecV1) String() string { return proto.CompactTextString(m) } func (*ServerInfoSpecV1) ProtoMessage() {} func (*ServerInfoSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{408} + return fileDescriptor_9198ee693835762e, []int{409} } func (m *ServerInfoSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -24986,7 +25043,7 @@ func (m *JamfSpecV1) Reset() { *m = JamfSpecV1{} } func (m *JamfSpecV1) String() string { return proto.CompactTextString(m) } func (*JamfSpecV1) ProtoMessage() {} func (*JamfSpecV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{409} + return fileDescriptor_9198ee693835762e, []int{410} } func (m *JamfSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25058,7 +25115,7 @@ func (m *JamfInventoryEntry) Reset() { *m = JamfInventoryEntry{} } func (m *JamfInventoryEntry) String() string { return proto.CompactTextString(m) } func (*JamfInventoryEntry) ProtoMessage() {} func (*JamfInventoryEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{410} + return fileDescriptor_9198ee693835762e, []int{411} } func (m *JamfInventoryEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25115,7 +25172,7 @@ type MessageWithHeader struct { func (m *MessageWithHeader) Reset() { *m = MessageWithHeader{} } func (*MessageWithHeader) ProtoMessage() {} func (*MessageWithHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{411} + return fileDescriptor_9198ee693835762e, []int{412} } func (m *MessageWithHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25181,7 +25238,7 @@ func (m *AWSMatcher) Reset() { *m = AWSMatcher{} } func (m *AWSMatcher) String() string { return proto.CompactTextString(m) } func (*AWSMatcher) ProtoMessage() {} func (*AWSMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{412} + return fileDescriptor_9198ee693835762e, []int{413} } func (m *AWSMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25226,7 +25283,7 @@ func (m *AWSOrganizationMatcher) Reset() { *m = AWSOrganizationMatcher{} func (m *AWSOrganizationMatcher) String() string { return proto.CompactTextString(m) } func (*AWSOrganizationMatcher) ProtoMessage() {} func (*AWSOrganizationMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{413} + return fileDescriptor_9198ee693835762e, []int{414} } func (m *AWSOrganizationMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25277,7 +25334,7 @@ func (m *AWSOrganizationUnitsMatcher) Reset() { *m = AWSOrganizationUnit func (m *AWSOrganizationUnitsMatcher) String() string { return proto.CompactTextString(m) } func (*AWSOrganizationUnitsMatcher) ProtoMessage() {} func (*AWSOrganizationUnitsMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{414} + return fileDescriptor_9198ee693835762e, []int{415} } func (m *AWSOrganizationUnitsMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25325,7 +25382,7 @@ func (m *AssumeRole) Reset() { *m = AssumeRole{} } func (m *AssumeRole) String() string { return proto.CompactTextString(m) } func (*AssumeRole) ProtoMessage() {} func (*AssumeRole) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{415} + return fileDescriptor_9198ee693835762e, []int{416} } func (m *AssumeRole) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25402,7 +25459,7 @@ func (m *InstallerParams) Reset() { *m = InstallerParams{} } func (m *InstallerParams) String() string { return proto.CompactTextString(m) } func (*InstallerParams) ProtoMessage() {} func (*InstallerParams) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{416} + return fileDescriptor_9198ee693835762e, []int{417} } func (m *InstallerParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25451,7 +25508,7 @@ func (m *HTTPProxySettings) Reset() { *m = HTTPProxySettings{} } func (m *HTTPProxySettings) String() string { return proto.CompactTextString(m) } func (*HTTPProxySettings) ProtoMessage() {} func (*HTTPProxySettings) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{417} + return fileDescriptor_9198ee693835762e, []int{418} } func (m *HTTPProxySettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25494,7 +25551,7 @@ func (m *AWSSSM) Reset() { *m = AWSSSM{} } func (m *AWSSSM) String() string { return proto.CompactTextString(m) } func (*AWSSSM) ProtoMessage() {} func (*AWSSSM) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{418} + return fileDescriptor_9198ee693835762e, []int{419} } func (m *AWSSSM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25537,7 +25594,7 @@ func (m *AzureInstallerParams) Reset() { *m = AzureInstallerParams{} } func (m *AzureInstallerParams) String() string { return proto.CompactTextString(m) } func (*AzureInstallerParams) ProtoMessage() {} func (*AzureInstallerParams) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{419} + return fileDescriptor_9198ee693835762e, []int{420} } func (m *AzureInstallerParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25594,7 +25651,7 @@ func (m *AzureMatcher) Reset() { *m = AzureMatcher{} } func (m *AzureMatcher) String() string { return proto.CompactTextString(m) } func (*AzureMatcher) ProtoMessage() {} func (*AzureMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{420} + return fileDescriptor_9198ee693835762e, []int{421} } func (m *AzureMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25649,7 +25706,7 @@ func (m *GCPMatcher) Reset() { *m = GCPMatcher{} } func (m *GCPMatcher) String() string { return proto.CompactTextString(m) } func (*GCPMatcher) ProtoMessage() {} func (*GCPMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{421} + return fileDescriptor_9198ee693835762e, []int{422} } func (m *GCPMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25695,7 +25752,7 @@ func (m *KubernetesMatcher) Reset() { *m = KubernetesMatcher{} } func (m *KubernetesMatcher) String() string { return proto.CompactTextString(m) } func (*KubernetesMatcher) ProtoMessage() {} func (*KubernetesMatcher) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{422} + return fileDescriptor_9198ee693835762e, []int{423} } func (m *KubernetesMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25737,7 +25794,7 @@ func (m *OktaOptions) Reset() { *m = OktaOptions{} } func (m *OktaOptions) String() string { return proto.CompactTextString(m) } func (*OktaOptions) ProtoMessage() {} func (*OktaOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{423} + return fileDescriptor_9198ee693835762e, []int{424} } func (m *OktaOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25783,7 +25840,7 @@ func (m *AccessGraphSync) Reset() { *m = AccessGraphSync{} } func (m *AccessGraphSync) String() string { return proto.CompactTextString(m) } func (*AccessGraphSync) ProtoMessage() {} func (*AccessGraphSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{424} + return fileDescriptor_9198ee693835762e, []int{425} } func (m *AccessGraphSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25828,7 +25885,7 @@ func (m *AccessGraphAWSSyncCloudTrailLogs) Reset() { *m = AccessGraphAWS func (m *AccessGraphAWSSyncCloudTrailLogs) String() string { return proto.CompactTextString(m) } func (*AccessGraphAWSSyncCloudTrailLogs) ProtoMessage() {} func (*AccessGraphAWSSyncCloudTrailLogs) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{425} + return fileDescriptor_9198ee693835762e, []int{426} } func (m *AccessGraphAWSSyncCloudTrailLogs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25871,7 +25928,7 @@ func (m *AccessGraphAWSSyncEKSAuditLogs) Reset() { *m = AccessGraphAWSSy func (m *AccessGraphAWSSyncEKSAuditLogs) String() string { return proto.CompactTextString(m) } func (*AccessGraphAWSSyncEKSAuditLogs) ProtoMessage() {} func (*AccessGraphAWSSyncEKSAuditLogs) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{426} + return fileDescriptor_9198ee693835762e, []int{427} } func (m *AccessGraphAWSSyncEKSAuditLogs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25920,7 +25977,7 @@ func (m *AccessGraphAWSSync) Reset() { *m = AccessGraphAWSSync{} } func (m *AccessGraphAWSSync) String() string { return proto.CompactTextString(m) } func (*AccessGraphAWSSync) ProtoMessage() {} func (*AccessGraphAWSSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{427} + return fileDescriptor_9198ee693835762e, []int{428} } func (m *AccessGraphAWSSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -25964,7 +26021,7 @@ func (m *AccessGraphAzureSync) Reset() { *m = AccessGraphAzureSync{} } func (m *AccessGraphAzureSync) String() string { return proto.CompactTextString(m) } func (*AccessGraphAzureSync) ProtoMessage() {} func (*AccessGraphAzureSync) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{428} + return fileDescriptor_9198ee693835762e, []int{429} } func (m *AccessGraphAzureSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -26020,7 +26077,7 @@ func (m *TargetHealth) Reset() { *m = TargetHealth{} } func (m *TargetHealth) String() string { return proto.CompactTextString(m) } func (*TargetHealth) ProtoMessage() {} func (*TargetHealth) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{429} + return fileDescriptor_9198ee693835762e, []int{430} } func (m *TargetHealth) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -26126,6 +26183,7 @@ func init() { proto.RegisterType((*MongoAtlas)(nil), "types.MongoAtlas") proto.RegisterType((*InstanceV1)(nil), "types.InstanceV1") proto.RegisterType((*InstanceSpecV1)(nil), "types.InstanceSpecV1") + proto.RegisterType((*AuditQueueStatus)(nil), "types.AuditQueueStatus") proto.RegisterType((*SystemClockMeasurement)(nil), "types.SystemClockMeasurement") proto.RegisterType((*InstanceControlLogEntry)(nil), "types.InstanceControlLogEntry") proto.RegisterMapType((map[string]string)(nil), "types.InstanceControlLogEntry.LabelsEntry") @@ -26573,2359 +26631,2367 @@ func init() { func init() { proto.RegisterFile("teleport/legacy/types/types.proto", fileDescriptor_9198ee693835762e) } var fileDescriptor_9198ee693835762e = []byte{ - // 37618 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xfd, 0x6b, 0x90, 0x1c, 0x49, - 0x72, 0x20, 0x06, 0x6f, 0x55, 0xf5, 0xa3, 0xda, 0xfb, 0x55, 0x1d, 0x78, 0x35, 0x7a, 0x80, 0x69, - 0x4c, 0xce, 0x0c, 0x06, 0x98, 0x9d, 0x01, 0x66, 0x1a, 0x3b, 0xd8, 0xdd, 0x99, 0xd9, 0x9d, 0xa9, - 0x7e, 0x00, 0x68, 0xa0, 0x1b, 0xe8, 0xc9, 0x6a, 0xa0, 0x77, 0xb9, 0x8f, 0x9c, 0xec, 0xaa, 0xe8, - 0xee, 0x1c, 0x54, 0x65, 0xd6, 0x64, 0x66, 0x35, 0xd0, 0xb7, 0xbc, 0xc7, 0xc7, 0xdb, 0xbd, 0xe7, - 0x47, 0xf1, 0xc8, 0x23, 0x8f, 0xa4, 0x4c, 0xa4, 0x9d, 0xc9, 0xec, 0x74, 0x0f, 0x8a, 0x32, 0x13, - 0x8f, 0x27, 0x51, 0x76, 0x3a, 0x8a, 0x94, 0xdd, 0x43, 0x92, 0x49, 0xbc, 0x33, 0xe3, 0x49, 0x3a, - 0xd1, 0xd6, 0x9a, 0x26, 0x91, 0x92, 0x9d, 0x20, 0xb3, 0xb3, 0x93, 0x7e, 0xd0, 0x4c, 0x7b, 0x26, - 0x93, 0x2c, 0x3c, 0x3c, 0x32, 0x23, 0x32, 0xb3, 0xfa, 0x31, 0x83, 0xe1, 0x11, 0x6b, 0xfc, 0x03, - 0x74, 0x79, 0xb8, 0x7b, 0x64, 0xbc, 0x3c, 0x3c, 0x3c, 0xdc, 0x3d, 0xe0, 0x85, 0x98, 0xb7, 0x79, - 0x37, 0x08, 0xe3, 0xab, 0x6d, 0xbe, 0xed, 0x36, 0xf7, 0xae, 0xc6, 0x7b, 0x5d, 0x1e, 0xc9, 0x7f, - 0xaf, 0x74, 0xc3, 0x20, 0x0e, 0xd8, 0x20, 0xfe, 0x98, 0x39, 0xb9, 0x1d, 0x6c, 0x07, 0x08, 0xb9, - 0x2a, 0xfe, 0x92, 0x85, 0x33, 0xcf, 0x6f, 0x07, 0xc1, 0x76, 0x9b, 0x5f, 0xc5, 0x5f, 0x9b, 0xbd, - 0xad, 0xab, 0xad, 0x5e, 0xe8, 0xc6, 0x5e, 0xe0, 0x53, 0xf9, 0xb9, 0x6c, 0x79, 0x14, 0x87, 0xbd, - 0x66, 0x4c, 0xa5, 0xb3, 0xd9, 0xd2, 0xd8, 0xeb, 0xf0, 0x28, 0x76, 0x3b, 0x5d, 0x42, 0xb8, 0x9c, - 0x7c, 0x9e, 0x1b, 0xc7, 0xa2, 0x44, 0xb0, 0xbe, 0xba, 0xfb, 0xa6, 0xfe, 0x93, 0x50, 0xaf, 0x27, - 0xa8, 0xcd, 0xa0, 0xd3, 0x0d, 0x7c, 0xee, 0xc7, 0x5b, 0xdc, 0x8d, 0x7b, 0x21, 0x8f, 0x04, 0x41, - 0x02, 0x74, 0x14, 0x94, 0xe8, 0x5e, 0x2a, 0xee, 0x81, 0x0e, 0x8f, 0xdd, 0x96, 0x1b, 0xbb, 0x84, - 0x75, 0xb1, 0x0f, 0xd6, 0x96, 0xeb, 0xb4, 0xf8, 0xae, 0xd7, 0xe4, 0x84, 0xf7, 0x72, 0x31, 0x5e, - 0xc8, 0xa3, 0xa0, 0x17, 0x36, 0x93, 0x4a, 0xaf, 0xf7, 0xe9, 0xf6, 0xb0, 0x17, 0xc5, 0xbc, 0x45, - 0x2c, 0x9d, 0x90, 0x7f, 0xdc, 0xf3, 0x42, 0xde, 0xe1, 0xbe, 0xea, 0xb0, 0xd7, 0x8b, 0xe9, 0x1e, - 0x85, 0x6e, 0xb7, 0xcb, 0xc3, 0xf4, 0x0f, 0xd5, 0xbf, 0x09, 0x7a, 0xd4, 0x0c, 0xba, 0xb2, 0x23, - 0xe4, 0x5f, 0x12, 0xc1, 0xfa, 0xd5, 0x0a, 0x8c, 0xdc, 0xe1, 0xbc, 0x5b, 0x6f, 0x7b, 0xbb, 0x9c, - 0xbd, 0x08, 0x03, 0x77, 0xdd, 0x0e, 0x9f, 0x2e, 0x5d, 0x28, 0x5d, 0x1a, 0x99, 0x9f, 0x7c, 0xb2, - 0x3f, 0x3b, 0x1a, 0xf1, 0x70, 0x97, 0x87, 0x8e, 0xef, 0x76, 0xb8, 0x8d, 0x85, 0xec, 0xf3, 0x30, - 0x22, 0xfe, 0x8f, 0xba, 0x6e, 0x93, 0x4f, 0x97, 0x11, 0x73, 0xfc, 0xc9, 0xfe, 0xec, 0x88, 0xaf, - 0x80, 0x76, 0x5a, 0xce, 0x96, 0x61, 0x78, 0xe9, 0x71, 0xd7, 0x0b, 0x79, 0x34, 0x3d, 0x70, 0xa1, - 0x74, 0x69, 0x74, 0x6e, 0xe6, 0x8a, 0x1c, 0xf2, 0x2b, 0x6a, 0xc8, 0xaf, 0xac, 0xab, 0x21, 0x9f, - 0x3f, 0xf1, 0x5f, 0xed, 0xcf, 0x7e, 0xee, 0xc9, 0xfe, 0xec, 0x30, 0x97, 0x24, 0x7f, 0xe5, 0x77, - 0x67, 0x4b, 0xb6, 0xa2, 0x67, 0xef, 0xc2, 0xc0, 0xfa, 0x5e, 0x97, 0x4f, 0x8f, 0x5c, 0x28, 0x5d, - 0x9a, 0x98, 0x7b, 0xfe, 0x8a, 0x9c, 0xa2, 0xc9, 0xc7, 0xa7, 0x7f, 0x09, 0xac, 0xf9, 0xea, 0x93, - 0xfd, 0xd9, 0x01, 0x81, 0x62, 0x23, 0x15, 0x7b, 0x1d, 0x86, 0x6e, 0x05, 0x51, 0xbc, 0xbc, 0x38, - 0x0d, 0xf8, 0xc9, 0xa7, 0x9e, 0xec, 0xcf, 0x4e, 0xed, 0x04, 0x51, 0xec, 0x78, 0xad, 0xd7, 0x82, - 0x8e, 0x17, 0xf3, 0x4e, 0x37, 0xde, 0xb3, 0x09, 0x89, 0x9d, 0x84, 0x41, 0xec, 0xa7, 0xe9, 0x51, - 0x81, 0x6d, 0xcb, 0x1f, 0xd6, 0x63, 0x18, 0x37, 0x6a, 0x61, 0xa3, 0x30, 0x7c, 0xff, 0xee, 0x9d, - 0xbb, 0xf7, 0x36, 0xee, 0xd6, 0x3e, 0xc7, 0xaa, 0x30, 0x70, 0xf7, 0xde, 0xe2, 0x52, 0xad, 0xc4, - 0x86, 0xa1, 0x52, 0x5f, 0x5b, 0xab, 0x95, 0xd9, 0x18, 0x54, 0x17, 0xeb, 0xeb, 0xf5, 0xf9, 0x7a, - 0x63, 0xa9, 0x56, 0x61, 0x27, 0x60, 0x72, 0x63, 0xf9, 0xee, 0xe2, 0xbd, 0x8d, 0x86, 0xb3, 0xb8, - 0xd4, 0xb8, 0xb3, 0x7e, 0x6f, 0xad, 0x36, 0xc0, 0x26, 0x00, 0xee, 0xdc, 0x9f, 0x5f, 0xb2, 0xef, - 0x2e, 0xad, 0x2f, 0x35, 0x6a, 0x83, 0xec, 0x24, 0xd4, 0x14, 0x89, 0xd3, 0x58, 0xb2, 0x1f, 0x2c, - 0x2f, 0x2c, 0xd5, 0x86, 0x6e, 0x0f, 0x54, 0x2b, 0xb5, 0x01, 0x7b, 0x78, 0x85, 0xbb, 0x11, 0x5f, - 0x5e, 0xb4, 0x7e, 0x6a, 0x00, 0xaa, 0x76, 0x20, 0xa7, 0x3f, 0xbb, 0x0c, 0x83, 0x8d, 0xd8, 0x8d, - 0xd5, 0xb0, 0x9d, 0x78, 0xb2, 0x3f, 0x3b, 0x29, 0x96, 0x06, 0xd7, 0xda, 0x25, 0x31, 0x04, 0xea, - 0xda, 0x8e, 0x1b, 0xa9, 0x71, 0x43, 0xd4, 0xae, 0x00, 0xe8, 0xa8, 0x88, 0xc1, 0x2e, 0xc2, 0xc0, - 0x6a, 0xd0, 0xe2, 0xd3, 0x15, 0xc4, 0x64, 0x4f, 0xf6, 0x67, 0x27, 0x3a, 0x41, 0x4b, 0x47, 0xc4, - 0x72, 0xf6, 0x1a, 0x8c, 0x2c, 0xf4, 0xc2, 0x90, 0xfb, 0xa2, 0x6f, 0x07, 0x10, 0x79, 0xe2, 0xc9, - 0xfe, 0x2c, 0x34, 0x25, 0xd0, 0xf1, 0x5a, 0x76, 0x8a, 0xc0, 0x1a, 0x30, 0xdc, 0x88, 0xdd, 0x30, - 0xe6, 0xad, 0xe9, 0xc1, 0x43, 0xe7, 0xc3, 0x79, 0x9a, 0x0f, 0x53, 0x91, 0x24, 0x49, 0xeb, 0x96, - 0x33, 0x83, 0x38, 0xb1, 0x5b, 0x30, 0x7a, 0x33, 0x74, 0x9b, 0x7c, 0x8d, 0x87, 0x5e, 0xd0, 0x9a, - 0x1e, 0xba, 0x50, 0xba, 0x54, 0x99, 0xbf, 0xf8, 0x64, 0x7f, 0xf6, 0xf4, 0xb6, 0x00, 0x3b, 0x5d, - 0x84, 0xa7, 0xd4, 0x3f, 0xd8, 0x9f, 0xad, 0x2e, 0x92, 0x98, 0xb2, 0x75, 0x52, 0xf6, 0x21, 0x8c, - 0xae, 0xb8, 0x51, 0x8c, 0x5d, 0xcb, 0x5b, 0xd3, 0xc3, 0x87, 0x7e, 0xa2, 0x45, 0x9f, 0x78, 0xba, - 0xed, 0x46, 0xb1, 0x13, 0x4a, 0xba, 0xcc, 0x77, 0xea, 0x2c, 0xd9, 0x3d, 0xa8, 0x36, 0x9a, 0x3b, - 0xbc, 0xd5, 0x6b, 0xf3, 0xe9, 0x2a, 0xb2, 0x3f, 0x43, 0x33, 0x59, 0x8d, 0xa7, 0x2a, 0x9e, 0x9f, - 0x21, 0xde, 0x2c, 0x22, 0x88, 0xd6, 0xf7, 0x09, 0x93, 0xb7, 0xab, 0x3f, 0xf7, 0xd7, 0x67, 0x3f, - 0xf7, 0x67, 0xbe, 0x7f, 0xe1, 0x73, 0xd6, 0x7f, 0x5a, 0x86, 0x5a, 0x96, 0x09, 0xdb, 0x82, 0xf1, - 0xfb, 0xdd, 0x96, 0x1b, 0xf3, 0x85, 0xb6, 0xc7, 0xfd, 0x38, 0xc2, 0x49, 0x72, 0x70, 0x9b, 0x5e, - 0xa2, 0x7a, 0xa7, 0x7b, 0x48, 0xe8, 0x34, 0x25, 0x65, 0xa6, 0x55, 0x26, 0xdb, 0xb4, 0x9e, 0x06, - 0x0a, 0x8c, 0x08, 0x67, 0xd8, 0xf1, 0xea, 0x91, 0xa2, 0xa6, 0x4f, 0x3d, 0xc4, 0x96, 0x26, 0x90, - 0xdf, 0xda, 0xdc, 0xc3, 0x99, 0x79, 0xf4, 0x09, 0x24, 0x48, 0x0a, 0x26, 0x90, 0x00, 0x5b, 0xbf, - 0x5f, 0x82, 0x09, 0x9b, 0x24, 0xf4, 0x2d, 0xee, 0xb6, 0x78, 0x28, 0xa6, 0xff, 0x1d, 0xcf, 0x6f, - 0xd1, 0x9a, 0xc2, 0xe9, 0xff, 0xd0, 0xf3, 0x75, 0x51, 0x81, 0xe5, 0xec, 0x0d, 0x18, 0x6e, 0xf4, - 0x36, 0x11, 0x55, 0xae, 0xa9, 0xd3, 0x38, 0x62, 0xbd, 0x4d, 0x27, 0x83, 0xae, 0xd0, 0xd8, 0x55, - 0x18, 0x7e, 0xc0, 0xc3, 0xc8, 0x0b, 0x7c, 0x5a, 0x5b, 0x28, 0x8a, 0x76, 0x25, 0x48, 0x27, 0x20, - 0x2c, 0x76, 0x13, 0xaa, 0xab, 0xb4, 0x19, 0x91, 0x10, 0x9d, 0xa4, 0x29, 0xa3, 0xc0, 0xe9, 0x54, - 0x51, 0xbb, 0x96, 0x3e, 0x55, 0x14, 0x96, 0xf5, 0xaf, 0xca, 0x50, 0x5b, 0x74, 0x63, 0x77, 0xd3, - 0x8d, 0xa8, 0x3f, 0x1f, 0x5c, 0x63, 0xe7, 0x8c, 0x86, 0xa2, 0xd8, 0x14, 0x5f, 0xfe, 0x89, 0x9b, - 0xf7, 0x72, 0xb6, 0x79, 0xa3, 0x42, 0xa2, 0x53, 0xf3, 0xd2, 0x46, 0x7d, 0xe5, 0xf0, 0x46, 0xd5, - 0xa8, 0x51, 0x55, 0xd5, 0xa8, 0xb4, 0x29, 0xec, 0x2b, 0x30, 0xd0, 0xe8, 0xf2, 0x26, 0x09, 0x91, - 0xe7, 0x88, 0xd4, 0x6c, 0x9c, 0x40, 0x78, 0x70, 0x6d, 0x7e, 0x8c, 0xd8, 0x0c, 0x44, 0x5d, 0xde, - 0xb4, 0x91, 0x8c, 0x2d, 0xc1, 0x90, 0x10, 0x88, 0xbd, 0x08, 0x85, 0xc5, 0xe8, 0xdc, 0xf9, 0x62, - 0x06, 0x88, 0xf2, 0xe0, 0xda, 0xfc, 0x04, 0xb1, 0x18, 0x8a, 0x10, 0x62, 0x13, 0x71, 0xba, 0x4b, - 0x0c, 0x6b, 0xbb, 0x84, 0xb6, 0x22, 0xff, 0xa0, 0x02, 0x27, 0x8b, 0xbe, 0x49, 0xef, 0xa4, 0xa1, - 0x03, 0x3a, 0xe9, 0x12, 0x54, 0xc5, 0x7e, 0x24, 0x76, 0x56, 0x59, 0xc5, 0xfc, 0x98, 0xe8, 0x8f, - 0x1d, 0x82, 0xd9, 0x49, 0x29, 0x7b, 0x31, 0xd9, 0xde, 0xaa, 0x29, 0x3f, 0xda, 0xde, 0x92, 0x4d, - 0xed, 0x66, 0xba, 0x69, 0xe0, 0x2e, 0x98, 0xf6, 0xb9, 0x02, 0xa7, 0x13, 0x29, 0x24, 0x88, 0x3e, - 0x91, 0x92, 0x1d, 0x67, 0x09, 0xaa, 0xaa, 0x59, 0xd3, 0x63, 0xc8, 0x68, 0x2a, 0xd3, 0x81, 0x0f, - 0xae, 0xc9, 0x99, 0xd2, 0xa2, 0xdf, 0x3a, 0x1b, 0x85, 0xc3, 0xae, 0x41, 0x75, 0x2d, 0x0c, 0x1e, - 0xef, 0x2d, 0x2f, 0x46, 0xd3, 0xe3, 0x17, 0x2a, 0x97, 0x46, 0xe6, 0xcf, 0x3c, 0xd9, 0x9f, 0x3d, - 0xd1, 0x15, 0x30, 0xc7, 0x6b, 0x45, 0x3a, 0x91, 0x42, 0x64, 0xb3, 0x30, 0x1a, 0xf2, 0xb6, 0xbb, - 0xe7, 0x6c, 0x87, 0x41, 0xaf, 0x3b, 0x3d, 0x81, 0x3d, 0x0f, 0x08, 0xba, 0x29, 0x20, 0xec, 0x39, - 0x18, 0x91, 0x08, 0x5e, 0x2b, 0x9a, 0x9e, 0x14, 0x6c, 0xed, 0x2a, 0x02, 0x96, 0x5b, 0xd1, 0xed, - 0x81, 0x6a, 0xa9, 0x56, 0xbe, 0x3d, 0x50, 0x2d, 0xd7, 0x2a, 0x72, 0x4f, 0xbd, 0x3d, 0x50, 0x1d, - 0xa8, 0x0d, 0xde, 0x1e, 0xa8, 0x0e, 0xe2, 0x2e, 0x3b, 0x52, 0x83, 0xdb, 0x03, 0xd5, 0xd1, 0xda, - 0x98, 0x3d, 0xba, 0xc8, 0xa3, 0x66, 0xe8, 0x75, 0x71, 0x93, 0x10, 0xd5, 0xc7, 0x41, 0x33, 0x68, - 0xdb, 0x95, 0xfb, 0xf6, 0xb2, 0x3d, 0xb4, 0x50, 0x5f, 0xe0, 0x61, 0x6c, 0x57, 0xea, 0x1b, 0x0d, - 0x7b, 0x7c, 0x71, 0xcf, 0x77, 0x3b, 0x5e, 0x73, 0xc5, 0xdd, 0xe4, 0xed, 0xc8, 0xae, 0xdc, 0x5c, - 0x58, 0xb3, 0x7c, 0x38, 0x5d, 0x3c, 0x95, 0xd8, 0x3a, 0x8c, 0xad, 0xbb, 0xe1, 0x36, 0x8f, 0x6f, - 0x71, 0xb7, 0x1d, 0xef, 0xe0, 0xf7, 0x8f, 0xce, 0x9d, 0xa0, 0xee, 0xd3, 0x8b, 0xe6, 0x9f, 0x7b, - 0xb2, 0x3f, 0x7b, 0x26, 0x46, 0x88, 0xb3, 0x83, 0x20, 0xad, 0x43, 0x0c, 0x2e, 0x56, 0x1d, 0x26, - 0xd2, 0x9e, 0x5f, 0xf1, 0xa2, 0x98, 0x5d, 0x85, 0x11, 0x05, 0x11, 0x32, 0xbf, 0x52, 0x38, 0x46, - 0x76, 0x8a, 0x63, 0xfd, 0xe3, 0x32, 0x40, 0x5a, 0xf2, 0x8c, 0x8a, 0x85, 0x2f, 0x1a, 0x62, 0xe1, - 0x54, 0x76, 0x55, 0xf7, 0x17, 0x08, 0xef, 0x65, 0x04, 0xc2, 0x99, 0x2c, 0xe9, 0x21, 0xa2, 0x40, - 0x5b, 0xf4, 0xbf, 0x38, 0x9c, 0x0e, 0x06, 0x2d, 0xf7, 0x4b, 0x90, 0x4c, 0x20, 0xea, 0x50, 0x5c, - 0xc7, 0x5d, 0x35, 0xa9, 0x92, 0x52, 0x76, 0x16, 0xc4, 0x04, 0xa3, 0x4e, 0x1d, 0x7e, 0xb2, 0x3f, - 0x5b, 0xe9, 0x85, 0x1e, 0x4e, 0x3a, 0x76, 0x15, 0x68, 0xda, 0x51, 0x07, 0x8a, 0xb5, 0x32, 0xd5, - 0x74, 0x9d, 0x26, 0x0f, 0xe3, 0xb4, 0xc7, 0xa7, 0x4b, 0x6a, 0x76, 0xb2, 0x2e, 0x98, 0x53, 0x73, - 0x7a, 0x00, 0xa7, 0xc1, 0xa5, 0xc2, 0x5e, 0xb9, 0x62, 0xa0, 0x2e, 0xf9, 0x71, 0xb8, 0x37, 0x7f, - 0x41, 0x6d, 0xd0, 0x2d, 0x59, 0xe6, 0xb4, 0xb1, 0x50, 0x1b, 0x5e, 0xb3, 0x02, 0x76, 0x0d, 0xc4, - 0x8a, 0xa0, 0xde, 0x07, 0xaa, 0xa7, 0xbe, 0xd1, 0x98, 0x3f, 0x45, 0x9c, 0xc6, 0xdd, 0x47, 0x3a, - 0xb9, 0xc0, 0x66, 0xef, 0x80, 0x58, 0x32, 0xd4, 0xef, 0x8c, 0x88, 0x6e, 0x2e, 0xac, 0x2d, 0xb4, - 0x83, 0x5e, 0xab, 0xf1, 0xc1, 0x4a, 0x4a, 0xbc, 0xdd, 0xec, 0xea, 0xc4, 0x37, 0x17, 0xd6, 0xd8, - 0x3b, 0x30, 0x58, 0xff, 0x13, 0xbd, 0x90, 0x93, 0xaa, 0x36, 0xa6, 0xea, 0x14, 0xb0, 0xf9, 0x33, - 0x44, 0x38, 0xe9, 0x8a, 0x9f, 0xba, 0x8a, 0x8b, 0xe5, 0xa2, 0xe6, 0xf5, 0x95, 0x06, 0xa9, 0x61, - 0x2c, 0xd3, 0x2d, 0xeb, 0x2b, 0xda, 0x67, 0xc7, 0x46, 0xab, 0x05, 0x15, 0xbb, 0x0a, 0xe5, 0xfa, - 0x22, 0x1e, 0x46, 0x46, 0xe7, 0x46, 0x54, 0xb5, 0x8b, 0xf3, 0x27, 0x89, 0x64, 0xcc, 0xd5, 0x97, - 0x41, 0xb9, 0xbe, 0xc8, 0xe6, 0x61, 0x70, 0x75, 0xaf, 0xf1, 0xc1, 0x0a, 0x89, 0x5e, 0xb5, 0xe4, - 0x11, 0x76, 0x0f, 0xc5, 0x4c, 0x94, 0x7e, 0x71, 0x67, 0x2f, 0xfa, 0xb8, 0xad, 0x7f, 0x31, 0xa2, - 0xb1, 0x35, 0x18, 0xa9, 0xb7, 0x3a, 0x9e, 0x7f, 0x3f, 0xe2, 0x21, 0x1e, 0x4d, 0x46, 0xe7, 0xa6, - 0x33, 0xdf, 0x9d, 0x94, 0xcf, 0x4f, 0x3f, 0xd9, 0x9f, 0x3d, 0xe9, 0x8a, 0x9f, 0x4e, 0x2f, 0xe2, - 0xa1, 0xc6, 0x2d, 0x65, 0xc2, 0xd6, 0x00, 0x56, 0x03, 0x7f, 0x3b, 0xa8, 0xc7, 0x6d, 0x37, 0xca, - 0x08, 0xf3, 0xb4, 0x20, 0xd1, 0xa4, 0x4e, 0x75, 0x04, 0xcc, 0x71, 0x05, 0x50, 0x63, 0xa8, 0xf1, - 0x60, 0x37, 0x60, 0xe8, 0x5e, 0xe8, 0x36, 0xdb, 0x7c, 0x7a, 0x1c, 0xb9, 0x9d, 0x24, 0x6e, 0x12, - 0xa8, 0x5a, 0x3a, 0x4d, 0x0c, 0x6b, 0x01, 0x82, 0xf5, 0x23, 0x98, 0x44, 0x9c, 0xd9, 0x00, 0x96, - 0x9f, 0x93, 0xac, 0x06, 0x95, 0x87, 0x7c, 0x4f, 0xae, 0x22, 0x5b, 0xfc, 0xc9, 0x3e, 0x0f, 0x83, - 0xbb, 0x6e, 0xbb, 0xc7, 0x49, 0xe3, 0x54, 0x8b, 0x7e, 0x21, 0xe8, 0x74, 0x5c, 0xbf, 0x85, 0xb4, - 0x0f, 0xe6, 0x6c, 0x89, 0xf3, 0x76, 0xf9, 0x4b, 0x25, 0xeb, 0x63, 0x98, 0xca, 0x75, 0x96, 0x90, - 0x77, 0xda, 0xd1, 0x17, 0xe5, 0x9d, 0x76, 0xe6, 0xfd, 0x2a, 0x4c, 0x2e, 0xf2, 0x2d, 0xb7, 0xd7, - 0x8e, 0x93, 0x7d, 0x4f, 0x2e, 0xd1, 0x93, 0xa2, 0x09, 0x2d, 0x59, 0xe4, 0xa8, 0xcd, 0xce, 0xce, - 0x22, 0x5b, 0xff, 0xb4, 0x04, 0xe3, 0x46, 0xfb, 0xd9, 0x75, 0x18, 0xa9, 0xf7, 0x5a, 0x5e, 0x8c, - 0x23, 0x29, 0x2b, 0x95, 0xe3, 0x25, 0x80, 0xf9, 0xf1, 0x52, 0xa8, 0xec, 0xcb, 0x00, 0x36, 0x8f, - 0xc3, 0xbd, 0x85, 0xa0, 0xe7, 0xc7, 0xf8, 0x11, 0x83, 0xf3, 0x67, 0xc5, 0xc0, 0x84, 0x02, 0xea, - 0x34, 0x05, 0x58, 0x1f, 0x98, 0x14, 0x99, 0xdd, 0x81, 0x5a, 0x63, 0xa7, 0xb7, 0xb5, 0xd5, 0xe6, - 0x4a, 0x6d, 0x88, 0x50, 0x94, 0x54, 0xe7, 0x67, 0x9f, 0xec, 0xcf, 0x3e, 0x17, 0xc9, 0x32, 0x47, - 0x69, 0x17, 0xfa, 0xf8, 0xe6, 0x08, 0xad, 0xdf, 0xd3, 0x75, 0x49, 0xb5, 0xb9, 0xbd, 0x9e, 0x88, - 0xa8, 0x52, 0xaa, 0xd9, 0xe6, 0x44, 0x54, 0x22, 0xa0, 0x2e, 0x4b, 0x71, 0x51, 0xce, 0x89, 0x8b, - 0x51, 0x9a, 0x1c, 0x15, 0xf7, 0x51, 0x24, 0x85, 0x44, 0xb2, 0x78, 0x2a, 0x9f, 0x7c, 0xf1, 0xbc, - 0x07, 0x63, 0xab, 0xae, 0xef, 0x6e, 0xf3, 0x96, 0xe8, 0x49, 0x29, 0x0e, 0x47, 0xe4, 0x2e, 0xdb, - 0x91, 0x70, 0xec, 0x77, 0xbd, 0xdd, 0x06, 0x01, 0x7b, 0x53, 0x09, 0x9b, 0xc1, 0x02, 0x61, 0x33, - 0x4e, 0xb5, 0x0f, 0xa2, 0xb0, 0x51, 0x22, 0xe6, 0x2b, 0x30, 0xfa, 0xe0, 0x2e, 0x8f, 0x17, 0xef, - 0x36, 0x70, 0x76, 0x49, 0x65, 0x0f, 0xab, 0xdc, 0xf5, 0x79, 0xec, 0xb4, 0xfc, 0x08, 0x4d, 0x2b, - 0x5a, 0x95, 0x3a, 0xbe, 0xf5, 0xeb, 0x80, 0x5d, 0xc4, 0x5e, 0x83, 0x21, 0x9b, 0x6f, 0x8b, 0xcd, - 0xb3, 0x94, 0x4e, 0xbb, 0x10, 0x21, 0x7a, 0xbf, 0x4a, 0x1c, 0xd4, 0xf3, 0x78, 0x2b, 0xda, 0xf1, - 0xb6, 0x62, 0xea, 0xdc, 0x44, 0xcf, 0x23, 0xb0, 0xa6, 0xe7, 0x11, 0xc4, 0xd0, 0xf3, 0x08, 0x26, - 0xe4, 0xb9, 0xbd, 0xd8, 0xa0, 0x3e, 0x57, 0x03, 0x64, 0x2f, 0x6a, 0x82, 0x31, 0x34, 0xb4, 0x34, - 0x81, 0x8d, 0x33, 0xbb, 0x89, 0xf3, 0x30, 0x31, 0x08, 0xc8, 0x99, 0x2d, 0x81, 0xa6, 0xbd, 0x25, - 0x45, 0x65, 0x0d, 0x18, 0x5d, 0x12, 0xa7, 0x68, 0x6f, 0xc1, 0x6d, 0xee, 0xa8, 0x3e, 0x56, 0x52, - 0x59, 0x2b, 0x49, 0x65, 0x11, 0x47, 0x60, 0x53, 0x00, 0xf5, 0x0e, 0xd4, 0x70, 0xd9, 0x3a, 0x8c, - 0x36, 0x78, 0x33, 0xe4, 0x71, 0x23, 0x0e, 0x42, 0x9e, 0xd9, 0x64, 0xb4, 0x92, 0xf9, 0xe7, 0xd5, - 0x41, 0x3e, 0x42, 0xa0, 0x13, 0x09, 0xa8, 0xce, 0x55, 0x43, 0x96, 0x27, 0xb2, 0x4e, 0x10, 0xee, - 0x2d, 0xce, 0xd3, 0xc6, 0x93, 0x6a, 0x29, 0x12, 0xac, 0x9f, 0xc8, 0x04, 0xa4, 0xb5, 0x69, 0x9e, - 0xc8, 0x24, 0x16, 0x8e, 0xd4, 0x62, 0x03, 0x75, 0x5b, 0xda, 0x86, 0x26, 0xd3, 0x5e, 0x46, 0xb0, - 0x36, 0x52, 0xad, 0x08, 0x35, 0x63, 0x63, 0xa4, 0x08, 0x8b, 0x75, 0x81, 0xa9, 0x51, 0x93, 0x0a, - 0x67, 0x9b, 0x47, 0x11, 0xed, 0x4e, 0x67, 0x33, 0x83, 0x9f, 0x22, 0xcc, 0xbf, 0x4c, 0xcc, 0xcf, - 0xab, 0x69, 0x40, 0x87, 0x70, 0x51, 0xa8, 0xd5, 0x53, 0xc0, 0x5b, 0x08, 0xa2, 0xa5, 0xc7, 0x31, - 0x0f, 0x7d, 0xb7, 0x9d, 0x18, 0xd5, 0x50, 0x10, 0x71, 0x82, 0x9a, 0x03, 0xad, 0x21, 0xb3, 0x05, - 0x18, 0xaf, 0x47, 0x51, 0xaf, 0xc3, 0xed, 0xa0, 0xcd, 0xeb, 0xf6, 0x5d, 0x69, 0x64, 0x9b, 0x3f, - 0xff, 0x64, 0x7f, 0xf6, 0xac, 0x8b, 0x05, 0x4e, 0x18, 0xb4, 0xb9, 0xe3, 0x86, 0xfa, 0xec, 0x36, - 0x69, 0xd8, 0x3d, 0x80, 0x7b, 0x5d, 0xee, 0x37, 0xb8, 0x1b, 0x36, 0x77, 0x32, 0x1b, 0x57, 0x5a, - 0x30, 0x7f, 0x8e, 0x5a, 0x78, 0x32, 0xe8, 0x72, 0x3f, 0x42, 0x98, 0xfe, 0x55, 0x29, 0x26, 0xdb, - 0x80, 0xc9, 0xe5, 0xfa, 0xea, 0x5a, 0xd0, 0xf6, 0x9a, 0x7b, 0xa4, 0x0b, 0x4e, 0xa0, 0xa9, 0xf1, - 0x34, 0x71, 0xcd, 0x94, 0x4a, 0xe9, 0xe6, 0xb9, 0x1d, 0xa7, 0x8b, 0x50, 0x87, 0x34, 0xc2, 0x2c, - 0x17, 0xf6, 0x75, 0x31, 0x07, 0x23, 0xa1, 0xde, 0xae, 0xbb, 0xdb, 0xf2, 0x48, 0x92, 0x1e, 0x59, - 0xeb, 0x1b, 0x8d, 0x2b, 0x5a, 0xa9, 0x54, 0xbc, 0x66, 0xe4, 0x44, 0x44, 0xa8, 0x13, 0xbb, 0xdb, - 0x91, 0x39, 0x11, 0x13, 0x6c, 0x76, 0x1b, 0x60, 0x31, 0x68, 0xf6, 0x3a, 0xdc, 0x8f, 0x17, 0xe7, - 0xa7, 0x6b, 0xe6, 0x51, 0x2c, 0x29, 0x48, 0x25, 0x63, 0x2b, 0x68, 0x1a, 0x33, 0x51, 0xa3, 0x66, - 0x3f, 0x0a, 0xa7, 0xb4, 0x95, 0xa3, 0xcd, 0xa2, 0x29, 0x64, 0x7b, 0x2e, 0xbf, 0x12, 0xb5, 0x89, - 0x74, 0x89, 0x6a, 0xb8, 0xa0, 0xad, 0xc9, 0xe2, 0xb9, 0x54, 0x5c, 0xc9, 0xcc, 0x57, 0xa1, 0x96, - 0xed, 0x86, 0x82, 0xbd, 0xfe, 0xa4, 0xbe, 0xd7, 0x8f, 0x68, 0x9b, 0xfa, 0xed, 0x81, 0xea, 0x78, - 0x6d, 0x42, 0xeb, 0xfb, 0xa5, 0xc7, 0x5e, 0x14, 0x47, 0xd6, 0x77, 0x8c, 0xf5, 0x2f, 0x64, 0xd3, - 0x1d, 0xbe, 0xb7, 0x16, 0xf2, 0x2d, 0xef, 0xb1, 0xbe, 0xeb, 0x3e, 0xe4, 0x7b, 0x4e, 0x17, 0xa1, - 0xba, 0x6c, 0x4a, 0x50, 0xd9, 0x17, 0xa0, 0x7a, 0x67, 0xb5, 0x71, 0x87, 0xef, 0x2d, 0x2f, 0xd2, - 0xc6, 0x2f, 0xc9, 0x3a, 0x91, 0x23, 0x48, 0x8d, 0x99, 0x9e, 0x60, 0x5a, 0xf3, 0xa9, 0x1c, 0x16, - 0x35, 0x2f, 0xb4, 0x7b, 0x51, 0xcc, 0xc3, 0xe5, 0x45, 0xbd, 0xe6, 0xa6, 0x04, 0x66, 0xa4, 0x62, - 0x82, 0x6a, 0xfd, 0xbf, 0x65, 0x94, 0xc1, 0x62, 0xb9, 0x2d, 0xfb, 0x51, 0xec, 0xfa, 0x4d, 0x9e, - 0x30, 0xc0, 0xe5, 0xe6, 0x11, 0x34, 0xb3, 0xdc, 0x52, 0x64, 0xb3, 0xea, 0xf2, 0x91, 0xab, 0x96, - 0xaa, 0x86, 0x34, 0x8a, 0x2d, 0x2f, 0xd2, 0xa1, 0x83, 0x54, 0x0d, 0x09, 0xcd, 0x54, 0x99, 0x22, - 0xb3, 0x8b, 0x30, 0xbc, 0x5c, 0x5f, 0xad, 0xf7, 0xe2, 0x1d, 0xdc, 0x01, 0xaa, 0xf2, 0xbc, 0x23, - 0xd6, 0x8a, 0xdb, 0x8b, 0x77, 0x6c, 0x55, 0xc8, 0xae, 0xe2, 0x39, 0xd2, 0xe7, 0x71, 0x34, 0x3d, - 0x88, 0xbb, 0x31, 0xae, 0xa9, 0x48, 0x82, 0x32, 0xc7, 0x48, 0x01, 0x62, 0xaf, 0xc2, 0xe0, 0x83, - 0xb5, 0x85, 0xe5, 0x45, 0xda, 0x49, 0x71, 0x1f, 0xdc, 0xed, 0x36, 0xcd, 0x2f, 0x91, 0x28, 0x6c, - 0x09, 0x26, 0x1a, 0xbc, 0xd9, 0x0b, 0xbd, 0x58, 0x5a, 0x06, 0xa2, 0xe9, 0x61, 0xac, 0x03, 0xe5, - 0x4c, 0x44, 0x25, 0xd2, 0x8c, 0xa0, 0xd7, 0x95, 0x21, 0xb2, 0x7e, 0xb3, 0x94, 0x0a, 0x69, 0x76, - 0xd1, 0x50, 0x13, 0xd1, 0x2c, 0x98, 0xd9, 0xbf, 0xa5, 0xc2, 0x68, 0x03, 0x5b, 0xe8, 0x45, 0x71, - 0xd0, 0x59, 0xf2, 0x5b, 0xdd, 0xc0, 0xf3, 0x63, 0xa4, 0x92, 0x9d, 0x6f, 0x3d, 0xd9, 0x9f, 0x7d, - 0xbe, 0x89, 0xa5, 0x0e, 0xa7, 0xe2, 0xac, 0x16, 0x50, 0x40, 0xfd, 0x29, 0xc6, 0xc3, 0xfa, 0x6f, - 0xca, 0xc6, 0xe6, 0x2a, 0x3e, 0xcf, 0xe6, 0xdd, 0xb6, 0xd7, 0x44, 0x7b, 0x0e, 0x36, 0x34, 0x99, - 0x55, 0xf8, 0x79, 0x61, 0x5a, 0x2a, 0x7b, 0xc8, 0xe4, 0x5d, 0x40, 0xcd, 0xde, 0x87, 0x31, 0xa1, - 0x26, 0xd1, 0xcf, 0x68, 0xba, 0x8c, 0x9d, 0x7d, 0x0e, 0x0d, 0xbc, 0x11, 0x0f, 0x13, 0x36, 0x86, - 0x7e, 0xa5, 0x53, 0xb0, 0x16, 0x4c, 0xaf, 0x87, 0xae, 0x1f, 0x79, 0xf1, 0x92, 0xdf, 0x0c, 0xf7, - 0x50, 0xad, 0x5b, 0xf2, 0xdd, 0xcd, 0x36, 0x6f, 0x91, 0xa2, 0x7a, 0xe9, 0xc9, 0xfe, 0xec, 0x4b, - 0xb1, 0xc4, 0x71, 0x78, 0x82, 0xe4, 0x70, 0x89, 0xa5, 0x71, 0xee, 0xcb, 0x49, 0xa8, 0x81, 0xaa, - 0x5b, 0xf1, 0x3e, 0x69, 0x20, 0xd5, 0xc9, 0x92, 0xd1, 0x10, 0xa2, 0x4e, 0xff, 0x4c, 0x9d, 0xc0, - 0xba, 0xd7, 0x47, 0x50, 0xe2, 0x42, 0x13, 0x20, 0x6d, 0x86, 0xc8, 0x85, 0x86, 0xd2, 0x30, 0x33, - 0xc2, 0x29, 0xaa, 0xf5, 0x07, 0xa5, 0x54, 0x9f, 0x60, 0xef, 0xc2, 0x28, 0x2d, 0x41, 0x8d, 0x0d, - 0x6e, 0x08, 0x6a, 0xbd, 0x66, 0x15, 0x46, 0x0d, 0x9d, 0xbd, 0x01, 0xc3, 0xf5, 0x85, 0x15, 0x6d, - 0xb2, 0xa1, 0x61, 0xc6, 0x6d, 0xb6, 0xb3, 0x54, 0x0a, 0x4d, 0xcc, 0xaa, 0xf5, 0x95, 0x86, 0xd9, - 0xcd, 0x38, 0xab, 0xe2, 0x76, 0x54, 0xd0, 0xaf, 0x1a, 0xf2, 0xa7, 0xef, 0xc9, 0xff, 0xb1, 0x54, - 0xa4, 0xb6, 0xb0, 0x79, 0x18, 0xdf, 0x08, 0xc2, 0x87, 0x38, 0x61, 0xb4, 0x4e, 0xc0, 0xa9, 0xf4, - 0x48, 0x15, 0x64, 0x1b, 0x64, 0x92, 0xe8, 0xdf, 0xa6, 0xf5, 0x86, 0xf9, 0x6d, 0x19, 0x0e, 0x06, - 0x81, 0x18, 0x87, 0x84, 0x63, 0xb2, 0xdc, 0x70, 0x1c, 0xd2, 0x4f, 0x30, 0xd6, 0x84, 0x8e, 0x6e, - 0xfd, 0x83, 0x92, 0xae, 0x9e, 0x88, 0x4e, 0x5e, 0x0c, 0x3a, 0xae, 0xe7, 0x6b, 0xcd, 0xc1, 0x4e, - 0x6e, 0x21, 0x34, 0xfb, 0x25, 0x1a, 0x32, 0xbb, 0x06, 0x55, 0xf9, 0x2b, 0x11, 0xde, 0x68, 0x24, - 0x25, 0x42, 0x73, 0xe7, 0x51, 0x88, 0xb9, 0x91, 0xa9, 0x1c, 0x77, 0x64, 0x7e, 0xa3, 0xa4, 0x6b, - 0x16, 0x9f, 0x74, 0xf7, 0xca, 0xec, 0x5a, 0xe5, 0xe3, 0xec, 0x5a, 0x9f, 0xba, 0x09, 0xff, 0xa8, - 0x04, 0xa3, 0x9a, 0x19, 0x49, 0xb4, 0x61, 0x2d, 0x0c, 0x3e, 0xe2, 0xcd, 0xd8, 0x6c, 0x43, 0x57, - 0x02, 0x33, 0x6d, 0x48, 0x50, 0x3f, 0x4d, 0x1b, 0x16, 0x60, 0xb8, 0xde, 0x6e, 0x07, 0xe2, 0x98, - 0x20, 0xcf, 0x50, 0x13, 0x4a, 0xeb, 0x93, 0xd0, 0xf9, 0xb3, 0xea, 0x82, 0xca, 0x15, 0x00, 0x43, - 0x35, 0x53, 0x94, 0xd6, 0xcf, 0x96, 0x12, 0x2e, 0xb9, 0x4e, 0x29, 0x1d, 0xb3, 0x53, 0xd8, 0x1d, - 0xa8, 0xa9, 0xdf, 0xf7, 0x76, 0x79, 0x18, 0x7a, 0x2d, 0xb5, 0x34, 0xd0, 0x06, 0x90, 0x30, 0x09, - 0xa8, 0x50, 0xb7, 0x01, 0x64, 0x09, 0xad, 0xff, 0xb3, 0x44, 0x07, 0xe2, 0x23, 0x6f, 0x8b, 0xe6, - 0x16, 0x56, 0x3e, 0x8e, 0x4a, 0xf1, 0x3e, 0x0c, 0xda, 0xbc, 0xe5, 0x45, 0xd4, 0x93, 0x53, 0xfa, - 0xe1, 0x1b, 0x0b, 0x52, 0x2d, 0x37, 0x14, 0x3f, 0x75, 0x7d, 0x00, 0xcb, 0xc5, 0xb1, 0x63, 0x39, - 0xba, 0xd1, 0xe6, 0x8f, 0x3d, 0x29, 0x6b, 0x48, 0x35, 0x41, 0x75, 0xc0, 0x8b, 0x9c, 0x2d, 0x51, - 0x42, 0x3a, 0xab, 0x2e, 0x57, 0x0c, 0x1a, 0xeb, 0xeb, 0x00, 0x69, 0x95, 0xa2, 0x3b, 0x69, 0xb2, - 0x7b, 0xfe, 0xb6, 0x54, 0x3c, 0xa9, 0x0f, 0xb0, 0x3b, 0x9b, 0x49, 0x19, 0x9d, 0x11, 0xf4, 0xee, - 0xcc, 0x12, 0x5a, 0xff, 0x5b, 0x05, 0xca, 0x75, 0x9c, 0x6f, 0x77, 0xf8, 0x5e, 0xec, 0x6e, 0xde, - 0xf0, 0xda, 0x86, 0xac, 0x78, 0x88, 0x50, 0x67, 0xcb, 0x33, 0xcc, 0x65, 0x1a, 0xb2, 0x90, 0x15, - 0x77, 0xc2, 0xcd, 0xb7, 0x90, 0x50, 0x93, 0x15, 0x0f, 0xc3, 0xcd, 0xb7, 0xb2, 0x64, 0x09, 0x22, - 0xb3, 0x60, 0x48, 0xca, 0x0d, 0x5a, 0x62, 0xf0, 0x64, 0x7f, 0x76, 0x48, 0x8a, 0x17, 0x9b, 0x4a, - 0xd8, 0x59, 0xa8, 0x34, 0xd6, 0xee, 0x92, 0x80, 0x47, 0xb3, 0x74, 0xd4, 0xf5, 0x6d, 0x01, 0x13, - 0x75, 0xae, 0x2c, 0xd6, 0xd7, 0xd0, 0xea, 0x33, 0x98, 0xd6, 0xd9, 0x6e, 0xb9, 0xdd, 0xac, 0xdd, - 0x27, 0x41, 0x64, 0x5f, 0x81, 0xd1, 0x3b, 0x8b, 0x0b, 0xb7, 0x82, 0x28, 0xce, 0x9a, 0x45, 0x1e, - 0xb6, 0x9a, 0x68, 0x81, 0xca, 0xed, 0x72, 0x1a, 0x3e, 0x73, 0xe0, 0xb4, 0x60, 0x25, 0x86, 0xc4, - 0x6b, 0x72, 0x32, 0x21, 0xdc, 0x4d, 0x6f, 0xc9, 0x5e, 0x79, 0xb2, 0x3f, 0xfb, 0x22, 0x7e, 0x41, - 0x24, 0x51, 0x1c, 0x65, 0x7c, 0xc8, 0x70, 0xed, 0xc3, 0x86, 0x7d, 0x13, 0x4e, 0xe5, 0x4b, 0x1a, - 0xc9, 0xed, 0xda, 0xc5, 0x27, 0xfb, 0xb3, 0x56, 0x21, 0xff, 0xc8, 0x98, 0xbf, 0xc5, 0x4c, 0xac, - 0xef, 0x95, 0x61, 0x54, 0x33, 0x33, 0xb3, 0x2f, 0x90, 0xab, 0x45, 0xc9, 0x38, 0x6e, 0x6a, 0x18, - 0xa2, 0x54, 0xda, 0x24, 0x3b, 0x41, 0x8b, 0x93, 0xe3, 0x45, 0x6a, 0x6c, 0x2b, 0x1f, 0xc5, 0xd8, - 0xf6, 0x65, 0x00, 0x39, 0x85, 0xb1, 0x9f, 0x34, 0xed, 0x51, 0xf3, 0xf0, 0xd1, 0xa7, 0x55, 0x8a, - 0xcc, 0x1e, 0xc0, 0x89, 0xf5, 0xb0, 0x17, 0xc5, 0x8d, 0xbd, 0x28, 0xe6, 0x1d, 0xc1, 0x6d, 0x2d, - 0x08, 0xda, 0xb4, 0x7c, 0x5e, 0x12, 0xa7, 0x3e, 0xf4, 0x5b, 0x72, 0x22, 0x2c, 0xc7, 0x0f, 0x70, - 0xba, 0x41, 0xa0, 0x9b, 0xe0, 0x8a, 0x18, 0x58, 0x36, 0x8c, 0xe9, 0x06, 0x3c, 0xb1, 0xef, 0xd3, - 0xb5, 0x34, 0xdd, 0x14, 0x69, 0xfb, 0x3e, 0x7d, 0x65, 0xfe, 0x9a, 0xdc, 0x24, 0xb1, 0xbe, 0xa0, - 0xdb, 0xb3, 0x8f, 0x2a, 0x97, 0xac, 0x1f, 0x2b, 0xa5, 0x42, 0xfe, 0xc1, 0x9b, 0xec, 0x1d, 0x18, - 0x92, 0x6e, 0x00, 0xe4, 0x2d, 0x71, 0x2a, 0xb1, 0xa0, 0xe8, 0x3e, 0x02, 0xf2, 0x22, 0xe9, 0x9f, - 0xed, 0xcf, 0x96, 0x9e, 0xec, 0xcf, 0x7e, 0xce, 0x26, 0x92, 0xe4, 0x0e, 0xca, 0x34, 0x47, 0x2b, - 0xee, 0x78, 0xdb, 0xf2, 0x66, 0xd1, 0x1d, 0x94, 0xf5, 0x8f, 0x06, 0x61, 0xc2, 0x44, 0xd3, 0x7d, - 0x05, 0x4a, 0x47, 0xf2, 0x15, 0x78, 0x1f, 0xaa, 0x34, 0xdf, 0x94, 0x02, 0xfe, 0x12, 0xde, 0xcc, - 0x11, 0xcc, 0xf0, 0x81, 0x01, 0x39, 0x1c, 0x76, 0xd0, 0xe6, 0x76, 0x42, 0xc5, 0xe6, 0xb4, 0x3b, - 0xe7, 0x4a, 0xaa, 0x42, 0x2a, 0xab, 0xb0, 0xbe, 0x9c, 0x93, 0xdb, 0xe7, 0xd7, 0x61, 0x48, 0x1c, - 0xe7, 0x12, 0x7b, 0x1f, 0x7e, 0xa5, 0x38, 0xe9, 0x65, 0x9c, 0xab, 0x24, 0x12, 0xdb, 0x80, 0xea, - 0x8a, 0x1b, 0xc5, 0x0d, 0xce, 0xfd, 0x23, 0x78, 0x01, 0xcd, 0x52, 0x57, 0x9d, 0x40, 0x17, 0x9b, - 0x88, 0x73, 0x3f, 0xe3, 0xc6, 0x91, 0x30, 0x63, 0xdf, 0x02, 0x58, 0x08, 0xfc, 0x38, 0x0c, 0xda, - 0x2b, 0xc1, 0xf6, 0xf4, 0x10, 0x1a, 0x5a, 0x9e, 0xcf, 0x0c, 0x40, 0x8a, 0x20, 0x6d, 0x2d, 0x89, - 0x35, 0xb1, 0x29, 0x0b, 0x9c, 0x76, 0xb0, 0xad, 0xaf, 0x83, 0x14, 0x9f, 0xdd, 0x80, 0x9a, 0xb2, - 0x62, 0xdd, 0xef, 0x6e, 0x87, 0x38, 0x41, 0x86, 0x53, 0xbd, 0x90, 0x3f, 0x8e, 0x9d, 0x1e, 0xc1, - 0x8d, 0x7d, 0x33, 0x43, 0xc3, 0xbe, 0x09, 0x67, 0xb2, 0x30, 0x35, 0xca, 0xd5, 0xf4, 0x08, 0xa6, - 0xb3, 0x2b, 0x98, 0xf7, 0xfd, 0x58, 0xb0, 0x9b, 0x30, 0x29, 0x3a, 0x64, 0x95, 0xbb, 0x51, 0x4f, - 0xfa, 0x0e, 0x92, 0x1d, 0x50, 0x39, 0x39, 0xd0, 0x2a, 0x6c, 0x07, 0xcd, 0x87, 0x1a, 0x92, 0x9d, - 0xa5, 0x62, 0xd7, 0x61, 0x54, 0xfa, 0xde, 0x84, 0xcb, 0xfe, 0x56, 0x40, 0xd7, 0x56, 0xea, 0x36, - 0x87, 0x4a, 0x1e, 0xcc, 0x89, 0x32, 0x5b, 0x47, 0xb4, 0xf6, 0xcb, 0x70, 0xba, 0xb8, 0x0e, 0xf6, - 0xa7, 0xe1, 0x14, 0xf5, 0x67, 0x9b, 0x87, 0x1a, 0xce, 0x11, 0xbc, 0x92, 0x5e, 0xa7, 0x71, 0x7a, - 0xa1, 0x99, 0x30, 0x48, 0x04, 0x8e, 0x60, 0x91, 0x99, 0x14, 0xc5, 0xf5, 0xb0, 0x0f, 0x61, 0x54, - 0xaf, 0xb6, 0x7c, 0x74, 0x07, 0xaf, 0x03, 0xea, 0xd2, 0x59, 0x32, 0x17, 0x26, 0x6d, 0xfe, 0x71, - 0x8f, 0x47, 0xb1, 0x72, 0x31, 0x23, 0x8d, 0xe5, 0x6c, 0xae, 0x16, 0x85, 0x90, 0x18, 0x29, 0x6b, - 0xa1, 0xa4, 0x74, 0x94, 0x13, 0xed, 0xcf, 0x09, 0xf6, 0x59, 0x7e, 0xd6, 0x0f, 0xca, 0x70, 0xa6, - 0xcf, 0x74, 0x16, 0x12, 0x4f, 0xd3, 0x0c, 0x51, 0xe2, 0x65, 0x14, 0x42, 0xe9, 0x0f, 0x79, 0x01, - 0xca, 0xa4, 0x81, 0x0d, 0xcc, 0xd7, 0x9e, 0xec, 0xcf, 0x8e, 0x19, 0x2b, 0xb5, 0xbc, 0xbc, 0xc8, - 0x6e, 0xc3, 0x80, 0xe8, 0x86, 0x23, 0xb8, 0x59, 0x29, 0x13, 0xf5, 0x44, 0xec, 0xe9, 0x02, 0x02, - 0xfb, 0x06, 0x79, 0xb0, 0x2f, 0x40, 0x65, 0x7d, 0x7d, 0x05, 0xa5, 0x43, 0x05, 0x67, 0xf7, 0x78, - 0x1c, 0xb7, 0x0d, 0x61, 0x34, 0x2e, 0x68, 0x93, 0x1e, 0xb1, 0x05, 0x3a, 0xfb, 0x1a, 0x0c, 0xd1, - 0xcd, 0xf5, 0x20, 0x2e, 0xe5, 0x57, 0x0f, 0x5e, 0xca, 0x57, 0xf4, 0xbb, 0x6b, 0xb4, 0x0c, 0xe5, - 0xee, 0xab, 0x89, 0xdf, 0xcc, 0x97, 0x61, 0xf4, 0xe0, 0x4b, 0xc5, 0xbe, 0x86, 0x46, 0xeb, 0xc7, - 0x4b, 0xca, 0xd3, 0x8d, 0x26, 0x3f, 0xbb, 0xa0, 0xd6, 0x09, 0x1a, 0x32, 0x88, 0x8b, 0x0e, 0x62, - 0xcf, 0x03, 0xc8, 0x9f, 0xf7, 0xef, 0x53, 0xa7, 0x8f, 0xd9, 0x1a, 0x84, 0xbd, 0x9d, 0xb0, 0x24, - 0xc3, 0x73, 0x05, 0x35, 0x81, 0xcc, 0x5a, 0x93, 0x65, 0xb6, 0x89, 0x6a, 0xfd, 0x7a, 0x39, 0xdd, - 0x35, 0x6e, 0x78, 0xed, 0x98, 0x87, 0x6c, 0x46, 0x6e, 0x02, 0xe9, 0x61, 0xcd, 0x4e, 0x7e, 0xb3, - 0xe9, 0x74, 0x47, 0x91, 0x4d, 0x4b, 0xb6, 0x8e, 0x57, 0xb5, 0xad, 0xa3, 0x82, 0x5b, 0xc7, 0x44, - 0xdf, 0x4d, 0xe2, 0xd5, 0x02, 0x49, 0x88, 0xa2, 0xbf, 0x40, 0xda, 0xbd, 0x04, 0xe3, 0x77, 0x83, - 0xa5, 0xc7, 0x71, 0x82, 0x28, 0x44, 0x7e, 0xd5, 0x36, 0x81, 0x82, 0xe3, 0xbd, 0x76, 0x8b, 0x87, - 0xeb, 0x3b, 0xae, 0x6f, 0xb8, 0x46, 0xd9, 0x39, 0xb8, 0xc0, 0xbd, 0xcb, 0x1f, 0x99, 0xb8, 0xd2, - 0x03, 0x2b, 0x07, 0xcf, 0x0e, 0x4e, 0x35, 0x37, 0x38, 0xd6, 0xcf, 0x96, 0x55, 0x77, 0x3d, 0x98, - 0x7b, 0x46, 0xdd, 0x5e, 0xde, 0x32, 0xdc, 0x5e, 0x4e, 0x24, 0xd7, 0x5b, 0x89, 0xc7, 0xd9, 0x5c, - 0xa1, 0xd3, 0x4b, 0xe2, 0xbe, 0x36, 0x54, 0xec, 0xbe, 0xf6, 0xfd, 0x2a, 0x8c, 0xe9, 0x4c, 0x44, - 0xef, 0xd4, 0x5b, 0xad, 0x50, 0xef, 0x1d, 0xb7, 0xd5, 0x0a, 0x6d, 0x84, 0x1a, 0xde, 0x6a, 0x95, - 0x03, 0xbd, 0xd5, 0xbe, 0x0d, 0x23, 0x0b, 0x9d, 0x96, 0xe1, 0x95, 0x62, 0x15, 0x7c, 0xf4, 0x95, - 0x04, 0x49, 0xae, 0xe9, 0xe4, 0x2e, 0xa7, 0xd9, 0x69, 0xe5, 0x7d, 0x51, 0x52, 0x96, 0x86, 0xa3, - 0xdb, 0xe0, 0xa7, 0x71, 0x74, 0xbb, 0x0e, 0x23, 0xf7, 0x23, 0xbe, 0xde, 0xf3, 0x7d, 0xde, 0xc6, - 0x5e, 0xaa, 0x4a, 0x9b, 0x41, 0x2f, 0xe2, 0x4e, 0x8c, 0x50, 0xfd, 0x03, 0x12, 0x54, 0x7d, 0xd8, - 0x87, 0x0f, 0x18, 0xf6, 0x6b, 0x50, 0x5d, 0xe3, 0x3c, 0xc4, 0x3e, 0x1d, 0x4d, 0xcf, 0x4e, 0x5d, - 0xce, 0x43, 0x47, 0x74, 0xac, 0xe1, 0x00, 0x47, 0x88, 0x86, 0xd7, 0xdc, 0xd8, 0x51, 0xbd, 0xe6, - 0x5e, 0x80, 0xb1, 0x6e, 0x6f, 0xb3, 0xed, 0x35, 0x91, 0x2f, 0xb9, 0xdb, 0xd9, 0xa3, 0x12, 0x26, - 0xd8, 0x46, 0xec, 0x6b, 0x30, 0x8e, 0xb6, 0x92, 0x64, 0x22, 0x4e, 0x18, 0x1b, 0xbe, 0x51, 0x26, - 0x75, 0xf2, 0xa6, 0x00, 0x39, 0x05, 0x2e, 0xa7, 0x26, 0x23, 0x76, 0x1b, 0x86, 0xb7, 0xbd, 0xd8, - 0xd9, 0xe9, 0x6d, 0x4e, 0x4f, 0x1a, 0xfe, 0x9a, 0x37, 0xbd, 0xf8, 0x56, 0x6f, 0x53, 0x0e, 0x79, - 0xc2, 0x1a, 0x25, 0xf7, 0xb6, 0x17, 0xef, 0xf4, 0x74, 0x73, 0xc8, 0xd0, 0x36, 0xe2, 0x66, 0xdd, - 0xff, 0x6a, 0x07, 0xbb, 0xff, 0x4d, 0x99, 0xee, 0x7f, 0xcc, 0x01, 0x96, 0x8f, 0x03, 0x99, 0x66, - 0xf8, 0x51, 0x6f, 0x5c, 0x51, 0xc1, 0x12, 0x57, 0x72, 0x01, 0x24, 0x57, 0x76, 0xdf, 0xbc, 0xb2, - 0xa0, 0x80, 0x37, 0x08, 0x68, 0x4f, 0x35, 0xb3, 0x20, 0xd6, 0x80, 0x9a, 0xd7, 0xe9, 0xf4, 0x62, - 0x77, 0xb3, 0xcd, 0x69, 0x8a, 0x4e, 0x9f, 0x30, 0xdc, 0xae, 0x8c, 0x09, 0xbe, 0xac, 0x70, 0xb5, - 0x69, 0x6e, 0x4f, 0x7a, 0x26, 0x74, 0xa6, 0x01, 0x13, 0xe6, 0x4a, 0x78, 0x0a, 0x5e, 0x30, 0x33, - 0xf3, 0x70, 0xb2, 0xa8, 0xf6, 0x63, 0x5e, 0xba, 0x49, 0x3f, 0xca, 0x6a, 0x6d, 0xe4, 0xf6, 0x40, - 0x15, 0x6a, 0xa3, 0xd2, 0x83, 0xd2, 0x86, 0xb5, 0x64, 0x5e, 0xd9, 0xec, 0x4e, 0x6f, 0x93, 0x87, - 0x3e, 0x8f, 0x79, 0x44, 0x16, 0x8f, 0xc8, 0x1e, 0xa8, 0x77, 0xbb, 0x91, 0xf5, 0xab, 0x65, 0x18, - 0xae, 0x6f, 0x34, 0x70, 0x0f, 0x7d, 0x4d, 0x77, 0x1a, 0x28, 0xa5, 0x51, 0x04, 0xa9, 0xd3, 0x80, - 0xee, 0x2a, 0x70, 0xb5, 0xc0, 0x24, 0x87, 0xd1, 0x2a, 0x9a, 0x49, 0xce, 0x30, 0xc4, 0xa5, 0xfe, - 0x13, 0x95, 0x23, 0xf8, 0x4f, 0x24, 0x97, 0x4c, 0x03, 0x87, 0x5f, 0x32, 0xbd, 0x03, 0xa3, 0xcb, - 0x7e, 0xcc, 0xb7, 0xc3, 0x54, 0xda, 0x24, 0xe6, 0xc1, 0x04, 0xac, 0xdb, 0x31, 0x34, 0x6c, 0xb1, - 0x94, 0xe5, 0xc5, 0x56, 0x72, 0xa1, 0x85, 0x4b, 0x59, 0xde, 0x7f, 0x65, 0x6c, 0xbb, 0x0a, 0xd1, - 0x5a, 0xcc, 0xac, 0x53, 0xe5, 0x75, 0x57, 0x32, 0x2d, 0x8c, 0xb2, 0x63, 0xe7, 0xa7, 0x8a, 0xbd, - 0xee, 0xac, 0xbf, 0x5a, 0x82, 0x93, 0x45, 0xcb, 0x8f, 0x7d, 0x15, 0xc6, 0x82, 0x70, 0xdb, 0xf5, - 0xbd, 0x3f, 0x21, 0x5b, 0xa4, 0x5d, 0x40, 0xe8, 0x70, 0xdd, 0xc2, 0xa8, 0xc3, 0x45, 0x87, 0x68, - 0x2d, 0x37, 0xed, 0xa5, 0x85, 0x1d, 0xa2, 0x81, 0xad, 0x5f, 0x28, 0xc3, 0x68, 0xbd, 0xdb, 0x7d, - 0xc6, 0x9d, 0xd3, 0xbf, 0x64, 0x6c, 0xc7, 0xca, 0x9e, 0x93, 0xb4, 0xeb, 0x00, 0x37, 0xd4, 0xc3, - 0x76, 0xe4, 0xff, 0xae, 0x02, 0x93, 0x19, 0x3e, 0x7a, 0x9b, 0x4a, 0x47, 0xf4, 0x25, 0x2f, 0x1f, - 0xd1, 0x97, 0xbc, 0x72, 0x34, 0x5f, 0xf2, 0x81, 0x4f, 0xb3, 0xc5, 0xbe, 0x02, 0x95, 0x7a, 0xb7, - 0x9b, 0x75, 0xa9, 0xea, 0x76, 0x1f, 0x5c, 0x93, 0x86, 0x46, 0xb7, 0xdb, 0xb5, 0x05, 0x86, 0xb1, - 0xef, 0x0d, 0x7d, 0x42, 0x6f, 0xf1, 0xe1, 0x83, 0xb7, 0x8b, 0xea, 0x91, 0xb6, 0x8b, 0x91, 0xa7, - 0xb6, 0x5d, 0x58, 0xaf, 0xc3, 0x08, 0x36, 0x15, 0x5d, 0xb6, 0x2f, 0x00, 0xca, 0x45, 0xf2, 0xd6, - 0x36, 0xba, 0x82, 0x24, 0xe6, 0x4f, 0x96, 0x61, 0x10, 0x7f, 0x3f, 0xa3, 0x0b, 0x63, 0xce, 0x58, - 0x18, 0x35, 0x6d, 0x61, 0x7c, 0xf2, 0x25, 0xf1, 0x77, 0x2a, 0x00, 0x0b, 0xf7, 0xec, 0x86, 0x34, - 0xa2, 0xb3, 0x1b, 0x30, 0xe9, 0xb6, 0xdb, 0xc1, 0x23, 0xde, 0x72, 0x82, 0xd0, 0xdb, 0xf6, 0x7c, - 0xd9, 0x9f, 0xca, 0xbb, 0xc8, 0x2c, 0xd2, 0x6f, 0xfd, 0xa9, 0xe8, 0x9e, 0x2c, 0xd1, 0xf9, 0x74, - 0x78, 0xbc, 0x13, 0xb4, 0x94, 0x3d, 0xcd, 0xe0, 0x43, 0x45, 0x05, 0x7c, 0x56, 0x65, 0x89, 0xce, - 0x67, 0x07, 0xed, 0x83, 0xea, 0x70, 0x65, 0xf0, 0xa1, 0xa2, 0x02, 0x3e, 0xd2, 0xa8, 0x18, 0xb1, - 0x15, 0xc0, 0x3b, 0xa2, 0x47, 0x4e, 0x33, 0xe4, 0x2d, 0xee, 0xc7, 0x9e, 0xdb, 0x8e, 0xc8, 0x02, - 0x8b, 0x57, 0x0d, 0xb9, 0x42, 0xdd, 0x02, 0x85, 0x85, 0x0b, 0x69, 0x19, 0xbb, 0x02, 0xc3, 0x1d, - 0xf7, 0xb1, 0xe3, 0x6e, 0x4b, 0x3f, 0xbb, 0x71, 0x69, 0xb1, 0x23, 0x90, 0xbe, 0x23, 0x76, 0xdc, - 0xc7, 0xf5, 0x6d, 0x2e, 0x5a, 0xc1, 0x1f, 0x77, 0x83, 0x48, 0x6b, 0xc5, 0x50, 0xda, 0x8a, 0x4c, - 0x91, 0xde, 0x0a, 0x2a, 0xa2, 0x56, 0x58, 0xbf, 0x5c, 0x82, 0xe7, 0x96, 0xf1, 0x2b, 0xe2, 0xbd, - 0x05, 0xee, 0xc7, 0x3c, 0x5c, 0xe3, 0x61, 0xc7, 0x43, 0xbf, 0x9f, 0x06, 0x8f, 0xd9, 0x8b, 0x50, - 0xa9, 0xdb, 0x77, 0x69, 0x56, 0xcb, 0xad, 0xcb, 0xf0, 0x01, 0x13, 0xa5, 0x89, 0x51, 0xb7, 0x7c, - 0xc8, 0x65, 0x53, 0x1d, 0xc6, 0xea, 0x51, 0xe4, 0x6d, 0xfb, 0x1d, 0x19, 0x9c, 0x58, 0x31, 0xbc, - 0xcc, 0x08, 0x9e, 0xbb, 0x04, 0xd6, 0x49, 0xac, 0xff, 0xa4, 0x04, 0x53, 0xf5, 0x6e, 0xd7, 0xfc, - 0x64, 0xd3, 0xc3, 0xb1, 0x74, 0x74, 0x0f, 0x47, 0x0f, 0x26, 0x8c, 0xe6, 0xca, 0x29, 0x95, 0x9e, - 0x7d, 0x0e, 0xe8, 0x19, 0xf9, 0xd9, 0xdd, 0x04, 0xe4, 0x44, 0xa6, 0x83, 0x4c, 0x86, 0xb1, 0xf5, - 0xef, 0x8e, 0xa0, 0x64, 0xa1, 0x2d, 0x82, 0xa2, 0x0a, 0x4a, 0x05, 0x51, 0x05, 0x5f, 0x06, 0x4d, - 0x59, 0xd3, 0x77, 0x6b, 0xed, 0xb8, 0xa0, 0x9b, 0x43, 0x53, 0x64, 0xf6, 0x30, 0x1b, 0x5f, 0x50, - 0xc1, 0xd6, 0xbc, 0x98, 0x5d, 0xd6, 0x4f, 0x25, 0xb4, 0xe0, 0x16, 0xb0, 0x65, 0x1f, 0x9d, 0x76, - 0x78, 0xe3, 0xa1, 0xd7, 0x7d, 0xc0, 0x43, 0x6f, 0x6b, 0x8f, 0x16, 0x00, 0x76, 0xbe, 0x47, 0xa5, - 0x4e, 0xf4, 0xd0, 0xeb, 0x3a, 0xbb, 0x58, 0x6e, 0x17, 0xd0, 0xb0, 0xf7, 0x60, 0xd8, 0xe6, 0x8f, - 0x42, 0x2f, 0x56, 0x3e, 0xa6, 0x13, 0x89, 0x75, 0x1f, 0xa1, 0x72, 0x2d, 0x84, 0xf2, 0x87, 0x2e, - 0x2b, 0xa9, 0x9c, 0xcd, 0x49, 0x7d, 0x4b, 0xfa, 0x92, 0x8e, 0xa7, 0xad, 0xad, 0x6f, 0x34, 0xfa, - 0xa9, 0x5b, 0xec, 0x32, 0x0c, 0xa2, 0xd2, 0x46, 0xc7, 0x41, 0x0c, 0xbc, 0xc5, 0xe3, 0x93, 0xae, - 0x51, 0x22, 0x06, 0x1a, 0x8b, 0x94, 0x57, 0x8c, 0xda, 0x92, 0x34, 0x48, 0x56, 0xe3, 0x1c, 0x39, - 0x96, 0xc6, 0xb9, 0x02, 0x35, 0x5b, 0x06, 0x95, 0xb7, 0xea, 0x5d, 0xf4, 0x94, 0x88, 0xa6, 0x01, - 0x57, 0xf2, 0x85, 0x27, 0xfb, 0xb3, 0xe7, 0x28, 0xe0, 0xbc, 0xe5, 0xb8, 0x5d, 0xe9, 0x60, 0x61, - 0x88, 0x91, 0x2c, 0x25, 0xfb, 0x32, 0x0c, 0x08, 0xd1, 0x4b, 0x91, 0x08, 0xea, 0x4a, 0x36, 0x95, - 0xc6, 0x72, 0x71, 0x36, 0x03, 0x43, 0x26, 0x20, 0x09, 0x73, 0x60, 0xc2, 0x9c, 0xee, 0xe4, 0xc2, - 0x39, 0x9d, 0xf6, 0xa7, 0x59, 0x4e, 0xf7, 0xb4, 0x04, 0x73, 0x9a, 0x08, 0xd4, 0x57, 0x40, 0x66, - 0x91, 0x2e, 0x41, 0x75, 0x7d, 0x61, 0x6d, 0x2d, 0x08, 0x63, 0x79, 0xda, 0x4d, 0xf7, 0x1b, 0x01, - 0xb3, 0x5d, 0x7f, 0x9b, 0x4b, 0x05, 0x22, 0x6e, 0x76, 0x1d, 0xb1, 0x8d, 0x1b, 0x0a, 0x84, 0x22, - 0x65, 0xdf, 0x82, 0x53, 0xf7, 0x23, 0x5e, 0xf7, 0xf7, 0x50, 0xa5, 0xd0, 0x96, 0xca, 0x04, 0x4e, - 0x3d, 0xbc, 0x69, 0xec, 0x45, 0xdc, 0x71, 0xfd, 0x3d, 0x47, 0xaa, 0x22, 0xc5, 0x0b, 0xa7, 0x98, - 0x0b, 0xbb, 0x0a, 0x95, 0xd5, 0x85, 0x35, 0x3a, 0x16, 0x2b, 0x0f, 0xeb, 0xd5, 0x85, 0x35, 0x39, - 0x91, 0x3a, 0x66, 0xc0, 0xcb, 0xea, 0xc2, 0x1a, 0x7b, 0x0b, 0x2a, 0x2b, 0x2b, 0xab, 0xe4, 0xea, - 0xa9, 0x08, 0x56, 0x56, 0x56, 0x65, 0x5b, 0x3c, 0x7f, 0x8b, 0x87, 0xdc, 0x6f, 0xea, 0xf3, 0x56, - 0xe0, 0x8b, 0x39, 0xbb, 0xbe, 0xd2, 0x20, 0x57, 0x4e, 0x6d, 0xce, 0xae, 0xaf, 0xd0, 0x9c, 0xcd, - 0x47, 0xb8, 0x7c, 0x76, 0x01, 0x18, 0xdf, 0xc4, 0x46, 0xb3, 0x69, 0x18, 0x6e, 0x4a, 0x1c, 0xe2, - 0xa6, 0x7e, 0x32, 0x06, 0x03, 0x6e, 0xb8, 0x4d, 0x3b, 0xae, 0x8d, 0x7f, 0xb3, 0x57, 0xa0, 0x16, - 0xf6, 0x7c, 0xc7, 0x8d, 0xe4, 0xf5, 0x70, 0x2f, 0xe2, 0xa1, 0x94, 0xe8, 0xf6, 0x78, 0xd8, 0xf3, - 0xeb, 0x91, 0xd0, 0x4b, 0xc5, 0x2a, 0xb1, 0xfe, 0x5e, 0x09, 0xb4, 0xa5, 0x5a, 0xb5, 0x79, 0xcb, - 0x0b, 0x79, 0x33, 0x26, 0x35, 0xe0, 0x34, 0x39, 0xbd, 0x23, 0x2c, 0xe3, 0xf4, 0x8e, 0x30, 0xf6, - 0x55, 0x18, 0xa6, 0xed, 0x8a, 0xc4, 0xb3, 0xea, 0x2e, 0xba, 0xf5, 0x93, 0x99, 0x03, 0x72, 0x5b, - 0x9d, 0x22, 0x12, 0xbb, 0xc3, 0xed, 0x8d, 0xf5, 0x85, 0xb6, 0xeb, 0x75, 0x22, 0xda, 0x73, 0x50, - 0x40, 0x7d, 0xf4, 0x28, 0x76, 0x9a, 0x08, 0xd5, 0x77, 0x87, 0x04, 0xd5, 0xba, 0xa9, 0x2e, 0x1d, - 0x0f, 0x89, 0x45, 0x99, 0x85, 0xc1, 0x07, 0xe9, 0x71, 0x7c, 0x7e, 0xe4, 0xc9, 0xfe, 0xac, 0xec, - 0x5b, 0x5b, 0xc2, 0x2d, 0x0e, 0x23, 0xc9, 0x14, 0x17, 0xbc, 0xc4, 0x0f, 0xe4, 0x35, 0x2e, 0x79, - 0x89, 0xc9, 0x6e, 0x23, 0x54, 0x28, 0x8a, 0x4b, 0x7e, 0x0b, 0x11, 0xca, 0x88, 0x80, 0xdd, 0xc3, - 0xfd, 0x16, 0xae, 0x08, 0xbd, 0x75, 0x84, 0xa6, 0x29, 0x5e, 0xff, 0x6d, 0x09, 0xa7, 0x22, 0x3b, - 0x0d, 0x43, 0x5b, 0x41, 0xd8, 0x71, 0x29, 0xe8, 0xc3, 0xa6, 0x5f, 0x6c, 0x06, 0xaa, 0xdd, 0x30, - 0xd8, 0xf5, 0x5a, 0x9c, 0xf6, 0x15, 0x3b, 0xf9, 0xcd, 0x2e, 0xc1, 0x50, 0x27, 0x68, 0xa5, 0x7b, - 0x46, 0x2d, 0x9d, 0xc8, 0x57, 0x56, 0x45, 0x81, 0x4d, 0xe5, 0xec, 0x65, 0x98, 0xd8, 0x72, 0xdb, - 0xed, 0x4d, 0xb7, 0xf9, 0xd0, 0x41, 0x10, 0xd9, 0x99, 0xc7, 0x15, 0x14, 0xd1, 0x67, 0xde, 0x87, - 0x41, 0xfc, 0x43, 0x4c, 0x1d, 0x3f, 0xe9, 0x3b, 0x7b, 0x80, 0x0e, 0x34, 0xe3, 0xaa, 0x66, 0x27, - 0x3d, 0xff, 0xd8, 0x63, 0x0a, 0x88, 0x57, 0xc0, 0xbf, 0x52, 0x82, 0x21, 0xb9, 0x20, 0x04, 0x8f, - 0x8e, 0xba, 0x8f, 0x1f, 0xb1, 0xf1, 0x6f, 0x71, 0x90, 0xd0, 0x2e, 0xca, 0x89, 0x03, 0x44, 0xe9, - 0x25, 0xf9, 0x25, 0xa8, 0x11, 0x42, 0xd4, 0xf5, 0xb6, 0xb6, 0xb8, 0xe3, 0xb5, 0x68, 0x7e, 0x4e, - 0x44, 0x74, 0x5c, 0x13, 0xe0, 0xe5, 0x96, 0x60, 0xa5, 0xf4, 0xbe, 0xa6, 0x4b, 0x61, 0x28, 0x36, - 0x10, 0x68, 0xc1, 0x8d, 0x04, 0x2b, 0x19, 0x73, 0x2f, 0xaf, 0xd2, 0xf1, 0x5b, 0x06, 0x25, 0x2b, - 0x09, 0x5f, 0xe0, 0x61, 0x2c, 0x9a, 0x6b, 0xfd, 0xe5, 0x12, 0x4c, 0x98, 0xeb, 0x8c, 0x5d, 0x81, - 0x21, 0xca, 0x83, 0x50, 0xc2, 0xdb, 0x16, 0x31, 0xa2, 0x43, 0x32, 0x03, 0x82, 0x91, 0xf7, 0x80, - 0xb0, 0x84, 0xe6, 0x4f, 0x1c, 0x48, 0xc1, 0x45, 0xcd, 0x9f, 0x56, 0xa2, 0xad, 0xca, 0x98, 0x05, - 0x43, 0x36, 0x8f, 0x7a, 0xed, 0x58, 0xf7, 0x12, 0x09, 0x11, 0x62, 0x53, 0x89, 0xf5, 0x2f, 0x64, - 0x17, 0x8a, 0xfd, 0xce, 0x8c, 0x0e, 0x28, 0x1d, 0x27, 0x3a, 0xe0, 0x3b, 0x70, 0xd2, 0x0e, 0xda, - 0x3c, 0xaa, 0xfb, 0x7b, 0x8f, 0x76, 0x78, 0xc8, 0xd7, 0xc2, 0x60, 0x4b, 0x39, 0xb4, 0x8c, 0xce, - 0xbd, 0x60, 0xec, 0xb7, 0x45, 0x88, 0xd2, 0x23, 0x21, 0x14, 0x25, 0x42, 0x2a, 0x63, 0x91, 0x10, - 0xcd, 0x19, 0x07, 0x98, 0xc2, 0x4a, 0xc4, 0x64, 0x0e, 0x35, 0x43, 0x91, 0x4d, 0xbf, 0xac, 0x5f, - 0x2a, 0xc1, 0x4c, 0xff, 0x2a, 0x51, 0x8b, 0x92, 0x7f, 0xa6, 0xea, 0xab, 0xd4, 0xa2, 0x24, 0x34, - 0x13, 0xca, 0xa0, 0x21, 0x33, 0x1b, 0x4e, 0xd5, 0x9b, 0x4d, 0xde, 0x8d, 0x05, 0x63, 0x72, 0x81, - 0x4f, 0xd4, 0xdb, 0xaa, 0x34, 0xb4, 0xba, 0x88, 0x20, 0x83, 0x22, 0x54, 0x58, 0x00, 0x4a, 0x84, - 0x62, 0x52, 0xeb, 0xf7, 0x4b, 0x00, 0x8d, 0xc6, 0xad, 0x3b, 0x7c, 0x6f, 0xcd, 0xf5, 0x50, 0x5f, - 0x95, 0x5b, 0xce, 0x1d, 0x12, 0xdc, 0x63, 0xe4, 0xf9, 0x26, 0x77, 0xaa, 0x87, 0x7c, 0xcf, 0xf0, - 0x7c, 0x53, 0xa8, 0xb2, 0x55, 0xde, 0xae, 0x1b, 0x73, 0x41, 0x88, 0xd7, 0x56, 0xaa, 0x55, 0x08, - 0xcd, 0x50, 0x6a, 0xc8, 0xec, 0x5b, 0x30, 0x91, 0xfe, 0x4a, 0xfc, 0xf7, 0x26, 0x92, 0xcd, 0xc1, - 0x2c, 0x9c, 0x7f, 0xfe, 0xc9, 0xfe, 0xec, 0x8c, 0xc6, 0x35, 0xeb, 0xc4, 0x96, 0x61, 0xf6, 0xf6, - 0xc0, 0xbf, 0xfc, 0xeb, 0xb3, 0x25, 0x74, 0xb2, 0x5c, 0x5f, 0x69, 0xa8, 0x66, 0x5e, 0x84, 0x81, - 0x24, 0xf6, 0x6c, 0x8c, 0x54, 0x0f, 0xd3, 0x17, 0x06, 0xcb, 0xc5, 0x21, 0x23, 0x6d, 0x0f, 0x6e, - 0x7e, 0x66, 0x3b, 0x44, 0x29, 0xbb, 0x09, 0xc3, 0x47, 0xfa, 0x72, 0x14, 0x98, 0x05, 0x5f, 0xac, - 0xa8, 0x85, 0xee, 0xbd, 0x60, 0xcb, 0xab, 0xcf, 0x31, 0xa9, 0x7b, 0x37, 0xc3, 0xb6, 0x2d, 0x60, - 0xd6, 0x7e, 0x09, 0xe0, 0xf6, 0xc6, 0xfa, 0x0f, 0xed, 0x30, 0x59, 0x3f, 0x59, 0x86, 0xa9, 0xd4, - 0x01, 0x5b, 0xb5, 0xf3, 0x8b, 0x00, 0x69, 0x93, 0x0e, 0x6f, 0x68, 0x37, 0x69, 0xe8, 0xdb, 0x30, - 0xaa, 0x55, 0x7e, 0x84, 0x96, 0x76, 0xd3, 0x96, 0x3a, 0x50, 0xcb, 0x7e, 0xf8, 0xa7, 0x6c, 0x6b, - 0xd7, 0xc0, 0x17, 0xb3, 0x6f, 0xc7, 0x8d, 0x64, 0xbc, 0xc3, 0xb8, 0x9c, 0x7d, 0xe2, 0xb7, 0x3e, - 0xfb, 0xc4, 0x6f, 0xab, 0x0e, 0xb5, 0xfa, 0x36, 0x37, 0x7a, 0x85, 0xbd, 0x5e, 0xd0, 0x23, 0x68, - 0xfe, 0x4e, 0xa1, 0x5a, 0x3f, 0x58, 0x3f, 0x51, 0x86, 0x49, 0x31, 0x93, 0xeb, 0xbd, 0x78, 0x27, - 0x08, 0xbd, 0x78, 0xef, 0x99, 0xbd, 0xb8, 0x7c, 0xd7, 0x30, 0x08, 0xcd, 0x28, 0xcd, 0x51, 0x6f, - 0x5b, 0xff, 0xfb, 0x4b, 0x5d, 0x17, 0x19, 0x84, 0x13, 0x05, 0x54, 0xec, 0x35, 0xc3, 0x1b, 0x62, - 0x5a, 0xe5, 0x84, 0xfa, 0xc1, 0xfe, 0xec, 0x98, 0x42, 0x5f, 0x4f, 0x73, 0x44, 0xcd, 0x99, 0xae, - 0xf7, 0xb2, 0xa7, 0xd0, 0x39, 0x42, 0x77, 0xbd, 0x37, 0x1d, 0xee, 0x2f, 0xc3, 0x20, 0xee, 0x08, - 0x14, 0xbf, 0x82, 0x07, 0x3b, 0xdc, 0x7b, 0x0c, 0xff, 0x53, 0x01, 0x60, 0xb7, 0x60, 0x58, 0xfc, - 0xb1, 0xea, 0x76, 0xc9, 0x35, 0x89, 0x25, 0x16, 0x53, 0x84, 0x76, 0x3d, 0x7f, 0x5b, 0x37, 0x9a, - 0xb6, 0xb9, 0xd3, 0x71, 0xbb, 0xc6, 0x09, 0x54, 0x22, 0x1a, 0xc6, 0xd7, 0x6a, 0x7f, 0xe3, 0x6b, - 0xe9, 0x50, 0xe3, 0xeb, 0x16, 0x40, 0xc3, 0xdb, 0xf6, 0x3d, 0x7f, 0xbb, 0xde, 0xde, 0xa6, 0xcc, - 0x5a, 0x97, 0xfb, 0x8f, 0xc2, 0x95, 0x14, 0x19, 0xd7, 0xc8, 0x73, 0xe8, 0x3f, 0x28, 0x61, 0x8e, - 0xdb, 0xde, 0x36, 0xd2, 0x10, 0x68, 0x9c, 0xd9, 0x5d, 0x80, 0x7a, 0x33, 0xf6, 0x76, 0xc5, 0x6a, - 0x89, 0xe8, 0xb8, 0xa8, 0x3e, 0x79, 0xa1, 0x7e, 0x87, 0xef, 0xa1, 0x89, 0x43, 0x79, 0x62, 0xb9, - 0x88, 0x2a, 0x66, 0xbd, 0x11, 0x63, 0x9e, 0x72, 0x60, 0x5d, 0x38, 0x55, 0x6f, 0xb5, 0x3c, 0xd1, - 0x06, 0xb7, 0xbd, 0x2e, 0x93, 0xa6, 0x21, 0xeb, 0xb1, 0x62, 0xd6, 0x97, 0x95, 0xf3, 0x90, 0x9b, - 0x50, 0x39, 0x2a, 0xd7, 0x5a, 0xa6, 0x9a, 0x62, 0xc6, 0x56, 0x03, 0x26, 0xcc, 0xc6, 0x9b, 0xb9, - 0xbf, 0xc6, 0xa0, 0x6a, 0x37, 0xea, 0x4e, 0xe3, 0x56, 0xfd, 0xcd, 0x5a, 0x89, 0xd5, 0x60, 0x8c, - 0x7e, 0xcd, 0x39, 0x73, 0x6f, 0x5d, 0xaf, 0x95, 0x0d, 0xc8, 0x5b, 0x6f, 0xce, 0xd5, 0x2a, 0x33, - 0xe5, 0xe9, 0x52, 0x26, 0x03, 0xc9, 0x70, 0xad, 0x2a, 0x6f, 0xd1, 0x84, 0x2e, 0x5a, 0x55, 0xdf, - 0xce, 0xae, 0x43, 0xa5, 0xd1, 0xb8, 0x95, 0xc9, 0xe1, 0x91, 0xee, 0xee, 0x72, 0x07, 0x8b, 0x0c, - 0x49, 0x23, 0x08, 0x04, 0x9d, 0x38, 0xf2, 0x95, 0x0d, 0xba, 0x74, 0xbb, 0xec, 0x77, 0xec, 0x13, - 0x74, 0xb7, 0x37, 0xd6, 0x49, 0x31, 0x57, 0x74, 0xe9, 0x36, 0x25, 0xe9, 0x3e, 0x7a, 0xa4, 0xef, - 0xab, 0x82, 0xc0, 0xb2, 0x61, 0x54, 0x9b, 0xc8, 0x52, 0x61, 0xec, 0x04, 0x49, 0x5a, 0x32, 0x52, - 0x18, 0x05, 0xc4, 0xa6, 0x12, 0x71, 0x94, 0x59, 0x09, 0x9a, 0x6e, 0x9b, 0x34, 0x4f, 0x3c, 0xca, - 0xb4, 0x05, 0xc0, 0x96, 0x70, 0xeb, 0x37, 0x4b, 0x50, 0x5b, 0x13, 0x5a, 0x3a, 0x06, 0x06, 0x06, - 0x0f, 0xb9, 0xff, 0xe0, 0x4d, 0xf6, 0xba, 0x5a, 0x72, 0xa5, 0xe4, 0x16, 0x60, 0x10, 0x97, 0x5c, - 0xc6, 0x5d, 0x85, 0x96, 0x5d, 0x23, 0x4d, 0x41, 0x57, 0x3e, 0x7a, 0xc6, 0x28, 0x4a, 0x41, 0x97, - 0xcd, 0x18, 0xa5, 0x92, 0xd1, 0xcd, 0xc2, 0x20, 0x7e, 0x0e, 0x09, 0x47, 0xfc, 0xf2, 0x58, 0x00, - 0x6c, 0x09, 0xd7, 0x64, 0xd3, 0x7e, 0x39, 0xd7, 0x86, 0xb9, 0x1f, 0xaa, 0xac, 0x4b, 0x66, 0xe3, - 0x0e, 0xf0, 0x37, 0xb9, 0xd3, 0x27, 0xeb, 0x52, 0x86, 0x81, 0xcc, 0x26, 0x30, 0x27, 0xef, 0x5d, - 0x65, 0x50, 0xad, 0x6e, 0x92, 0xce, 0x25, 0x5c, 0xf9, 0x3a, 0x9c, 0xcc, 0xf6, 0x2f, 0xde, 0xa7, - 0xd4, 0x61, 0xd2, 0x84, 0xab, 0xab, 0x95, 0x33, 0x85, 0xf5, 0x3e, 0x98, 0xb3, 0xb3, 0xf8, 0xd6, - 0xaf, 0x55, 0x60, 0x04, 0xff, 0xb4, 0x7b, 0x52, 0xcb, 0xaf, 0x6f, 0x34, 0xc8, 0x9e, 0xab, 0x6b, - 0xf9, 0xee, 0xa3, 0x48, 0x79, 0x80, 0x1b, 0x02, 0x2b, 0x41, 0x26, 0x52, 0x79, 0xbf, 0xac, 0x6e, - 0x12, 0x12, 0x52, 0x79, 0xc6, 0x88, 0x32, 0xa4, 0x84, 0x8c, 0x21, 0x5d, 0xf2, 0xd8, 0xa1, 0xfb, - 0xe3, 0x22, 0x5d, 0xd0, 0x36, 0x43, 0xba, 0x24, 0x1a, 0xba, 0xe3, 0x6e, 0x34, 0xc4, 0x49, 0x44, - 0x77, 0xc7, 0x15, 0xdf, 0x68, 0x9c, 0x42, 0x08, 0x89, 0xdd, 0x83, 0xa9, 0xfa, 0x46, 0xe3, 0x9e, - 0x76, 0x89, 0xbb, 0xbc, 0x48, 0x17, 0xd9, 0x2f, 0x3c, 0xd9, 0x9f, 0x3d, 0x2f, 0x28, 0xf5, 0x2b, - 0x5e, 0xf3, 0xe8, 0x96, 0xa7, 0x65, 0x3f, 0x56, 0x82, 0xd3, 0x19, 0xa8, 0xdb, 0xbe, 0xef, 0x7b, - 0xb1, 0x1a, 0x78, 0x2b, 0xbd, 0xa4, 0xd6, 0x91, 0x10, 0x65, 0xd5, 0x8d, 0x9b, 0x3b, 0x3c, 0x94, - 0x3e, 0xf6, 0xd9, 0xaa, 0xdd, 0xb6, 0xd3, 0x13, 0x48, 0xba, 0x0b, 0x7f, 0x71, 0x4d, 0xd6, 0xdf, - 0x9e, 0xcc, 0x4e, 0x0b, 0xd2, 0x09, 0x8e, 0x29, 0x3e, 0xde, 0x81, 0xc1, 0xba, 0x38, 0x9a, 0x93, - 0x20, 0x55, 0x96, 0x8a, 0x64, 0x56, 0xc8, 0x2d, 0x1f, 0x4f, 0xef, 0x46, 0x86, 0x19, 0x01, 0x60, - 0x0b, 0x30, 0x52, 0xdf, 0x68, 0x2c, 0x2f, 0x2f, 0xae, 0xaf, 0xcb, 0xd4, 0x15, 0x95, 0xf9, 0x97, - 0xd5, 0xa8, 0x7b, 0x5e, 0xcb, 0xc9, 0x7a, 0x41, 0xa6, 0xc7, 0xf3, 0x94, 0x8e, 0x7d, 0x05, 0xe0, - 0x76, 0xe0, 0xf9, 0xf2, 0x46, 0x89, 0x86, 0xf4, 0xfc, 0x93, 0xfd, 0xd9, 0xd1, 0x8f, 0x02, 0xcf, - 0xa7, 0x2b, 0x28, 0xf1, 0xed, 0x29, 0x92, 0xad, 0xfd, 0x2d, 0xe6, 0xcf, 0x7c, 0x20, 0xa3, 0x23, - 0x06, 0xd3, 0xf9, 0xb3, 0x19, 0xe4, 0xae, 0x3e, 0x14, 0x1a, 0xeb, 0xc0, 0x64, 0xa3, 0xb7, 0xbd, - 0xcd, 0xc5, 0xe6, 0x47, 0xa6, 0xfd, 0x21, 0x32, 0xed, 0x25, 0xf9, 0x45, 0xa5, 0xb9, 0xc1, 0x6d, - 0xf7, 0x78, 0x34, 0xff, 0x9a, 0x58, 0xeb, 0xbf, 0xb3, 0x3f, 0x4b, 0xde, 0x95, 0x42, 0x71, 0x8e, - 0x14, 0x7d, 0xde, 0xb0, 0x9f, 0xe5, 0xcd, 0xee, 0xc1, 0x90, 0xf4, 0x44, 0xa0, 0x5c, 0x0a, 0x2f, - 0x1c, 0x20, 0x57, 0x24, 0x62, 0x3f, 0x1f, 0x21, 0x59, 0xca, 0x36, 0xa0, 0xba, 0xe0, 0x85, 0xcd, - 0x36, 0x5f, 0x58, 0x26, 0xf5, 0xe8, 0xc5, 0x03, 0x58, 0x2a, 0x54, 0xd9, 0x2f, 0x4d, 0xfc, 0xd5, - 0xf4, 0x74, 0x75, 0x49, 0x61, 0xb0, 0xbf, 0x5a, 0x82, 0xe7, 0x92, 0xaf, 0xaf, 0x6f, 0x73, 0x3f, - 0xa6, 0xd9, 0x4a, 0xbd, 0x34, 0x72, 0x50, 0x2f, 0xbd, 0x9d, 0xeb, 0xa5, 0x4b, 0x69, 0x2f, 0xb9, - 0x82, 0x99, 0xd3, 0x91, 0xdc, 0xf2, 0x7d, 0x76, 0x50, 0xad, 0xcc, 0x01, 0x48, 0x7d, 0x6c, 0xc8, - 0x4d, 0xfb, 0xe5, 0x03, 0x1a, 0x9c, 0x22, 0x53, 0x14, 0x7b, 0xf2, 0xdb, 0x08, 0x2b, 0x4a, 0xa0, - 0xec, 0x8e, 0xca, 0x7b, 0x22, 0x15, 0xb7, 0x0b, 0x07, 0xf0, 0x96, 0xb9, 0x50, 0x4e, 0x1c, 0x90, - 0x74, 0x49, 0x8e, 0xf6, 0x8a, 0xbb, 0x49, 0xba, 0xda, 0x21, 0xa3, 0xbd, 0xe2, 0xa6, 0xa3, 0xdd, - 0x76, 0xb3, 0xa3, 0xbd, 0xe2, 0x6e, 0xb2, 0x05, 0x99, 0x3f, 0x4a, 0x26, 0x1b, 0x7a, 0xfe, 0x20, - 0x6e, 0xca, 0xac, 0x5e, 0x90, 0x47, 0xea, 0x1b, 0x30, 0xd2, 0xe8, 0xba, 0x4d, 0xde, 0xf6, 0xb6, - 0x62, 0x72, 0x7c, 0x7b, 0xe9, 0x00, 0x56, 0x09, 0x2e, 0x39, 0xec, 0xa8, 0x9f, 0xfa, 0xb9, 0x35, - 0xc1, 0x11, 0x5f, 0xb8, 0xbe, 0xb6, 0x4a, 0x46, 0xfe, 0x83, 0xbe, 0x70, 0x7d, 0x6d, 0x95, 0xd4, - 0xb2, 0x6e, 0xc7, 0x50, 0xcb, 0xd6, 0x56, 0x59, 0x17, 0x26, 0xd6, 0x79, 0x18, 0xba, 0x5b, 0x41, - 0xd8, 0x91, 0x57, 0x49, 0xf2, 0x0e, 0xe0, 0xf2, 0x41, 0xfc, 0x0c, 0x02, 0x79, 0x83, 0x12, 0x2b, - 0x98, 0x93, 0xbd, 0x7f, 0xca, 0xf0, 0x17, 0x7d, 0x32, 0xef, 0xc5, 0x9b, 0xbd, 0xe6, 0x43, 0x1e, - 0xd3, 0xcd, 0xc1, 0x41, 0x7d, 0x92, 0xe0, 0xca, 0x3e, 0xd9, 0x54, 0x3f, 0xf5, 0x3e, 0x49, 0x70, - 0xc4, 0x34, 0xa0, 0x2c, 0x51, 0xec, 0xd0, 0x69, 0x20, 0x11, 0xe5, 0x34, 0xe8, 0x97, 0x2e, 0x8a, - 0xed, 0xc0, 0xd8, 0x7c, 0xd0, 0xf3, 0x85, 0xb6, 0xde, 0x75, 0xbd, 0x70, 0xfa, 0x04, 0xb2, 0x7d, - 0xe5, 0xa0, 0x0f, 0xd6, 0xd0, 0x65, 0xf0, 0xd9, 0xa6, 0x80, 0x88, 0x03, 0x81, 0x00, 0xe9, 0x97, - 0xc2, 0x3a, 0x2a, 0x6b, 0xc1, 0x28, 0x4e, 0xe5, 0x45, 0xbe, 0x1b, 0x74, 0xa3, 0xe9, 0x93, 0x58, - 0xd1, 0xc5, 0xc3, 0x16, 0x85, 0xc4, 0x96, 0x8e, 0x54, 0xb8, 0x34, 0x9c, 0x16, 0x42, 0xf4, 0x9b, - 0x3a, 0x0d, 0x91, 0xdd, 0x84, 0x81, 0x25, 0x7f, 0xf7, 0x8d, 0xe9, 0x53, 0xc8, 0x7e, 0xf6, 0x00, - 0xf6, 0x02, 0x4d, 0x1a, 0x1c, 0xb8, 0xbf, 0xfb, 0x86, 0x6e, 0x70, 0x10, 0x25, 0xd9, 0xfb, 0xc2, - 0xd3, 0xc7, 0xba, 0x2f, 0x6c, 0xc1, 0xe8, 0x4d, 0xee, 0xf3, 0xd0, 0x6b, 0xde, 0x5b, 0x5e, 0x5c, - 0x98, 0x3e, 0x73, 0x68, 0x5b, 0x35, 0x6c, 0xd9, 0xd6, 0x6d, 0x09, 0x70, 0x02, 0xaf, 0xd5, 0xd4, - 0x6b, 0xd1, 0x10, 0xad, 0x5f, 0x1d, 0x84, 0xd9, 0x43, 0x3a, 0x8e, 0x3d, 0x50, 0xfb, 0xb0, 0xd4, - 0xe1, 0x3e, 0x7f, 0xb4, 0xfe, 0xbe, 0x72, 0xe8, 0x16, 0xfd, 0x0e, 0x4c, 0x64, 0x54, 0x1f, 0x2d, - 0x37, 0x72, 0x46, 0xed, 0xb1, 0x33, 0xa8, 0x33, 0xff, 0xa6, 0x02, 0x03, 0xa8, 0x1a, 0xbe, 0x08, - 0x95, 0x46, 0x6f, 0x53, 0x77, 0x5c, 0x88, 0x8c, 0xad, 0x49, 0x94, 0xb2, 0x77, 0x61, 0x94, 0xc2, - 0x8a, 0x35, 0xfb, 0x02, 0x76, 0x92, 0x8a, 0x41, 0xce, 0x06, 0x3d, 0x6a, 0xe8, 0xec, 0x3d, 0x18, - 0x5b, 0xf3, 0xba, 0xbc, 0xed, 0xf9, 0x5c, 0x0b, 0xe1, 0xc3, 0x79, 0xdb, 0x25, 0x78, 0xce, 0x99, - 0x41, 0x27, 0x30, 0x03, 0xa0, 0x07, 0x8e, 0x1e, 0x00, 0xfd, 0x1e, 0x8c, 0x2d, 0xf2, 0x2d, 0xcf, - 0xf7, 0x0c, 0xd5, 0x10, 0x2b, 0x6e, 0x25, 0x70, 0x93, 0xda, 0x20, 0x60, 0xf3, 0x30, 0x6e, 0xf3, - 0x6e, 0x10, 0x79, 0x71, 0x10, 0xee, 0xdd, 0xb7, 0x97, 0xc9, 0xd7, 0x11, 0x4d, 0xdb, 0x61, 0x52, - 0xe0, 0xf4, 0x42, 0x7d, 0xd7, 0x35, 0x49, 0xd8, 0x5d, 0x98, 0x4a, 0x01, 0xa6, 0x6f, 0x35, 0xdd, - 0x5c, 0x27, 0x7c, 0xf2, 0xb1, 0x52, 0x79, 0x52, 0xf3, 0x9b, 0x6c, 0xbe, 0x45, 0x91, 0x57, 0xd9, - 0x6f, 0x0a, 0xf9, 0x56, 0xf1, 0x37, 0xd9, 0x7c, 0xcb, 0xfa, 0xfb, 0x15, 0x38, 0xd3, 0x47, 0x8c, - 0xb3, 0xbb, 0xe6, 0x74, 0x7d, 0xf1, 0x60, 0xa9, 0x7f, 0xf8, 0x34, 0x5d, 0x81, 0xda, 0xd2, 0x1d, - 0x34, 0xc9, 0xd0, 0x3d, 0x51, 0x5d, 0x1d, 0x23, 0xb0, 0xf9, 0xfc, 0x21, 0x5e, 0x15, 0x69, 0xf7, - 0x4a, 0x46, 0x04, 0x5a, 0x86, 0x72, 0xe6, 0xff, 0x57, 0xa6, 0x79, 0xfb, 0x0e, 0xe8, 0x79, 0x4e, - 0x8d, 0xcc, 0x04, 0x29, 0x58, 0x9f, 0x91, 0x1a, 0x36, 0x7b, 0x1f, 0xc6, 0x96, 0xee, 0x48, 0x3b, - 0xf4, 0x2d, 0x65, 0xfa, 0xa4, 0x2e, 0xe4, 0x0f, 0xd5, 0x3d, 0x78, 0xc6, 0x08, 0x6a, 0x50, 0xb0, - 0xfb, 0x70, 0x42, 0x7e, 0x9b, 0xb7, 0xe5, 0x35, 0x65, 0x96, 0x67, 0xcf, 0x6d, 0xd3, 0x0c, 0x7b, - 0xf1, 0xc9, 0xfe, 0xec, 0x2c, 0x7f, 0x88, 0x97, 0x60, 0x54, 0xee, 0x44, 0x88, 0xa0, 0x07, 0x96, - 0x16, 0xd0, 0xeb, 0xf9, 0x5d, 0xed, 0x11, 0xac, 0x50, 0xd4, 0x26, 0xea, 0x16, 0xb8, 0x12, 0xc9, - 0xfa, 0x3f, 0x86, 0x60, 0xa6, 0xbf, 0x8a, 0xc9, 0x3e, 0x30, 0x07, 0xf0, 0xe2, 0xa1, 0x4a, 0xe9, - 0xe1, 0x63, 0xf8, 0x35, 0x38, 0xb9, 0xe4, 0xc7, 0x3c, 0xec, 0x86, 0x9e, 0xca, 0x0b, 0x7b, 0x2b, - 0x88, 0x54, 0xfc, 0x2e, 0x5e, 0x5b, 0xf1, 0xa4, 0x9c, 0x22, 0xd1, 0xf1, 0xb6, 0x5b, 0xbf, 0xb6, - 0x2a, 0xe2, 0xc0, 0x96, 0x60, 0x42, 0x83, 0xb7, 0x7b, 0xdb, 0xba, 0xb3, 0x93, 0xce, 0xb3, 0xdd, - 0xd3, 0x83, 0x1b, 0x33, 0x44, 0x18, 0x23, 0x1c, 0xbb, 0xb1, 0xd7, 0xbc, 0xbd, 0x71, 0xa7, 0x41, - 0xc3, 0x29, 0x63, 0x84, 0x11, 0xea, 0x7c, 0xf4, 0xe8, 0xa1, 0xa1, 0x23, 0xa6, 0xc8, 0x33, 0xbf, - 0x30, 0x70, 0x1c, 0x49, 0xf8, 0x25, 0x80, 0x74, 0x29, 0xe9, 0x39, 0x89, 0xd2, 0xa5, 0x67, 0xa6, - 0x01, 0x50, 0x50, 0x76, 0x0b, 0x26, 0xd3, 0x5f, 0xf7, 0x1e, 0xf9, 0xca, 0x0b, 0x40, 0xda, 0xeb, - 0xb5, 0x95, 0x1b, 0x88, 0x32, 0xfd, 0xd8, 0x91, 0x21, 0x63, 0x73, 0x50, 0xdd, 0x08, 0xc2, 0x87, - 0x5b, 0x62, 0x8c, 0x07, 0xd2, 0x83, 0xd1, 0x23, 0x82, 0xe9, 0x07, 0x00, 0x85, 0x27, 0x96, 0xcb, - 0x92, 0xbf, 0xeb, 0x85, 0x01, 0x3a, 0x88, 0xe9, 0xde, 0xde, 0x3c, 0x05, 0x1b, 0xb9, 0xe8, 0x52, - 0x30, 0xbb, 0x0c, 0x83, 0xf5, 0x66, 0x1c, 0x84, 0x24, 0xfe, 0xe4, 0x4c, 0x11, 0x00, 0x63, 0xa6, - 0x08, 0x80, 0xe8, 0x44, 0x21, 0x93, 0x86, 0xd3, 0x4e, 0x34, 0x05, 0x91, 0x28, 0x15, 0x07, 0x3b, - 0x9b, 0x6f, 0xa1, 0x7d, 0xbb, 0x9a, 0x7e, 0x7f, 0xc8, 0xb7, 0x72, 0x77, 0x51, 0x84, 0x26, 0xba, - 0x3d, 0x1d, 0x71, 0xf2, 0x1c, 0xc2, 0x6e, 0x4f, 0xa7, 0x88, 0x71, 0x29, 0x9b, 0x40, 0x65, 0xe2, - 0x09, 0xf5, 0x2b, 0xc9, 0xf7, 0x46, 0x89, 0x27, 0x92, 0xe9, 0x65, 0xee, 0x01, 0x3a, 0x81, 0xf5, - 0x67, 0xa1, 0xef, 0x6a, 0x13, 0x4a, 0xfc, 0xf1, 0x56, 0xdb, 0x8a, 0x7b, 0x84, 0xd5, 0xf6, 0x5a, - 0x92, 0xd7, 0x40, 0x4f, 0xd5, 0x89, 0x10, 0x5d, 0x7d, 0xa4, 0x0c, 0x07, 0xe6, 0xd4, 0xaf, 0x1c, - 0x67, 0xea, 0xff, 0xad, 0xea, 0x71, 0xa6, 0x3e, 0x0d, 0x6d, 0xf9, 0xa8, 0x43, 0x5b, 0x39, 0xda, - 0xd0, 0xce, 0xc3, 0x78, 0xf2, 0xea, 0xc6, 0x9a, 0x1b, 0x1b, 0xc2, 0x38, 0x79, 0x99, 0xc3, 0xe9, - 0xba, 0x46, 0x12, 0x69, 0x93, 0x44, 0xd3, 0x4f, 0x90, 0xc3, 0x60, 0x5e, 0x3f, 0xc9, 0xd0, 0xeb, - 0xe8, 0x42, 0x06, 0x29, 0x75, 0xa3, 0x81, 0x51, 0xf2, 0x34, 0xcf, 0xa5, 0xe7, 0xa2, 0xd2, 0x50, - 0x64, 0x00, 0xbd, 0x71, 0x67, 0x68, 0x10, 0x65, 0x97, 0xd8, 0xf0, 0xb1, 0x96, 0x98, 0x8c, 0xd7, - 0x0a, 0x57, 0x82, 0x6d, 0x4f, 0xc5, 0x52, 0xab, 0x78, 0xad, 0xd0, 0x69, 0x0b, 0x68, 0x26, 0x5e, - 0x4b, 0xa2, 0xb2, 0xd7, 0x61, 0x48, 0xfc, 0x58, 0x5e, 0xa4, 0x45, 0x81, 0x16, 0x33, 0x24, 0x32, - 0x03, 0xd8, 0x25, 0x92, 0xaa, 0x66, 0xa9, 0xe3, 0x7a, 0x6d, 0x5a, 0x0a, 0x69, 0x35, 0x5c, 0x40, - 0xb3, 0xd5, 0x20, 0x2a, 0x6b, 0xc2, 0x98, 0xcd, 0xb7, 0xd6, 0xc2, 0x20, 0xe6, 0xcd, 0x98, 0xb7, - 0xe8, 0x3c, 0xad, 0x4c, 0x4a, 0xf3, 0x41, 0x20, 0x6d, 0x05, 0x18, 0xeb, 0x5c, 0xfa, 0x9d, 0xfd, - 0x59, 0x10, 0x20, 0x99, 0x1d, 0x41, 0xac, 0x34, 0x31, 0xfe, 0x5d, 0x45, 0xac, 0xaf, 0x34, 0x9d, - 0x29, 0xfb, 0x8e, 0xd8, 0x65, 0x92, 0x2e, 0x49, 0x2b, 0x1b, 0xeb, 0x53, 0xd9, 0x5b, 0x85, 0x95, - 0xcd, 0x6a, 0xbd, 0x5d, 0x58, 0x69, 0x61, 0x25, 0xec, 0x2b, 0x30, 0xba, 0xb0, 0xbc, 0x10, 0xf8, - 0x5b, 0xde, 0x76, 0xe3, 0x56, 0x1d, 0x0f, 0xe5, 0x24, 0x26, 0x9a, 0x9e, 0xd3, 0x44, 0xb8, 0x13, - 0xed, 0xb8, 0x46, 0xfa, 0xaa, 0x14, 0x9f, 0xdd, 0x84, 0x09, 0xf5, 0xd3, 0xe6, 0x5b, 0x42, 0x55, - 0x9c, 0xd0, 0xb2, 0xa9, 0x24, 0x1c, 0x44, 0x47, 0x98, 0xda, 0x62, 0x86, 0x4c, 0x4c, 0xc6, 0x45, - 0xde, 0x6d, 0x07, 0x7b, 0xe2, 0xf3, 0xd6, 0x3d, 0x1e, 0xe2, 0xe9, 0x9b, 0x26, 0x63, 0x2b, 0x29, - 0x71, 0x62, 0xcf, 0x74, 0x22, 0x34, 0x89, 0x84, 0xd6, 0x49, 0x53, 0xfc, 0x81, 0x17, 0x79, 0x9b, - 0x5e, 0xdb, 0x8b, 0xf7, 0x64, 0xcc, 0x99, 0x54, 0xbb, 0xd4, 0xba, 0xd8, 0x4d, 0x4a, 0x75, 0xad, - 0x33, 0x47, 0x6a, 0xfd, 0x4a, 0x19, 0xce, 0x1d, 0x64, 0x83, 0x62, 0x0d, 0x53, 0x0e, 0x5e, 0x3a, - 0x82, 0xdd, 0xea, 0x70, 0x49, 0xb8, 0xd4, 0xe7, 0x88, 0x83, 0x9d, 0xd1, 0xdf, 0xb2, 0x9b, 0x3d, - 0xec, 0xec, 0x92, 0x98, 0xfb, 0xa4, 0x89, 0x94, 0xae, 0xc3, 0xc8, 0x42, 0xe0, 0xc7, 0xfc, 0x71, - 0x9c, 0xc9, 0x43, 0x28, 0x81, 0xd9, 0x24, 0x52, 0x0a, 0xd5, 0xfa, 0xed, 0x0a, 0x9c, 0x3f, 0xd0, - 0x08, 0xc3, 0xd6, 0xcd, 0x5e, 0xbb, 0x7c, 0x14, 0xcb, 0xcd, 0xe1, 0xdd, 0x36, 0x97, 0x8b, 0x98, - 0x39, 0x3c, 0x13, 0x86, 0x0d, 0x4c, 0x66, 0x47, 0xbb, 0xd9, 0x0e, 0x36, 0xd1, 0x4c, 0xe7, 0xf9, - 0xdb, 0x94, 0x55, 0x4d, 0x66, 0x75, 0xc0, 0x52, 0x67, 0xbb, 0x1d, 0x6c, 0x4a, 0x73, 0x9f, 0xe7, - 0xeb, 0x1a, 0x59, 0x01, 0xf5, 0xcc, 0x6f, 0x97, 0xa8, 0xe3, 0xdf, 0x80, 0x61, 0xfc, 0xfc, 0xa4, - 0xdb, 0xe5, 0xb5, 0x10, 0x4a, 0x76, 0xcf, 0xbc, 0x16, 0x92, 0x68, 0xec, 0x1a, 0x54, 0x17, 0xdc, - 0x76, 0x5b, 0xcb, 0xfc, 0x88, 0xb6, 0x99, 0x26, 0xc2, 0x32, 0x01, 0x66, 0x0a, 0x51, 0x6c, 0x85, - 0xf2, 0x6f, 0x6d, 0xff, 0x41, 0x01, 0x4c, 0x64, 0x99, 0x2d, 0x48, 0x43, 0xc6, 0xa7, 0x88, 0x30, - 0x58, 0x63, 0x40, 0x7b, 0x8a, 0x48, 0x00, 0x8c, 0xa7, 0x88, 0xf0, 0x2d, 0xa5, 0xef, 0x0f, 0xc1, - 0xf3, 0x07, 0x5b, 0x27, 0xd9, 0x7d, 0x73, 0x58, 0x5f, 0x3d, 0x92, 0x4d, 0xf3, 0xf0, 0x71, 0x55, - 0x0f, 0x49, 0xc9, 0x0e, 0xb9, 0x94, 0x4f, 0x91, 0xf0, 0x83, 0xfd, 0x59, 0x2d, 0x6a, 0xf1, 0x76, - 0xe0, 0xf9, 0x9a, 0x93, 0xc0, 0xc7, 0x39, 0x45, 0x61, 0x74, 0xee, 0xfa, 0xd1, 0xbe, 0x2c, 0xa5, - 0x93, 0xb2, 0xea, 0x88, 0x0a, 0x06, 0xfb, 0x1a, 0x0c, 0xa0, 0xf5, 0x65, 0x40, 0x05, 0x1e, 0x1d, - 0xa5, 0x32, 0x41, 0x41, 0xd5, 0xa0, 0x6d, 0x28, 0x63, 0x7f, 0x41, 0x8e, 0x33, 0x6f, 0x43, 0x2d, - 0xfb, 0x51, 0xec, 0x22, 0x0c, 0x60, 0xd3, 0xb4, 0x0c, 0x12, 0x99, 0x6f, 0xc3, 0xf2, 0x99, 0x9f, - 0x2e, 0x01, 0xa4, 0x95, 0x08, 0x75, 0x6b, 0x39, 0x8a, 0x7a, 0x49, 0x36, 0x73, 0x54, 0xb7, 0x3c, - 0x84, 0xe8, 0x3b, 0xa8, 0xc4, 0x61, 0x5f, 0xc3, 0x0c, 0x16, 0xe8, 0x9a, 0x8f, 0x83, 0x72, 0x6b, - 0x7d, 0x7d, 0x8d, 0xc8, 0xa5, 0xdf, 0x1b, 0xaa, 0xf3, 0x89, 0x4f, 0xbf, 0x8c, 0x6e, 0xd9, 0x89, - 0xe3, 0xae, 0x23, 0x59, 0xda, 0xfd, 0xc8, 0x67, 0xbe, 0xab, 0x8e, 0xb6, 0x98, 0xfe, 0x53, 0x4f, - 0xbd, 0x44, 0x1f, 0x46, 0xe9, 0x3f, 0x8d, 0xbc, 0x4d, 0x66, 0xfa, 0x4f, 0x9d, 0x48, 0xac, 0xe8, - 0x82, 0x3c, 0x53, 0x5a, 0x26, 0xcf, 0x43, 0x52, 0x4c, 0x15, 0x50, 0x33, 0x17, 0xce, 0xe4, 0xa1, - 0xf2, 0x41, 0xb5, 0x4a, 0x9a, 0xc0, 0xaa, 0x88, 0x31, 0x22, 0xe9, 0x59, 0x60, 0xfa, 0xf0, 0xb1, - 0x7e, 0xa9, 0x0c, 0x67, 0xfb, 0xda, 0xc6, 0xd8, 0x9a, 0xb9, 0xb2, 0x5e, 0x3e, 0xcc, 0x98, 0x76, - 0xe8, 0xa2, 0x9a, 0xf9, 0xfb, 0x4a, 0x48, 0x7d, 0x15, 0xc6, 0x1a, 0xbd, 0xcd, 0xac, 0x49, 0x41, - 0x66, 0x34, 0xd6, 0xe0, 0xba, 0xfa, 0xa2, 0xe3, 0x8b, 0x61, 0x53, 0x69, 0x96, 0x28, 0x04, 0x42, - 0x8b, 0xbb, 0x4a, 0xd2, 0xc4, 0xe5, 0xb3, 0xb6, 0x9a, 0x44, 0x62, 0x3a, 0xc6, 0xdc, 0x77, 0xfd, - 0x58, 0x8f, 0xf8, 0x95, 0x10, 0x7d, 0x3a, 0x4a, 0x88, 0xf5, 0x1f, 0x95, 0x8b, 0x2d, 0x39, 0x37, - 0x17, 0xd6, 0x8e, 0x63, 0xc9, 0xb9, 0xb9, 0xb0, 0x76, 0x78, 0x4f, 0xfd, 0x97, 0xaa, 0xa7, 0xa4, - 0xd3, 0xa8, 0xdc, 0x1c, 0xd5, 0x6d, 0xa4, 0x72, 0x1a, 0xa5, 0x8d, 0x34, 0xca, 0x38, 0x8d, 0x12, - 0x32, 0x7b, 0x0b, 0x46, 0x56, 0x02, 0x99, 0x7e, 0x55, 0xf5, 0x8f, 0xcc, 0xba, 0xa6, 0x80, 0xfa, - 0x4e, 0x9a, 0x60, 0x8a, 0xc3, 0xb3, 0x39, 0x5f, 0x54, 0x30, 0x1a, 0xae, 0xb6, 0xcc, 0x7c, 0x33, - 0xef, 0xec, 0x4c, 0x32, 0xeb, 0x3f, 0x1e, 0x04, 0xeb, 0xf0, 0x1b, 0x07, 0xf6, 0x75, 0xb3, 0xef, - 0xae, 0x1c, 0xf9, 0xae, 0xe2, 0x48, 0xbb, 0x73, 0xbd, 0xd7, 0xf2, 0xb8, 0xdf, 0x34, 0x53, 0x9d, - 0x12, 0x4c, 0xdf, 0xd9, 0x14, 0xde, 0x27, 0xc9, 0x6d, 0x35, 0xf3, 0x4f, 0x2a, 0xa9, 0x3c, 0xc9, - 0x68, 0x51, 0xa5, 0x4f, 0xa0, 0x45, 0xb1, 0x3b, 0x50, 0xd3, 0x21, 0x9a, 0x34, 0x41, 0x25, 0xd7, - 0x60, 0x94, 0xf9, 0xa8, 0x1c, 0xa1, 0xa9, 0x8a, 0x55, 0x8e, 0xae, 0x8a, 0x65, 0x2c, 0xd1, 0x03, - 0xc7, 0xb3, 0x44, 0x53, 0x6a, 0xd4, 0x88, 0x74, 0x91, 0x41, 0x33, 0x35, 0x6a, 0x81, 0x3e, 0xa2, - 0xa3, 0xab, 0xec, 0xae, 0xf8, 0x53, 0xcb, 0xfe, 0x97, 0x64, 0x77, 0x95, 0xf4, 0x45, 0xd9, 0x5d, - 0x13, 0x12, 0xa1, 0xd7, 0xd8, 0x3d, 0x5f, 0x3e, 0x65, 0x38, 0x9c, 0xea, 0x35, 0x61, 0xcf, 0x77, - 0xb2, 0xcf, 0x19, 0x26, 0x88, 0xd6, 0xaf, 0x0d, 0x14, 0xeb, 0x91, 0xe9, 0xa5, 0xd4, 0x31, 0xf4, - 0xc8, 0x84, 0xe8, 0xb3, 0x99, 0xa9, 0xf7, 0xe1, 0x84, 0x8a, 0x67, 0x5a, 0xa3, 0x28, 0x85, 0xfb, - 0xf6, 0x0a, 0x0d, 0x31, 0x1a, 0x46, 0x93, 0x48, 0xa8, 0x24, 0xb4, 0xa1, 0x17, 0x1a, 0x86, 0xd1, - 0x02, 0xfa, 0x99, 0xbf, 0xa7, 0x36, 0x47, 0x7d, 0x10, 0x30, 0x29, 0x51, 0xa9, 0x68, 0x10, 0x7a, - 0x3d, 0x63, 0x18, 0x4d, 0x12, 0x29, 0xa9, 0x13, 0x1b, 0xfd, 0x7d, 0xf3, 0x58, 0xa1, 0xdb, 0xf5, - 0x4d, 0x2e, 0x19, 0x22, 0xb6, 0x0d, 0x67, 0xd3, 0x53, 0x97, 0x76, 0xa8, 0x44, 0x8e, 0xb2, 0xc1, - 0x97, 0x9f, 0xec, 0xcf, 0xbe, 0xac, 0x9d, 0xda, 0xf4, 0xb3, 0x69, 0x86, 0x7b, 0x7f, 0x5e, 0x42, - 0xde, 0xce, 0x87, 0xae, 0xdf, 0xdc, 0xd1, 0xe6, 0x3c, 0xca, 0xdb, 0x4d, 0x84, 0xe6, 0x32, 0x20, - 0xa6, 0xc8, 0xd6, 0xef, 0x94, 0x8b, 0xad, 0x57, 0x74, 0xf7, 0x78, 0x0c, 0xeb, 0x95, 0xa4, 0x38, - 0x7c, 0x97, 0xf8, 0x97, 0x6a, 0x97, 0x78, 0x19, 0x86, 0xd7, 0xc5, 0x26, 0x95, 0x64, 0x16, 0x45, - 0xcf, 0xae, 0x58, 0x82, 0x6c, 0x55, 0xc6, 0x3e, 0x00, 0xb6, 0xe6, 0x86, 0xdc, 0x8f, 0x17, 0x82, - 0x4e, 0xd7, 0x0d, 0xe3, 0x0e, 0x3e, 0xf6, 0x28, 0xb7, 0x06, 0xf4, 0xe2, 0xe9, 0x62, 0xa9, 0xd3, - 0xd4, 0x8a, 0x75, 0x2d, 0x25, 0x4f, 0xcc, 0xae, 0xc2, 0xb0, 0x72, 0x58, 0xaa, 0xa4, 0xc9, 0xd9, - 0xf3, 0xce, 0x4a, 0x0a, 0x4b, 0xec, 0x4a, 0x2a, 0xe7, 0x86, 0x7a, 0x5d, 0x85, 0xa2, 0xd2, 0x08, - 0xa8, 0x0b, 0xa3, 0x04, 0xd3, 0xfa, 0xcf, 0x87, 0x60, 0xba, 0xdf, 0xbd, 0x26, 0xbb, 0x67, 0x76, - 0xed, 0x4b, 0x87, 0xdc, 0x83, 0x1e, 0xde, 0xb1, 0xbf, 0x35, 0xf8, 0x74, 0xe5, 0xb9, 0x21, 0x82, - 0xcb, 0x9f, 0x58, 0x04, 0x57, 0x8e, 0x27, 0x82, 0xbf, 0x0c, 0xb0, 0xce, 0x3b, 0xdd, 0xb6, 0x1b, - 0xf3, 0xe4, 0x32, 0x4f, 0x66, 0xed, 0x26, 0x68, 0x26, 0xbe, 0x26, 0x45, 0x66, 0xef, 0xc1, 0x98, - 0xfa, 0xa5, 0x39, 0x05, 0xc9, 0xc7, 0xe6, 0x14, 0x71, 0xf6, 0x1e, 0x51, 0x27, 0x10, 0xb2, 0x43, - 0x5b, 0x5a, 0x49, 0xea, 0x12, 0x79, 0xef, 0xa3, 0xad, 0x4c, 0x53, 0x76, 0x18, 0x24, 0x42, 0x13, - 0xd1, 0x00, 0x5a, 0xea, 0x56, 0xd4, 0x44, 0x74, 0x2e, 0x99, 0x4f, 0xc9, 0x92, 0xe5, 0xb7, 0x93, - 0xea, 0xf1, 0xb7, 0x13, 0xd3, 0x5a, 0xa4, 0x1e, 0x19, 0x2e, 0xb0, 0x16, 0x65, 0x42, 0x00, 0x4c, - 0x22, 0xf1, 0x29, 0x12, 0x62, 0x9a, 0x06, 0xf1, 0x53, 0x5a, 0x54, 0x90, 0x33, 0x0f, 0x9a, 0x24, - 0x32, 0x22, 0x6f, 0xf7, 0x8d, 0x75, 0x77, 0x9b, 0x32, 0x42, 0x51, 0x44, 0xde, 0xee, 0x1b, 0x4e, - 0xec, 0x6e, 0x9b, 0x11, 0x79, 0x88, 0x66, 0xfd, 0xc1, 0x48, 0xf1, 0xe5, 0xb9, 0x76, 0xc1, 0xce, - 0xac, 0xcc, 0xe1, 0x0c, 0x9d, 0x71, 0xe9, 0x24, 0xf5, 0x99, 0x1f, 0xc9, 0x8c, 0x0d, 0xb0, 0x72, - 0xc4, 0x0d, 0xf0, 0x93, 0x5f, 0x45, 0xb1, 0x57, 0x61, 0x70, 0x7d, 0xa5, 0xb1, 0x50, 0xa7, 0x99, - 0x2d, 0x35, 0xff, 0x76, 0xe4, 0x34, 0x75, 0xb3, 0xa3, 0x44, 0x61, 0x9b, 0x30, 0xb9, 0xda, 0x8b, - 0xa4, 0x43, 0xd5, 0x0d, 0x8f, 0xb7, 0x5b, 0xe9, 0x03, 0x80, 0x59, 0x27, 0xe1, 0x06, 0x3e, 0x5c, - 0x8e, 0x5a, 0xda, 0x54, 0xa7, 0x17, 0x91, 0xb7, 0x96, 0xb3, 0x85, 0x54, 0x3f, 0xd8, 0x9f, 0x1d, - 0x92, 0x08, 0x76, 0x96, 0x21, 0x7b, 0x00, 0x55, 0xec, 0x91, 0xba, 0xbf, 0x87, 0x2f, 0x50, 0x1c, - 0xec, 0xbc, 0xa0, 0x0d, 0x9b, 0x14, 0x66, 0xf8, 0xb8, 0xb6, 0xec, 0x6d, 0xd7, 0x17, 0x5d, 0x44, - 0xbc, 0x66, 0xae, 0xc0, 0xe8, 0x42, 0xe0, 0x4b, 0x67, 0xf5, 0xa5, 0x8f, 0xd3, 0xb8, 0xd0, 0x52, - 0x71, 0x5c, 0xe8, 0xcc, 0x9b, 0x30, 0x91, 0xe0, 0xdf, 0x0d, 0xe2, 0xa3, 0x91, 0xa4, 0x55, 0x2c, - 0xfb, 0x62, 0x1a, 0x49, 0x67, 0x36, 0x3a, 0xad, 0xe0, 0x34, 0x42, 0x82, 0xc8, 0xa6, 0x92, 0x99, - 0x2f, 0x98, 0xb5, 0x1c, 0x91, 0xea, 0xc7, 0x2a, 0x68, 0x1c, 0x94, 0x64, 0xec, 0xf3, 0x30, 0x52, - 0x8f, 0xe3, 0xd0, 0xdb, 0xec, 0x25, 0xee, 0xe3, 0xb2, 0x17, 0x14, 0xd0, 0x4e, 0xcb, 0xd9, 0x6d, - 0x28, 0x2f, 0x7d, 0x4c, 0xae, 0xdd, 0x73, 0x47, 0xec, 0x58, 0xad, 0xdf, 0xe6, 0x87, 0x9e, 0xec, - 0xcf, 0x96, 0xf9, 0xc7, 0x76, 0x79, 0xe9, 0x63, 0xb6, 0x01, 0x83, 0xd8, 0x33, 0x64, 0xd7, 0x79, - 0xeb, 0xb8, 0xec, 0x90, 0x58, 0x36, 0xd0, 0x0f, 0x62, 0x87, 0x7f, 0x6c, 0x4b, 0x7e, 0xe2, 0x23, - 0x97, 0x55, 0xa2, 0x9c, 0x63, 0x7f, 0xe4, 0xb2, 0x2f, 0x3f, 0xd2, 0xf3, 0xed, 0xf2, 0xb2, 0x4f, - 0x1f, 0xb9, 0xac, 0x52, 0xdb, 0x7d, 0x92, 0x8f, 0x5c, 0xf6, 0xd3, 0x8f, 0xf4, 0x7c, 0x5b, 0xf2, - 0x9b, 0xf9, 0x0f, 0x95, 0xce, 0xf1, 0x11, 0xe6, 0xd1, 0x95, 0xd8, 0xca, 0x69, 0xfa, 0x8d, 0xe3, - 0x56, 0x93, 0x18, 0x77, 0x89, 0x4f, 0x26, 0xa9, 0x2e, 0x41, 0xf1, 0x4e, 0xf2, 0x71, 0x37, 0x94, - 0xd1, 0x8b, 0xfa, 0x06, 0xca, 0x13, 0xa8, 0x19, 0x28, 0xaa, 0xa0, 0xd6, 0x3f, 0x19, 0x84, 0x0b, - 0x87, 0xf9, 0x75, 0xb1, 0x2d, 0x80, 0x7b, 0xfe, 0x66, 0xe0, 0x86, 0x2d, 0xcf, 0xdf, 0xa6, 0x1c, - 0x59, 0xd7, 0x8f, 0xe8, 0x14, 0x76, 0x25, 0xa5, 0x14, 0x85, 0x32, 0x24, 0x2b, 0x48, 0x60, 0xb6, - 0xc6, 0x99, 0x7d, 0x1b, 0xaa, 0x36, 0x6f, 0x06, 0xbb, 0x9c, 0xee, 0xb3, 0x47, 0xe7, 0xbe, 0x70, - 0xd4, 0x5a, 0x14, 0x1d, 0xd6, 0x81, 0x49, 0x99, 0x42, 0x82, 0xd8, 0x09, 0x4f, 0xf6, 0x21, 0x8c, - 0xca, 0x27, 0xc4, 0xeb, 0x5b, 0x31, 0xdd, 0x79, 0x1f, 0x9e, 0xb8, 0xb6, 0x24, 0xd4, 0x09, 0xf9, - 0x28, 0xb9, 0xe3, 0x6e, 0x19, 0xf9, 0x12, 0x64, 0xe2, 0x5a, 0x8d, 0xe5, 0xcc, 0xdf, 0x2e, 0xc3, - 0x84, 0xd9, 0x60, 0xb6, 0x02, 0xb5, 0x65, 0xdf, 0x8b, 0x3d, 0xb7, 0x6d, 0x06, 0x26, 0xd2, 0xed, - 0x87, 0x27, 0xcb, 0x9c, 0xc2, 0xb8, 0xbd, 0x1c, 0xa5, 0x50, 0x51, 0x85, 0xa6, 0x18, 0xc5, 0xd2, - 0xb7, 0x4c, 0xbe, 0xcb, 0x44, 0x23, 0xfe, 0x82, 0x7c, 0x84, 0x2c, 0x2d, 0x75, 0xe4, 0x3b, 0x6c, - 0xe6, 0x9b, 0x33, 0x59, 0x62, 0xb6, 0x0b, 0x4c, 0x48, 0x5b, 0x59, 0xc2, 0xc3, 0x79, 0xbe, 0x15, - 0x84, 0x47, 0xc9, 0x58, 0xfb, 0x2a, 0x75, 0xce, 0xf3, 0x28, 0xc8, 0x43, 0x22, 0x77, 0x36, 0x91, - 0x3e, 0xd3, 0x49, 0x05, 0x35, 0xcc, 0xac, 0xc2, 0x98, 0x3e, 0x6a, 0x18, 0xc8, 0xe2, 0x75, 0x3c, - 0x15, 0x66, 0x2f, 0x03, 0x59, 0x04, 0xc0, 0x96, 0x70, 0x76, 0x8e, 0x52, 0xbc, 0x97, 0xd3, 0x78, - 0x8f, 0x34, 0x95, 0xbb, 0xf5, 0xdd, 0x12, 0x9c, 0x2e, 0x0e, 0x82, 0x60, 0x1f, 0x65, 0xdc, 0x1a, - 0x4b, 0x07, 0x39, 0x7d, 0xaa, 0xc8, 0x89, 0x4f, 0xe6, 0xd8, 0x68, 0xfd, 0x83, 0x81, 0x9c, 0x51, - 0xa7, 0x80, 0x23, 0xbb, 0x59, 0x38, 0x8e, 0x25, 0xed, 0x18, 0x9e, 0x1f, 0xc7, 0xc2, 0xd1, 0x7b, - 0x17, 0x26, 0x90, 0x71, 0x3a, 0xb9, 0xb4, 0x9b, 0x7a, 0xf9, 0xc9, 0x5a, 0x00, 0x64, 0x06, 0x97, - 0x2d, 0x03, 0x43, 0xc8, 0xbc, 0x90, 0x5d, 0x49, 0x32, 0x40, 0xed, 0xba, 0x42, 0x72, 0xd8, 0x44, - 0x11, 0x97, 0xa6, 0x05, 0x2c, 0x20, 0x62, 0x5f, 0x82, 0x71, 0x35, 0x9c, 0xf2, 0x5d, 0x4d, 0x2d, - 0x88, 0x53, 0xad, 0x45, 0xf9, 0xb4, 0xa6, 0x6d, 0x22, 0xb2, 0x8e, 0x4c, 0xb6, 0x4d, 0x40, 0xde, - 0xaa, 0xc7, 0x47, 0xc8, 0x68, 0xfe, 0x0a, 0xcd, 0xbe, 0xe7, 0x30, 0xa3, 0x79, 0xa8, 0x68, 0x1d, - 0x37, 0xce, 0x4c, 0xbd, 0x2c, 0x6f, 0xb6, 0x0d, 0xe3, 0x08, 0xc2, 0x65, 0x2b, 0x2a, 0x1b, 0x3a, - 0xb4, 0xb2, 0x97, 0xa9, 0xb2, 0xb3, 0xb2, 0x32, 0x49, 0x99, 0xaf, 0xca, 0xe4, 0xcb, 0xae, 0xc1, - 0x28, 0xf6, 0x13, 0x25, 0x82, 0xd3, 0x1c, 0x51, 0x64, 0xaf, 0xaa, 0x74, 0x70, 0x3a, 0x96, 0xf5, - 0xe3, 0x65, 0x98, 0x90, 0x7a, 0x98, 0x0c, 0x9f, 0x79, 0x66, 0xe3, 0x9c, 0xde, 0x31, 0xe2, 0x9c, - 0xd4, 0xfb, 0x89, 0x7a, 0xd3, 0x8e, 0x14, 0x95, 0xba, 0x03, 0x2c, 0x4f, 0xc3, 0x6c, 0x18, 0xd3, - 0xa1, 0x07, 0xc7, 0x24, 0xbd, 0x99, 0x3e, 0xb5, 0x49, 0x8a, 0x2f, 0x46, 0x99, 0x45, 0xb6, 0xc1, - 0xc3, 0xfa, 0xcb, 0x65, 0x18, 0xd7, 0xa2, 0x52, 0x9f, 0xd9, 0x8e, 0x7f, 0xdb, 0xe8, 0xf8, 0xe9, - 0x24, 0xf5, 0x6c, 0xd2, 0xb2, 0x23, 0xf5, 0x7b, 0x0f, 0xa6, 0x72, 0x24, 0xd9, 0xe0, 0xde, 0xd2, - 0x51, 0x82, 0x7b, 0x5f, 0xcb, 0xbf, 0x9c, 0x87, 0x3a, 0x40, 0xfa, 0xec, 0x91, 0xfe, 0x54, 0xdf, - 0x4f, 0x94, 0xe1, 0x24, 0xfd, 0xc2, 0xf7, 0x7a, 0xe5, 0xc5, 0xd8, 0x33, 0x3b, 0x16, 0x75, 0x63, - 0x2c, 0x66, 0xcd, 0xb1, 0xd0, 0x1a, 0xd8, 0x7f, 0x48, 0xac, 0xef, 0x02, 0x4c, 0xf7, 0x23, 0x38, - 0x72, 0xa6, 0xfa, 0x34, 0x77, 0x6b, 0xf9, 0x08, 0xb9, 0x5b, 0x57, 0xa0, 0x86, 0x55, 0x51, 0x3a, - 0x8c, 0xe8, 0xbe, 0xbd, 0x4c, 0x9d, 0x84, 0x2a, 0x8b, 0x7c, 0x5e, 0x99, 0x72, 0x68, 0x44, 0x19, - 0x27, 0x92, 0x1c, 0x25, 0xfb, 0xf7, 0x4b, 0x30, 0x81, 0xc0, 0xa5, 0x5d, 0xee, 0xc7, 0xc8, 0x6c, - 0x80, 0x92, 0x7a, 0x26, 0x31, 0x3e, 0x8d, 0x38, 0xf4, 0xfc, 0x6d, 0x0a, 0xf2, 0xd9, 0xa4, 0x20, - 0x9f, 0x77, 0x65, 0x70, 0xd2, 0x95, 0x66, 0xd0, 0xb9, 0xba, 0x1d, 0xba, 0xbb, 0x5e, 0x4c, 0xc1, - 0x69, 0x57, 0x55, 0xd6, 0xc8, 0xab, 0x6e, 0xd7, 0xbb, 0x8a, 0x7d, 0x7a, 0x35, 0xc3, 0x0a, 0x03, - 0xa8, 0xe4, 0x87, 0x72, 0xac, 0x36, 0xeb, 0xeb, 0x62, 0x7e, 0x11, 0xfb, 0x11, 0x38, 0x23, 0xbd, - 0x06, 0x16, 0x02, 0x3f, 0xf6, 0xfc, 0x5e, 0xd0, 0x8b, 0xe6, 0xdd, 0xe6, 0xc3, 0x5e, 0x37, 0xa2, - 0x94, 0xd5, 0xd8, 0xf2, 0x66, 0x52, 0xe8, 0x6c, 0xca, 0x52, 0xe3, 0x31, 0x89, 0x62, 0x06, 0xec, - 0x16, 0x4c, 0xc9, 0xa2, 0x7a, 0x2f, 0x0e, 0x1a, 0x4d, 0xb7, 0x2d, 0xb4, 0xe8, 0x61, 0xe4, 0x2a, - 0x23, 0x19, 0x7a, 0x71, 0xe0, 0x44, 0x12, 0xae, 0xbb, 0xbe, 0xe4, 0x88, 0xd8, 0x32, 0x4c, 0xda, - 0xdc, 0x6d, 0xad, 0xba, 0x8f, 0x17, 0xdc, 0xae, 0xdb, 0xf4, 0x62, 0xf9, 0xe2, 0x6d, 0x45, 0x5e, - 0x7b, 0x84, 0xdc, 0x6d, 0x39, 0x1d, 0xf7, 0xb1, 0xd3, 0xa4, 0x42, 0xd3, 0x73, 0xd3, 0xa0, 0x4b, - 0x58, 0x79, 0x7e, 0xc2, 0x6a, 0x24, 0xcb, 0xca, 0xf3, 0xfb, 0xb3, 0x4a, 0xe9, 0x14, 0xab, 0x75, - 0x37, 0xdc, 0xe6, 0xb1, 0x3c, 0x0b, 0xc3, 0x85, 0xd2, 0xa5, 0x92, 0xc6, 0x2a, 0xc6, 0x32, 0x07, - 0x0f, 0xac, 0x59, 0x56, 0x1a, 0x9d, 0x98, 0x79, 0x1b, 0xa1, 0x17, 0x73, 0xbd, 0x85, 0xa3, 0xf8, - 0x59, 0xd8, 0xff, 0x98, 0x90, 0xaa, 0x5f, 0x13, 0x73, 0x94, 0x29, 0x37, 0xad, 0x91, 0x63, 0x39, - 0x6e, 0xc5, 0xad, 0xcc, 0x51, 0x26, 0xdc, 0xf4, 0x76, 0x8e, 0x63, 0x3b, 0x35, 0x6e, 0x7d, 0x1a, - 0x9a, 0xa3, 0x64, 0x77, 0x45, 0xa7, 0xc5, 0xdc, 0x17, 0x33, 0x9a, 0x72, 0x0d, 0x4d, 0xe0, 0xa7, - 0xbd, 0x44, 0xba, 0x46, 0x2d, 0x54, 0xc5, 0x4e, 0x41, 0xe6, 0xa1, 0x2c, 0x31, 0xfb, 0x51, 0x98, - 0xbc, 0x1f, 0xf1, 0x1b, 0xcb, 0x6b, 0x0d, 0xf5, 0xc4, 0x18, 0x7a, 0x6b, 0x4d, 0xcc, 0xbd, 0x79, - 0x88, 0xd0, 0xb9, 0xa2, 0xd3, 0x88, 0x0d, 0x91, 0xcb, 0x71, 0xeb, 0x45, 0xdc, 0xd9, 0xf2, 0xba, - 0x51, 0xf2, 0xbc, 0xa6, 0x3e, 0x6e, 0x99, 0xaa, 0xac, 0x5b, 0x30, 0x95, 0x63, 0xc3, 0x26, 0x00, - 0x04, 0xd0, 0xb9, 0x7f, 0xb7, 0xb1, 0xb4, 0x5e, 0xfb, 0x1c, 0xab, 0xc1, 0x18, 0xfe, 0x5e, 0xba, - 0x5b, 0x9f, 0x5f, 0x59, 0x5a, 0xac, 0x95, 0xd8, 0x14, 0x8c, 0x23, 0x64, 0x71, 0xb9, 0x21, 0x41, - 0xe5, 0xdb, 0x03, 0xd5, 0xc1, 0xda, 0x90, 0x5d, 0x93, 0x4b, 0x57, 0xa6, 0x00, 0x17, 0x7b, 0x8a, - 0xf5, 0xf3, 0x65, 0x38, 0xab, 0xb6, 0x15, 0x1e, 0x3f, 0x0a, 0xc2, 0x87, 0x9e, 0xbf, 0xfd, 0x8c, - 0xef, 0x0e, 0x37, 0x8c, 0xdd, 0xe1, 0xa5, 0xcc, 0x4e, 0x9d, 0x69, 0xe5, 0x01, 0x5b, 0xc4, 0x6f, - 0x8c, 0xc0, 0xf9, 0x03, 0xa9, 0xd8, 0x07, 0x62, 0x37, 0xf7, 0xb8, 0x1f, 0x2f, 0xb7, 0xda, 0x5c, - 0xe8, 0xb5, 0x41, 0x2f, 0xa6, 0xdc, 0x56, 0x2f, 0xa2, 0x33, 0x93, 0x4c, 0x95, 0xe5, 0xb5, 0xda, - 0xdc, 0x89, 0x65, 0xb1, 0x31, 0xdd, 0xf2, 0xd4, 0x82, 0xe5, 0x1d, 0xce, 0xbb, 0xf5, 0xb6, 0xb7, - 0xcb, 0x97, 0xfd, 0x98, 0x87, 0xbb, 0x98, 0x83, 0x20, 0x61, 0xf9, 0x90, 0xf3, 0xae, 0xe3, 0x8a, - 0x52, 0xc7, 0xa3, 0x62, 0x93, 0x65, 0x8e, 0x9a, 0xdd, 0xd0, 0x58, 0xe2, 0xd1, 0x60, 0xd5, 0x7d, - 0x4c, 0x11, 0xbf, 0xf4, 0xc2, 0x70, 0xc2, 0x52, 0x7a, 0x73, 0x74, 0xdc, 0xc7, 0x76, 0x9e, 0x84, - 0x7d, 0x0b, 0x4e, 0xd1, 0x06, 0x44, 0xaf, 0x9b, 0xa8, 0x16, 0xcb, 0xb7, 0x53, 0x5e, 0x11, 0xa7, - 0x39, 0x95, 0x02, 0x4a, 0xbd, 0x58, 0x54, 0xd4, 0xea, 0x62, 0x2e, 0x6c, 0x5d, 0x6c, 0xc8, 0x99, - 0xee, 0x58, 0xe5, 0x51, 0xa4, 0x32, 0xc1, 0x92, 0xed, 0x45, 0xef, 0x4c, 0xa7, 0x23, 0xcb, 0xed, - 0xbe, 0x94, 0xec, 0x16, 0x4c, 0x6c, 0xf0, 0x4d, 0x7d, 0x7c, 0x86, 0x12, 0x51, 0x55, 0x7b, 0xc4, - 0x37, 0xfb, 0x0f, 0x4e, 0x86, 0x8e, 0x79, 0xe8, 0x70, 0xf9, 0x78, 0x6f, 0x45, 0x9c, 0xb6, 0x7d, - 0x1e, 0xe2, 0xa1, 0x79, 0x18, 0x85, 0xc1, 0x74, 0xaa, 0x21, 0x9b, 0xe5, 0x74, 0xbf, 0x85, 0xc9, - 0x17, 0xdb, 0x04, 0xc7, 0xa4, 0x69, 0x19, 0x5f, 0x4c, 0x93, 0x8a, 0x7d, 0x08, 0x93, 0x76, 0xd0, - 0x8b, 0x3d, 0x7f, 0xbb, 0x21, 0x8e, 0xa5, 0x7c, 0x5b, 0x6e, 0x48, 0xe9, 0x03, 0x6c, 0x99, 0x52, - 0x8a, 0x30, 0x90, 0x40, 0x27, 0x22, 0xa8, 0xb1, 0x23, 0x98, 0x04, 0xec, 0xdb, 0x30, 0x21, 0xdf, - 0x83, 0x48, 0x2a, 0x18, 0x21, 0xdb, 0x32, 0xc5, 0x90, 0x1b, 0x85, 0x0f, 0xde, 0xa4, 0x00, 0x4d, - 0x84, 0x16, 0x55, 0x90, 0xe1, 0xc6, 0xbe, 0x41, 0x9d, 0xb5, 0xe6, 0xf9, 0xdb, 0xc9, 0x34, 0x06, - 0xec, 0xf9, 0xd7, 0xd3, 0x2e, 0xe9, 0x8a, 0xcf, 0x55, 0xd3, 0xb8, 0x4f, 0xb4, 0x79, 0x9e, 0x0f, - 0x8b, 0xe1, 0x7c, 0x3d, 0x8a, 0xbc, 0x28, 0xa6, 0x0c, 0x70, 0x4b, 0x8f, 0x79, 0xb3, 0x27, 0x90, - 0x37, 0x82, 0xf0, 0x21, 0x0f, 0x65, 0x16, 0x97, 0xc1, 0xf9, 0x2b, 0x4f, 0xf6, 0x67, 0x5f, 0x75, - 0x11, 0xd1, 0xa1, 0xa4, 0x71, 0x0e, 0x57, 0xa8, 0xce, 0x23, 0x89, 0xab, 0xb5, 0xe1, 0x60, 0xa6, - 0xec, 0xdb, 0x70, 0x7a, 0xc1, 0x8d, 0xf8, 0xb2, 0x1f, 0x71, 0x3f, 0xf2, 0x62, 0x6f, 0x97, 0x53, - 0xa7, 0xe2, 0xe6, 0x57, 0x95, 0x59, 0x01, 0x9a, 0x6e, 0x24, 0x16, 0x66, 0x82, 0xe2, 0xd0, 0xa0, - 0xe8, 0x59, 0x01, 0x8a, 0xb9, 0x30, 0x1b, 0x26, 0x1a, 0x8d, 0x5b, 0x8b, 0x9e, 0x9b, 0xac, 0xab, - 0x71, 0xec, 0xaf, 0x57, 0xd1, 0x01, 0x26, 0xda, 0x71, 0x5a, 0x9e, 0x9b, 0x2c, 0xa8, 0x3e, 0x9d, - 0x95, 0xe1, 0x60, 0xed, 0x97, 0xa0, 0x96, 0x1d, 0x4a, 0xf6, 0x35, 0x18, 0x91, 0xa1, 0xda, 0x3c, - 0xda, 0x21, 0xa3, 0x8d, 0x8a, 0xfc, 0x4d, 0xe0, 0x26, 0x11, 0xe5, 0x10, 0x96, 0x81, 0xe0, 0x5c, - 0x0f, 0x9a, 0xba, 0xf5, 0x39, 0x3b, 0x65, 0xc6, 0x5a, 0x30, 0x26, 0x47, 0x8b, 0xe3, 0xe3, 0x91, - 0x99, 0xbc, 0x78, 0x7a, 0x51, 0x86, 0xbf, 0x0c, 0x15, 0x94, 0x73, 0x42, 0x22, 0x18, 0x55, 0x18, - 0x5c, 0xe7, 0x01, 0xaa, 0x8a, 0xd0, 0x3a, 0x0b, 0x67, 0xfa, 0x7c, 0xb3, 0xf5, 0x6b, 0x25, 0xbc, - 0x18, 0xef, 0x53, 0x25, 0xfb, 0x1a, 0x9c, 0x44, 0xca, 0x85, 0xc0, 0xf7, 0x79, 0x33, 0x46, 0x79, - 0xa4, 0x3c, 0xf1, 0x2a, 0x32, 0xe2, 0x49, 0x36, 0xb8, 0x99, 0x20, 0x38, 0x59, 0x87, 0xbc, 0x42, - 0x0e, 0xec, 0x7d, 0x38, 0xd7, 0xf2, 0x22, 0x22, 0x73, 0xe2, 0x9d, 0x90, 0x47, 0x3b, 0x41, 0xbb, - 0xe5, 0x44, 0xbc, 0x19, 0xf8, 0x2d, 0x99, 0xea, 0x65, 0xdc, 0x9e, 0x49, 0x71, 0xd6, 0x15, 0x4a, - 0x43, 0x62, 0x58, 0xff, 0xaa, 0x0c, 0xd3, 0x24, 0x24, 0x6d, 0xde, 0x0c, 0xd0, 0xea, 0xf9, 0x8c, - 0x6f, 0xca, 0x4b, 0xc6, 0xa6, 0xfc, 0x62, 0xf2, 0xe2, 0x48, 0x51, 0x23, 0x0f, 0xc8, 0xd3, 0x72, - 0x3f, 0x93, 0xa7, 0xe5, 0x10, 0x46, 0x88, 0x3a, 0x3f, 0xad, 0x74, 0xbf, 0x7e, 0x19, 0x5b, 0xac, - 0x65, 0xa8, 0xde, 0xe1, 0x7b, 0x98, 0xb4, 0x40, 0xf4, 0x6f, 0x9c, 0x1e, 0xfe, 0xaa, 0x2a, 0x31, - 0x97, 0x8d, 0xff, 0xb2, 0x59, 0x18, 0xc4, 0x14, 0x08, 0x7a, 0x8a, 0x53, 0x04, 0xd8, 0xf2, 0x3f, - 0xeb, 0x7f, 0x2f, 0xc1, 0xd9, 0x55, 0xd7, 0xef, 0xb9, 0xed, 0x3b, 0x7c, 0x6f, 0xd5, 0xf5, 0xdd, - 0x6d, 0x7c, 0x5f, 0x8e, 0x5c, 0x51, 0xaf, 0xc2, 0x30, 0x3d, 0x5c, 0x8d, 0xfc, 0xab, 0xd2, 0x71, - 0x21, 0xff, 0x96, 0xb5, 0xc2, 0x62, 0x77, 0x60, 0x54, 0xcb, 0x39, 0x45, 0x99, 0x3e, 0x54, 0xcf, - 0xab, 0x6f, 0xa6, 0x7c, 0x2d, 0xc5, 0xb9, 0xa9, 0xdc, 0x34, 0x37, 0xd5, 0x5d, 0x18, 0x53, 0x56, - 0x36, 0xe4, 0x56, 0x29, 0xe6, 0x36, 0x93, 0xda, 0xe6, 0x73, 0x39, 0xa8, 0x46, 0x09, 0x8e, 0x99, - 0xa7, 0x7e, 0xab, 0x04, 0x17, 0xb2, 0x3d, 0x9f, 0xa6, 0x8f, 0xfb, 0xa4, 0x4d, 0xde, 0x83, 0x53, - 0x1d, 0xec, 0x40, 0xcc, 0x6d, 0xd7, 0x49, 0xba, 0x90, 0xc4, 0x89, 0xca, 0xf1, 0xd0, 0xb7, 0x93, - 0xa5, 0x13, 0x52, 0x21, 0x0b, 0xdd, 0x09, 0xa9, 0x93, 0xa7, 0xb7, 0x7e, 0xa6, 0x0c, 0xe7, 0x0e, - 0x9a, 0x93, 0x89, 0xb1, 0xbc, 0x54, 0x64, 0x2c, 0x67, 0x5d, 0x38, 0x81, 0x12, 0x67, 0x61, 0x87, - 0x37, 0x1f, 0x62, 0xda, 0xdf, 0x3b, 0x72, 0xd0, 0x8a, 0xc3, 0x5b, 0x5e, 0x2b, 0x0c, 0x6f, 0x39, - 0x2d, 0xe5, 0x60, 0x13, 0x79, 0x48, 0x6b, 0xa6, 0x18, 0x03, 0xbb, 0x88, 0x35, 0xe3, 0x00, 0xe9, - 0x5b, 0xf3, 0x74, 0xbb, 0xf0, 0x4a, 0x9f, 0x35, 0x91, 0x1d, 0x19, 0x7c, 0xb4, 0xaa, 0x24, 0x63, - 0xec, 0x54, 0x89, 0x3e, 0x71, 0x52, 0xa8, 0xd5, 0xeb, 0xdb, 0x2d, 0xb8, 0x7e, 0xd8, 0x7d, 0x98, - 0xd4, 0x9e, 0xbc, 0xc7, 0xb9, 0x65, 0xda, 0x0e, 0xb3, 0x79, 0x05, 0xa5, 0x1f, 0x4c, 0x86, 0xc6, - 0x9e, 0xe0, 0x3a, 0x0e, 0x3e, 0x2a, 0x51, 0xab, 0xf7, 0xe2, 0x9d, 0xb5, 0x90, 0x53, 0xf2, 0xe8, - 0x1f, 0xb2, 0xfc, 0x54, 0x66, 0xe3, 0x8e, 0x64, 0x41, 0xfc, 0xcd, 0x49, 0x38, 0x59, 0x44, 0x26, - 0xfa, 0x65, 0xbd, 0x50, 0x6e, 0xa1, 0xa9, 0xea, 0x7b, 0x25, 0x18, 0x93, 0xdb, 0xcb, 0x0d, 0x0c, - 0xec, 0xa4, 0xde, 0x71, 0xa5, 0xd2, 0x2e, 0xe0, 0xce, 0x56, 0x26, 0xe2, 0xf3, 0x07, 0xfb, 0xb3, - 0xef, 0x1f, 0xcd, 0x56, 0xd4, 0x0c, 0xf0, 0xc2, 0x22, 0x16, 0x73, 0x2e, 0xad, 0x42, 0x7e, 0x8d, - 0x6d, 0x54, 0xcb, 0xe6, 0x61, 0x9c, 0xf6, 0xcb, 0x40, 0x7f, 0xee, 0x57, 0x3e, 0xda, 0xa5, 0x0a, - 0x72, 0x3e, 0x31, 0x06, 0x09, 0xbb, 0x06, 0x95, 0xfb, 0x73, 0x37, 0x68, 0x14, 0x54, 0xa2, 0xf1, - 0xfb, 0x73, 0x37, 0xd0, 0x20, 0x2d, 0x26, 0xf4, 0x78, 0x6f, 0xce, 0x88, 0x5b, 0xbc, 0x3f, 0x77, - 0x83, 0xfd, 0x69, 0x38, 0xb5, 0x98, 0xec, 0xb8, 0x32, 0x57, 0x5a, 0x0b, 0x73, 0xb2, 0x0e, 0xf5, - 0x59, 0x9d, 0x5f, 0x2c, 0x5c, 0x9d, 0x2f, 0x68, 0x5b, 0xbb, 0x4c, 0xc4, 0xd6, 0xca, 0x3e, 0x6b, - 0x5c, 0x5c, 0x0f, 0xfb, 0x08, 0x26, 0xd0, 0x3f, 0x02, 0xd3, 0xc7, 0x89, 0x11, 0xa4, 0x9c, 0x42, - 0xf9, 0x9a, 0xdf, 0x28, 0xac, 0x79, 0x46, 0x3a, 0x5a, 0x60, 0x12, 0x3a, 0xc7, 0xed, 0x19, 0xd1, - 0x8e, 0x19, 0xce, 0xec, 0x36, 0x4c, 0xd2, 0x01, 0xe8, 0xde, 0xd6, 0xfa, 0x0e, 0x5f, 0x74, 0xf7, - 0xc8, 0xf7, 0x08, 0x6d, 0x2a, 0x74, 0x6a, 0x72, 0x82, 0x2d, 0x27, 0xde, 0xe1, 0x4e, 0xcb, 0x35, - 0x8e, 0x0a, 0x19, 0x42, 0xf6, 0x1d, 0x18, 0x5d, 0x09, 0x9a, 0xe2, 0xec, 0x8b, 0x92, 0x4f, 0xba, - 0x1f, 0x7d, 0x5d, 0x08, 0xaa, 0xb6, 0x04, 0x67, 0x0e, 0x34, 0x3f, 0xd8, 0x9f, 0x7d, 0xe7, 0xb8, - 0xd3, 0x46, 0xab, 0xc0, 0xd6, 0x6b, 0x63, 0x0b, 0x50, 0xdd, 0xe0, 0x9b, 0xa2, 0xb5, 0x3e, 0xa5, - 0x0f, 0x52, 0xab, 0x4e, 0x81, 0x29, 0x34, 0x9a, 0x7e, 0x19, 0xa1, 0xd1, 0x04, 0x63, 0x21, 0x4c, - 0x61, 0xff, 0xac, 0xb9, 0x51, 0xf4, 0x28, 0x08, 0x5b, 0x6d, 0x1e, 0x45, 0x7d, 0x03, 0x1c, 0xe7, - 0x0a, 0x3b, 0xff, 0x9c, 0xec, 0xfc, 0xae, 0xc6, 0xc1, 0x48, 0x34, 0x96, 0x65, 0xcf, 0x3e, 0x84, - 0x09, 0x7a, 0x93, 0x60, 0xf5, 0x46, 0x1d, 0x57, 0xc2, 0x98, 0x91, 0xd2, 0xd5, 0x2c, 0x54, 0x97, - 0xcc, 0x08, 0x4b, 0xf2, 0x28, 0x77, 0xb6, 0x5c, 0xd3, 0x39, 0x55, 0x27, 0x61, 0x6b, 0x30, 0xba, - 0xc8, 0x77, 0xbd, 0x26, 0xc7, 0x5c, 0x90, 0x94, 0x64, 0x48, 0xe5, 0xed, 0xd4, 0x4a, 0xe4, 0x76, - 0xde, 0x42, 0x80, 0xcc, 0x2c, 0x69, 0x26, 0x4d, 0x48, 0x10, 0xd9, 0x75, 0xa8, 0x2c, 0x2f, 0xae, - 0x51, 0x8e, 0xa1, 0xa9, 0xe4, 0xe5, 0x8f, 0x35, 0x7a, 0x04, 0x5b, 0x5e, 0xb2, 0x79, 0x2d, 0x23, - 0x43, 0xd1, 0xf2, 0xe2, 0x1a, 0xdb, 0x82, 0x71, 0xe9, 0x57, 0xc5, 0x5d, 0xd9, 0xb7, 0x93, 0x7d, - 0xfa, 0xf6, 0x4a, 0x61, 0xdf, 0x4e, 0x93, 0xbf, 0x16, 0x51, 0xeb, 0xeb, 0xde, 0x60, 0x2b, 0x0e, - 0x95, 0x8b, 0x7c, 0xcb, 0xed, 0xb5, 0x95, 0x21, 0x7c, 0x7d, 0x7d, 0x05, 0x43, 0x1e, 0xe9, 0x50, - 0xd9, 0x92, 0x85, 0x49, 0xff, 0xf5, 0x4f, 0x61, 0x96, 0xe7, 0xc3, 0xde, 0x86, 0x81, 0x7b, 0x0f, - 0x63, 0x97, 0xb2, 0x09, 0xa9, 0x7e, 0x14, 0x20, 0xd5, 0x7c, 0x19, 0xab, 0xf4, 0xd0, 0x78, 0x4e, - 0x10, 0x69, 0xc4, 0x50, 0xdc, 0x72, 0xc3, 0xd6, 0x23, 0x37, 0xc4, 0x3c, 0xc7, 0x27, 0x0c, 0x16, - 0x5a, 0x89, 0x1c, 0x8a, 0x1d, 0x02, 0x64, 0xbc, 0x12, 0x74, 0x16, 0xec, 0x47, 0xe0, 0x6c, 0xe4, - 0x6d, 0xfb, 0xf8, 0x16, 0x93, 0xe3, 0xb6, 0xb7, 0x83, 0xd0, 0x8b, 0x77, 0x3a, 0x4e, 0xd4, 0xf3, - 0x62, 0x8e, 0x69, 0x7d, 0x26, 0x92, 0x33, 0x5b, 0x43, 0xe1, 0xd5, 0x15, 0x5a, 0x43, 0x60, 0xd9, - 0x67, 0xa2, 0xe2, 0x02, 0xf6, 0x0d, 0x18, 0xd7, 0x45, 0x72, 0x34, 0x7d, 0xea, 0x42, 0xe5, 0xd2, - 0x44, 0xb2, 0x55, 0x67, 0x45, 0xb8, 0x7a, 0xe4, 0x5c, 0xdb, 0x23, 0x22, 0xf3, 0x91, 0x73, 0x8d, - 0x17, 0xb3, 0xe1, 0x4c, 0x24, 0xed, 0x8b, 0x3d, 0xdf, 0x7b, 0x8c, 0x6f, 0x21, 0x50, 0x68, 0x2c, - 0xa6, 0xf7, 0x49, 0xb7, 0xbe, 0x06, 0x62, 0xdd, 0xbf, 0xbb, 0xfc, 0xb5, 0xfb, 0x11, 0x0f, 0x29, - 0x42, 0xf6, 0xa4, 0xa4, 0xbd, 0xef, 0x7b, 0x8f, 0x53, 0x28, 0xfb, 0x6e, 0x09, 0x4e, 0xe3, 0x4c, - 0x58, 0x58, 0x59, 0x16, 0xe2, 0xed, 0x81, 0xe7, 0xce, 0x87, 0xc1, 0xa3, 0x88, 0x87, 0xd3, 0xd3, - 0x7d, 0x66, 0xda, 0xdb, 0x85, 0x33, 0xed, 0x25, 0x7a, 0x8a, 0xa8, 0xed, 0xa1, 0x00, 0x75, 0x76, - 0x3d, 0xd7, 0xd9, 0x94, 0x9c, 0x8c, 0xb4, 0x7d, 0x85, 0x75, 0x49, 0x1b, 0xea, 0xed, 0x81, 0x2a, - 0xab, 0x9d, 0xb8, 0x3d, 0x50, 0x3d, 0x53, 0x9b, 0xb6, 0xa7, 0x68, 0x49, 0xd2, 0x34, 0x5a, 0xbd, - 0x51, 0xb7, 0x87, 0xd7, 0x96, 0x1f, 0x34, 0xda, 0x41, 0x6c, 0xcf, 0x20, 0x17, 0xa2, 0x15, 0x9c, - 0xb8, 0x1f, 0x7b, 0x32, 0xda, 0xc5, 0xda, 0x81, 0x93, 0x45, 0xcd, 0x67, 0xd3, 0x19, 0x55, 0x39, - 0xd5, 0x89, 0x9f, 0x83, 0x91, 0x2d, 0x2f, 0x8c, 0x62, 0xa7, 0xe7, 0x49, 0xc5, 0x66, 0xd0, 0xae, - 0x22, 0xe0, 0xbe, 0xd7, 0x62, 0x67, 0xa1, 0x8a, 0x37, 0xe8, 0x3d, 0xca, 0xeb, 0x3f, 0x68, 0x0f, - 0x8b, 0xdf, 0xf7, 0xbd, 0x96, 0xf5, 0x9f, 0x95, 0x70, 0xaf, 0x64, 0xaf, 0xe2, 0xeb, 0x5c, 0x89, - 0x23, 0x31, 0x5e, 0x54, 0xb9, 0xdd, 0x6e, 0xe6, 0xdd, 0x40, 0x44, 0x61, 0xaf, 0xc1, 0xd0, 0x0d, - 0xb7, 0xc9, 0x13, 0x1f, 0x6d, 0x44, 0xde, 0x42, 0x88, 0x7e, 0x74, 0x92, 0x38, 0xe2, 0x1c, 0x2d, - 0x65, 0x48, 0x3d, 0x8e, 0x79, 0x24, 0x05, 0xfd, 0x42, 0x5d, 0xf9, 0x65, 0xe3, 0x39, 0x9a, 0x64, - 0x8f, 0x9b, 0x22, 0x64, 0xb2, 0xc0, 0x14, 0x72, 0xb0, 0xfe, 0x75, 0x29, 0x15, 0xfe, 0xec, 0x15, - 0x18, 0xb0, 0xd7, 0x92, 0xef, 0x97, 0xb9, 0x8c, 0x33, 0x9f, 0x8f, 0x08, 0xec, 0x1b, 0x70, 0x4a, - 0xe3, 0x93, 0x4b, 0x49, 0xf3, 0x32, 0xa6, 0xda, 0xd5, 0xbe, 0xa4, 0x38, 0x2f, 0x4d, 0x31, 0x0f, - 0x34, 0x1a, 0xa4, 0x05, 0x8b, 0xdc, 0xf7, 0x24, 0x6f, 0xad, 0xb1, 0x3a, 0xef, 0x16, 0x22, 0x64, - 0x1b, 0x5b, 0xc4, 0x41, 0x66, 0xda, 0xb5, 0x7e, 0xbd, 0x64, 0x08, 0x75, 0x76, 0xd1, 0x38, 0x6e, - 0xa0, 0x00, 0xca, 0x58, 0x0f, 0xe5, 0xc1, 0xe3, 0xcb, 0x00, 0xf5, 0x5e, 0x1c, 0x2c, 0xf9, 0x61, - 0xd0, 0x6e, 0x93, 0x4f, 0xac, 0x3c, 0x13, 0xf6, 0xe2, 0xc0, 0xe1, 0x08, 0x36, 0x72, 0x78, 0x26, - 0xc8, 0x85, 0xd9, 0x7b, 0x2a, 0x9f, 0x34, 0x7b, 0x8f, 0x38, 0x40, 0x19, 0x72, 0xec, 0x0d, 0x50, - 0x0b, 0x42, 0x8f, 0x0b, 0xee, 0x7a, 0xbb, 0x4e, 0xd4, 0x0e, 0x8c, 0x57, 0x3c, 0x08, 0x8d, 0xfd, - 0x85, 0x12, 0x9c, 0x96, 0x69, 0x70, 0xee, 0xf6, 0x3a, 0x9b, 0x3c, 0x7c, 0xe0, 0xb6, 0xbd, 0x56, - 0xfa, 0x60, 0x63, 0x1a, 0x78, 0xae, 0x55, 0x53, 0x8c, 0x2f, 0x2d, 0x72, 0x32, 0x2d, 0x8f, 0xe3, - 0x63, 0xa1, 0xb3, 0x9b, 0x94, 0xea, 0x0b, 0xbe, 0x98, 0x9e, 0x2d, 0xc3, 0xe8, 0x9a, 0xe7, 0x2f, - 0xb8, 0xcd, 0x1d, 0x9e, 0x26, 0xc9, 0x7c, 0x45, 0x66, 0xb5, 0x12, 0x53, 0xb8, 0xb9, 0xc3, 0x0f, - 0xd8, 0x63, 0x74, 0x5a, 0xeb, 0x57, 0x4a, 0xf0, 0xc2, 0xa1, 0x1f, 0x2c, 0x8e, 0xca, 0x4b, 0x47, - 0x3a, 0x2a, 0x13, 0x16, 0xfb, 0x26, 0x9c, 0xd2, 0x59, 0xad, 0x87, 0xae, 0xa7, 0x07, 0x81, 0x16, - 0x74, 0x40, 0x2c, 0x50, 0xb2, 0xfa, 0x75, 0x31, 0x13, 0xeb, 0xff, 0x2e, 0xc1, 0x48, 0x12, 0xb6, - 0xf9, 0x8c, 0x9e, 0xbb, 0xae, 0x1b, 0xe7, 0x2e, 0xf5, 0x62, 0x70, 0xd2, 0x2a, 0xe9, 0xd8, 0x58, - 0x70, 0xf9, 0x33, 0xa9, 0x25, 0x2d, 0x41, 0xc0, 0x9f, 0x2f, 0xc3, 0xa8, 0x10, 0xd5, 0xd2, 0xf9, - 0xe5, 0x87, 0xeb, 0xfd, 0xcf, 0xa4, 0x5d, 0xfd, 0x1f, 0x3b, 0xd4, 0x4e, 0xa0, 0xff, 0x73, 0x09, - 0x2f, 0x45, 0x75, 0x0a, 0xd1, 0x1b, 0x02, 0xa4, 0xf7, 0x86, 0xd8, 0xfa, 0x6d, 0x84, 0xca, 0x47, - 0xde, 0x56, 0xa8, 0x27, 0xe8, 0x91, 0xb7, 0xb6, 0x2d, 0x60, 0xec, 0x7d, 0x18, 0xbc, 0x8f, 0x57, - 0x3c, 0xe6, 0x8b, 0x07, 0x09, 0x7f, 0x2c, 0x94, 0xf2, 0xbe, 0x17, 0x99, 0x2f, 0x00, 0x4a, 0x42, - 0xd6, 0x80, 0xe1, 0x85, 0x90, 0xbb, 0x31, 0x6f, 0x51, 0x87, 0x1c, 0x29, 0x89, 0x76, 0x53, 0x92, - 0x64, 0x93, 0x68, 0x13, 0x27, 0x21, 0xc7, 0x58, 0xda, 0x46, 0xf4, 0x09, 0x8c, 0x9e, 0xd9, 0x41, - 0x7f, 0xcf, 0x18, 0xf4, 0xf3, 0xb9, 0x41, 0x97, 0xcd, 0x3b, 0xd2, 0xd8, 0xff, 0x66, 0x09, 0x4e, - 0x17, 0x13, 0xb2, 0x17, 0x61, 0xe8, 0xde, 0xfa, 0x5a, 0xea, 0x87, 0x8b, 0x4d, 0x09, 0xba, 0x68, - 0xbd, 0xb2, 0xa9, 0x88, 0xbd, 0x0e, 0x43, 0x1f, 0xd8, 0x0b, 0xa9, 0xbb, 0x29, 0x0a, 0xb8, 0x8f, - 0x85, 0x8a, 0x68, 0xec, 0x6a, 0x84, 0xa4, 0x8f, 0x6d, 0xe5, 0xa9, 0x8d, 0xed, 0x4f, 0x94, 0x61, - 0xb2, 0xde, 0x6c, 0xf2, 0x28, 0x12, 0xea, 0x1b, 0x8f, 0xe2, 0x67, 0x76, 0x60, 0x8b, 0xdf, 0xa8, - 0x30, 0xda, 0x76, 0xa4, 0x51, 0xfd, 0xc7, 0x25, 0xf9, 0xd2, 0x8f, 0xa0, 0xda, 0xf5, 0xf8, 0xa3, - 0xe4, 0x42, 0x22, 0x79, 0xd0, 0xb2, 0x74, 0xc8, 0x83, 0x96, 0x42, 0x67, 0xf4, 0xda, 0xb1, 0x7a, - 0x4f, 0x8b, 0x74, 0x46, 0x84, 0x18, 0x3a, 0x23, 0x42, 0xc4, 0x3e, 0x57, 0xef, 0x76, 0xc3, 0x60, - 0x57, 0x2e, 0x7b, 0x7a, 0xe3, 0xd3, 0x95, 0x20, 0x23, 0x6d, 0xb8, 0x04, 0x89, 0xcf, 0x58, 0xe4, - 0xfe, 0x9e, 0xee, 0xfc, 0xda, 0xe2, 0xbe, 0x7e, 0x7a, 0xc2, 0x72, 0xab, 0x01, 0x6c, 0x2d, 0x0c, - 0x3a, 0x41, 0xcc, 0x5b, 0xb2, 0x3d, 0x98, 0x6d, 0xfd, 0xd0, 0x47, 0xcb, 0xd6, 0xbd, 0xb8, 0x6d, - 0x3c, 0x5a, 0x16, 0x0b, 0x80, 0x2d, 0xe1, 0xd6, 0x4f, 0x56, 0xe0, 0xbc, 0xd1, 0xa7, 0x8b, 0xe1, - 0x9e, 0xdd, 0xf3, 0x97, 0xfc, 0xd0, 0x6b, 0xee, 0x60, 0x6e, 0xa7, 0xbb, 0x00, 0x36, 0x77, 0xa3, - 0xc0, 0xd7, 0x94, 0xb5, 0x2b, 0x42, 0xfd, 0x0a, 0x11, 0x9a, 0x37, 0x90, 0x4c, 0x11, 0xa7, 0x94, - 0xca, 0xd6, 0x38, 0xb0, 0x3a, 0x8c, 0xcb, 0x5f, 0xa2, 0x31, 0xdd, 0x44, 0x11, 0x7f, 0x4e, 0x66, - 0x5a, 0x42, 0x96, 0x5d, 0x2c, 0x31, 0x13, 0x40, 0x6a, 0x14, 0xec, 0x3b, 0x30, 0x26, 0xd6, 0xe9, - 0xa2, 0x17, 0x75, 0xdb, 0x6e, 0x62, 0xea, 0xbf, 0x5e, 0x34, 0x45, 0xb2, 0xcd, 0xb9, 0xa2, 0x13, - 0xca, 0x37, 0x31, 0x67, 0x69, 0xfa, 0x9c, 0xc1, 0xf3, 0x5c, 0x8b, 0xca, 0x74, 0x6f, 0x6d, 0x9d, - 0x66, 0xe6, 0x43, 0x98, 0xca, 0xf1, 0x28, 0x78, 0x9d, 0xef, 0x2d, 0xf3, 0x75, 0xbe, 0xd9, 0xa2, - 0x8f, 0xd3, 0xf8, 0xe8, 0xef, 0xf4, 0xfd, 0x58, 0x09, 0xa6, 0xfb, 0xe1, 0x89, 0xe9, 0xb5, 0x16, - 0x7a, 0x1d, 0x37, 0x54, 0xa2, 0x08, 0xa7, 0x57, 0x57, 0x82, 0x0c, 0x9d, 0x53, 0x82, 0xd8, 0x5b, - 0x30, 0x22, 0x4f, 0xb1, 0x6e, 0x92, 0xf2, 0x4f, 0x26, 0x4f, 0x56, 0x40, 0x23, 0x79, 0xb2, 0x02, - 0x5a, 0xff, 0xd7, 0x10, 0x8c, 0xe9, 0xcb, 0x86, 0x59, 0x30, 0x24, 0x9f, 0x25, 0xd1, 0x63, 0xd9, - 0x5c, 0x84, 0xd8, 0x54, 0x92, 0xbe, 0xca, 0x52, 0x3e, 0xf4, 0x55, 0x96, 0x0d, 0x18, 0x5f, 0x0b, - 0x03, 0x7c, 0xa5, 0x16, 0x5d, 0x7f, 0x68, 0x8f, 0x3c, 0xa1, 0x99, 0x90, 0xc4, 0x0a, 0x47, 0xe7, - 0x22, 0x3c, 0xa4, 0x77, 0x09, 0xdb, 0x11, 0xc7, 0x0b, 0xc3, 0x80, 0x6a, 0xf0, 0x91, 0x7e, 0x8b, - 0x62, 0xb6, 0xe8, 0xcf, 0xc8, 0xcb, 0x89, 0x65, 0xfa, 0x2d, 0x0a, 0x88, 0x2e, 0x84, 0x07, 0x9f, - 0x96, 0x10, 0x66, 0x3f, 0x53, 0x82, 0xd1, 0xba, 0xef, 0xd3, 0x6b, 0x2f, 0x87, 0x64, 0x71, 0xff, - 0x26, 0xb9, 0x2e, 0xbe, 0xf3, 0x89, 0x5c, 0x17, 0x51, 0xa1, 0x8d, 0xf0, 0x34, 0x94, 0x56, 0x68, - 0xe4, 0x36, 0x4e, 0xc1, 0xec, 0x1d, 0xa8, 0x25, 0xd2, 0x6f, 0xd9, 0x6f, 0xf1, 0xc7, 0x3c, 0xc2, - 0xc8, 0xb8, 0x71, 0x7a, 0xc5, 0x5f, 0x3f, 0xfd, 0x64, 0x11, 0xd9, 0x3a, 0x80, 0x9b, 0x88, 0x1d, - 0xf2, 0xa8, 0x38, 0x9b, 0xde, 0x7e, 0x67, 0xe4, 0x92, 0xba, 0xb5, 0x13, 0xbf, 0xd1, 0x3b, 0xc4, - 0xbc, 0xb5, 0x4b, 0xc4, 0x57, 0x07, 0x26, 0xeb, 0x51, 0xd4, 0xeb, 0xf0, 0x46, 0xec, 0x86, 0xb1, - 0xe8, 0x6a, 0x32, 0x85, 0x1e, 0xc9, 0x91, 0xdf, 0x45, 0x52, 0x31, 0x23, 0xc2, 0x18, 0xbd, 0x07, - 0xb2, 0x8e, 0xfc, 0x19, 0xde, 0xec, 0x5d, 0x18, 0x6d, 0xf4, 0x36, 0x3b, 0x5e, 0x1c, 0xf3, 0xd6, - 0xfc, 0x1e, 0x85, 0x7a, 0xaa, 0x24, 0x29, 0x12, 0xec, 0x6c, 0x1a, 0x86, 0x2b, 0x0d, 0x9d, 0xbd, - 0x0f, 0xe7, 0x53, 0xb4, 0xc0, 0x77, 0x36, 0xf9, 0x8e, 0xdb, 0xde, 0x72, 0x82, 0x2d, 0x47, 0xae, - 0x03, 0xe9, 0x2c, 0x61, 0x9f, 0x4d, 0x90, 0xee, 0xf9, 0xf3, 0x88, 0x72, 0x6f, 0x4b, 0x2e, 0x90, - 0xdb, 0x03, 0xd5, 0x6a, 0x6d, 0xc4, 0x3e, 0x93, 0xef, 0x2f, 0x29, 0x8d, 0x7f, 0xa2, 0x04, 0xa7, - 0xf5, 0x45, 0x87, 0x95, 0xa3, 0xf9, 0x85, 0x5d, 0x81, 0x11, 0x5a, 0x13, 0x89, 0xa1, 0x00, 0xbd, - 0xa8, 0xcd, 0x38, 0xe7, 0x04, 0x85, 0x2d, 0x89, 0x65, 0x20, 0x78, 0x90, 0x00, 0x3a, 0x91, 0x11, - 0x40, 0xa2, 0x08, 0x2f, 0x8f, 0x3f, 0x27, 0xd7, 0x87, 0xf8, 0x6d, 0xae, 0x0f, 0x01, 0xb1, 0xbe, - 0x0a, 0x53, 0xe6, 0x4c, 0x68, 0xf0, 0x98, 0x5d, 0x86, 0x61, 0x35, 0x7d, 0x4a, 0xc5, 0xd3, 0x47, - 0x95, 0x5b, 0x1b, 0xc0, 0x72, 0xf4, 0x11, 0xfa, 0x38, 0xf3, 0x58, 0xdd, 0xa3, 0x29, 0x0f, 0xa3, - 0x1c, 0xe2, 0xfc, 0x09, 0xfa, 0xbe, 0x51, 0x23, 0x0d, 0x12, 0xbe, 0xb4, 0xfc, 0xe3, 0x27, 0xe0, - 0x44, 0x81, 0x32, 0x70, 0x88, 0xb2, 0x3e, 0x6b, 0x0a, 0xa8, 0x91, 0xe4, 0x11, 0x0a, 0x25, 0x96, - 0xbe, 0x0a, 0x83, 0x87, 0x8a, 0x23, 0x99, 0x5e, 0x2b, 0x23, 0x85, 0x24, 0xd9, 0x67, 0xa2, 0xb0, - 0xeb, 0x4f, 0xe9, 0x0c, 0x3e, 0xb5, 0xa7, 0x74, 0x30, 0x13, 0xb3, 0xb6, 0x51, 0x9b, 0xd9, 0xa1, - 0xb1, 0xc0, 0xc9, 0x89, 0x4d, 0x93, 0x44, 0xf2, 0x88, 0x82, 0xf6, 0x2e, 0x27, 0x1e, 0xc3, 0x3a, - 0x0f, 0x2c, 0x28, 0xe4, 0xa1, 0x91, 0xb0, 0x5f, 0x2a, 0x01, 0x23, 0x88, 0x2e, 0x33, 0xab, 0x07, - 0xc9, 0xcc, 0xd6, 0xd3, 0x91, 0x99, 0xe7, 0xd5, 0x37, 0x16, 0xcb, 0xce, 0x82, 0xcf, 0x62, 0x7f, - 0xab, 0x04, 0x53, 0xf2, 0xb1, 0x12, 0xfd, 0x63, 0x0f, 0x7c, 0x80, 0xa2, 0xf9, 0x74, 0x3e, 0xf6, - 0x5c, 0x84, 0xd5, 0xf6, 0xf9, 0xd6, 0xfc, 0x47, 0xb1, 0x1f, 0x01, 0x48, 0x56, 0x94, 0x7c, 0x6d, - 0x7a, 0x74, 0xee, 0x5c, 0x81, 0x14, 0x48, 0x90, 0xf0, 0xce, 0x5c, 0x4c, 0x9d, 0x93, 0x89, 0x5f, - 0x8f, 0xe1, 0x6c, 0x91, 0x72, 0x63, 0x7f, 0x5a, 0x3e, 0x16, 0x9a, 0x40, 0xe8, 0xf5, 0xa9, 0xe9, - 0x51, 0xac, 0xe5, 0x0b, 0xfd, 0x95, 0xf5, 0x2b, 0x45, 0x64, 0x52, 0x0f, 0x4b, 0x22, 0x6e, 0xc2, - 0xb8, 0x93, 0x7d, 0x30, 0x34, 0x4b, 0x81, 0x8f, 0xba, 0xe1, 0xd7, 0x47, 0xd3, 0x63, 0x58, 0x67, - 0xa1, 0x7c, 0x3b, 0xab, 0xd6, 0x82, 0x94, 0x6f, 0x99, 0xec, 0x19, 0x08, 0x62, 0x1f, 0x00, 0x4b, - 0x5e, 0xf9, 0x90, 0x30, 0x1e, 0xca, 0xa7, 0xab, 0x29, 0x3c, 0x32, 0x7d, 0x2d, 0x24, 0x54, 0xc5, - 0x46, 0x9e, 0xb1, 0x1c, 0x31, 0xe3, 0x70, 0x92, 0x1a, 0x2d, 0xa0, 0x32, 0x3f, 0xd6, 0xf2, 0x62, - 0x34, 0x3d, 0x61, 0xbc, 0xed, 0x95, 0x96, 0xcc, 0x3f, 0x4f, 0xdf, 0x79, 0x3a, 0x49, 0xb4, 0x65, - 0xe6, 0xa0, 0x2a, 0x64, 0xc7, 0xae, 0xc3, 0x08, 0xa6, 0x51, 0xbd, 0xa5, 0x3c, 0xb7, 0xc9, 0x8b, - 0x14, 0x13, 0xae, 0x3a, 0x3b, 0xa6, 0xff, 0x75, 0x8a, 0x2a, 0xce, 0xa9, 0x52, 0x2d, 0xc6, 0xfb, - 0x25, 0x32, 0xc4, 0xb5, 0xc2, 0x3d, 0x27, 0xec, 0x99, 0x29, 0x7a, 0x11, 0x89, 0x7d, 0x08, 0xa3, - 0xab, 0xee, 0x63, 0x65, 0xfa, 0xa3, 0x3b, 0xa4, 0xc3, 0x42, 0x60, 0xb1, 0x35, 0x1d, 0xf7, 0xb1, - 0xd3, 0xea, 0x65, 0x5f, 0x3a, 0x90, 0x21, 0xb0, 0x1a, 0x4b, 0xf6, 0x2d, 0x00, 0xed, 0xd2, 0x8b, - 0x1d, 0x5a, 0xc1, 0x0b, 0xea, 0xc5, 0xba, 0xc2, 0xcb, 0x30, 0xe4, 0xaf, 0x31, 0xcc, 0x68, 0x2e, - 0x27, 0x3f, 0x3b, 0xcd, 0xe5, 0xd4, 0x67, 0xa8, 0xb9, 0x7c, 0x98, 0xe6, 0x67, 0x43, 0x09, 0xbe, - 0x47, 0x57, 0x4e, 0x07, 0xd5, 0xa6, 0x7c, 0x60, 0x6a, 0xb8, 0x15, 0xec, 0x65, 0xaa, 0xc8, 0xf0, - 0x63, 0x21, 0xd4, 0xb2, 0xa7, 0x25, 0x7a, 0x78, 0xe2, 0xa5, 0xa3, 0x9c, 0xac, 0xa4, 0x49, 0x9d, - 0xa6, 0x91, 0xc3, 0x13, 0xb8, 0xae, 0x54, 0xe6, 0x0e, 0x97, 0x0f, 0x60, 0x94, 0xd8, 0xa1, 0x01, - 0x62, 0xda, 0xf0, 0x38, 0x36, 0xaa, 0x13, 0xe5, 0xe4, 0xbc, 0x45, 0x9b, 0x53, 0xc6, 0x3c, 0xa1, - 0x33, 0x62, 0x1d, 0xa8, 0xad, 0x04, 0xfe, 0xf6, 0x3a, 0x0f, 0x3b, 0x98, 0x98, 0x4e, 0xc8, 0xa6, - 0xb3, 0xc6, 0x41, 0x4c, 0x15, 0x1b, 0xf9, 0xeb, 0x3c, 0x7f, 0x5b, 0x36, 0xa3, 0x1d, 0xf8, 0xdb, - 0x4e, 0xcc, 0xc3, 0x8e, 0xcc, 0x78, 0x67, 0x3a, 0xb9, 0xe6, 0x58, 0xb3, 0x3f, 0x05, 0x33, 0xb9, - 0x15, 0x2a, 0xbf, 0x5e, 0x2c, 0xfb, 0x19, 0xc3, 0x5b, 0x28, 0x5b, 0x8e, 0x91, 0xa2, 0x9f, 0x53, - 0xbb, 0x11, 0x2e, 0x7e, 0x9a, 0x7c, 0xa6, 0x0c, 0x38, 0xa0, 0x86, 0x99, 0x4d, 0x38, 0xdb, 0x57, - 0xae, 0x16, 0x9c, 0x4d, 0xaf, 0x9a, 0x67, 0xd3, 0xb3, 0xfd, 0xf4, 0xaf, 0x48, 0x3b, 0x95, 0xde, - 0x1e, 0xa8, 0x9e, 0xa8, 0x9d, 0xec, 0xaf, 0xba, 0xfe, 0x4e, 0x39, 0xa3, 0x8f, 0x91, 0x39, 0xe4, - 0x02, 0x94, 0x0f, 0x50, 0x58, 0xcb, 0xcb, 0x8b, 0xec, 0x22, 0x69, 0x6c, 0xe5, 0xd4, 0x0c, 0xd3, - 0x33, 0x2f, 0x2d, 0xa5, 0xee, 0xf6, 0x69, 0x55, 0xb3, 0x77, 0x61, 0xa2, 0xc1, 0xdd, 0xb0, 0xb9, - 0x73, 0x87, 0xef, 0x3d, 0x0a, 0xc2, 0x96, 0xca, 0x95, 0x24, 0xdf, 0xbb, 0xc3, 0x12, 0x33, 0x1f, - 0xa5, 0x8e, 0xcb, 0x16, 0x55, 0x8a, 0xd5, 0x41, 0xac, 0xfd, 0x6c, 0xe1, 0x16, 0x27, 0x10, 0x0e, - 0xca, 0xbe, 0x2a, 0x0e, 0xe3, 0x6a, 0x18, 0x55, 0x3e, 0x7a, 0x0a, 0xdd, 0x26, 0x60, 0x81, 0x32, - 0xcf, 0x43, 0xeb, 0xfb, 0x15, 0x60, 0xb2, 0xa6, 0x05, 0xb7, 0xeb, 0x62, 0x52, 0x63, 0x0f, 0x1f, - 0x88, 0xaa, 0x11, 0x8e, 0xbb, 0xd9, 0xe6, 0xfa, 0xeb, 0x6a, 0x14, 0x46, 0x95, 0x94, 0x39, 0xd9, - 0x53, 0x78, 0x8e, 0xb0, 0xcf, 0x3e, 0x58, 0xfe, 0x34, 0xfb, 0xe0, 0x87, 0xf0, 0x5c, 0xbd, 0xdb, - 0x6d, 0x7b, 0xcd, 0xa4, 0x96, 0x1b, 0x41, 0xa8, 0x66, 0xaf, 0x91, 0x03, 0xd1, 0x4d, 0xd0, 0x72, - 0x5f, 0x7a, 0x10, 0x0b, 0x4d, 0x89, 0x95, 0xb6, 0x21, 0x3d, 0xfd, 0xba, 0x92, 0x13, 0x45, 0xd6, - 0x24, 0x8d, 0x44, 0xf1, 0xf0, 0x42, 0xa5, 0xc4, 0x0e, 0xa6, 0x2f, 0x80, 0x2b, 0xaf, 0x95, 0x62, - 0x45, 0x38, 0x21, 0x11, 0xe7, 0xca, 0x7a, 0x2f, 0x0e, 0x88, 0x31, 0xc5, 0xff, 0xa5, 0x91, 0x7a, - 0xf4, 0x29, 0xc6, 0xb9, 0x3c, 0x45, 0xb7, 0x7e, 0x7b, 0x00, 0xce, 0xe6, 0x87, 0x97, 0x4a, 0x93, - 0xf5, 0x51, 0x3a, 0x64, 0x7d, 0x14, 0xcd, 0x06, 0x79, 0x5b, 0xfa, 0xd4, 0x66, 0x83, 0x4c, 0xa8, - 0xfc, 0x09, 0x67, 0x43, 0x43, 0xc8, 0xfa, 0x54, 0x19, 0x1a, 0xf8, 0xa4, 0xca, 0x90, 0xce, 0x85, - 0x5d, 0x86, 0x41, 0x99, 0x75, 0x7e, 0x30, 0xbd, 0x3b, 0xcf, 0x26, 0x9c, 0x97, 0x18, 0xec, 0x4f, - 0xc1, 0x05, 0x29, 0x93, 0xb2, 0x8d, 0x9d, 0xdf, 0x53, 0x1c, 0x69, 0xe0, 0xe6, 0x9e, 0xec, 0xcf, - 0x5e, 0x91, 0x06, 0x5e, 0x27, 0xd7, 0x6d, 0xce, 0xe6, 0x9e, 0xa3, 0xbe, 0x4c, 0xab, 0xe4, 0x50, - 0xde, 0xcc, 0x87, 0x13, 0x05, 0x12, 0x9e, 0x52, 0x13, 0x7d, 0xda, 0xdd, 0x61, 0x2a, 0x34, 0x09, - 0x5b, 0x91, 0xf5, 0xa3, 0x30, 0x2d, 0xdf, 0x7b, 0x2d, 0x90, 0x1c, 0x87, 0xac, 0xcc, 0xd2, 0xa7, - 0x5e, 0x99, 0x62, 0x3f, 0x98, 0xed, 0x57, 0xfd, 0x71, 0x67, 0xf6, 0x6d, 0x18, 0x97, 0xd2, 0xb8, - 0x1e, 0xe9, 0xa7, 0x77, 0x74, 0x49, 0x90, 0x4f, 0xdb, 0x3a, 0x52, 0x7e, 0x3b, 0x6e, 0x94, 0xfb, - 0x4a, 0x93, 0x34, 0x3b, 0x0b, 0x2b, 0x4f, 0x65, 0x16, 0xf6, 0x19, 0xda, 0x81, 0xcf, 0x6a, 0x68, - 0x1f, 0xc3, 0x59, 0xa5, 0xed, 0x24, 0x99, 0xa7, 0x93, 0x79, 0x76, 0x11, 0xf0, 0x7a, 0x47, 0xef, - 0xd5, 0x8c, 0xaa, 0x84, 0xe5, 0xec, 0x1a, 0x54, 0xeb, 0x6b, 0xcb, 0xa8, 0xc3, 0xe8, 0x76, 0x61, - 0xb7, 0xeb, 0x49, 0xa5, 0xc7, 0xc8, 0x0f, 0x46, 0x88, 0xd6, 0x5f, 0x2c, 0xc3, 0xd8, 0x5a, 0xbb, - 0xb7, 0xed, 0xf9, 0x8b, 0x6e, 0xec, 0x3e, 0xb3, 0x97, 0x4b, 0x5f, 0x36, 0x2e, 0x97, 0x92, 0x24, - 0x10, 0x49, 0xc3, 0x8e, 0x74, 0xb3, 0xf4, 0xd3, 0x25, 0x98, 0x4c, 0x49, 0xa4, 0xb2, 0x75, 0x0b, - 0x06, 0xc4, 0x0f, 0xb2, 0x6c, 0x5d, 0xc8, 0x31, 0x46, 0xac, 0x2b, 0xc9, 0x5f, 0x74, 0xdd, 0xe3, - 0x9a, 0x7e, 0x77, 0xa2, 0x78, 0xe6, 0x8b, 0x30, 0x92, 0xb2, 0xcd, 0xeb, 0x70, 0x27, 0x75, 0x1d, - 0x6e, 0x44, 0xbf, 0x3e, 0xf8, 0xbb, 0x25, 0xa8, 0x65, 0x5b, 0xc2, 0xee, 0xc0, 0xb0, 0xe0, 0xe4, - 0xf1, 0x28, 0x9b, 0xd1, 0x31, 0x83, 0x79, 0x85, 0xd0, 0xe4, 0xe7, 0x61, 0xe7, 0x73, 0x09, 0xb1, - 0x15, 0x87, 0x19, 0x1b, 0x5f, 0xa5, 0x49, 0xb0, 0x0a, 0xbe, 0xee, 0x35, 0x53, 0xc3, 0x3c, 0x5d, - 0xdc, 0x0f, 0xfa, 0x57, 0xff, 0x7b, 0xc6, 0x57, 0x93, 0xf2, 0x78, 0xd1, 0x98, 0x5c, 0x85, 0x53, - 0x19, 0x27, 0xcd, 0x1c, 0x54, 0x13, 0x11, 0x5e, 0xd6, 0x1f, 0x6e, 0xc9, 0x89, 0xe9, 0x04, 0x8f, - 0xbd, 0x06, 0x43, 0xb2, 0x3e, 0x3d, 0x53, 0x75, 0x17, 0x21, 0xfa, 0x21, 0x58, 0xe2, 0x58, 0xbf, - 0x50, 0x81, 0xd3, 0xe9, 0xe7, 0xdd, 0xef, 0xb6, 0xdc, 0x98, 0xaf, 0xb9, 0xa1, 0xdb, 0x89, 0x0e, - 0x59, 0x01, 0x97, 0x72, 0x9f, 0x46, 0xa9, 0xa6, 0x24, 0x4c, 0xfb, 0x20, 0x2b, 0xf3, 0x41, 0x78, - 0xc1, 0x22, 0x3f, 0x48, 0x7d, 0x06, 0xbb, 0x03, 0x95, 0x06, 0x8f, 0x49, 0xb0, 0x5c, 0xcc, 0xf5, - 0xaa, 0xfe, 0x5d, 0x57, 0x1a, 0x3c, 0x96, 0x83, 0x28, 0x9f, 0xc4, 0x31, 0x92, 0x3a, 0x09, 0x2e, - 0x6c, 0x03, 0x86, 0x96, 0x1e, 0x77, 0x79, 0x33, 0xc6, 0x47, 0xf4, 0xb5, 0xec, 0x46, 0xc5, 0xfc, - 0x24, 0xae, 0x64, 0x79, 0x92, 0x4e, 0x95, 0xbc, 0x69, 0xa4, 0xf5, 0x96, 0x28, 0x33, 0xd7, 0xa1, - 0xaa, 0x2a, 0x3f, 0xce, 0xcc, 0x9d, 0xf9, 0x32, 0x8c, 0x6a, 0x95, 0x1c, 0x6b, 0xd2, 0xff, 0x62, - 0x09, 0x40, 0x08, 0x78, 0x9a, 0x38, 0x4b, 0x39, 0x5d, 0xbf, 0x94, 0x66, 0x33, 0xa7, 0xbd, 0xe2, - 0x21, 0x15, 0x1d, 0xa0, 0xf4, 0x2f, 0xc3, 0x64, 0xe3, 0xa1, 0xd7, 0x4d, 0x5f, 0x27, 0x36, 0x34, - 0xaa, 0xe8, 0xa1, 0xd7, 0x75, 0xc8, 0x2a, 0x97, 0xdd, 0x75, 0xb2, 0x74, 0xd6, 0xff, 0x53, 0x82, - 0x21, 0xf1, 0xd7, 0x83, 0xeb, 0xcf, 0xa8, 0xc8, 0xbc, 0x66, 0x88, 0xcc, 0x64, 0xc7, 0x0c, 0xda, - 0x32, 0x46, 0xe3, 0xfa, 0x21, 0xc2, 0x72, 0x9f, 0x06, 0x48, 0x22, 0xb3, 0x9b, 0x30, 0x4c, 0xae, - 0xc7, 0x14, 0xa5, 0xc9, 0x34, 0x86, 0xca, 0x29, 0x39, 0x31, 0xdf, 0x05, 0xdd, 0xac, 0xbd, 0x53, - 0x51, 0x8b, 0x73, 0x99, 0x7a, 0x27, 0x5a, 0xda, 0x60, 0x53, 0x36, 0x69, 0xb6, 0xbb, 0xf9, 0x33, - 0xc4, 0xa9, 0x5f, 0xae, 0xe8, 0x3a, 0xdd, 0xc1, 0x57, 0x0e, 0x62, 0x72, 0x9a, 0x98, 0x14, 0x5f, - 0xcf, 0x77, 0xe0, 0x74, 0xa3, 0x71, 0x0b, 0xc3, 0x14, 0xd6, 0x82, 0x30, 0xbe, 0x11, 0x84, 0x8f, - 0x28, 0x47, 0x5d, 0xc3, 0xf4, 0x7c, 0x2b, 0x72, 0xe9, 0x7d, 0xa5, 0xd0, 0xa5, 0xf7, 0x00, 0xef, - 0x38, 0xcb, 0x87, 0x33, 0x8d, 0xc6, 0x2d, 0xa9, 0x61, 0xfd, 0x61, 0xd4, 0xf7, 0x77, 0x4b, 0x30, - 0xd5, 0x68, 0xdc, 0xca, 0x54, 0xb5, 0x22, 0xd4, 0xef, 0xa6, 0xdb, 0xa6, 0x8a, 0x94, 0xfb, 0x4d, - 0x71, 0x47, 0xe0, 0x28, 0x94, 0xa4, 0x86, 0xde, 0x34, 0x5e, 0x13, 0x94, 0x4c, 0xd8, 0x1a, 0x0c, - 0xc9, 0x06, 0xd1, 0x60, 0x3e, 0x9f, 0xb2, 0x2b, 0x6a, 0x68, 0x1a, 0xfb, 0x28, 0x95, 0x40, 0xf3, - 0xfa, 0x4a, 0x40, 0xac, 0xdf, 0x3a, 0x03, 0xa3, 0xda, 0x8c, 0x62, 0x5f, 0x81, 0x31, 0xa2, 0xc7, - 0xe8, 0x56, 0xf2, 0x44, 0x3c, 0x2b, 0x04, 0xe4, 0x96, 0x84, 0xcb, 0x27, 0xa1, 0x7f, 0xb0, 0x3f, - 0x3b, 0x20, 0xba, 0xc6, 0x36, 0xd0, 0xd9, 0x3d, 0x18, 0x5f, 0x75, 0x1f, 0x6b, 0xb6, 0x4a, 0x99, - 0xbc, 0xe0, 0xb2, 0x90, 0x2a, 0x1d, 0xf7, 0xf1, 0x11, 0x9c, 0xf3, 0x4d, 0x7a, 0xf6, 0x10, 0x26, - 0xcc, 0x36, 0xd1, 0x0c, 0xcc, 0x8f, 0xd8, 0x9b, 0x85, 0x23, 0x76, 0xb6, 0x1b, 0x84, 0xb1, 0xb3, - 0x95, 0x90, 0xa7, 0xb5, 0x4e, 0x97, 0xec, 0x0c, 0x6b, 0xf6, 0x15, 0x98, 0xd2, 0xde, 0x6c, 0xbc, - 0x11, 0x84, 0x1d, 0x57, 0x1d, 0x98, 0xf1, 0x02, 0x0f, 0x9d, 0x61, 0xb7, 0x10, 0x6c, 0xe7, 0x31, - 0xd9, 0x37, 0x8a, 0x12, 0x42, 0x0c, 0xa6, 0x11, 0x0a, 0x05, 0x09, 0x21, 0xfa, 0x45, 0x28, 0xe4, - 0x53, 0x43, 0x6c, 0x1f, 0x14, 0xc1, 0x54, 0x95, 0xad, 0x3f, 0x52, 0x84, 0x52, 0x32, 0x72, 0x7d, - 0x22, 0x95, 0xe6, 0xa0, 0x32, 0xbf, 0x76, 0x03, 0x4f, 0x5d, 0xca, 0x0b, 0xd8, 0xdf, 0x71, 0xfd, - 0x26, 0x1e, 0x64, 0x29, 0xfc, 0x4f, 0xdf, 0x28, 0xe7, 0xd7, 0x6e, 0x30, 0x17, 0x4e, 0xac, 0xf1, - 0xb0, 0xe3, 0xc5, 0x5f, 0x7b, 0xf3, 0x4d, 0x6d, 0xa8, 0xaa, 0xf8, 0x69, 0x57, 0xe9, 0xd3, 0x66, - 0xbb, 0x88, 0xe2, 0x3c, 0x7e, 0xf3, 0xcd, 0xc2, 0x01, 0x49, 0x3e, 0xac, 0x88, 0x97, 0xd8, 0xb0, - 0x56, 0xdd, 0xc7, 0x69, 0xb4, 0x75, 0x44, 0xa9, 0x75, 0xce, 0xab, 0xa9, 0x95, 0x46, 0x6a, 0x1b, - 0x1b, 0x96, 0x49, 0xc4, 0xde, 0x45, 0x5b, 0xbd, 0x4a, 0xa5, 0x44, 0x49, 0x09, 0x66, 0x94, 0x2d, - 0x5e, 0xe5, 0x5f, 0xd2, 0x8f, 0x31, 0x1a, 0x3a, 0xbb, 0x9f, 0x58, 0x53, 0xe4, 0x51, 0x83, 0xee, - 0xc7, 0xaf, 0xea, 0xd6, 0x14, 0x79, 0x44, 0x31, 0x9a, 0x35, 0x99, 0x98, 0xe0, 0x64, 0xf8, 0xb9, - 0x6d, 0x72, 0xc9, 0x1b, 0x69, 0xc6, 0x8e, 0x6f, 0xa4, 0xe1, 0x30, 0xb0, 0x12, 0x34, 0x1f, 0xd2, - 0xbb, 0x64, 0x1f, 0x08, 0x29, 0xdc, 0x0e, 0x9a, 0x0f, 0x9f, 0x5e, 0x64, 0x16, 0xb2, 0x67, 0x77, - 0x65, 0x46, 0xc2, 0xb0, 0x45, 0x7d, 0x42, 0xd1, 0x3e, 0x27, 0x93, 0x23, 0x9c, 0x56, 0x96, 0xe6, - 0x29, 0x0c, 0x5b, 0xaa, 0x6b, 0x6d, 0x93, 0x9c, 0x71, 0xa8, 0x2d, 0xf2, 0xe8, 0x61, 0x1c, 0x74, - 0x17, 0xda, 0x5e, 0x17, 0x93, 0x7c, 0xd2, 0xfb, 0xe1, 0x47, 0x96, 0xc9, 0x2d, 0x49, 0xef, 0x34, - 0x15, 0x03, 0x3b, 0xc7, 0x92, 0x7d, 0x03, 0x26, 0xc4, 0xe4, 0x5e, 0x7a, 0x1c, 0x73, 0x5f, 0x8e, - 0xfc, 0x14, 0x6a, 0x74, 0xea, 0xbb, 0x8d, 0x42, 0x39, 0xa7, 0x70, 0xb1, 0xf3, 0x84, 0xc0, 0x78, - 0xd3, 0xcd, 0x60, 0xc5, 0x5a, 0x30, 0xbd, 0xea, 0x3e, 0x4e, 0x0f, 0x9a, 0xfa, 0x24, 0x65, 0x38, - 0xc1, 0x2e, 0x3d, 0xd9, 0x9f, 0x7d, 0x49, 0x4c, 0xb0, 0xf4, 0x49, 0xfb, 0x3e, 0xf3, 0xb5, 0x2f, - 0x27, 0xf6, 0x1d, 0x38, 0x43, 0xcd, 0x5a, 0xf4, 0x42, 0x0c, 0x87, 0xdc, 0x6b, 0xec, 0xb8, 0x98, - 0x69, 0xe1, 0x44, 0x9f, 0x0e, 0xbb, 0x5a, 0x2c, 0x12, 0x55, 0x87, 0xb5, 0x14, 0x1f, 0x27, 0x92, - 0x8c, 0xec, 0x7e, 0x35, 0xb0, 0x8f, 0x60, 0x42, 0xde, 0xb5, 0xdf, 0x0a, 0xa4, 0xb7, 0x15, 0xdd, - 0x12, 0x1d, 0x39, 0x7c, 0x51, 0x5e, 0xe0, 0xcb, 0x80, 0xe6, 0x8c, 0x09, 0x23, 0xc3, 0x99, 0xbd, - 0x83, 0x8e, 0xf7, 0xf2, 0xd5, 0xc5, 0xe5, 0x35, 0xbc, 0x33, 0xa2, 0x1d, 0xa8, 0xeb, 0xf9, 0x8e, - 0xb2, 0x35, 0x74, 0x13, 0x71, 0xa1, 0x63, 0xb3, 0x0d, 0x18, 0x6d, 0x34, 0x6e, 0xdd, 0xf0, 0x84, - 0x5e, 0xd2, 0x55, 0x57, 0x40, 0xf9, 0xaf, 0x7c, 0xb1, 0xf0, 0x2b, 0xc7, 0xa3, 0x68, 0xc7, 0xd9, - 0xf2, 0xda, 0xdc, 0x69, 0x06, 0xdd, 0x3d, 0x5b, 0xe7, 0x54, 0x10, 0xd2, 0x77, 0xe6, 0x29, 0x87, - 0xf4, 0x2d, 0xc3, 0xa4, 0x16, 0xfd, 0x81, 0xce, 0x84, 0xd3, 0xe9, 0xfb, 0x2b, 0x7a, 0x08, 0x5f, - 0x36, 0x89, 0x4c, 0x96, 0x4e, 0xc5, 0xf2, 0x9d, 0x3d, 0x6e, 0x2c, 0x9f, 0x07, 0x53, 0x72, 0x30, - 0x68, 0x1e, 0xe0, 0x48, 0xcf, 0xf4, 0xe9, 0xc3, 0xcb, 0x85, 0x7d, 0x78, 0x82, 0x46, 0x5a, 0x4d, - 0x32, 0xf4, 0x2d, 0xc9, 0x73, 0x65, 0x5b, 0xc0, 0x08, 0xe8, 0xc6, 0xee, 0xa6, 0x1b, 0x71, 0xac, - 0xeb, 0xb9, 0x3e, 0x75, 0xbd, 0x54, 0x58, 0xd7, 0x84, 0xaa, 0x6b, 0x53, 0x56, 0x53, 0xc0, 0x91, - 0xf9, 0xaa, 0x1e, 0x35, 0xbf, 0xb0, 0x63, 0xcf, 0x19, 0x77, 0x14, 0x79, 0x04, 0x99, 0x45, 0x20, - 0x3b, 0x69, 0xb3, 0xfd, 0x5e, 0xc0, 0x99, 0x3d, 0x86, 0xd3, 0xf9, 0xaf, 0xc0, 0x3a, 0xcf, 0x63, - 0x9d, 0xe7, 0x8d, 0x3a, 0xb3, 0x48, 0x72, 0xde, 0x98, 0xcd, 0xca, 0xd6, 0xda, 0x87, 0x3f, 0xfb, - 0x6e, 0x09, 0xce, 0xac, 0xde, 0xa8, 0x3f, 0xe0, 0xa1, 0x54, 0x4b, 0x30, 0x41, 0x37, 0x25, 0xdf, - 0x79, 0x9e, 0xee, 0xb1, 0xb2, 0x57, 0xa1, 0x4a, 0xe3, 0x40, 0x51, 0x21, 0x54, 0xf7, 0x17, 0x3b, - 0x5b, 0xae, 0xb3, 0xab, 0xb1, 0x28, 0xc8, 0xd0, 0xf3, 0x73, 0xbf, 0x3b, 0x5b, 0xb2, 0xfb, 0x55, - 0xc5, 0xda, 0x30, 0x63, 0x76, 0x8b, 0x8a, 0xb6, 0xdc, 0xe1, 0xed, 0xf6, 0xf4, 0x2c, 0xce, 0xe8, - 0xd7, 0x9e, 0xec, 0xcf, 0x5e, 0xca, 0xf5, 0x6e, 0x12, 0xc1, 0x29, 0x30, 0xf5, 0x8b, 0xbd, 0xfe, - 0xfc, 0x58, 0xa7, 0x40, 0xe9, 0x9e, 0xbe, 0x60, 0x64, 0xe9, 0xcc, 0x95, 0x27, 0xa9, 0x67, 0xcf, - 0x8b, 0xf5, 0xde, 0x57, 0x41, 0xb4, 0x0b, 0xd4, 0xf9, 0x3f, 0x57, 0x82, 0xe9, 0x0d, 0xbe, 0xb9, - 0x2e, 0x14, 0x14, 0xdf, 0x6d, 0x27, 0x1b, 0x0c, 0x0e, 0xf0, 0x0b, 0x38, 0xc0, 0xb3, 0x69, 0x88, - 0x72, 0x21, 0x9a, 0x7c, 0x34, 0xe6, 0x11, 0xdf, 0xc4, 0xeb, 0x53, 0x51, 0x9c, 0xee, 0x5c, 0xd9, - 0xa1, 0xee, 0x5b, 0xd7, 0xed, 0x81, 0xea, 0x78, 0x6d, 0xa2, 0x20, 0x04, 0xd1, 0xfa, 0x8d, 0x72, - 0x66, 0x87, 0x66, 0xcb, 0x30, 0x4c, 0x0b, 0xaf, 0xef, 0x69, 0xe7, 0x7c, 0xe1, 0xf2, 0x1a, 0xa6, - 0x35, 0x6c, 0x2b, 0x7a, 0xf6, 0x48, 0xb0, 0xc2, 0xde, 0xa7, 0xa3, 0xf7, 0xb7, 0xe4, 0x06, 0x8c, - 0x20, 0x43, 0xd5, 0x58, 0x3c, 0x7e, 0xee, 0x00, 0x33, 0xc3, 0x04, 0xea, 0x1c, 0xaa, 0x36, 0xf6, - 0x10, 0x2a, 0x8d, 0xc6, 0x2d, 0x3a, 0xbd, 0x7f, 0x9d, 0x44, 0xf5, 0x67, 0x50, 0xa1, 0xa8, 0xc5, - 0xfa, 0xf5, 0x12, 0x8c, 0x1b, 0x5b, 0x3c, 0xbb, 0xae, 0x65, 0x57, 0x48, 0xaf, 0xdf, 0x0d, 0x1c, - 0x94, 0xfa, 0xd9, 0xbc, 0x0b, 0xd7, 0xb5, 0xec, 0xda, 0x7d, 0xe8, 0x70, 0x4a, 0x64, 0x53, 0x89, - 0x28, 0x9f, 0xf5, 0x4a, 0x3f, 0x9f, 0x75, 0x99, 0x29, 0x71, 0xa0, 0xf8, 0x75, 0x04, 0xeb, 0xe7, - 0x5f, 0x86, 0x09, 0xf3, 0x68, 0xce, 0x5e, 0x83, 0x21, 0xbc, 0xe4, 0x51, 0x76, 0x1e, 0xb4, 0x4f, - 0xe1, 0x3d, 0x90, 0x11, 0xd6, 0x29, 0x71, 0xd8, 0xcb, 0x00, 0x49, 0xfc, 0x93, 0xba, 0x4c, 0x18, - 0x7c, 0xb2, 0x3f, 0x5b, 0x7a, 0xdd, 0xd6, 0x0a, 0xd8, 0xb7, 0x01, 0xee, 0x06, 0x2d, 0x8e, 0xbe, - 0x5c, 0x51, 0x62, 0x1a, 0x28, 0xf4, 0xf1, 0x7a, 0x85, 0x7c, 0xbc, 0x86, 0x24, 0xf2, 0x93, 0xfd, - 0xd9, 0x53, 0x7e, 0xd0, 0xe2, 0x0e, 0xa6, 0xcd, 0x31, 0x9c, 0xa8, 0x52, 0x8e, 0xec, 0x6d, 0x18, - 0xb4, 0x7b, 0x6d, 0xae, 0xee, 0x09, 0x46, 0xd5, 0x56, 0xdb, 0x6b, 0xf3, 0xd4, 0x60, 0x11, 0xf6, - 0xb2, 0xee, 0xd3, 0x02, 0xc0, 0xde, 0x03, 0x10, 0x7a, 0x14, 0x3d, 0xd5, 0x37, 0x98, 0x5e, 0xfa, - 0x6a, 0x2a, 0x58, 0xee, 0xb1, 0x3e, 0x8d, 0x84, 0xdd, 0x83, 0x61, 0xfd, 0xb6, 0x52, 0x4b, 0x93, - 0xa5, 0xdf, 0x68, 0x6b, 0xd6, 0x0f, 0x7a, 0x85, 0x28, 0x7b, 0x91, 0xa9, 0xb8, 0xb0, 0x77, 0x61, - 0x44, 0xb0, 0x17, 0x22, 0x2c, 0xa2, 0xe3, 0x15, 0x5e, 0x20, 0x69, 0x1f, 0x24, 0xc4, 0xa0, 0xf1, - 0x18, 0x51, 0x42, 0xc0, 0xbe, 0x01, 0x23, 0xf5, 0x6e, 0x97, 0xba, 0xfa, 0x40, 0xdf, 0xbf, 0x8b, - 0xb9, 0xae, 0x3e, 0xe9, 0x76, 0xbb, 0xf9, 0x9e, 0x4e, 0xf9, 0xb1, 0xed, 0x24, 0x4b, 0x33, 0x55, - 0x70, 0xa0, 0xbf, 0xde, 0xab, 0xb9, 0x0a, 0xa6, 0x55, 0xe2, 0xe1, 0x5c, 0x25, 0x26, 0x5f, 0xd6, - 0x85, 0x5a, 0xaa, 0xdd, 0x52, 0x5d, 0x70, 0x50, 0x5d, 0xaf, 0xe7, 0xea, 0xd2, 0x07, 0x30, 0x57, - 0x5d, 0x8e, 0x3b, 0x6b, 0xc1, 0x84, 0xda, 0x29, 0xa9, 0xbe, 0xd1, 0x83, 0xea, 0x7b, 0x39, 0x57, - 0xdf, 0x89, 0xd6, 0x66, 0xbe, 0x9e, 0x0c, 0x4f, 0xf6, 0x2e, 0x8c, 0x2b, 0x08, 0xae, 0x0f, 0xf4, - 0xb9, 0x23, 0xcb, 0x64, 0x6b, 0x53, 0xbe, 0x90, 0x69, 0xbc, 0xad, 0xa3, 0x23, 0xeb, 0xd4, 0x72, - 0x76, 0x8c, 0x1b, 0xd4, 0xd9, 0x59, 0x61, 0x22, 0xb3, 0xaf, 0xc3, 0xe8, 0x72, 0x47, 0x34, 0x24, - 0xf0, 0xdd, 0x98, 0x53, 0x02, 0x07, 0xe5, 0xc7, 0xa8, 0x95, 0x68, 0x53, 0x15, 0x3d, 0xb8, 0xbc, - 0xb4, 0x48, 0x3f, 0xef, 0x6a, 0x14, 0xa2, 0xf3, 0xe4, 0xf5, 0x34, 0xcd, 0x61, 0x95, 0xdc, 0xe1, - 0x7c, 0x81, 0x2f, 0xa1, 0xc6, 0x9e, 0x1e, 0x5a, 0x13, 0x50, 0x75, 0x3d, 0x9c, 0x79, 0x12, 0x53, - 0xe7, 0xc9, 0xbe, 0x02, 0xa3, 0xf5, 0x8d, 0x86, 0x10, 0x58, 0x75, 0xfb, 0x6e, 0x34, 0x5d, 0x4b, - 0xc3, 0x5d, 0xdc, 0x47, 0xf2, 0xba, 0xd2, 0x71, 0xc3, 0x8c, 0xd3, 0x7e, 0x8a, 0xcf, 0xbe, 0x06, - 0x27, 0x37, 0x3c, 0xbf, 0x15, 0x3c, 0x8a, 0x68, 0x9b, 0x22, 0x41, 0x37, 0x95, 0xde, 0x81, 0x3e, - 0x92, 0xe5, 0x89, 0x52, 0x9a, 0x13, 0x7c, 0x85, 0x1c, 0xd8, 0x9f, 0xcc, 0x71, 0x96, 0x33, 0x88, - 0x1d, 0x34, 0x83, 0xe6, 0x72, 0x33, 0x28, 0x5f, 0x7d, 0x76, 0x3a, 0x15, 0x56, 0xc3, 0x02, 0x60, - 0xe6, 0xfe, 0x7e, 0x3b, 0xf0, 0xfc, 0xe9, 0x13, 0x28, 0x0b, 0x9f, 0xcb, 0xa6, 0x79, 0x42, 0xbc, - 0xb5, 0xa0, 0xed, 0x35, 0xf7, 0xe4, 0xf3, 0xaf, 0xd9, 0xc3, 0xc7, 0x47, 0x81, 0x71, 0x6f, 0x53, - 0xc0, 0x9a, 0x7d, 0x1d, 0xc6, 0xc4, 0xff, 0x89, 0x75, 0xe4, 0xa4, 0xe1, 0x7d, 0xae, 0x61, 0x52, - 0x3d, 0x38, 0x46, 0x82, 0x6f, 0x91, 0xe1, 0xc4, 0x60, 0xc5, 0xbe, 0x0c, 0x20, 0xf4, 0x37, 0x12, - 0xc7, 0xa7, 0xd2, 0x37, 0x45, 0x51, 0xfd, 0xcb, 0x0b, 0xe2, 0x14, 0x99, 0xbd, 0x0b, 0xa3, 0xe2, - 0x57, 0xa3, 0xd7, 0x0a, 0xc4, 0xda, 0x38, 0x8d, 0xb4, 0x32, 0x97, 0x86, 0xa0, 0x8d, 0x24, 0xdc, - 0xc8, 0xa5, 0x91, 0xa2, 0xb3, 0x5b, 0x30, 0x89, 0x2f, 0xc5, 0xd2, 0xab, 0x83, 0x1e, 0x8f, 0xa6, - 0xcf, 0x68, 0x97, 0xf7, 0xa2, 0xc8, 0xf1, 0x92, 0x32, 0xfd, 0x50, 0x95, 0x21, 0x63, 0x11, 0x9c, - 0xc8, 0x5f, 0x26, 0x47, 0xd3, 0xd3, 0xd8, 0x49, 0xea, 0x28, 0x91, 0xc7, 0x90, 0xf2, 0x58, 0x8c, - 0x88, 0x26, 0xb8, 0xd4, 0xed, 0x96, 0x5e, 0x61, 0x11, 0x77, 0x66, 0x03, 0xbb, 0xb9, 0xb0, 0x96, - 0x7d, 0x1c, 0xf5, 0x2c, 0xb6, 0x00, 0x87, 0x79, 0xbb, 0xd9, 0x75, 0x0e, 0x78, 0x20, 0xb5, 0x80, - 0x9a, 0xfd, 0x09, 0x38, 0xa5, 0x24, 0x08, 0x15, 0xd1, 0xbc, 0x9e, 0x39, 0xa6, 0x24, 0x6e, 0x6d, - 0x26, 0x55, 0xe7, 0xa6, 0x74, 0x71, 0x15, 0xcc, 0x85, 0x51, 0x1c, 0x56, 0xaa, 0xf1, 0xb9, 0x83, - 0x6a, 0xbc, 0x94, 0xab, 0xf1, 0x34, 0x4e, 0x94, 0x7c, 0x65, 0x3a, 0x4f, 0xf9, 0xda, 0x19, 0xae, - 0x23, 0x9a, 0x6d, 0xe7, 0xb0, 0xb7, 0xe8, 0xb5, 0x33, 0xb9, 0x02, 0x73, 0x13, 0xce, 0x24, 0xd1, - 0x25, 0xb2, 0xbc, 0xd5, 0x3a, 0x6f, 0x48, 0xe4, 0x9c, 0x0b, 0x85, 0x81, 0x2c, 0x24, 0x52, 0xaa, - 0xc5, 0x68, 0x8f, 0x08, 0x3d, 0x8f, 0xfa, 0x1a, 0x4a, 0x24, 0x4d, 0xf9, 0x71, 0x0a, 0x1f, 0x14, - 0x2a, 0xe4, 0xc0, 0xee, 0xc3, 0x89, 0x64, 0xd7, 0xd6, 0x18, 0xcf, 0xa6, 0xcf, 0x6f, 0xa6, 0x5b, - 0x7d, 0x31, 0xdf, 0x22, 0x7a, 0xe6, 0xc2, 0x19, 0x63, 0x9f, 0xd6, 0x58, 0x5f, 0x48, 0xdf, 0x7d, - 0x36, 0x37, 0xf9, 0x62, 0xf6, 0xfd, 0xf8, 0xb0, 0x8f, 0x60, 0x26, 0xbb, 0x37, 0x6b, 0xb5, 0xbc, - 0x80, 0xb5, 0xbc, 0xfa, 0x64, 0x7f, 0xf6, 0x62, 0x6e, 0x7b, 0x2f, 0xae, 0xe8, 0x00, 0x6e, 0xec, - 0xdb, 0x30, 0x6d, 0xee, 0xcf, 0x5a, 0x4d, 0x56, 0xfa, 0x40, 0x76, 0xb2, 0xb1, 0x17, 0xd7, 0xd0, - 0x97, 0x07, 0x8b, 0x61, 0xb6, 0x70, 0x76, 0x6b, 0xd5, 0xbc, 0x98, 0x36, 0x28, 0xb7, 0x4a, 0x8a, - 0xab, 0x3b, 0x8c, 0x25, 0x7b, 0x04, 0xcf, 0x17, 0x6d, 0x13, 0x5a, 0xa5, 0x2f, 0x25, 0xd6, 0xe8, - 0xcf, 0x17, 0x6f, 0x39, 0xc5, 0x35, 0x1f, 0xc2, 0x96, 0x7d, 0x03, 0x4e, 0x69, 0xeb, 0x4b, 0xab, - 0xef, 0x65, 0xac, 0x0f, 0x93, 0xaa, 0xe8, 0x0b, 0xb3, 0xb8, 0x96, 0x62, 0x1e, 0xac, 0x03, 0x27, - 0x54, 0xc3, 0xd1, 0xec, 0x4f, 0x5b, 0xcf, 0x45, 0x43, 0xaa, 0xe6, 0x31, 0xe6, 0x2f, 0x90, 0x54, - 0x9d, 0x6e, 0x6d, 0x3a, 0xdd, 0x94, 0x50, 0x9f, 0xe9, 0x05, 0x7c, 0xd9, 0x2d, 0x18, 0x6a, 0xac, - 0x2d, 0xdf, 0xb8, 0xb1, 0x34, 0xfd, 0x0a, 0xd6, 0xa0, 0xc2, 0xa6, 0x25, 0xd0, 0x38, 0x34, 0x91, - 0xdf, 0x6b, 0xd7, 0xdb, 0xda, 0x32, 0x6e, 0xce, 0x24, 0x2a, 0xfb, 0x93, 0xe8, 0x71, 0x2a, 0x24, - 0x6a, 0x3d, 0x8a, 0xbc, 0x6d, 0x5f, 0x3e, 0x6c, 0xfa, 0xaa, 0xe1, 0x78, 0xa0, 0x9e, 0xba, 0x5d, - 0xe0, 0x7e, 0xcc, 0xc3, 0x1c, 0x7a, 0xea, 0xe0, 0xa4, 0x9e, 0x52, 0x77, 0x53, 0x56, 0xba, 0x10, - 0xcf, 0x57, 0x24, 0xfa, 0x6d, 0xdb, 0x8b, 0x9d, 0x9d, 0x9e, 0xd1, 0xfc, 0xe9, 0xcf, 0x1b, 0x1e, - 0x55, 0x37, 0xbd, 0xf8, 0x56, 0x6f, 0x53, 0xeb, 0xb5, 0x97, 0xa8, 0xc2, 0x73, 0xf2, 0xb4, 0xdc, - 0xa7, 0xe7, 0xa6, 0xb6, 0x33, 0x74, 0x11, 0xfb, 0xb3, 0x25, 0x38, 0xbd, 0x11, 0x84, 0x0f, 0xdb, - 0x81, 0xdb, 0x52, 0xad, 0x22, 0x19, 0xfe, 0xda, 0x41, 0x32, 0xfc, 0x0b, 0x39, 0x19, 0x6e, 0x3d, - 0x22, 0x36, 0x4e, 0xf2, 0x52, 0x70, 0x4e, 0x9e, 0xf7, 0xa9, 0x8a, 0xfd, 0x49, 0xb8, 0x50, 0x5c, - 0xa2, 0x4d, 0xca, 0xd7, 0x71, 0x52, 0xbe, 0xf9, 0x64, 0x7f, 0xf6, 0xf5, 0x7e, 0x35, 0x15, 0x4f, - 0xd0, 0x43, 0x59, 0xb3, 0xb7, 0xa1, 0xb2, 0xba, 0xb0, 0x36, 0x7d, 0xc5, 0xb8, 0x03, 0x5f, 0x5d, - 0x58, 0xd3, 0x3a, 0x4a, 0x9a, 0x56, 0x3b, 0x4d, 0xc3, 0xb4, 0xba, 0xba, 0xb0, 0x26, 0x36, 0xf2, - 0x15, 0xcf, 0xef, 0x3d, 0x36, 0x75, 0xd4, 0xab, 0xe9, 0x46, 0xde, 0x16, 0xa5, 0xfd, 0x35, 0xd4, - 0x02, 0x6a, 0xf6, 0x28, 0xc3, 0x53, 0x8e, 0xc7, 0x1b, 0x07, 0x8d, 0xc7, 0x1b, 0xb9, 0xf1, 0xc8, - 0x56, 0x9c, 0x1d, 0x8b, 0x82, 0x2a, 0xd8, 0xc7, 0x70, 0x2e, 0x0f, 0xd5, 0xc6, 0xe0, 0x4d, 0x1c, - 0x83, 0xd7, 0x9f, 0xec, 0xcf, 0x5e, 0x2e, 0xe2, 0x5e, 0xdc, 0xff, 0x07, 0xb2, 0x64, 0xdf, 0x06, - 0x98, 0xe7, 0x6e, 0x87, 0xda, 0x38, 0x77, 0x4c, 0x5b, 0xc3, 0x26, 0x77, 0x3b, 0x05, 0xb6, 0x86, - 0x94, 0xa3, 0xd8, 0xb3, 0xd3, 0x5f, 0x5a, 0x53, 0xae, 0xa5, 0x7b, 0xb6, 0xc6, 0xa4, 0xcf, 0x9e, - 0x5d, 0xc4, 0x81, 0x6d, 0xc0, 0x58, 0xbd, 0xdb, 0x4d, 0x15, 0xc6, 0x2f, 0xe0, 0x12, 0x55, 0xee, - 0x1c, 0x5a, 0x51, 0x1a, 0x68, 0x2f, 0x36, 0xf1, 0x22, 0x15, 0xd1, 0x60, 0x24, 0x76, 0x6d, 0xfd, - 0x77, 0x5a, 0x65, 0x34, 0xfd, 0x16, 0xce, 0x2b, 0xdc, 0xb5, 0x0d, 0x5e, 0xda, 0x77, 0x1b, 0xcf, - 0xac, 0xf4, 0xe1, 0x73, 0x7b, 0xa0, 0x7a, 0xa9, 0x76, 0xf9, 0xf6, 0x40, 0xf5, 0x72, 0xed, 0x55, - 0xfb, 0x5c, 0xa3, 0xbe, 0xba, 0xb2, 0xdc, 0x52, 0xea, 0xa4, 0x7a, 0xbe, 0x5b, 0xb6, 0xd7, 0xbe, - 0x78, 0x50, 0xa9, 0xf6, 0x38, 0xe2, 0x5f, 0x1a, 0x80, 0x51, 0xad, 0x36, 0x76, 0x19, 0x06, 0xd7, - 0xdc, 0x78, 0x47, 0xd9, 0xa5, 0xd0, 0x3f, 0xb9, 0x2b, 0x00, 0xba, 0x49, 0x07, 0x31, 0xd8, 0x55, - 0x18, 0x5e, 0xe5, 0xf1, 0x4e, 0xd0, 0x52, 0x26, 0x29, 0xb4, 0xb8, 0x74, 0x24, 0x48, 0xb7, 0xb8, - 0x10, 0x96, 0xe0, 0xbd, 0xb1, 0xc3, 0x43, 0x65, 0x47, 0x43, 0xde, 0x8f, 0x04, 0x40, 0xe7, 0x8d, - 0x18, 0xec, 0x3d, 0x18, 0x43, 0x47, 0x97, 0x25, 0xbf, 0x19, 0xb4, 0x30, 0x36, 0x35, 0x3d, 0x84, - 0x62, 0xda, 0x38, 0x2e, 0x0b, 0x8c, 0xc1, 0xd0, 0x08, 0xd0, 0x59, 0x14, 0x1f, 0x40, 0x6d, 0xb7, - 0xc9, 0x3f, 0x5e, 0x3a, 0x8b, 0xca, 0xf7, 0x51, 0x0d, 0x7b, 0x76, 0x82, 0xc8, 0xae, 0xc3, 0x88, - 0x4c, 0x37, 0x17, 0xb4, 0xd4, 0x73, 0xf3, 0x32, 0xd9, 0xba, 0xcc, 0x54, 0x67, 0x1a, 0x83, 0x53, - 0x54, 0xf4, 0xa6, 0x17, 0x3f, 0x8c, 0xa0, 0x52, 0xe9, 0x4d, 0x8f, 0x94, 0x39, 0x6f, 0x7c, 0x1d, - 0x9d, 0xbd, 0x0f, 0x63, 0x8b, 0xdc, 0xdf, 0x13, 0x9c, 0x30, 0x32, 0xae, 0xaa, 0xbf, 0x06, 0xec, - 0xef, 0x61, 0xbd, 0xd9, 0xe8, 0x38, 0x83, 0x82, 0xdd, 0x80, 0x09, 0xf1, 0x5b, 0xf2, 0x43, 0x1e, - 0x23, 0xe9, 0x23, 0xc9, 0xc8, 0x83, 0x12, 0x55, 0x64, 0xb8, 0x64, 0xa8, 0xac, 0xbf, 0x56, 0x82, - 0xd9, 0x43, 0xf6, 0x48, 0xa1, 0xce, 0xa7, 0xf2, 0xb5, 0x91, 0x3c, 0xe8, 0x27, 0x53, 0x1e, 0x24, - 0x05, 0x8e, 0xe9, 0xbe, 0x67, 0x92, 0x60, 0xca, 0x11, 0xc9, 0x58, 0xcf, 0x3c, 0x43, 0x9b, 0xae, - 0x91, 0x72, 0x44, 0x82, 0xac, 0x15, 0xa8, 0x65, 0xf7, 0x4e, 0xf6, 0x25, 0x18, 0xd7, 0x5f, 0xdf, - 0x56, 0x33, 0x56, 0x26, 0x86, 0x0c, 0xb7, 0x8d, 0xf3, 0x80, 0x81, 0x68, 0x5d, 0x84, 0x09, 0x73, - 0x97, 0x60, 0x27, 0x61, 0x30, 0x0e, 0x82, 0x36, 0xf1, 0xb0, 0xe5, 0x0f, 0xeb, 0x17, 0x4b, 0x70, - 0xa2, 0x40, 0x11, 0x61, 0x17, 0x61, 0x40, 0xac, 0x00, 0xdd, 0xcb, 0x53, 0x2c, 0x11, 0xdd, 0xc3, - 0x4a, 0x94, 0xb3, 0x37, 0x60, 0x78, 0xf1, 0x6e, 0xa3, 0x51, 0xbf, 0xab, 0x16, 0x88, 0x3c, 0xaf, - 0xf8, 0x91, 0x13, 0xb9, 0xa6, 0x73, 0x18, 0xa1, 0xb1, 0xd7, 0x61, 0x68, 0x79, 0x0d, 0x09, 0xb4, - 0x97, 0xd4, 0xbd, 0x6e, 0x16, 0x9f, 0x90, 0xac, 0x1f, 0x2f, 0x01, 0xcb, 0x6b, 0x55, 0xec, 0x4d, - 0x18, 0xd5, 0x75, 0x37, 0xd9, 0x2f, 0xe8, 0x31, 0xa3, 0xe9, 0x17, 0xb6, 0x8e, 0xc3, 0x16, 0x61, - 0x10, 0x9f, 0x22, 0x4e, 0xbc, 0xd2, 0x0a, 0x25, 0xf9, 0x99, 0x9c, 0x24, 0x1f, 0xc4, 0xc7, 0x8d, - 0x6d, 0x49, 0x6c, 0xfd, 0x9b, 0x12, 0xb0, 0x62, 0x07, 0xef, 0x23, 0x79, 0xc5, 0xbe, 0xa5, 0x65, - 0x38, 0xd3, 0x3d, 0xbc, 0x7d, 0x05, 0xd4, 0x57, 0x5f, 0x9a, 0x0b, 0xed, 0xa2, 0x61, 0x9d, 0xef, - 0x9f, 0x16, 0xe7, 0x32, 0x0c, 0x3e, 0xe0, 0xe1, 0xa6, 0x0a, 0xa3, 0x42, 0xf1, 0xb3, 0x2b, 0x00, - 0xba, 0xf8, 0x41, 0x0c, 0xc3, 0xd5, 0x7c, 0xf0, 0xa8, 0xae, 0xe6, 0xbf, 0x57, 0x82, 0x93, 0x45, - 0xf6, 0x9f, 0x43, 0x52, 0xde, 0x58, 0x99, 0x6c, 0x3d, 0xe8, 0x46, 0x2b, 0x83, 0x39, 0x92, 0x1c, - 0x3d, 0xb3, 0x30, 0x28, 0x7a, 0x48, 0x4d, 0x0b, 0xbc, 0x62, 0x10, 0x5d, 0x18, 0xd9, 0x12, 0x2e, - 0x10, 0xd2, 0x17, 0x29, 0x07, 0x25, 0x82, 0x7c, 0x88, 0x52, 0xc2, 0x05, 0xc2, 0x6a, 0xd0, 0xe2, - 0xca, 0xf4, 0x8e, 0x08, 0x1d, 0x01, 0xb0, 0x25, 0x9c, 0x5d, 0x84, 0xe1, 0x7b, 0xfe, 0x0a, 0x77, - 0x77, 0x95, 0xe4, 0x43, 0xb7, 0xdf, 0xc0, 0x77, 0xda, 0x02, 0x66, 0xab, 0x42, 0xeb, 0xa7, 0x4b, - 0x30, 0x95, 0x33, 0x3d, 0x1d, 0x9e, 0xd5, 0xe7, 0xe0, 0x2c, 0x06, 0x47, 0x69, 0x9f, 0xfc, 0xfc, - 0x81, 0xe2, 0xcf, 0xb7, 0xfe, 0xeb, 0x21, 0x38, 0xd3, 0xe7, 0x26, 0x20, 0xcd, 0xf2, 0x52, 0x3a, - 0x34, 0xcb, 0xcb, 0x37, 0x61, 0x7c, 0xa1, 0xed, 0x7a, 0x9d, 0x68, 0x3d, 0x48, 0xbf, 0x38, 0x0d, - 0xd6, 0xc6, 0x32, 0x0a, 0x56, 0x4c, 0xa2, 0x7a, 0xcf, 0x36, 0x91, 0xc2, 0x89, 0x83, 0xbc, 0x21, - 0xc2, 0x60, 0x96, 0xcb, 0xb3, 0x52, 0xf9, 0x23, 0x92, 0x67, 0xc5, 0x8c, 0xbc, 0x1f, 0x78, 0xaa, - 0x91, 0xf7, 0xc5, 0x81, 0x59, 0x83, 0x9f, 0x26, 0x4c, 0x6f, 0x21, 0x1b, 0x5e, 0x33, 0x94, 0xf3, - 0x95, 0x3e, 0x3c, 0xae, 0xe6, 0x96, 0x19, 0x25, 0x3e, 0x8c, 0x8e, 0x41, 0x17, 0xfb, 0x47, 0x81, - 0x9b, 0xe9, 0x24, 0xf5, 0x68, 0xf0, 0xef, 0xc0, 0xc9, 0x22, 0x53, 0xe2, 0x74, 0xd5, 0x08, 0xa9, - 0xe8, 0x1b, 0xff, 0x72, 0x74, 0x83, 0xe4, 0xc3, 0x42, 0x83, 0xa4, 0xca, 0x1e, 0x34, 0xd2, 0x3f, - 0xf4, 0x39, 0x5d, 0x0b, 0x12, 0xf7, 0xe0, 0x1c, 0x43, 0xd6, 0x5f, 0x2a, 0x65, 0x72, 0x6c, 0x65, - 0xe9, 0xd9, 0x3b, 0x46, 0x2a, 0xd4, 0x57, 0xf2, 0xa9, 0x50, 0x8b, 0xd3, 0x6a, 0xc9, 0xdb, 0xd4, - 0xd7, 0x60, 0x88, 0xfc, 0xea, 0xb4, 0xf4, 0x64, 0x39, 0x7f, 0x3a, 0xc2, 0xb1, 0xbe, 0x5f, 0x36, - 0x53, 0xcc, 0xfc, 0x51, 0x5c, 0xd7, 0xc7, 0x50, 0x6c, 0x6f, 0xc0, 0xc4, 0x9a, 0x9c, 0xe7, 0x6a, - 0xf2, 0x0e, 0xa4, 0xe6, 0xef, 0x2e, 0x5d, 0xd2, 0x14, 0xcc, 0xde, 0x0c, 0x95, 0xe0, 0x23, 0x33, - 0xfd, 0xdc, 0x08, 0x42, 0x79, 0x49, 0x35, 0x98, 0xf2, 0x91, 0xf9, 0x7c, 0x9c, 0xad, 0x20, 0xcc, - 0x5d, 0x56, 0x65, 0xa8, 0xac, 0x9b, 0x99, 0xa1, 0xa6, 0xf4, 0xb1, 0x32, 0x6a, 0x5a, 0x2a, 0x3b, - 0x13, 0x69, 0x12, 0x81, 0x54, 0xc6, 0xdb, 0x19, 0xa8, 0xb5, 0x05, 0xcf, 0x1f, 0xc8, 0x48, 0xe8, - 0x18, 0xd0, 0x4d, 0x7e, 0x65, 0xa2, 0x7a, 0x0e, 0x24, 0xb5, 0x35, 0x3a, 0x6b, 0x25, 0xcd, 0x2b, - 0xb0, 0xbc, 0x88, 0x89, 0x0d, 0xde, 0x86, 0x31, 0x3d, 0xe6, 0x8d, 0x38, 0x17, 0x84, 0xc8, 0x0d, - 0x88, 0x81, 0xb5, 0x47, 0x15, 0xf2, 0x72, 0x2b, 0xb2, 0xfe, 0x97, 0x0a, 0x4c, 0xf7, 0x8b, 0xac, - 0x67, 0x7f, 0x25, 0xc9, 0xc4, 0x87, 0x61, 0xe3, 0x81, 0x19, 0x6f, 0x38, 0x3a, 0xf7, 0xf6, 0x21, - 0xa1, 0xf9, 0x57, 0x0a, 0x89, 0x65, 0x40, 0x4a, 0x12, 0xae, 0x87, 0xfa, 0x04, 0x66, 0x71, 0x72, - 0xb4, 0x1c, 0x0e, 0x76, 0x71, 0xc5, 0xec, 0x03, 0x38, 0x65, 0xf3, 0x66, 0xd0, 0xe9, 0x70, 0xbf, - 0xa5, 0xc7, 0xb4, 0xd3, 0x52, 0xa2, 0xa4, 0x74, 0x09, 0x82, 0xc9, 0xb2, 0x90, 0x92, 0xdd, 0x85, - 0xa9, 0x34, 0xeb, 0xad, 0x7a, 0xe3, 0x4d, 0x7b, 0x0a, 0x35, 0xcd, 0xd2, 0xab, 0x5e, 0x78, 0xd3, - 0x4d, 0x53, 0x39, 0x52, 0x76, 0x15, 0x60, 0xc1, 0xf5, 0xd7, 0xc2, 0xa0, 0xc9, 0x29, 0xa9, 0x50, - 0x95, 0xdc, 0xb5, 0x5d, 0xcc, 0x94, 0x27, 0xc0, 0xb6, 0x86, 0x32, 0xe3, 0xc0, 0x4c, 0xff, 0x8e, - 0x2a, 0x08, 0xaa, 0xf9, 0xbc, 0x19, 0xab, 0x75, 0x2a, 0x37, 0xd0, 0x82, 0x8f, 0x1e, 0x6b, 0xf3, - 0x1d, 0x18, 0xd3, 0x17, 0x38, 0x2a, 0x4b, 0xe2, 0x37, 0x89, 0x2f, 0xa9, 0x2c, 0x09, 0x80, 0x2d, - 0xe1, 0xa9, 0x47, 0x47, 0xb9, 0xd8, 0xa3, 0x23, 0x95, 0x3c, 0x95, 0xc3, 0x24, 0x8f, 0xa8, 0x1c, - 0xf7, 0x62, 0xad, 0x72, 0xfc, 0xad, 0x57, 0x8e, 0x79, 0x80, 0x6d, 0x09, 0x7f, 0xaa, 0x95, 0xff, - 0xc3, 0x12, 0x0c, 0xd8, 0xbd, 0x36, 0xc5, 0xf1, 0x9b, 0xd1, 0xb2, 0x14, 0xc7, 0x9f, 0xdf, 0x67, - 0x52, 0xcc, 0x54, 0x65, 0x2e, 0x1f, 0xaa, 0x32, 0x1f, 0x43, 0x06, 0xe2, 0xf1, 0x4f, 0x4a, 0x81, - 0x81, 0xf4, 0x98, 0xe3, 0xe6, 0x9c, 0x6f, 0x15, 0x96, 0xf5, 0x73, 0x25, 0x38, 0x55, 0x78, 0x73, - 0x2e, 0x6a, 0x95, 0xd2, 0x4f, 0xdb, 0x02, 0xb2, 0x22, 0x4f, 0x62, 0x1c, 0x27, 0xd7, 0xdf, 0xd1, - 0xdb, 0x62, 0xbd, 0x00, 0x23, 0x89, 0xdf, 0x96, 0x38, 0x46, 0xca, 0xa1, 0x93, 0x09, 0xe3, 0xc9, - 0xfd, 0xe7, 0x6f, 0x96, 0x01, 0xc4, 0x27, 0x7c, 0x86, 0xa1, 0x5e, 0xb2, 0x0f, 0xfa, 0x84, 0x7a, - 0x65, 0xfb, 0x23, 0x4b, 0xc7, 0xfe, 0x5c, 0x09, 0x86, 0xa4, 0xbe, 0x78, 0xb0, 0x46, 0xba, 0xf1, - 0x74, 0x34, 0xd2, 0x1a, 0x4e, 0x77, 0xe3, 0x3c, 0x2b, 0xcb, 0xac, 0x7f, 0x58, 0x86, 0x21, 0xf1, - 0x49, 0xcf, 0xec, 0xc3, 0x42, 0xc5, 0x31, 0x67, 0xa2, 0x49, 0x07, 0x3c, 0xa3, 0xb6, 0x94, 0x79, - 0x46, 0xed, 0x84, 0x4e, 0x86, 0x05, 0x0f, 0xe6, 0xd2, 0xcc, 0x77, 0xfd, 0x9e, 0x4d, 0xd3, 0x42, - 0xd7, 0xfe, 0x69, 0x49, 0xe6, 0x1b, 0x55, 0xc4, 0xec, 0xeb, 0x30, 0xa1, 0x1e, 0x4b, 0x91, 0xd9, - 0x28, 0xc9, 0x73, 0x4e, 0xb9, 0xdb, 0xab, 0xc7, 0x52, 0xf4, 0xec, 0x95, 0x06, 0xbe, 0xae, 0xfa, - 0x74, 0x75, 0x64, 0xd6, 0x02, 0xd6, 0xd9, 0x72, 0x9d, 0x47, 0xdc, 0x7d, 0xc8, 0xa3, 0xd8, 0x91, - 0x6e, 0xd1, 0xe4, 0x60, 0xa7, 0xd8, 0xaf, 0xde, 0xa8, 0x4b, 0x8f, 0x68, 0xcc, 0x89, 0x23, 0x5f, - 0xbd, 0xc9, 0xd1, 0xe8, 0x5e, 0x43, 0x9d, 0x2d, 0x77, 0x43, 0x16, 0x4a, 0x3a, 0xeb, 0x5f, 0x0f, - 0xc9, 0x25, 0x44, 0xaf, 0x2b, 0x6d, 0xc2, 0xc4, 0xbd, 0xe5, 0xc5, 0x05, 0xcd, 0x87, 0xc0, 0x7c, - 0xe0, 0x6a, 0xe9, 0x71, 0xcc, 0x43, 0xdf, 0x6d, 0x2b, 0x5b, 0x56, 0xaa, 0xd2, 0x05, 0x5e, 0xab, - 0x59, 0xec, 0x5f, 0x90, 0xe1, 0x28, 0xea, 0x90, 0x26, 0xd4, 0xa4, 0x8e, 0xf2, 0x11, 0xeb, 0x88, - 0xdc, 0x4e, 0xbb, 0x4f, 0x1d, 0x26, 0x47, 0xb6, 0x83, 0x66, 0xad, 0x9d, 0xde, 0xa6, 0x56, 0x4b, - 0xe5, 0xe0, 0x5a, 0x5e, 0xa4, 0x5a, 0x9e, 0xa3, 0x1b, 0xa3, 0xc2, 0x7a, 0x72, 0x5c, 0x53, 0xe1, - 0x37, 0x70, 0xa8, 0xf0, 0xd3, 0xa4, 0xc1, 0xe0, 0xbf, 0x4d, 0x69, 0xc0, 0x16, 0x33, 0xeb, 0x82, - 0x25, 0xea, 0xd6, 0xb6, 0xe7, 0x6b, 0xaf, 0x09, 0x1e, 0xb8, 0x2c, 0xd8, 0x72, 0x9a, 0x8b, 0x70, - 0xf8, 0xd0, 0x04, 0x54, 0x2a, 0x7f, 0xe3, 0x30, 0xe5, 0x22, 0x34, 0x33, 0x10, 0xae, 0xc0, 0x08, - 0x65, 0x38, 0x9c, 0xdf, 0x23, 0x9f, 0xbf, 0x9a, 0xe1, 0x3e, 0xde, 0x9a, 0xdf, 0x4b, 0x4f, 0xc6, - 0x94, 0x23, 0xd1, 0x4c, 0xcf, 0x99, 0x32, 0x60, 0xf7, 0x60, 0x24, 0x7d, 0x7d, 0xca, 0x7c, 0xf0, - 0x35, 0x81, 0x53, 0x52, 0x6f, 0x95, 0x26, 0xad, 0xe0, 0xb1, 0xa9, 0x94, 0x07, 0x5b, 0x81, 0x1a, - 0x86, 0x1c, 0xf0, 0x96, 0x5c, 0x35, 0xcb, 0x8b, 0x32, 0x8b, 0x1e, 0xe9, 0x71, 0xb1, 0x2c, 0xa3, - 0xe5, 0x96, 0xc9, 0xd9, 0x90, 0xa3, 0xb4, 0x7e, 0xaa, 0x0c, 0xb5, 0xec, 0xec, 0x63, 0xef, 0xc2, - 0x68, 0xf2, 0xfa, 0x57, 0x92, 0x25, 0x09, 0x0d, 0xdd, 0xe9, 0x73, 0x61, 0x46, 0xbe, 0x24, 0x1d, - 0x9d, 0xcd, 0x41, 0x55, 0x2c, 0x62, 0x3f, 0x7d, 0x13, 0x01, 0xc5, 0x76, 0x8f, 0x60, 0xba, 0x51, - 0x4d, 0xe1, 0xb1, 0x06, 0x9c, 0x10, 0x8b, 0xa6, 0xe1, 0xf9, 0xdb, 0x6d, 0xbe, 0x12, 0x6c, 0x07, - 0xbd, 0xf8, 0xbe, 0xbd, 0x42, 0x32, 0x5c, 0xda, 0x0f, 0xdc, 0x4e, 0xdb, 0x28, 0x0e, 0x75, 0xe3, - 0x7e, 0x11, 0x35, 0x7b, 0x5d, 0x6e, 0x33, 0xcb, 0x8b, 0xe4, 0xb2, 0x8b, 0xfa, 0x07, 0xfa, 0xbc, - 0x1b, 0x1f, 0x4f, 0x48, 0x9a, 0x64, 0xfd, 0xdd, 0x32, 0x8c, 0x6a, 0xd3, 0x8f, 0x5d, 0x86, 0xea, - 0x72, 0xb4, 0x12, 0x34, 0x1f, 0x26, 0x8f, 0x44, 0x8c, 0x3f, 0xd9, 0x9f, 0x1d, 0xf1, 0x22, 0xa7, - 0x8d, 0x40, 0x3b, 0x29, 0x66, 0xf3, 0x30, 0x2e, 0xff, 0x52, 0x2a, 0x76, 0x39, 0xb5, 0x9b, 0x4b, - 0xe4, 0x02, 0xf5, 0xda, 0x24, 0x61, 0xdf, 0x02, 0x90, 0x00, 0xcc, 0xde, 0x56, 0x39, 0x7a, 0xde, - 0x39, 0xaa, 0xa0, 0x20, 0x6f, 0x9b, 0xc6, 0x90, 0x7d, 0x28, 0x1f, 0x17, 0x53, 0xcb, 0x65, 0xe0, - 0xe8, 0x89, 0xf3, 0x04, 0x7f, 0xa7, 0x38, 0x7f, 0xa7, 0xce, 0x92, 0x5e, 0x3e, 0x9f, 0x11, 0x07, - 0x91, 0x5d, 0x1e, 0xee, 0xd5, 0x63, 0x44, 0xd4, 0x30, 0xac, 0xff, 0xa9, 0xa4, 0x2d, 0x32, 0x76, - 0x17, 0x46, 0x92, 0x09, 0x44, 0xde, 0xf2, 0xc9, 0x91, 0x5d, 0xc1, 0x6d, 0xbe, 0x35, 0xff, 0x1c, - 0x45, 0x10, 0x9c, 0x48, 0xa6, 0xa1, 0xb1, 0xe6, 0x14, 0x90, 0xbd, 0x0f, 0x03, 0xd8, 0x75, 0xe5, - 0x43, 0x9b, 0xa6, 0x76, 0xf9, 0x01, 0xd1, 0x67, 0xd8, 0x10, 0xa4, 0x64, 0x6f, 0x50, 0x0a, 0x18, - 0xd9, 0xf9, 0x13, 0xda, 0x56, 0x2d, 0xbe, 0x23, 0xd9, 0xde, 0xd3, 0x14, 0xae, 0xda, 0xec, 0xf9, - 0x6b, 0x65, 0xa8, 0x65, 0x97, 0x36, 0x7b, 0x0f, 0xc6, 0xd4, 0xf6, 0x7b, 0xcb, 0xa5, 0x37, 0x80, - 0xc7, 0xe8, 0x0d, 0x5e, 0xb5, 0x07, 0xef, 0xb8, 0xba, 0x77, 0xbd, 0x6d, 0x10, 0x08, 0x5d, 0x68, - 0x9d, 0x72, 0xfd, 0x6b, 0x8b, 0x2a, 0x0e, 0xe2, 0x6e, 0xe6, 0x4d, 0x2b, 0x85, 0xc6, 0xde, 0x82, - 0xca, 0xea, 0x8d, 0x3a, 0x25, 0x51, 0xa8, 0x65, 0x37, 0x69, 0xba, 0x32, 0x37, 0x62, 0xa3, 0x04, - 0x3e, 0x5b, 0xd1, 0x9e, 0x7f, 0x1b, 0x32, 0x42, 0x3a, 0x14, 0x38, 0x69, 0xdc, 0xe1, 0xef, 0xc0, - 0xdd, 0x1e, 0xa8, 0x56, 0x6a, 0x03, 0xf4, 0xd2, 0xce, 0x3c, 0x4c, 0xe5, 0x58, 0x68, 0x8b, 0x54, - 0x76, 0xc9, 0xc1, 0x8b, 0xd4, 0xf2, 0x61, 0x4c, 0x9f, 0x12, 0x87, 0xbc, 0xc0, 0x78, 0x1a, 0xb3, - 0xbc, 0xc9, 0xfe, 0x1a, 0x7a, 0xb2, 0x3f, 0x5b, 0xf6, 0x5a, 0x98, 0xdb, 0xed, 0x12, 0x54, 0x95, - 0xb0, 0x23, 0x19, 0x83, 0x66, 0x70, 0xe5, 0xb0, 0x60, 0x27, 0xa5, 0xd6, 0x2b, 0x30, 0x4c, 0xa3, - 0x7e, 0xb0, 0xf1, 0xdb, 0xfa, 0x5e, 0x19, 0x26, 0x6d, 0x2e, 0x34, 0x4d, 0x2e, 0x5f, 0x3d, 0x7e, - 0x66, 0xb5, 0xdb, 0xe2, 0x17, 0x0e, 0x8c, 0xb6, 0x1d, 0xf0, 0x82, 0xff, 0x2f, 0x97, 0xe0, 0x44, - 0x01, 0x2e, 0x9b, 0x83, 0x51, 0xf5, 0xb0, 0x7f, 0xda, 0x89, 0x98, 0x79, 0x4f, 0x39, 0xb8, 0x61, - 0x67, 0xea, 0x48, 0xec, 0x3a, 0x8c, 0x2c, 0x7a, 0x6e, 0xbb, 0xde, 0x6a, 0x25, 0x29, 0xdf, 0x70, - 0x8b, 0xc4, 0x57, 0xb9, 0x5d, 0x01, 0xd5, 0xd7, 0x7f, 0x82, 0xca, 0x5e, 0xa5, 0x49, 0x51, 0x49, - 0xba, 0x15, 0x27, 0xc5, 0x0f, 0xf6, 0x67, 0x41, 0x7e, 0xd3, 0x7a, 0x32, 0x45, 0xf0, 0xd5, 0x11, - 0x09, 0x4c, 0xc3, 0x3e, 0x9f, 0xd9, 0xa1, 0x2b, 0x7e, 0x75, 0x24, 0xdb, 0xbc, 0x23, 0xbd, 0x79, - 0xfa, 0xef, 0x94, 0xe1, 0x74, 0x31, 0xe1, 0x27, 0x1a, 0xca, 0xd7, 0x60, 0x04, 0xdf, 0xd2, 0xd5, - 0x5e, 0x4a, 0x9a, 0x78, 0xb2, 0x3f, 0x0b, 0xf2, 0xe1, 0x5d, 0xc4, 0x4f, 0x11, 0xd8, 0x16, 0x8c, - 0xaf, 0xb8, 0x51, 0x7c, 0x8b, 0xbb, 0x61, 0xbc, 0xc9, 0xdd, 0xf8, 0x08, 0x9b, 0xa0, 0xf2, 0xb1, - 0x9a, 0xc6, 0x67, 0xd9, 0x76, 0x14, 0x65, 0x66, 0x9b, 0x32, 0xd9, 0x26, 0x13, 0x65, 0xe0, 0x08, - 0x13, 0xe5, 0x63, 0x98, 0x6c, 0xf0, 0x8e, 0xdb, 0xdd, 0x09, 0x42, 0x95, 0xce, 0xe5, 0x0a, 0x8c, - 0x27, 0xa0, 0xc2, 0xd9, 0x62, 0x16, 0x1b, 0xf8, 0x5a, 0x47, 0xa4, 0xa2, 0xc4, 0x2c, 0xb6, 0x7e, - 0xbe, 0x0c, 0x67, 0xea, 0x4d, 0x72, 0x18, 0xa7, 0x02, 0x15, 0xd7, 0xf2, 0x19, 0xd7, 0xcd, 0xae, - 0xc2, 0xc8, 0xaa, 0xfb, 0x78, 0x85, 0xbb, 0x11, 0x8f, 0xe8, 0x19, 0x2e, 0xb9, 0x63, 0xb8, 0x8f, - 0xd3, 0x6b, 0x0b, 0x3b, 0xc5, 0xd1, 0x35, 0xf0, 0x81, 0x4f, 0xa9, 0x81, 0x5b, 0x30, 0x74, 0x2b, - 0x68, 0xb7, 0x78, 0x48, 0xb7, 0xb2, 0x78, 0x57, 0xba, 0x83, 0x10, 0x9b, 0x4a, 0xac, 0xdf, 0x2b, - 0xc1, 0x44, 0xf2, 0xc5, 0xf8, 0x09, 0x9f, 0x79, 0x97, 0x5c, 0x84, 0x61, 0xac, 0x68, 0x79, 0x51, - 0xdf, 0x34, 0xda, 0x02, 0xe4, 0x78, 0x2d, 0x5b, 0x15, 0xea, 0x3d, 0x31, 0xf8, 0xe9, 0x7a, 0xc2, - 0xfa, 0x3b, 0x78, 0x0d, 0xab, 0xb7, 0x52, 0xec, 0x44, 0xda, 0x87, 0x94, 0x8e, 0xf8, 0x21, 0xe5, - 0xa7, 0x36, 0x24, 0x95, 0xbe, 0x43, 0xf2, 0xe7, 0xcb, 0x30, 0x9a, 0x7c, 0xec, 0x0f, 0xd9, 0x73, - 0x5d, 0x49, 0xbb, 0x8e, 0x94, 0x82, 0xad, 0xa1, 0xc9, 0x0a, 0xca, 0x74, 0xf6, 0x3e, 0x0c, 0xd1, - 0x62, 0x2a, 0x65, 0xe2, 0x3b, 0x32, 0xa3, 0x3b, 0x3f, 0x41, 0xac, 0x87, 0x70, 0x40, 0x23, 0x9b, - 0xe8, 0x30, 0xc7, 0xdd, 0x06, 0xdf, 0xa4, 0x5b, 0xf9, 0x67, 0x76, 0x8f, 0x2a, 0xce, 0x71, 0x97, - 0x36, 0xec, 0x48, 0xbb, 0xd3, 0xff, 0x30, 0x02, 0xb5, 0x2c, 0xc9, 0xe1, 0x0f, 0xa2, 0xad, 0xf5, - 0x36, 0xb1, 0x2f, 0xc6, 0xe4, 0x83, 0x68, 0xdd, 0xde, 0xa6, 0x2d, 0x60, 0xe8, 0xe9, 0x13, 0x7a, - 0xbb, 0xd8, 0xea, 0x31, 0xf2, 0xf4, 0x09, 0xbd, 0x5d, 0xc3, 0xd3, 0x27, 0xf4, 0x76, 0x51, 0x07, - 0x5f, 0x69, 0x60, 0xfe, 0x97, 0x01, 0x44, 0x95, 0x3a, 0x78, 0x3b, 0xca, 0x3e, 0x47, 0xad, 0xd0, - 0xc4, 0x56, 0x39, 0xcf, 0xdd, 0x90, 0x1e, 0xef, 0x22, 0x71, 0x86, 0x5b, 0xe5, 0x26, 0x82, 0x9d, - 0x58, 0xc0, 0x6d, 0x1d, 0x89, 0xb5, 0x81, 0x69, 0x3f, 0xd5, 0x02, 0x1e, 0x3a, 0x74, 0x01, 0x2b, - 0xdf, 0xec, 0x93, 0x3a, 0x6b, 0x47, 0x5f, 0xcd, 0x05, 0x7c, 0x9f, 0xa6, 0xe1, 0x64, 0x8d, 0x12, - 0xbf, 0xe3, 0xd9, 0xab, 0x7a, 0x28, 0x33, 0x95, 0xb7, 0x0a, 0x64, 0x62, 0xf8, 0xe4, 0x04, 0x96, - 0x32, 0x61, 0x5f, 0x85, 0x51, 0x3d, 0xab, 0x8f, 0xcc, 0x3d, 0x73, 0x4e, 0xa6, 0x6b, 0x4e, 0xd3, - 0xf9, 0x98, 0x77, 0xf6, 0x7a, 0xfa, 0x9e, 0x4d, 0x38, 0xb3, 0x10, 0xf8, 0x51, 0xaf, 0xa3, 0xae, - 0xc2, 0xd2, 0xb7, 0x4a, 0x00, 0x87, 0x02, 0x53, 0x84, 0x34, 0x09, 0x45, 0x5d, 0xa3, 0xa9, 0xe4, - 0x2f, 0xc6, 0x01, 0xa4, 0x1f, 0x23, 0xb6, 0x0e, 0xa3, 0x68, 0x7c, 0x20, 0xcf, 0xd5, 0x51, 0x53, - 0x6c, 0xa4, 0x25, 0x8b, 0x62, 0x61, 0xc8, 0x37, 0x5d, 0xdc, 0x4e, 0x5b, 0xc5, 0x6e, 0x19, 0x6f, - 0xba, 0xa4, 0xc8, 0xec, 0x5b, 0x30, 0x21, 0xcf, 0x69, 0x1b, 0x7c, 0x53, 0xce, 0x9d, 0x31, 0xe3, - 0x1a, 0xcc, 0x2c, 0x54, 0x2e, 0x80, 0x68, 0xf2, 0xc1, 0x20, 0x77, 0x01, 0x35, 0x5d, 0x00, 0x75, - 0x7c, 0x76, 0x1f, 0x4e, 0xdc, 0x72, 0x23, 0x09, 0xd4, 0xd2, 0xb3, 0x8c, 0xa3, 0x71, 0x03, 0x23, - 0x5a, 0x76, 0xdc, 0x48, 0xd9, 0x90, 0x0a, 0xd3, 0xb1, 0x14, 0xd1, 0xb3, 0xef, 0x95, 0x60, 0xda, - 0x30, 0x31, 0x91, 0x87, 0x14, 0x26, 0x6d, 0x9f, 0x30, 0x02, 0xf5, 0xfb, 0xa1, 0xc9, 0x21, 0xc9, - 0x58, 0xb1, 0xc2, 0xb4, 0x5c, 0x0f, 0x15, 0xe9, 0xc7, 0x83, 0x16, 0x2a, 0xae, 0xe9, 0x49, 0x73, - 0xa1, 0x66, 0xd6, 0xb5, 0x42, 0x63, 0x0b, 0x30, 0xbe, 0x38, 0xdf, 0x58, 0x58, 0xeb, 0x6d, 0xb6, - 0xbd, 0xa6, 0x38, 0x64, 0xd7, 0x90, 0x0e, 0x6f, 0x6b, 0x5a, 0x9b, 0x51, 0xd3, 0xe9, 0x62, 0x49, - 0xe6, 0xac, 0x6d, 0xd2, 0xb0, 0xba, 0x7a, 0x58, 0x71, 0x0a, 0x9b, 0x7a, 0x3a, 0x27, 0xdb, 0x0e, - 0x7d, 0x59, 0xd1, 0xba, 0x9e, 0x1d, 0x77, 0x36, 0x01, 0x65, 0x8f, 0x64, 0xbc, 0x5d, 0xf6, 0x5a, - 0xd2, 0x89, 0x51, 0x4c, 0x08, 0x4a, 0x36, 0x88, 0x3f, 0xac, 0x37, 0x74, 0x79, 0x48, 0xea, 0xe9, - 0x81, 0xf2, 0xd0, 0xfa, 0xef, 0x87, 0x60, 0x32, 0x33, 0x3d, 0xe9, 0xbc, 0x5c, 0xca, 0x9d, 0x97, - 0x1b, 0x00, 0xd2, 0x5a, 0x73, 0x44, 0xb3, 0x8a, 0x0a, 0x13, 0x1f, 0xa5, 0x6c, 0x13, 0xc9, 0xda, - 0xd6, 0xd8, 0x08, 0xa6, 0x52, 0x72, 0x1c, 0xd1, 0xcc, 0x95, 0x30, 0x95, 0xc2, 0x47, 0x63, 0x9a, - 0xb2, 0x61, 0xb3, 0x30, 0x88, 0x69, 0xe2, 0xf5, 0x28, 0x7d, 0x4f, 0x00, 0x6c, 0x09, 0x67, 0x2f, - 0xc2, 0x90, 0x50, 0xe6, 0x96, 0x17, 0x49, 0x18, 0xe3, 0x1e, 0x27, 0xb4, 0x3d, 0xa1, 0x39, 0x51, - 0x11, 0xbb, 0x0e, 0x63, 0xf2, 0x2f, 0xca, 0x46, 0x36, 0x64, 0xfa, 0x1c, 0x3a, 0x5e, 0x4b, 0x25, - 0x24, 0x33, 0xf0, 0xc4, 0x29, 0xa7, 0xd1, 0xdb, 0xfc, 0x88, 0x37, 0x85, 0x84, 0x19, 0x4e, 0x4f, - 0x39, 0x91, 0x04, 0x8a, 0x2a, 0x52, 0x04, 0xa1, 0x53, 0x51, 0xac, 0x5c, 0x15, 0xcf, 0xb6, 0xa8, - 0x53, 0xc9, 0x18, 0x39, 0x9b, 0x4a, 0xd8, 0x65, 0x69, 0x4c, 0x45, 0xf5, 0x54, 0x7a, 0xfb, 0xa2, - 0xe9, 0x11, 0x0d, 0x24, 0xa8, 0xa3, 0x26, 0xc5, 0xa2, 0x72, 0xf1, 0xf7, 0x52, 0xc7, 0xf5, 0xda, - 0x24, 0xde, 0xb0, 0x72, 0xc4, 0xe5, 0x02, 0x6a, 0xa7, 0x08, 0xec, 0x5d, 0x98, 0x90, 0xaf, 0x60, - 0x77, 0x3a, 0x81, 0x8f, 0xec, 0x47, 0x53, 0x37, 0x1d, 0x7a, 0x42, 0x5c, 0x14, 0xc9, 0x5a, 0x32, - 0xb8, 0x62, 0x5f, 0xc3, 0x8b, 0x9a, 0x9e, 0x34, 0xf3, 0x8e, 0xa5, 0xfb, 0x1a, 0x92, 0x46, 0x12, - 0x6e, 0xeb, 0x48, 0xec, 0xcb, 0x30, 0x2e, 0x7e, 0xde, 0xf4, 0x76, 0xb9, 0xac, 0x70, 0x3c, 0xbd, - 0x76, 0x45, 0xaa, 0x6d, 0x51, 0x22, 0xeb, 0x33, 0x31, 0xd9, 0x07, 0x70, 0x0a, 0x39, 0x35, 0x83, - 0x2e, 0x6f, 0xd5, 0xb7, 0xb6, 0xbc, 0xb6, 0x27, 0xfd, 0xb9, 0x26, 0x52, 0x7f, 0x08, 0x59, 0x31, - 0x62, 0x38, 0x6e, 0x8a, 0x62, 0x17, 0x53, 0xb2, 0x0d, 0xa8, 0x2d, 0xf4, 0xa2, 0x38, 0xe8, 0xd4, - 0xe3, 0x38, 0xf4, 0x36, 0x7b, 0x31, 0x8f, 0xa6, 0x27, 0x8d, 0xec, 0x54, 0x62, 0x71, 0x24, 0x85, - 0xd2, 0x2e, 0xd5, 0x44, 0x0a, 0xc7, 0x4d, 0x48, 0xec, 0x1c, 0x13, 0xeb, 0x9f, 0x97, 0x60, 0xdc, - 0x20, 0x65, 0x6f, 0xc1, 0xd8, 0x8d, 0xd0, 0xe3, 0x7e, 0xab, 0xbd, 0xa7, 0x1d, 0x98, 0xf1, 0x34, - 0xb5, 0x45, 0x70, 0xd9, 0x6a, 0x03, 0x2d, 0xb1, 0x37, 0x95, 0x0b, 0x9d, 0x2d, 0xaf, 0xca, 0x64, - 0x11, 0x34, 0x41, 0x2b, 0x69, 0xba, 0x3c, 0x9c, 0xa0, 0x34, 0x3b, 0x35, 0x14, 0xf6, 0x15, 0x18, - 0x92, 0x57, 0x3a, 0xe4, 0xf9, 0x77, 0xb6, 0xa8, 0x99, 0x32, 0x31, 0x09, 0x4e, 0x44, 0x74, 0x46, - 0x88, 0x6c, 0x22, 0xb2, 0x7e, 0xa6, 0x04, 0x2c, 0x8f, 0x7a, 0x88, 0xfd, 0xed, 0x50, 0x27, 0x87, - 0xf7, 0x93, 0xd5, 0x58, 0x31, 0xee, 0x52, 0x45, 0x4d, 0xb2, 0x40, 0x76, 0x3c, 0xad, 0x3a, 0xdd, - 0x20, 0x28, 0x8b, 0xad, 0xef, 0x96, 0x01, 0x52, 0x6c, 0xf6, 0x25, 0xf9, 0x86, 0xf0, 0x07, 0x3d, - 0xb7, 0xed, 0x6d, 0x79, 0x66, 0x12, 0x6f, 0x64, 0xf2, 0xb1, 0x2a, 0xb1, 0x4d, 0x44, 0xf6, 0x1e, - 0x4c, 0x36, 0xd6, 0x4c, 0x5a, 0xcd, 0x6b, 0x3d, 0xea, 0x3a, 0x19, 0xf2, 0x2c, 0x36, 0x7a, 0xf8, - 0xea, 0xa3, 0x21, 0x3d, 0x7c, 0xe5, 0x40, 0x50, 0x89, 0x10, 0x2c, 0x8d, 0x35, 0x8a, 0xd2, 0x68, - 0x25, 0x17, 0x13, 0xf8, 0x75, 0x51, 0xd7, 0xe9, 0x52, 0xf8, 0x86, 0x90, 0x13, 0x06, 0x5e, 0xda, - 0x91, 0x83, 0x7d, 0x92, 0x8f, 0xfc, 0x2c, 0x9a, 0x1f, 0x3b, 0x41, 0xcc, 0xc9, 0xea, 0xf2, 0xcc, - 0x9e, 0xbf, 0xd2, 0xfb, 0xc0, 0x41, 0x23, 0xa7, 0x82, 0xd1, 0x3a, 0xba, 0xf3, 0xbe, 0x96, 0x1e, - 0x96, 0xe4, 0xcd, 0x60, 0xc1, 0x35, 0xf9, 0xdf, 0x28, 0xc1, 0xa9, 0x42, 0x5a, 0x76, 0x05, 0x20, - 0xb5, 0x6d, 0x51, 0x2f, 0xa1, 0xc4, 0x4c, 0x93, 0xc4, 0xd9, 0x1a, 0x06, 0xfb, 0x66, 0xd6, 0x2a, - 0x75, 0xf8, 0x46, 0x38, 0xa3, 0x72, 0xb3, 0x9a, 0x56, 0xa9, 0x02, 0x5b, 0x94, 0xf5, 0xcb, 0x15, - 0x98, 0xd2, 0x72, 0xd0, 0xc9, 0x6f, 0x3d, 0xc4, 0xe3, 0xfa, 0x21, 0x8c, 0x89, 0xd6, 0x78, 0x4d, - 0x0a, 0xd0, 0x92, 0x77, 0xd7, 0xaf, 0xe6, 0xa2, 0xe2, 0x89, 0xdb, 0x15, 0x1d, 0x59, 0x3a, 0xa8, - 0xa1, 0xe8, 0xc4, 0x97, 0xf1, 0x9b, 0xf9, 0x48, 0x2d, 0x83, 0x39, 0x8b, 0x60, 0x7c, 0x71, 0xcf, - 0x77, 0x3b, 0x49, 0x6d, 0xf2, 0x0e, 0xfb, 0xf3, 0x7d, 0x6b, 0x33, 0xb0, 0x65, 0x75, 0x69, 0xfc, - 0xa8, 0x2c, 0x2b, 0x48, 0x5d, 0x62, 0x50, 0xcd, 0xbc, 0x07, 0x53, 0xb9, 0x8f, 0x3e, 0x56, 0xf2, - 0xe6, 0x0d, 0x60, 0xf9, 0xef, 0x38, 0xba, 0xbb, 0x99, 0xd8, 0xf3, 0x5c, 0xbf, 0x25, 0x6f, 0xc4, - 0xe7, 0x74, 0x77, 0xb3, 0xff, 0xb5, 0xac, 0x67, 0x26, 0x78, 0xd6, 0x57, 0xdd, 0xfb, 0xc6, 0xa9, - 0xfc, 0xf9, 0x7e, 0x63, 0xda, 0xdf, 0xfa, 0xc1, 0xae, 0x03, 0xad, 0xc1, 0x4c, 0xe2, 0x9e, 0x3c, - 0x0f, 0x5a, 0xa9, 0x12, 0x5b, 0x0c, 0x1d, 0xee, 0xda, 0x52, 0x5f, 0xb2, 0xe5, 0x0f, 0x6d, 0xfd, - 0xfe, 0x4e, 0x05, 0xce, 0xf4, 0xf9, 0x0e, 0xb6, 0x97, 0x9d, 0x92, 0xd2, 0xb6, 0xf2, 0xe6, 0xc1, - 0x9f, 0xff, 0x34, 0x26, 0x26, 0xfb, 0x92, 0xcc, 0x74, 0xd4, 0x0c, 0xfc, 0x2d, 0x6f, 0x9b, 0xac, - 0x0a, 0x78, 0x39, 0xf1, 0x30, 0x81, 0x66, 0x53, 0x1c, 0x49, 0x28, 0x7b, 0x0f, 0x06, 0x31, 0xc9, - 0x45, 0x26, 0xa7, 0xae, 0xc0, 0x40, 0xb8, 0x96, 0x15, 0x5a, 0xfc, 0x34, 0xb2, 0x42, 0x0b, 0x00, - 0xfb, 0x22, 0x54, 0xea, 0x1b, 0x0d, 0x1a, 0xe5, 0x09, 0x9d, 0x7c, 0xa3, 0x91, 0x3e, 0x57, 0xe7, - 0x1a, 0xef, 0xca, 0x09, 0x0a, 0x41, 0x78, 0x73, 0x61, 0x8d, 0xc6, 0x58, 0x27, 0xbc, 0xb9, 0xb0, - 0x96, 0x12, 0x6e, 0x9b, 0x61, 0xb4, 0x37, 0x17, 0xd6, 0x3e, 0xbb, 0x45, 0xf4, 0xff, 0x2f, 0xcb, - 0xf4, 0x4c, 0xb2, 0x61, 0xef, 0xc1, 0x98, 0x72, 0x4a, 0xd4, 0x84, 0x1e, 0x79, 0xbb, 0x92, 0xeb, - 0x6f, 0xc6, 0x85, 0xc0, 0x20, 0x50, 0x0f, 0x3f, 0x26, 0xde, 0xb9, 0xfa, 0xed, 0x7b, 0xc2, 0x21, - 0x1b, 0xda, 0x63, 0x92, 0xb0, 0x6b, 0x50, 0x5d, 0xe7, 0xbe, 0xeb, 0xc7, 0x89, 0x99, 0x17, 0x5d, - 0x28, 0x63, 0x84, 0x99, 0x3a, 0x48, 0x82, 0x88, 0xde, 0x7e, 0xbd, 0xcd, 0xa8, 0x19, 0x7a, 0x98, - 0xc6, 0x2d, 0xd9, 0xd9, 0xa5, 0xb7, 0x9f, 0x56, 0x62, 0x32, 0xc8, 0x10, 0x59, 0x3f, 0x5b, 0x82, - 0x61, 0x1a, 0x48, 0xf9, 0x60, 0xf0, 0x76, 0xba, 0x33, 0x91, 0x33, 0xff, 0xb6, 0x97, 0x75, 0xe6, - 0xdf, 0x96, 0xb9, 0xd2, 0x46, 0x28, 0x8a, 0x2e, 0xb9, 0xf0, 0x94, 0x31, 0x8d, 0x14, 0xe2, 0x6e, - 0xbe, 0xc7, 0x9a, 0xa0, 0x1e, 0x35, 0xaa, 0xca, 0xfa, 0x05, 0xfa, 0xb2, 0x9b, 0x0b, 0x6b, 0x6c, - 0x0e, 0xaa, 0x2b, 0x81, 0xcc, 0x3f, 0x48, 0xdf, 0x86, 0x42, 0xac, 0x4d, 0x30, 0xbd, 0x83, 0x14, - 0x9e, 0xf8, 0xbe, 0xb5, 0x30, 0xa0, 0x93, 0x91, 0xf6, 0x7d, 0x5d, 0x09, 0xcc, 0x7c, 0x5f, 0x82, - 0x7a, 0xe4, 0xef, 0xfb, 0xb0, 0x48, 0x48, 0x48, 0x01, 0xb3, 0x04, 0x23, 0x98, 0x9e, 0x79, 0x97, - 0xd3, 0xe3, 0xd4, 0xa3, 0x73, 0xaf, 0xf4, 0x13, 0x10, 0x8b, 0x0a, 0x91, 0x84, 0x54, 0x4a, 0x69, - 0x6d, 0xc0, 0x85, 0xc3, 0xd0, 0xd9, 0x35, 0xa8, 0xb8, 0x8f, 0x54, 0xea, 0xf8, 0x17, 0xfa, 0x55, - 0x52, 0xdf, 0x68, 0x10, 0x7b, 0x81, 0x6d, 0xfd, 0x5a, 0x49, 0x4f, 0xd2, 0x91, 0xc5, 0x61, 0x57, - 0xe1, 0x64, 0xc4, 0xe3, 0x5e, 0x57, 0x59, 0xa7, 0xb6, 0x82, 0xd0, 0x71, 0x43, 0xea, 0x79, 0x7b, - 0x0a, 0xcb, 0xa4, 0x4d, 0xea, 0x46, 0x10, 0xd6, 0x43, 0x9f, 0x5d, 0x80, 0x51, 0xcf, 0x8f, 0xf9, - 0x36, 0x85, 0xbc, 0xc8, 0x1d, 0x51, 0x07, 0xb1, 0x9b, 0x70, 0x3a, 0x69, 0x97, 0x23, 0x9f, 0xfc, - 0x6b, 0x61, 0x28, 0x42, 0x46, 0x0b, 0x97, 0x8f, 0xfd, 0xd9, 0x41, 0x9b, 0xdb, 0x27, 0x13, 0x02, - 0x09, 0x6c, 0x09, 0xa8, 0xc5, 0x0b, 0x7a, 0xfd, 0xc1, 0x35, 0xf4, 0x37, 0xbf, 0xad, 0xc7, 0x08, - 0x52, 0x91, 0x92, 0xcf, 0x33, 0xfd, 0x7a, 0xe6, 0xc1, 0x35, 0xbb, 0x80, 0xca, 0xfa, 0xfd, 0xb2, - 0xce, 0xac, 0xc1, 0xc3, 0xdd, 0x67, 0x78, 0xa7, 0x2d, 0xbe, 0xa3, 0xcd, 0x36, 0xef, 0x80, 0x8d, - 0xf6, 0x8b, 0x99, 0x8d, 0x76, 0xb6, 0x1f, 0x0b, 0xd2, 0x73, 0x8f, 0xbc, 0xd3, 0xfe, 0x78, 0x05, - 0x4e, 0x17, 0x7f, 0x87, 0xde, 0x35, 0xa5, 0x03, 0xba, 0xe6, 0x12, 0x54, 0x6f, 0x05, 0x51, 0xac, - 0xb9, 0x7e, 0xe1, 0xcd, 0xd4, 0x0e, 0xc1, 0xec, 0xa4, 0x94, 0xbd, 0x08, 0x43, 0xe2, 0xef, 0x44, - 0xc6, 0x22, 0x3f, 0xcc, 0x2c, 0xe5, 0xb5, 0x6c, 0x2a, 0x62, 0x37, 0xa1, 0x6a, 0x53, 0xf4, 0x5a, - 0xa6, 0xa7, 0x15, 0x38, 0x51, 0xb0, 0x59, 0x48, 0x10, 0xe3, 0x51, 0x15, 0x82, 0xb1, 0x3a, 0x0c, - 0xd3, 0x64, 0xca, 0x78, 0x35, 0x14, 0xcc, 0x40, 0xf9, 0x29, 0x74, 0x93, 0x6d, 0x2b, 0x3a, 0xb1, - 0x2d, 0xe0, 0xfd, 0xf4, 0xf2, 0xa2, 0x0a, 0x44, 0xc3, 0x6d, 0x41, 0xde, 0x5f, 0x9b, 0x5e, 0x76, - 0x09, 0x22, 0x9b, 0x85, 0xd1, 0x90, 0xb7, 0xdd, 0x3d, 0xb9, 0xe7, 0x50, 0xbf, 0x03, 0x82, 0xe4, - 0x66, 0xf3, 0x1c, 0x8c, 0x48, 0x04, 0xaf, 0x45, 0x56, 0x20, 0xbb, 0x8a, 0x80, 0xe5, 0x56, 0x64, - 0xad, 0xc3, 0x74, 0xbf, 0x31, 0x15, 0xe7, 0xdc, 0xd8, 0x0d, 0xb7, 0x39, 0x1e, 0x2a, 0xda, 0x14, - 0xa5, 0x9c, 0xba, 0x4d, 0xad, 0x63, 0xd9, 0x2d, 0x2c, 0xb2, 0xc7, 0x62, 0xed, 0x97, 0xf5, 0xbd, - 0x32, 0x80, 0x32, 0x2e, 0x3e, 0xb3, 0x8b, 0xe8, 0x8b, 0xc6, 0x22, 0x3a, 0xa5, 0x25, 0x7f, 0x15, - 0xcd, 0x3a, 0xd2, 0x1d, 0xdd, 0x3d, 0x98, 0x30, 0xf1, 0x0f, 0x7f, 0xa3, 0x7b, 0x3d, 0xb5, 0xbb, - 0x52, 0x44, 0x07, 0xde, 0xde, 0x48, 0xb8, 0xb5, 0x09, 0x27, 0x6f, 0xf2, 0x38, 0xb5, 0xc2, 0xaa, - 0x9b, 0xfa, 0x83, 0xd9, 0xbe, 0x06, 0x23, 0x84, 0x9f, 0x6c, 0x8c, 0xd2, 0x64, 0x48, 0x09, 0xe4, - 0xd0, 0x64, 0xa8, 0x10, 0x84, 0xc0, 0x5d, 0xe4, 0x6d, 0x1e, 0xf3, 0xcf, 0xb6, 0x9a, 0x06, 0x30, - 0xd9, 0x14, 0x6c, 0xd9, 0xd1, 0x6a, 0x38, 0xb4, 0x7f, 0x1e, 0xc0, 0xa9, 0xe4, 0xdb, 0x9f, 0x26, - 0xdf, 0xab, 0x30, 0xa9, 0x34, 0x38, 0x8d, 0xe3, 0x01, 0xae, 0x5a, 0xdf, 0x2f, 0xc1, 0x8c, 0xa2, - 0xd8, 0xf0, 0x12, 0x1f, 0xbd, 0x23, 0x11, 0xb3, 0x77, 0x61, 0x54, 0xa3, 0xa1, 0x60, 0x0c, 0xbc, - 0xd6, 0x79, 0xe4, 0xc5, 0x3b, 0x4e, 0x24, 0xe1, 0xfa, 0xb5, 0x8e, 0x86, 0xce, 0x36, 0x61, 0xa6, - 0x51, 0x5f, 0x5d, 0x49, 0xc3, 0xa9, 0xee, 0x06, 0x37, 0x82, 0x76, 0x3b, 0x78, 0x74, 0xdf, 0x5e, - 0x51, 0xef, 0x18, 0x62, 0x5e, 0x1a, 0xbc, 0x23, 0xd2, 0x62, 0xb2, 0xfc, 0xc0, 0xd9, 0x42, 0x44, - 0xa7, 0x17, 0xb6, 0x23, 0xfb, 0x00, 0x2e, 0xd6, 0x7f, 0x51, 0x82, 0xe7, 0x92, 0xd8, 0x9e, 0x82, - 0xf6, 0x65, 0x5a, 0x50, 0x7a, 0x9a, 0x2d, 0x28, 0x3f, 0x95, 0x16, 0xdc, 0x4d, 0xc7, 0x67, 0xd9, - 0x4f, 0x32, 0x00, 0xa8, 0xef, 0x67, 0xfa, 0xf8, 0xd0, 0xa8, 0x9c, 0xcb, 0xe5, 0x14, 0xd0, 0x52, - 0x07, 0x58, 0xef, 0x68, 0x1d, 0x52, 0xc0, 0xd0, 0x20, 0x2e, 0x65, 0x89, 0xbf, 0x57, 0x86, 0xc9, - 0x7b, 0xcb, 0x8b, 0x0b, 0x89, 0xdb, 0xe1, 0x33, 0x2b, 0x34, 0x8b, 0x1d, 0xfb, 0x8c, 0xb6, 0xf5, - 0x97, 0x9c, 0xd6, 0x7d, 0x38, 0x91, 0xe9, 0x06, 0xd4, 0xf3, 0xbe, 0x2a, 0x83, 0x34, 0x12, 0xb0, - 0xd2, 0xf1, 0x4e, 0x17, 0xb1, 0x7f, 0x70, 0xcd, 0xce, 0x60, 0x5b, 0xff, 0x62, 0x2c, 0xc3, 0x97, - 0x84, 0xf1, 0x6b, 0x30, 0xb2, 0x1c, 0x45, 0x3d, 0x1e, 0xde, 0xb7, 0x57, 0x74, 0xdb, 0x9c, 0x87, - 0x40, 0x31, 0x87, 0xec, 0x14, 0x81, 0x5d, 0x86, 0x2a, 0x3d, 0xde, 0xa2, 0xa4, 0x1b, 0x5e, 0x93, - 0x24, 0x6f, 0xbf, 0xd8, 0x49, 0x31, 0x7b, 0x0b, 0xc6, 0xe4, 0xdf, 0x72, 0x46, 0x53, 0x87, 0xa3, - 0x35, 0x9e, 0xd0, 0xe5, 0x0a, 0xb0, 0x0d, 0x34, 0xf6, 0x2a, 0x54, 0xea, 0x0b, 0x36, 0xd9, 0x5f, - 0xe9, 0x68, 0x15, 0x3a, 0xd2, 0x48, 0x6e, 0x9c, 0xb3, 0x17, 0x6c, 0x71, 0x40, 0x52, 0x99, 0x78, - 0xe8, 0xea, 0x08, 0x67, 0x80, 0x32, 0xef, 0x66, 0x54, 0x05, 0x84, 0xb1, 0xab, 0x30, 0xbc, 0xe8, - 0x45, 0xdd, 0xb6, 0xbb, 0x47, 0x17, 0x47, 0xf2, 0xf9, 0x73, 0x09, 0x32, 0x92, 0x88, 0x48, 0x10, - 0xbb, 0xac, 0x5e, 0xfe, 0xad, 0xa6, 0xb1, 0x1e, 0x7d, 0x9e, 0xf7, 0x4d, 0x43, 0xb1, 0x47, 0x0e, - 0x0f, 0xc5, 0xce, 0x07, 0x51, 0xc3, 0xd3, 0x0c, 0xa2, 0xde, 0x84, 0x33, 0x37, 0xd1, 0x5c, 0x6a, - 0xe6, 0xc7, 0xbc, 0x6f, 0x2f, 0xd3, 0x05, 0x14, 0xde, 0xff, 0x4a, 0x8b, 0x6a, 0x36, 0xc5, 0xa6, - 0xd3, 0x0b, 0x3d, 0xfd, 0x4a, 0xbe, 0x0f, 0x23, 0xf6, 0x35, 0x38, 0x59, 0x54, 0x44, 0xd7, 0x54, - 0x98, 0x55, 0xaa, 0xb8, 0x02, 0x3d, 0xab, 0x54, 0x11, 0x07, 0xb6, 0x02, 0x35, 0x09, 0xaf, 0xb7, - 0x3a, 0x9e, 0x2f, 0xaf, 0xda, 0xc6, 0xd3, 0x20, 0x5a, 0xe2, 0xea, 0x8a, 0x42, 0x79, 0xe5, 0x66, - 0x84, 0xeb, 0x64, 0x28, 0xd9, 0x5f, 0x29, 0xc1, 0x98, 0xcd, 0xe5, 0x83, 0x20, 0x28, 0x3e, 0x27, - 0xc8, 0x79, 0x20, 0x89, 0x9f, 0x69, 0xc4, 0xa1, 0xe7, 0x6f, 0x53, 0x28, 0xce, 0x3a, 0x85, 0xe2, - 0xbc, 0xfb, 0x89, 0x42, 0x71, 0x24, 0xab, 0xe8, 0xc9, 0xfe, 0xec, 0x58, 0x48, 0x75, 0xe2, 0x2a, - 0x32, 0xbe, 0x40, 0x74, 0x1d, 0xc6, 0x65, 0xdf, 0xf7, 0xe5, 0x73, 0x04, 0xbc, 0x25, 0x1b, 0x39, - 0x89, 0x82, 0x1d, 0xbb, 0x4e, 0x26, 0x3b, 0xea, 0x25, 0x08, 0xb9, 0x86, 0x16, 0x72, 0x60, 0xf3, - 0xf2, 0xfa, 0x4f, 0x6c, 0xa5, 0x32, 0x2c, 0xb7, 0x96, 0xda, 0x66, 0x54, 0x6c, 0x88, 0x83, 0xd3, - 0x48, 0x9f, 0x3c, 0x06, 0x09, 0xbb, 0x0a, 0x43, 0xab, 0xee, 0xe3, 0x3a, 0xdd, 0xb0, 0x8f, 0xce, - 0x8d, 0x2b, 0xf1, 0x87, 0xc0, 0xf9, 0xea, 0x3f, 0x93, 0xf1, 0x01, 0x9f, 0xb3, 0x09, 0x8d, 0xfd, - 0x99, 0x12, 0x9c, 0x96, 0xcb, 0x58, 0xb5, 0xb2, 0xc1, 0xe3, 0x58, 0xf4, 0x03, 0xa5, 0x13, 0xbe, - 0x90, 0x3c, 0x57, 0x70, 0xaf, 0x18, 0x0f, 0x93, 0x45, 0x58, 0x24, 0x19, 0x92, 0x8e, 0x8b, 0xa8, - 0xd4, 0x78, 0x20, 0xa2, 0x90, 0x9e, 0xad, 0xc3, 0xe8, 0xea, 0x8d, 0x7a, 0x52, 0xed, 0x09, 0xe3, - 0xcc, 0x66, 0x48, 0x3e, 0x0d, 0xad, 0xc8, 0x3b, 0x5f, 0x67, 0x83, 0xc7, 0x91, 0x3b, 0x0b, 0x4b, - 0x98, 0x9e, 0xe1, 0x64, 0x6a, 0xa5, 0xea, 0x3e, 0x6c, 0xf2, 0xec, 0x73, 0x06, 0x09, 0x22, 0x7b, - 0x4f, 0x06, 0x0c, 0x62, 0x16, 0x1f, 0x71, 0x1a, 0x3f, 0x95, 0xa6, 0xdb, 0x92, 0x4f, 0x5e, 0x50, - 0x81, 0x6e, 0x5f, 0xd3, 0x09, 0xd8, 0x3d, 0x50, 0xe9, 0x1e, 0xee, 0xe1, 0x25, 0x36, 0x56, 0x7f, - 0x3a, 0x0d, 0xd2, 0x51, 0x9e, 0x33, 0x81, 0xbc, 0xee, 0xce, 0x7c, 0x48, 0x9e, 0x96, 0xdd, 0x87, - 0x69, 0xee, 0xc7, 0xa1, 0xeb, 0x78, 0x2d, 0xca, 0x0e, 0x9b, 0x5c, 0x82, 0xd1, 0x23, 0xff, 0xea, - 0xfa, 0x67, 0x49, 0xa0, 0x2d, 0x2f, 0xca, 0x6b, 0x71, 0x25, 0x35, 0xed, 0x53, 0x48, 0xbd, 0xdc, - 0x32, 0xc1, 0x14, 0x6f, 0xb0, 0x07, 0xa7, 0x0a, 0xa9, 0xd8, 0x0c, 0x54, 0x5b, 0x5e, 0x94, 0x3e, - 0x45, 0x57, 0xb5, 0x93, 0xdf, 0xec, 0x3c, 0x80, 0xcc, 0xaa, 0x29, 0xaa, 0x55, 0xaa, 0x04, 0x42, - 0xf0, 0x46, 0xf3, 0x65, 0x98, 0xd8, 0x0e, 0xdd, 0xee, 0x8e, 0xc3, 0xfd, 0x56, 0x37, 0xf0, 0x7c, - 0xda, 0x3f, 0xec, 0x71, 0x84, 0x2e, 0x11, 0xd0, 0xfa, 0xa2, 0x9a, 0xa8, 0xec, 0x75, 0x3d, 0x58, - 0xb8, 0x82, 0xa3, 0x34, 0xdc, 0x71, 0x1f, 0x3b, 0xee, 0x36, 0x37, 0xfc, 0x96, 0xe8, 0x1e, 0xef, - 0xa7, 0x4b, 0x70, 0xb6, 0xef, 0x5c, 0x64, 0xd7, 0x41, 0xe6, 0x45, 0xe3, 0x2d, 0x67, 0x27, 0x8e, - 0xbb, 0x91, 0xa3, 0x4e, 0xd6, 0x2a, 0xa5, 0xd5, 0x29, 0x2a, 0xbe, 0x25, 0x4a, 0xd5, 0x61, 0x3b, - 0x62, 0xef, 0xc1, 0x39, 0xcf, 0x8f, 0x78, 0xb3, 0x17, 0x72, 0x47, 0x31, 0x68, 0x7a, 0xad, 0xd0, - 0x09, 0x5d, 0x7f, 0x5b, 0xc5, 0x4a, 0xdb, 0x67, 0x15, 0x0e, 0x65, 0x66, 0x58, 0xf0, 0x5a, 0xa1, - 0x8d, 0x08, 0xd6, 0xdf, 0x28, 0xc3, 0x74, 0xbf, 0xb9, 0xca, 0xa6, 0x61, 0x98, 0xfb, 0x7a, 0x6f, - 0xaa, 0x9f, 0xe2, 0x78, 0x9b, 0x6c, 0xc1, 0xd4, 0x97, 0xd5, 0x26, 0x3d, 0xb2, 0xc6, 0x5e, 0x04, - 0x73, 0xc3, 0xa5, 0x9e, 0x1c, 0x6b, 0xea, 0xdb, 0xee, 0x79, 0x80, 0x74, 0x9f, 0x95, 0x46, 0x55, - 0x7b, 0xc4, 0x6d, 0x86, 0x52, 0x24, 0xb2, 0xd3, 0x30, 0x24, 0xf7, 0x31, 0xb9, 0x31, 0xdb, 0xf4, - 0x4b, 0x28, 0x54, 0xd4, 0xc9, 0xb8, 0x01, 0x57, 0xe6, 0xc7, 0x8c, 0xce, 0x1e, 0xea, 0xc8, 0xc1, - 0x29, 0x9c, 0xcf, 0xc3, 0x9f, 0x7c, 0x3e, 0x5b, 0x7f, 0x69, 0x5c, 0x2a, 0x8b, 0xf5, 0x5e, 0xbc, - 0xa3, 0xd4, 0xcb, 0xb9, 0xa2, 0x68, 0x3a, 0xe9, 0x2e, 0xaf, 0x45, 0xd3, 0x99, 0x31, 0x74, 0xea, - 0x5a, 0xbd, 0x5c, 0x78, 0xad, 0xfe, 0x1a, 0x8c, 0x2c, 0xec, 0xf0, 0xe6, 0xc3, 0x24, 0x44, 0xa9, - 0x4a, 0xf7, 0x96, 0x02, 0x28, 0x1f, 0xe5, 0x49, 0x11, 0xd8, 0x55, 0x00, 0x0c, 0xe2, 0x95, 0xa7, - 0x28, 0xed, 0x61, 0x3d, 0x8c, 0xf9, 0x25, 0x0f, 0x44, 0x0d, 0x05, 0xd9, 0x37, 0xec, 0x1b, 0xba, - 0xcb, 0xa2, 0x64, 0x1f, 0x85, 0x5b, 0x84, 0x9e, 0x22, 0x88, 0xe6, 0x69, 0x3b, 0x08, 0xe9, 0x3b, - 0xb5, 0xdc, 0x36, 0xa3, 0x23, 0xb1, 0x2f, 0xc2, 0xf0, 0x02, 0x0f, 0xe3, 0xf5, 0xf5, 0x15, 0xf4, - 0x13, 0x94, 0xef, 0xc9, 0x55, 0xf1, 0xed, 0xaf, 0x38, 0x6e, 0xff, 0x60, 0x7f, 0x76, 0x3c, 0xf6, - 0x3a, 0x3c, 0x79, 0x27, 0xc7, 0x56, 0xd8, 0x6c, 0x1e, 0x6a, 0xd2, 0x83, 0x28, 0x3d, 0xff, 0xa2, - 0x4e, 0x53, 0x95, 0x1a, 0x16, 0xb9, 0x1b, 0x3d, 0xe2, 0x9b, 0xc9, 0xcb, 0x67, 0x39, 0x7c, 0xb6, - 0xa4, 0x1e, 0x0c, 0xd4, 0x3f, 0x1b, 0x52, 0x19, 0x9a, 0x95, 0xf5, 0xe2, 0xeb, 0xf3, 0x14, 0xac, - 0x0e, 0xe3, 0x0b, 0x41, 0xa7, 0xeb, 0xc6, 0x1e, 0xbe, 0x1f, 0xbe, 0x47, 0xea, 0x0b, 0x4a, 0xd3, - 0xa6, 0x5e, 0x60, 0xe8, 0x42, 0x7a, 0x01, 0xbb, 0x01, 0x13, 0x76, 0xd0, 0x13, 0xdd, 0xae, 0xac, - 0x53, 0x63, 0x69, 0x42, 0xbf, 0x50, 0x94, 0x08, 0x85, 0x8a, 0x4c, 0x51, 0x46, 0x2a, 0x7f, 0x83, - 0x8a, 0xdd, 0x2d, 0xb8, 0x39, 0xd6, 0xd5, 0x12, 0xfd, 0xfd, 0xb3, 0x1c, 0xb3, 0x82, 0x4b, 0xe7, - 0x6b, 0x30, 0xda, 0x68, 0xdc, 0x5b, 0xe7, 0x51, 0x7c, 0xa3, 0x1d, 0x3c, 0x42, 0xad, 0xa4, 0x4a, - 0xcf, 0xf5, 0x46, 0x81, 0x13, 0x8b, 0x15, 0xb1, 0xd5, 0x0e, 0x1e, 0xd9, 0x3a, 0x16, 0xfb, 0xb6, - 0xe8, 0x0f, 0x4d, 0x87, 0xa7, 0x47, 0x0b, 0x0e, 0x3a, 0x66, 0xe0, 0xde, 0x9f, 0x2e, 0x02, 0x71, - 0xd8, 0x30, 0x3b, 0x4b, 0x43, 0xc7, 0x08, 0xba, 0x30, 0x78, 0xbc, 0x57, 0x6f, 0xb5, 0x42, 0x1e, - 0x45, 0xa4, 0x3e, 0xc8, 0x08, 0x3a, 0x34, 0xc2, 0xb9, 0xb2, 0xc0, 0x88, 0xa0, 0xd3, 0x08, 0xd8, - 0x82, 0xd0, 0x6b, 0xc5, 0x28, 0xa2, 0x7f, 0xe9, 0xf2, 0x1a, 0x6a, 0x00, 0x74, 0x45, 0x43, 0x63, - 0x2e, 0x3d, 0x51, 0xbd, 0xae, 0xa9, 0xbe, 0x6a, 0x34, 0x6c, 0x19, 0x26, 0x25, 0x40, 0x2c, 0x2d, - 0xf9, 0x58, 0xe7, 0x89, 0xf4, 0xb9, 0x30, 0x62, 0x83, 0x9b, 0x29, 0x3e, 0xd8, 0xa9, 0xe7, 0x63, - 0xc8, 0xd0, 0xb1, 0xf7, 0x60, 0x02, 0x5f, 0x42, 0x4a, 0x1c, 0x0b, 0x71, 0x23, 0x1f, 0x93, 0x09, - 0xfa, 0xa9, 0x24, 0xe3, 0x6f, 0x38, 0x16, 0x45, 0x3b, 0xa9, 0xbb, 0xe1, 0x7b, 0x30, 0x81, 0x2e, - 0x8d, 0x29, 0x83, 0x53, 0x29, 0x03, 0x2a, 0xc9, 0x32, 0x88, 0xdb, 0x51, 0xca, 0xe0, 0xa7, 0x4a, - 0x70, 0x56, 0x54, 0xe4, 0xc6, 0x62, 0x4c, 0xe5, 0x31, 0x1d, 0x85, 0x02, 0xfa, 0x6b, 0xca, 0x57, - 0xdc, 0x5e, 0xbf, 0xa2, 0x94, 0xca, 0x2b, 0x1a, 0xda, 0x95, 0xdd, 0x37, 0xaf, 0xd4, 0xd3, 0x9f, - 0x0d, 0x45, 0x24, 0x93, 0x9f, 0xf6, 0xe5, 0xa9, 0x2b, 0xef, 0x51, 0xb4, 0x53, 0xc4, 0x01, 0x3f, - 0x4a, 0x7c, 0x7c, 0xf1, 0x47, 0x9d, 0xf9, 0xc4, 0x1f, 0xd5, 0x97, 0xa7, 0xfe, 0x51, 0x71, 0x3b, - 0x2a, 0xfc, 0xa8, 0xeb, 0x30, 0x8e, 0xaa, 0x15, 0xa9, 0xb4, 0x21, 0xbd, 0x11, 0x87, 0x6b, 0xc2, - 0x28, 0xb0, 0xc7, 0xc4, 0xcf, 0x07, 0xf4, 0x8b, 0x7d, 0x11, 0xc8, 0xbb, 0x79, 0x47, 0xa8, 0x0a, - 0x67, 0xd3, 0xc3, 0x63, 0x0a, 0xd5, 0xef, 0xbd, 0x10, 0x8a, 0xb9, 0x3e, 0x13, 0x5b, 0xfd, 0x8c, - 0x66, 0xab, 0xbf, 0x3d, 0x50, 0x1d, 0xae, 0x55, 0x6f, 0x0f, 0x54, 0xa7, 0x6a, 0xcc, 0x1e, 0x49, - 0xc6, 0xd1, 0x3e, 0x55, 0xd8, 0x2e, 0xb4, 0x5c, 0x34, 0xea, 0xab, 0x2b, 0xe9, 0xf1, 0xfb, 0x87, - 0x2b, 0x24, 0xd1, 0x68, 0xdb, 0x01, 0x21, 0x89, 0xf7, 0x65, 0x70, 0xb9, 0xd6, 0x0d, 0xca, 0x72, - 0x61, 0x80, 0xb3, 0x96, 0x8b, 0x0c, 0x8d, 0x9d, 0xc1, 0xb6, 0xfe, 0xe6, 0x78, 0x86, 0x2f, 0x85, - 0x21, 0x58, 0x30, 0x24, 0x0d, 0x13, 0xd4, 0xc9, 0xe8, 0x07, 0x26, 0xcd, 0x16, 0x36, 0x95, 0xb0, - 0xb3, 0x50, 0x69, 0x34, 0xee, 0x51, 0x27, 0x63, 0x30, 0x42, 0x14, 0x05, 0xb6, 0x80, 0x89, 0x11, - 0xc2, 0x08, 0x03, 0xed, 0x15, 0x2a, 0xb1, 0x31, 0xda, 0x08, 0x15, 0xfd, 0xad, 0xcc, 0x04, 0x03, - 0x69, 0x7f, 0x93, 0x99, 0x20, 0x35, 0x0e, 0x2c, 0xc0, 0x74, 0x3d, 0x8a, 0x78, 0x28, 0x66, 0x04, - 0x39, 0xae, 0x87, 0x74, 0x94, 0xa5, 0x1d, 0x1d, 0x2b, 0x75, 0x9b, 0x91, 0xdd, 0x17, 0x91, 0x5d, - 0x82, 0x6a, 0xbd, 0xd7, 0xf2, 0xb8, 0xdf, 0x34, 0x92, 0x45, 0xba, 0x04, 0xb3, 0x93, 0x52, 0xf6, - 0x01, 0x9c, 0xca, 0x64, 0x20, 0xa6, 0x1e, 0x18, 0x4e, 0x85, 0xb4, 0x3a, 0x6a, 0xa7, 0x4e, 0x6e, - 0xb2, 0x4b, 0x8a, 0x29, 0x59, 0x1d, 0x6a, 0x4b, 0x18, 0x82, 0xbb, 0xc8, 0xe5, 0x0d, 0x79, 0x10, - 0x52, 0xca, 0xdc, 0x53, 0xf2, 0x49, 0x68, 0xcc, 0x2c, 0xde, 0x4a, 0x0a, 0xed, 0x1c, 0x3a, 0xbb, - 0x03, 0x27, 0xb2, 0x30, 0xb1, 0xd5, 0x4b, 0x1b, 0x08, 0x0a, 0xc9, 0x1c, 0x17, 0xdc, 0xec, 0x8b, - 0xa8, 0xd8, 0x26, 0x4c, 0xa5, 0x4e, 0x9e, 0xa6, 0x65, 0x44, 0xc5, 0xb0, 0x24, 0xe5, 0xca, 0x3a, - 0xf2, 0x1c, 0x4d, 0xc6, 0x13, 0xa9, 0xc3, 0x68, 0x62, 0x21, 0xb1, 0xf3, 0xec, 0x58, 0x0b, 0x26, - 0x1a, 0xde, 0xb6, 0xef, 0xf9, 0xdb, 0x77, 0xf8, 0xde, 0x9a, 0xeb, 0x85, 0x14, 0x4d, 0x30, 0x9d, - 0xdc, 0xc7, 0xee, 0x75, 0x3a, 0x3c, 0x0e, 0x71, 0xd5, 0x8b, 0x72, 0x0c, 0xcd, 0x2f, 0x61, 0x2e, - 0x38, 0x49, 0x27, 0xc4, 0xbd, 0xd3, 0x75, 0x3d, 0x43, 0x5b, 0x30, 0x79, 0x1a, 0xd6, 0xa9, 0xb1, - 0x23, 0x5a, 0xa7, 0xda, 0x30, 0xb5, 0xe4, 0x37, 0xc3, 0x3d, 0x74, 0x54, 0x50, 0x1f, 0x37, 0x7e, - 0xc8, 0xc7, 0xbd, 0x44, 0x1f, 0x77, 0xce, 0x55, 0x33, 0xac, 0xe8, 0xf3, 0xf2, 0x8c, 0x59, 0x03, - 0xa6, 0xf0, 0x08, 0xb2, 0xbc, 0xb8, 0xb6, 0xec, 0x7b, 0xb1, 0xe7, 0xc6, 0xbc, 0x45, 0x5a, 0x48, - 0xf2, 0x88, 0xa0, 0xb4, 0x42, 0x78, 0xad, 0xae, 0xe3, 0x29, 0x14, 0x9d, 0x69, 0x8e, 0xfe, 0x20, - 0x53, 0xc0, 0xe4, 0x1f, 0x92, 0x29, 0x60, 0x19, 0x26, 0xb3, 0x19, 0x2e, 0x6a, 0xa9, 0xf2, 0x10, - 0x61, 0x91, 0xd0, 0x41, 0x82, 0x1e, 0x6a, 0x9d, 0x46, 0x32, 0xa7, 0x4c, 0x6e, 0x8b, 0x8c, 0x55, - 0x61, 0xca, 0xb0, 0x2a, 0x18, 0x52, 0xe9, 0x38, 0x56, 0x85, 0x35, 0x80, 0x1b, 0x41, 0xd8, 0xe4, - 0x75, 0x8c, 0xfe, 0x67, 0xc6, 0x53, 0xab, 0x82, 0x69, 0x5a, 0x28, 0xd7, 0xcf, 0x96, 0xf8, 0xed, - 0x64, 0xa3, 0xff, 0x35, 0x1e, 0xcc, 0x85, 0x33, 0x6b, 0x21, 0xdf, 0xe2, 0x61, 0xc8, 0x5b, 0x74, - 0x20, 0x9a, 0xf7, 0xfc, 0x96, 0x7a, 0x3f, 0x97, 0xb2, 0xa5, 0x77, 0x15, 0x4a, 0x12, 0x7b, 0xb3, - 0x29, 0x91, 0xf4, 0xbd, 0xb9, 0x0f, 0x9f, 0x9c, 0x55, 0xe3, 0xe4, 0x71, 0xad, 0x1a, 0x4b, 0x30, - 0xb1, 0xec, 0x37, 0xdb, 0xbd, 0x16, 0x27, 0xdf, 0x7c, 0x7a, 0xfd, 0x16, 0x35, 0x43, 0x4f, 0x96, - 0x38, 0xe4, 0xc2, 0xaf, 0xaf, 0x2b, 0x93, 0xe8, 0x40, 0x5b, 0xc6, 0xe9, 0x4f, 0x6c, 0xcb, 0x60, - 0x75, 0x18, 0x6d, 0x86, 0x1c, 0xf3, 0x11, 0xb8, 0xed, 0x88, 0x14, 0xa0, 0xc2, 0x91, 0x5e, 0x48, - 0xd1, 0x6c, 0x9d, 0xc6, 0xba, 0x07, 0xd3, 0xfd, 0x10, 0xd9, 0x35, 0x18, 0x0c, 0xc4, 0x00, 0x66, - 0x9e, 0xca, 0xbf, 0x27, 0x86, 0x4f, 0x4e, 0x63, 0x9d, 0xad, 0xc4, 0xb5, 0x7e, 0x04, 0x4e, 0x17, - 0x23, 0x98, 0x27, 0xfe, 0xd2, 0x61, 0x27, 0xfe, 0x72, 0xfe, 0xc4, 0x6f, 0xfd, 0xe5, 0x72, 0xe6, - 0x6b, 0x8f, 0x66, 0x6a, 0xf8, 0x3c, 0xe4, 0xf7, 0x04, 0xe2, 0x5f, 0xe3, 0xd9, 0x9d, 0x61, 0x0e, - 0x8a, 0x45, 0x3f, 0x99, 0x20, 0x4e, 0x64, 0x09, 0xee, 0x87, 0x6d, 0x76, 0x1d, 0x46, 0xb5, 0xe9, - 0x8e, 0xbb, 0x6f, 0xbf, 0xc5, 0x61, 0xc3, 0x56, 0xba, 0x02, 0x4e, 0x03, 0x6d, 0xfe, 0xca, 0x44, - 0x21, 0x7f, 0xb1, 0x1a, 0x88, 0x3d, 0x5f, 0x6e, 0xac, 0xb6, 0xf8, 0x93, 0x31, 0xc0, 0x9d, 0x9e, - 0xdc, 0x04, 0xf0, 0x6f, 0xeb, 0x2f, 0x8c, 0x4b, 0x1d, 0x4e, 0x37, 0x28, 0xf4, 0x8b, 0xd2, 0xc9, - 0x18, 0x1a, 0xca, 0xc7, 0x31, 0x34, 0x54, 0x0e, 0x37, 0x34, 0x0c, 0x1c, 0x66, 0x68, 0xc8, 0x58, - 0x02, 0x06, 0x8f, 0x69, 0x09, 0x18, 0x3e, 0x96, 0x25, 0xc0, 0x30, 0x52, 0x54, 0x0f, 0x33, 0x52, - 0xfc, 0xb1, 0xdd, 0xe0, 0x59, 0xb5, 0x1b, 0x14, 0x29, 0xf9, 0xc7, 0xb2, 0x1b, 0xe4, 0x8e, 0xfd, - 0x53, 0x4f, 0xe7, 0xd8, 0xcf, 0x9e, 0xda, 0xb1, 0xff, 0xc4, 0xa7, 0x3d, 0xf6, 0x9f, 0x7c, 0x9a, - 0xc7, 0xfe, 0x53, 0x7f, 0x14, 0x8f, 0xfd, 0xa7, 0xff, 0xed, 0x1c, 0xfb, 0xaf, 0x41, 0x75, 0x2d, - 0x88, 0xe2, 0x1b, 0x41, 0xd8, 0xc1, 0x8d, 0x77, 0x8c, 0x6e, 0x59, 0x82, 0x08, 0xd3, 0x2e, 0x77, - 0x0c, 0x5d, 0x99, 0x10, 0xd9, 0xbc, 0x9a, 0x70, 0xea, 0x60, 0x3c, 0x9d, 0x5e, 0x74, 0xd1, 0x4c, - 0xa1, 0xf3, 0x71, 0x7e, 0xbe, 0xa9, 0xf3, 0xf2, 0x5d, 0x98, 0x52, 0x71, 0x82, 0xb8, 0xb5, 0xa2, - 0xcd, 0xe1, 0x6c, 0xba, 0x32, 0xd3, 0x80, 0x42, 0x55, 0x6a, 0xbc, 0xa0, 0x9d, 0x25, 0xed, 0x6b, - 0x4e, 0x18, 0xaa, 0x0d, 0xdf, 0x1e, 0xa8, 0xd6, 0x6a, 0x53, 0x47, 0x30, 0x27, 0xfc, 0x28, 0xd4, - 0xb2, 0x27, 0x9c, 0xc3, 0x1f, 0x04, 0x78, 0x6a, 0x39, 0x6e, 0xc5, 0xf9, 0x2b, 0x7b, 0xc2, 0x60, - 0x57, 0x01, 0xd6, 0x42, 0x6f, 0xd7, 0x8d, 0xf9, 0x1d, 0xe5, 0x88, 0x4e, 0x2f, 0x60, 0x48, 0xa8, - 0x58, 0x06, 0xb6, 0x86, 0x92, 0x1c, 0xae, 0xcb, 0x45, 0x87, 0x6b, 0xeb, 0x2f, 0x96, 0x61, 0x4a, - 0xe6, 0x54, 0x7c, 0xf6, 0x9d, 0x3d, 0xbe, 0x6a, 0x98, 0x4c, 0xce, 0xa5, 0x4f, 0x93, 0xe9, 0xad, - 0x3b, 0xc0, 0xdd, 0xe3, 0x5b, 0x70, 0x2a, 0xd7, 0x15, 0x68, 0x36, 0x59, 0x54, 0xd9, 0x2c, 0x73, - 0x86, 0x93, 0xe9, 0xe2, 0x4a, 0x1e, 0x5c, 0xb3, 0x73, 0x14, 0xd6, 0xaf, 0x0c, 0xe6, 0xf8, 0x93, - 0xe3, 0x87, 0xee, 0xca, 0x51, 0x3a, 0x9e, 0x2b, 0x47, 0xf9, 0x68, 0xae, 0x1c, 0x19, 0x3d, 0xa5, - 0x72, 0x14, 0x3d, 0xe5, 0x03, 0x18, 0x5f, 0xe7, 0x6e, 0x27, 0x5a, 0x0f, 0xe8, 0x0d, 0xb1, 0x01, - 0xe3, 0x3d, 0x29, 0x51, 0xa6, 0x4e, 0xfd, 0x49, 0xf8, 0x46, 0x2c, 0x08, 0xc4, 0x4e, 0x2c, 0x9f, - 0x15, 0xb3, 0x4d, 0x0e, 0xba, 0x29, 0x67, 0xf0, 0x00, 0x53, 0x4e, 0x03, 0xc6, 0x88, 0x2e, 0x7d, - 0x05, 0x21, 0xb5, 0x39, 0x88, 0x22, 0x84, 0xab, 0xda, 0x55, 0x56, 0x85, 0x89, 0xa4, 0x76, 0x69, - 0x6e, 0x30, 0x98, 0x88, 0x2e, 0x50, 0x77, 0x95, 0xa2, 0x0b, 0x86, 0xd3, 0x2e, 0x50, 0xf7, 0x9a, - 0xb2, 0x0b, 0x34, 0x24, 0xf6, 0x2e, 0x4c, 0xd4, 0xd7, 0x96, 0x75, 0xb2, 0x6a, 0xea, 0x4d, 0xe2, - 0x76, 0x3d, 0xc7, 0x20, 0xcd, 0xe0, 0x1e, 0x74, 0xfc, 0x1e, 0xf9, 0x43, 0x3a, 0x7e, 0x67, 0x0f, - 0x8a, 0x70, 0xcc, 0x83, 0xa2, 0xf5, 0xcf, 0xc7, 0x94, 0x7c, 0xf8, 0x6c, 0xef, 0xf7, 0xcc, 0x1b, - 0xbb, 0xca, 0x31, 0x6f, 0xec, 0x06, 0x0e, 0x53, 0x86, 0x35, 0x9d, 0x7b, 0xe8, 0x53, 0xdf, 0xbe, - 0x0d, 0x1f, 0x53, 0x8b, 0xce, 0x2c, 0xbe, 0xea, 0x51, 0x16, 0x5f, 0xa1, 0xe6, 0x3d, 0xf2, 0xe9, - 0x35, 0x6f, 0x38, 0xb6, 0xe6, 0xdd, 0x48, 0xb3, 0xa5, 0x8c, 0x1e, 0x1a, 0xfc, 0x79, 0x9e, 0x6c, - 0x55, 0x53, 0xc5, 0x29, 0x33, 0x93, 0xbc, 0x29, 0x3f, 0x54, 0xea, 0xfc, 0x87, 0xc5, 0xea, 0xfc, - 0xc1, 0x1b, 0xd0, 0x1f, 0x2b, 0xf4, 0x7f, 0xac, 0xd0, 0xff, 0xa1, 0x28, 0xf4, 0xf7, 0x80, 0xb9, - 0xbd, 0x78, 0x47, 0xe8, 0xc5, 0x4d, 0x4c, 0xdb, 0x2c, 0x86, 0x18, 0x55, 0x7b, 0x5a, 0x23, 0xf9, - 0x52, 0x7d, 0x8d, 0x18, 0xa5, 0x68, 0xf3, 0x48, 0x14, 0xeb, 0x69, 0x53, 0xb1, 0x1e, 0xac, 0x0d, - 0x1d, 0x59, 0xb1, 0x0e, 0x71, 0x9d, 0x6d, 0xb8, 0xa1, 0x8f, 0xdb, 0x14, 0x3e, 0xb8, 0x28, 0x93, - 0x07, 0x97, 0xd2, 0x0b, 0x8f, 0x7c, 0xd6, 0x60, 0x85, 0xc5, 0xe6, 0xa0, 0xaa, 0x88, 0xf5, 0x17, - 0xe8, 0x1e, 0x11, 0xcc, 0xc8, 0xcb, 0x4a, 0x30, 0xeb, 0x3f, 0x18, 0x50, 0xb2, 0x5c, 0x34, 0xe3, - 0xff, 0x63, 0xef, 0x5d, 0x63, 0xe4, 0x48, 0x92, 0xc3, 0x60, 0x56, 0xf7, 0x3c, 0x7a, 0x62, 0x5e, - 0x3d, 0xc9, 0x21, 0x39, 0x7c, 0x0e, 0x59, 0xbb, 0xe4, 0xed, 0xce, 0xee, 0x72, 0x97, 0xdc, 0xdb, - 0x07, 0xef, 0xf6, 0x71, 0x3d, 0xdd, 0x3d, 0x9c, 0x26, 0x7b, 0xba, 0xfb, 0xaa, 0x7a, 0xc8, 0x8f, - 0xb7, 0x77, 0x2a, 0x15, 0xbb, 0x6b, 0x66, 0x6a, 0xd9, 0xd3, 0xd5, 0x5b, 0x55, 0x4d, 0x72, 0x0e, - 0xfa, 0x20, 0x9d, 0x64, 0x9f, 0x60, 0x19, 0x27, 0x9d, 0x1e, 0x86, 0x17, 0x07, 0xd9, 0x96, 0x65, - 0x59, 0xd0, 0x0f, 0xc3, 0x16, 0xac, 0x1f, 0x16, 0x74, 0x80, 0x6d, 0xfd, 0x13, 0x2c, 0xc8, 0x92, - 0x1f, 0x90, 0xe1, 0xb3, 0xb0, 0x90, 0x4e, 0x7f, 0xec, 0x83, 0xfc, 0x4b, 0xb0, 0x01, 0x0b, 0x90, - 0x61, 0x64, 0x64, 0x66, 0x55, 0x66, 0x55, 0x75, 0xcf, 0xf0, 0xc8, 0x93, 0x7c, 0xb2, 0xfe, 0x90, - 0xd3, 0x91, 0x11, 0x51, 0xf9, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x68, 0xd9, 0xbe, 0xbd, 0x8f, 0x39, - 0xbc, 0xa3, 0xa5, 0x26, 0xa9, 0xf5, 0x89, 0xd5, 0x99, 0x78, 0x7f, 0xa7, 0x92, 0x7c, 0x57, 0xd1, - 0x9f, 0x5f, 0x86, 0x29, 0xae, 0x55, 0xb1, 0x73, 0x00, 0x6a, 0x14, 0xa9, 0x5c, 0xac, 0x1c, 0x87, - 0xdc, 0x60, 0x2f, 0x6f, 0x79, 0xe8, 0x94, 0x89, 0x38, 0xa9, 0x3d, 0x95, 0x6f, 0x19, 0x49, 0xed, - 0x63, 0x64, 0xf2, 0x26, 0x7b, 0x6d, 0x2a, 0x67, 0xd2, 0x89, 0xde, 0xec, 0xa6, 0x12, 0x28, 0xc4, - 0xa8, 0xf1, 0x39, 0x65, 0xea, 0x49, 0xe2, 0xaa, 0x4f, 0xff, 0xa5, 0xc6, 0x55, 0xaf, 0x02, 0xf0, - 0x3d, 0x37, 0x76, 0xb3, 0xb9, 0x8c, 0x22, 0x89, 0x3f, 0xf9, 0x08, 0xc3, 0xde, 0x88, 0xbc, 0x57, - 0x12, 0xa1, 0xfe, 0x6f, 0x08, 0x2c, 0x99, 0x66, 0xb3, 0xe2, 0xda, 0xbb, 0x7d, 0x2f, 0x08, 0xdd, - 0x4e, 0xad, 0xbf, 0xe3, 0x51, 0x25, 0x3d, 0xda, 0x17, 0xa4, 0x88, 0xd8, 0xf1, 0x9e, 0x10, 0x15, - 0xd3, 0x43, 0x60, 0xd5, 0xf7, 0x85, 0x31, 0x96, 0x1d, 0x02, 0x1d, 0x0a, 0x30, 0x18, 0x9c, 0xea, - 0xc1, 0xe6, 0x10, 0x1f, 0x30, 0x72, 0x5f, 0x26, 0xd4, 0x83, 0x03, 0x06, 0x32, 0x44, 0x19, 0x71, - 0xd2, 0x13, 0x96, 0x9f, 0x8b, 0x4e, 0x29, 0xd1, 0xd9, 0xe3, 0x62, 0xb6, 0xa2, 0xb9, 0x56, 0x82, - 0xb2, 0x7c, 0x80, 0x70, 0xd9, 0x27, 0x37, 0xb5, 0x06, 0x0e, 0xe0, 0x84, 0x12, 0xe6, 0xe4, 0xa8, - 0x77, 0x7d, 0x6b, 0x5c, 0xef, 0xd6, 0xf1, 0xdd, 0x43, 0xc6, 0x85, 0x9f, 0x9c, 0x94, 0x3b, 0xf3, - 0x0b, 0xe4, 0x6f, 0x69, 0x70, 0x3e, 0xb3, 0x24, 0x5a, 0xdd, 0xb3, 0x4a, 0x84, 0x7c, 0x49, 0x68, - 0xb0, 0xf4, 0xe3, 0xa3, 0x3e, 0x6d, 0x65, 0x88, 0x82, 0xf1, 0x5f, 0x22, 0xdf, 0xd4, 0xe0, 0x94, - 0x82, 0x11, 0xc9, 0xd0, 0x20, 0x8a, 0x44, 0x96, 0x39, 0xaf, 0x3f, 0x7c, 0x36, 0xf3, 0xfa, 0x39, - 0xb5, 0x2d, 0xb1, 0xb4, 0x54, 0x12, 0xe8, 0x8e, 0xa8, 0x21, 0x79, 0x08, 0x4b, 0x58, 0x24, 0xee, - 0x1d, 0xe9, 0x9c, 0xe5, 0xd7, 0x95, 0xcb, 0x71, 0xb5, 0x59, 0xe8, 0x1e, 0xaa, 0x71, 0xaf, 0x5f, - 0xff, 0xd6, 0x27, 0xab, 0xf3, 0x0a, 0xba, 0x88, 0x39, 0x6f, 0xc5, 0x97, 0x97, 0x6e, 0x7f, 0xc7, - 0x53, 0xcc, 0x2c, 0xc9, 0x4f, 0x90, 0x7f, 0xa5, 0xb1, 0xbb, 0x0b, 0xd6, 0x8c, 0x0d, 0xdf, 0xdb, - 0x8f, 0xca, 0x85, 0x73, 0xf7, 0x88, 0x6e, 0xeb, 0x3d, 0x9b, 0x6e, 0xbb, 0x8c, 0x55, 0x66, 0x32, - 0xc1, 0xda, 0xf1, 0xbd, 0xfd, 0xb8, 0xfa, 0x72, 0xc7, 0x8d, 0xac, 0x24, 0xf9, 0x8a, 0x06, 0xa7, - 0x15, 0x93, 0xab, 0x9c, 0xd5, 0x88, 0x07, 0x48, 0x8a, 0x1e, 0x19, 0x4a, 0x45, 0xeb, 0x57, 0xf9, - 0xfc, 0xbf, 0x82, 0x35, 0x88, 0x77, 0x0b, 0xac, 0x8b, 0xb5, 0xcf, 0xb0, 0xa4, 0x2a, 0x8c, 0xfe, - 0x0a, 0x71, 0x61, 0x09, 0xbd, 0xc5, 0x94, 0x47, 0x08, 0xcb, 0xa3, 0x1f, 0x21, 0x44, 0x99, 0xf5, - 0x30, 0xed, 0xc7, 0xe8, 0x97, 0x08, 0x69, 0xae, 0xe4, 0xff, 0x87, 0xd3, 0x29, 0x60, 0xb4, 0xda, - 0x4e, 0x8c, 0x5c, 0x6d, 0x2f, 0x7d, 0xe7, 0x93, 0xd5, 0x4f, 0x65, 0x7d, 0x2d, 0x6b, 0xa5, 0x8d, - 0xfe, 0x02, 0xb1, 0x01, 0xe2, 0x42, 0xae, 0x13, 0x65, 0x4f, 0xd0, 0x97, 0xf8, 0xfc, 0x90, 0xf0, - 0xa9, 0x2c, 0x97, 0xea, 0x20, 0x6f, 0x79, 0x31, 0x12, 0x71, 0x60, 0x4e, 0x4a, 0x79, 0x72, 0xc0, - 0xef, 0x0f, 0x47, 0x7c, 0xe4, 0x5b, 0x9f, 0xac, 0x2a, 0xd8, 0xf4, 0x64, 0x24, 0xe7, 0x52, 0x51, - 0x54, 0x50, 0x19, 0x91, 0xfc, 0x9a, 0x06, 0xcb, 0x14, 0x10, 0x4f, 0x2a, 0xde, 0xa8, 0x95, 0x71, - 0xb3, 0x7e, 0xef, 0xd9, 0xcc, 0xfa, 0x4b, 0x58, 0x47, 0x79, 0xd6, 0xa7, 0xba, 0x24, 0xb3, 0x72, - 0x38, 0xdb, 0x15, 0xc7, 0x44, 0x65, 0xb6, 0x9f, 0x3e, 0xc2, 0x6c, 0x67, 0x03, 0x70, 0xf8, 0x6c, - 0x1f, 0xf9, 0x15, 0xd2, 0x86, 0x39, 0x7e, 0x28, 0x62, 0x1d, 0x76, 0x41, 0x79, 0xc8, 0x2b, 0x17, - 0xb1, 0x93, 0x2a, 0xcf, 0x08, 0x93, 0x6a, 0xa1, 0xc2, 0x85, 0xf4, 0xe1, 0x38, 0xfb, 0xad, 0x5a, - 0xad, 0x56, 0x47, 0x5a, 0xad, 0x5e, 0xe0, 0x2d, 0xba, 0xc8, 0xf9, 0x27, 0x8c, 0x57, 0x72, 0xe4, - 0xc7, 0x0c, 0xc6, 0x64, 0x00, 0x44, 0x01, 0xb3, 0x45, 0x7b, 0x71, 0xbc, 0xad, 0xea, 0x53, 0xfc, - 0x9b, 0xab, 0xc9, 0x6f, 0x26, 0x57, 0x6e, 0x06, 0x6f, 0x62, 0xc3, 0x22, 0x87, 0x7a, 0x0f, 0x1c, - 0x26, 0xe1, 0x2f, 0x29, 0xb1, 0x37, 0x13, 0xa5, 0xec, 0x64, 0x27, 0xbe, 0x84, 0xb1, 0x51, 0x13, - 0x02, 0x3d, 0xc9, 0x8f, 0x34, 0x61, 0xa9, 0x34, 0x18, 0xf4, 0x5c, 0xa7, 0x8b, 0xad, 0x34, 0x86, - 0xb4, 0x4d, 0x7a, 0x9c, 0xd3, 0xd4, 0x66, 0x85, 0xfc, 0xb8, 0xe9, 0x0f, 0x13, 0xe2, 0x26, 0x45, - 0xab, 0xff, 0xb8, 0x96, 0xaa, 0x34, 0x79, 0x19, 0x66, 0xf0, 0x87, 0x14, 0x46, 0x0d, 0x6d, 0x37, - 0xac, 0x8a, 0x68, 0x15, 0x8a, 0x11, 0xa8, 0xb2, 0x24, 0x87, 0x74, 0xce, 0x33, 0x65, 0x89, 0x1b, - 0x18, 0x62, 0x93, 0xc2, 0xaa, 0x78, 0x1c, 0x96, 0x8f, 0x95, 0x2e, 0x3c, 0x9a, 0xf0, 0x27, 0x61, - 0xfa, 0xaf, 0xe6, 0xd4, 0x69, 0x47, 0x5e, 0x90, 0xf4, 0x76, 0x29, 0xa8, 0xb4, 0xd0, 0xdb, 0x25, - 0x6d, 0xfd, 0x1f, 0x6b, 0x70, 0xbc, 0x29, 0x65, 0xda, 0x6e, 0x7b, 0x38, 0x2e, 0xe3, 0x73, 0x4a, - 0x3f, 0xab, 0x34, 0xb7, 0x72, 0x8a, 0x6f, 0x3a, 0x53, 0x70, 0xca, 0x18, 0x59, 0xf5, 0xc1, 0x87, - 0xc3, 0x58, 0x31, 0x29, 0xdb, 0x30, 0x43, 0x67, 0xf0, 0x27, 0xcc, 0x0f, 0xa3, 0xff, 0x64, 0x0e, - 0x66, 0xa5, 0x15, 0x43, 0x3e, 0x0d, 0x73, 0xf2, 0x67, 0x65, 0xc3, 0x9f, 0x5c, 0x4b, 0x43, 0xc1, - 0x42, 0xcb, 0x9f, 0x63, 0xef, 0x2b, 0x96, 0x3f, 0xba, 0x2e, 0x10, 0xfa, 0x84, 0x27, 0xa1, 0xf7, - 0x33, 0x4e, 0x42, 0x38, 0xcb, 0x25, 0x4b, 0xcf, 0xd8, 0xf3, 0xd0, 0x3b, 0xe9, 0xf3, 0x10, 0x1a, - 0x9d, 0x24, 0xfa, 0xd1, 0xa7, 0x22, 0xfd, 0xa7, 0x35, 0x28, 0x26, 0xd7, 0xf4, 0xf7, 0xa4, 0x57, - 0x9e, 0xe0, 0x9a, 0xe8, 0x6b, 0xb9, 0x28, 0x3d, 0x92, 0x88, 0xe8, 0xf0, 0xfd, 0xea, 0xf4, 0xfa, - 0xae, 0x72, 0x83, 0x73, 0x56, 0x8d, 0x9b, 0x2b, 0x07, 0xb4, 0xca, 0x0e, 0x96, 0x3d, 0xf1, 0xf1, - 0x2f, 0xac, 0x1e, 0xd3, 0xef, 0xc1, 0x72, 0xb2, 0x3b, 0xf0, 0x16, 0xa7, 0x04, 0x8b, 0x2a, 0x3c, - 0x99, 0x5c, 0x2d, 0x49, 0x65, 0x24, 0xf1, 0xf5, 0xdf, 0xcb, 0x25, 0x79, 0x73, 0x07, 0x58, 0x2a, - 0xa3, 0x64, 0x27, 0x1d, 0x2e, 0xa3, 0x18, 0xc8, 0x10, 0x65, 0x4f, 0x92, 0xa9, 0x31, 0x8a, 0x01, - 0x90, 0xcf, 0x8e, 0x01, 0x40, 0xde, 0x4c, 0x3c, 0x0e, 0x90, 0xe2, 0x2a, 0x3e, 0x72, 0xee, 0x5b, - 0xf1, 0x03, 0x81, 0xd4, 0x9b, 0x80, 0x65, 0x25, 0x59, 0x89, 0xa0, 0x9f, 0x8c, 0x6d, 0xee, 0x21, - 0x16, 0x30, 0xe2, 0x4c, 0x64, 0xb2, 0x09, 0xd3, 0xb4, 0x9a, 0x5b, 0xf6, 0x80, 0x5f, 0xce, 0x90, - 0x28, 0x4a, 0x49, 0x2f, 0x3a, 0x1f, 0x4a, 0x81, 0x4a, 0x7a, 0x0e, 0xd5, 0x10, 0xe4, 0x89, 0xc5, - 0x11, 0xf5, 0x3f, 0xd5, 0xe8, 0xfa, 0xef, 0x3c, 0xf8, 0x2b, 0x96, 0x19, 0x91, 0x36, 0x69, 0x8c, - 0x7f, 0xf6, 0x1f, 0xe5, 0x58, 0xc2, 0x2b, 0x3e, 0x7d, 0x6e, 0xc0, 0x14, 0x0b, 0x5f, 0xc2, 0x5d, - 0xd2, 0x64, 0x2e, 0xac, 0x20, 0x8e, 0xfa, 0xc8, 0x22, 0x9b, 0x18, 0x9c, 0x40, 0x36, 0x9d, 0xe5, - 0x8e, 0x64, 0x3a, 0x93, 0xec, 0xf9, 0xf9, 0x67, 0x66, 0xcf, 0xff, 0x42, 0x94, 0xdb, 0xaa, 0x14, - 0x1e, 0x21, 0x17, 0xc6, 0xc5, 0x64, 0x2a, 0xb9, 0x54, 0xd6, 0x92, 0x98, 0x1d, 0x79, 0x53, 0x4e, - 0x4e, 0x27, 0x3d, 0x46, 0x3f, 0x24, 0x0d, 0x9d, 0xfe, 0xb5, 0x49, 0xd6, 0xc7, 0xbc, 0xa3, 0xae, - 0x28, 0x21, 0x37, 0x70, 0x9d, 0x24, 0x6c, 0x9d, 0x2c, 0xf8, 0xc6, 0x15, 0x98, 0xa0, 0x73, 0x93, - 0xf7, 0x26, 0xe2, 0xd1, 0xf9, 0x2b, 0xe3, 0xd1, 0x72, 0xba, 0x96, 0x71, 0x4f, 0x92, 0x53, 0xa9, - 0xe2, 0xb6, 0x25, 0xaf, 0x65, 0xc4, 0xa0, 0x2d, 0x88, 0x92, 0x50, 0xc9, 0x2d, 0xd8, 0xdf, 0xb1, - 0xd3, 0x39, 0x22, 0x63, 0x54, 0x52, 0x85, 0x85, 0xbb, 0x6e, 0xbf, 0xeb, 0x3d, 0x0a, 0x2a, 0x4e, - 0xf0, 0x20, 0xf4, 0x06, 0xdc, 0x27, 0x1d, 0xed, 0xfe, 0x8f, 0x58, 0x89, 0xd5, 0x65, 0x45, 0xf2, - 0x25, 0x89, 0x4a, 0x44, 0xd6, 0x61, 0x5e, 0x89, 0xe0, 0xce, 0xef, 0x3e, 0xd1, 0xc6, 0xa9, 0xc6, - 0x7f, 0x97, 0x6d, 0x9c, 0x0a, 0x09, 0xdd, 0xa5, 0x79, 0xfd, 0xa5, 0x1b, 0xd0, 0x54, 0xdd, 0x39, - 0x0e, 0x79, 0x1d, 0x0a, 0x2c, 0x78, 0x4f, 0xad, 0x22, 0x5f, 0x5a, 0x05, 0x08, 0x4b, 0x04, 0x94, - 0x13, 0x88, 0xa4, 0x0c, 0xf3, 0xeb, 0x5e, 0x58, 0xeb, 0x07, 0xa1, 0xdd, 0xef, 0x38, 0x51, 0xbc, - 0x7a, 0x6c, 0xec, 0x7d, 0x2f, 0xb4, 0x5c, 0x5e, 0xa2, 0xd2, 0xab, 0x34, 0xb4, 0xab, 0x6f, 0x79, - 0x6e, 0x9f, 0x89, 0xce, 0xd9, 0xb8, 0xab, 0x3f, 0xf4, 0xdc, 0x7e, 0x2a, 0x44, 0x7c, 0x8c, 0x4a, - 0xde, 0x87, 0xb9, 0xba, 0xdb, 0x1f, 0x3e, 0x16, 0x1d, 0x3d, 0x17, 0xdf, 0x93, 0xf5, 0x28, 0x3c, - 0xa3, 0x9b, 0x15, 0x82, 0xcf, 0x2c, 0xd3, 0xbd, 0xe7, 0xf7, 0xfe, 0xe1, 0xea, 0xb1, 0xaf, 0xff, - 0xe2, 0xea, 0xb1, 0x8f, 0x7f, 0x91, 0x45, 0xdc, 0x61, 0xaf, 0x72, 0x8d, 0x89, 0x86, 0xd7, 0x75, - 0x74, 0x9e, 0xe3, 0x8e, 0x47, 0x2a, 0x7f, 0x09, 0xa6, 0xd9, 0x4a, 0x4e, 0xa6, 0xf5, 0x8e, 0xa7, - 0xac, 0x21, 0x30, 0x88, 0x0e, 0xf3, 0x6e, 0xdf, 0x62, 0xbe, 0x98, 0x5e, 0xbf, 0xc7, 0xf2, 0xa1, - 0x15, 0x8c, 0x59, 0xb7, 0x8f, 0x1e, 0x98, 0xcd, 0x7e, 0xef, 0x40, 0x7f, 0x0d, 0x8a, 0x5c, 0x3a, - 0x47, 0x59, 0x84, 0xd1, 0xf7, 0xa3, 0x56, 0x31, 0x64, 0x89, 0xda, 0x71, 0xbb, 0xbe, 0x81, 0x50, - 0x7c, 0x78, 0xdb, 0x70, 0xc2, 0x47, 0x9e, 0xff, 0xc0, 0x70, 0x82, 0xd0, 0x77, 0x59, 0x52, 0x62, - 0x94, 0x49, 0x9f, 0x26, 0xef, 0xc0, 0x24, 0x7a, 0x94, 0x27, 0x36, 0xc9, 0xe4, 0x37, 0xd6, 0xe7, - 0xf9, 0x5a, 0x9e, 0x44, 0xf7, 0x74, 0x83, 0x11, 0x91, 0x1b, 0x30, 0x51, 0x71, 0xfa, 0x07, 0x89, - 0xd4, 0xa2, 0x29, 0xe2, 0x48, 0x36, 0x76, 0x9d, 0xfe, 0x81, 0x81, 0x24, 0xfa, 0x4f, 0xe7, 0xe0, - 0x44, 0x46, 0xb5, 0xee, 0x7c, 0xfa, 0xfb, 0x74, 0x83, 0x58, 0x57, 0x36, 0x08, 0x71, 0xe7, 0x3f, - 0xb2, 0xe3, 0x33, 0xf7, 0x8b, 0x9f, 0xd7, 0xe0, 0x94, 0xba, 0xaa, 0xf9, 0x13, 0x92, 0x3b, 0xaf, - 0x93, 0xcf, 0xc2, 0xd4, 0xa6, 0x63, 0x77, 0x1d, 0x91, 0x47, 0x30, 0x99, 0x36, 0x9c, 0x15, 0x32, - 0xb6, 0x71, 0xb4, 0x00, 0x06, 0x25, 0x15, 0x5e, 0x39, 0x76, 0x92, 0xd1, 0x45, 0x54, 0xa9, 0xac, - 0x4f, 0x8d, 0xf1, 0x9c, 0xf9, 0x38, 0x07, 0x67, 0xc7, 0xd0, 0xd0, 0x81, 0xa3, 0x43, 0x2f, 0x0f, - 0x1c, 0x2a, 0x17, 0x08, 0x25, 0xef, 0xc1, 0x62, 0x9b, 0x9f, 0x84, 0xc4, 0x70, 0xe4, 0x62, 0x21, - 0x23, 0x0e, 0x49, 0xc2, 0x83, 0xcc, 0x48, 0x22, 0x2b, 0x21, 0xd8, 0xf2, 0x63, 0x43, 0xb0, 0xc9, - 0x11, 0xcd, 0x26, 0xbe, 0xcb, 0x88, 0x66, 0x93, 0xe3, 0x23, 0x9a, 0x4d, 0x25, 0x22, 0x9a, 0x7d, - 0x43, 0x83, 0x65, 0xb5, 0x6b, 0xb8, 0x00, 0x88, 0xc3, 0xc1, 0x69, 0xa3, 0xc3, 0xc1, 0x8d, 0x0f, - 0x88, 0xfe, 0x3e, 0x4c, 0x99, 0x38, 0xc4, 0xd8, 0xec, 0x85, 0x48, 0x61, 0x4e, 0x0c, 0x05, 0xa2, - 0xb0, 0x37, 0x59, 0x3c, 0x66, 0x14, 0x27, 0xd3, 0x7f, 0x57, 0x83, 0xa2, 0x8a, 0xfc, 0xb4, 0xf3, - 0xe9, 0x5d, 0x65, 0x3e, 0x8d, 0xa8, 0xd0, 0xe8, 0x40, 0x7f, 0xef, 0x47, 0x81, 0xfe, 0xf2, 0xe3, - 0x18, 0xb0, 0x14, 0xb9, 0x20, 0x07, 0xc1, 0x66, 0xff, 0xeb, 0xb7, 0x93, 0xbd, 0x1d, 0x85, 0xa9, - 0xe4, 0x6d, 0xe7, 0x09, 0xa2, 0xc7, 0x75, 0x95, 0xc1, 0x51, 0xf5, 0xff, 0x9e, 0x1a, 0xbb, 0x23, - 0xcd, 0x67, 0x1d, 0xa6, 0x2a, 0xde, 0xbe, 0xed, 0x8a, 0x69, 0x8c, 0xf5, 0xec, 0x22, 0xc4, 0xe0, - 0x25, 0x47, 0x0b, 0x06, 0x78, 0x11, 0x26, 0x1b, 0x5e, 0xbf, 0x54, 0xe1, 0x7e, 0xe8, 0xc8, 0xa7, - 0xef, 0xf5, 0x2d, 0xbb, 0x6b, 0xb0, 0x02, 0x52, 0x07, 0x30, 0x3b, 0xbe, 0xe3, 0xf4, 0x4d, 0xf7, - 0xcb, 0x4e, 0x42, 0x05, 0xa5, 0xe3, 0xd5, 0x1b, 0xa2, 0x98, 0x65, 0xf7, 0xf2, 0x88, 0x68, 0x05, - 0xee, 0x97, 0xe5, 0x2d, 0x5b, 0xa2, 0x47, 0x29, 0xc3, 0x83, 0xde, 0x26, 0x66, 0xc5, 0xb5, 0xef, - 0x85, 0x94, 0xc9, 0xfc, 0x14, 0xf6, 0xf0, 0xb5, 0x4c, 0x29, 0xf3, 0xbb, 0x1a, 0x9c, 0x1d, 0x43, - 0xf3, 0x0c, 0x46, 0xe5, 0x2f, 0xba, 0xc3, 0x1d, 0x80, 0x98, 0x08, 0x93, 0xf3, 0xbb, 0x5d, 0xfe, - 0x2e, 0x65, 0x9e, 0x27, 0xe7, 0xa7, 0x00, 0x25, 0x39, 0x3f, 0x05, 0x50, 0x75, 0x6c, 0xd3, 0x71, - 0x77, 0xf7, 0x98, 0x03, 0xe0, 0x3c, 0x93, 0x94, 0x7b, 0x08, 0x91, 0xd5, 0x31, 0x86, 0xa3, 0xff, - 0xfb, 0x29, 0x38, 0x6d, 0x38, 0xbb, 0x2e, 0x3d, 0xb0, 0x6e, 0x07, 0x6e, 0x7f, 0x57, 0x89, 0x45, - 0xa7, 0x27, 0x04, 0x11, 0xcf, 0x73, 0x46, 0x21, 0xd1, 0x4c, 0x7c, 0x11, 0x0a, 0x54, 0x87, 0x91, - 0x64, 0x11, 0x5e, 0x7e, 0xf6, 0xbd, 0xae, 0xc3, 0x73, 0x72, 0x88, 0x62, 0xb2, 0xc6, 0xf5, 0x67, - 0x29, 0x13, 0x25, 0xd5, 0x9f, 0xff, 0xec, 0x93, 0x55, 0x60, 0xa9, 0xf9, 0x31, 0xb8, 0x2a, 0xd3, - 0xa1, 0xa3, 0x43, 0xee, 0xc4, 0x88, 0x43, 0xee, 0x16, 0x2c, 0x97, 0xba, 0x4c, 0x57, 0xb0, 0x7b, - 0x2d, 0xdf, 0xed, 0x77, 0xdc, 0x81, 0xdd, 0x13, 0x86, 0x1b, 0xec, 0x65, 0x3b, 0x2a, 0xb7, 0x06, - 0x11, 0x82, 0x91, 0x49, 0x46, 0x9b, 0x51, 0x69, 0x98, 0x18, 0xe8, 0x8c, 0xdf, 0x6b, 0x63, 0x33, - 0xba, 0xfd, 0x00, 0x5b, 0x11, 0x18, 0x51, 0x31, 0x1e, 0xaf, 0xd1, 0x8b, 0xa1, 0x5d, 0x37, 0x6f, - 0x3b, 0x2c, 0x42, 0x95, 0x48, 0x94, 0xc5, 0xbc, 0x56, 0xc2, 0x5e, 0x80, 0xce, 0xb6, 0x0a, 0x5e, - 0x4c, 0x67, 0x9a, 0x9b, 0x94, 0xae, 0x90, 0xa2, 0x0b, 0x82, 0x3d, 0x99, 0x8e, 0xe1, 0x91, 0x57, - 0xe9, 0x54, 0xd8, 0xf7, 0x42, 0x07, 0xa7, 0xf0, 0x4c, 0x7c, 0x18, 0xf7, 0x11, 0xca, 0x0e, 0xe3, - 0x12, 0x0a, 0x79, 0x07, 0x8e, 0x57, 0xcb, 0xd7, 0xc5, 0x6d, 0x44, 0xc5, 0xeb, 0x0c, 0xd1, 0xcb, - 0x04, 0xf0, 0x7b, 0x38, 0x86, 0x4e, 0xe7, 0x3a, 0x95, 0x26, 0x59, 0x68, 0xe4, 0x0a, 0x4c, 0xd7, - 0x2a, 0xb2, 0x96, 0xcc, 0xb3, 0xc1, 0x72, 0xd7, 0x39, 0x51, 0x48, 0x9a, 0xf1, 0x69, 0x71, 0xee, - 0xd0, 0x63, 0xdd, 0xe9, 0x23, 0x9c, 0x14, 0x6f, 0x24, 0xb5, 0x7c, 0x29, 0x25, 0x4a, 0x42, 0xcb, - 0x4f, 0xea, 0xf6, 0x6f, 0x23, 0xe9, 0x4d, 0xa7, 0xef, 0xf8, 0x71, 0x2a, 0x94, 0x49, 0xd6, 0xb7, - 0x94, 0x74, 0x37, 0x2a, 0x31, 0x54, 0x44, 0x62, 0xc0, 0x89, 0x96, 0xef, 0x3c, 0x74, 0xbd, 0x61, - 0xa0, 0x7e, 0x7c, 0x31, 0x3e, 0x09, 0x0d, 0x38, 0x82, 0x95, 0xac, 0x45, 0x36, 0x29, 0xcf, 0x7e, - 0xcb, 0x02, 0x04, 0x97, 0xbd, 0xae, 0x13, 0x30, 0x09, 0xf4, 0x57, 0x28, 0xfb, 0xad, 0xd4, 0xb6, - 0x31, 0x52, 0xf9, 0x1b, 0x98, 0xfd, 0x36, 0x85, 0x4b, 0xde, 0x86, 0x49, 0xfc, 0xc9, 0xcf, 0x0f, - 0xc7, 0x33, 0xd8, 0xc6, 0x67, 0x87, 0x0e, 0xc5, 0x34, 0x18, 0x01, 0xa9, 0xc1, 0x34, 0x3f, 0xc5, - 0x3f, 0x49, 0x0e, 0x47, 0x6e, 0x0e, 0x60, 0xb3, 0x8d, 0xd3, 0xeb, 0x5d, 0x98, 0x93, 0x3f, 0x48, - 0x57, 0xd9, 0xa6, 0x1d, 0xec, 0x39, 0x5d, 0xfa, 0x8b, 0xa7, 0x5f, 0xc6, 0x55, 0xb6, 0x87, 0x50, - 0x8b, 0xd6, 0xc3, 0x90, 0x50, 0xe8, 0x3e, 0x5d, 0x0b, 0xb6, 0x03, 0x5e, 0x15, 0x6e, 0xd7, 0x73, - 0xd1, 0x46, 0xdc, 0x35, 0x78, 0x91, 0xfe, 0x05, 0x58, 0x6e, 0x0c, 0x7b, 0x3d, 0xfb, 0x7e, 0xcf, - 0x11, 0xe9, 0xf9, 0x42, 0x3b, 0x74, 0xc8, 0x3a, 0x4c, 0xe2, 0x1f, 0x5c, 0xe7, 0x38, 0x1e, 0xe5, - 0x3f, 0x8c, 0x71, 0xd0, 0x75, 0x5a, 0xc3, 0x40, 0x76, 0xf4, 0xa7, 0x12, 0xc8, 0x8e, 0x02, 0xf4, - 0xdf, 0xd2, 0x60, 0x59, 0xf8, 0xa6, 0xf8, 0x76, 0xe7, 0x81, 0xe3, 0x73, 0xfd, 0xf1, 0x8a, 0x32, - 0xd7, 0x70, 0x11, 0x24, 0xa6, 0x11, 0x9b, 0x75, 0xb7, 0x44, 0x25, 0x54, 0x95, 0x2c, 0xab, 0xc2, - 0x87, 0x55, 0x86, 0xbc, 0x03, 0xb3, 0x7c, 0xcb, 0x95, 0x22, 0x94, 0x63, 0x1c, 0x4d, 0x7e, 0x3c, - 0x4e, 0x7a, 0x4a, 0xc9, 0xe8, 0xfa, 0x4f, 0x6a, 0x50, 0x54, 0x9b, 0xf2, 0xb4, 0x7a, 0x45, 0xb6, - 0xb6, 0xa9, 0x7e, 0x63, 0xcc, 0xd4, 0xfd, 0xd7, 0xb3, 0xc9, 0xbe, 0xe5, 0x73, 0xf7, 0x4d, 0x39, - 0x74, 0xac, 0x16, 0x5b, 0x11, 0xe2, 0xd0, 0xb1, 0xb2, 0x15, 0x21, 0x42, 0x8d, 0xc6, 0x24, 0x77, - 0xc8, 0x98, 0xbc, 0x27, 0xc6, 0x24, 0x3f, 0x7a, 0x62, 0x1c, 0x1f, 0x33, 0x0e, 0x66, 0xbc, 0x42, - 0x26, 0x8e, 0x64, 0xc3, 0x3b, 0x86, 0xa9, 0x9c, 0x18, 0x49, 0x52, 0x32, 0x73, 0x4e, 0xb2, 0x61, - 0x70, 0xf2, 0xe8, 0x4c, 0x0f, 0x11, 0xf7, 0x9f, 0x81, 0xb9, 0x52, 0x18, 0xda, 0x9d, 0x3d, 0xa7, - 0x5b, 0xa1, 0xe2, 0x49, 0x8a, 0x0d, 0x69, 0x73, 0xb8, 0x6c, 0x52, 0x91, 0x71, 0x59, 0x3a, 0x00, - 0x3b, 0xe0, 0x1e, 0xd7, 0x51, 0x3a, 0x00, 0x0a, 0x51, 0xd3, 0x01, 0x50, 0x08, 0x79, 0x15, 0xa6, - 0x6b, 0xfd, 0x87, 0x2e, 0xed, 0x13, 0x16, 0x1e, 0x12, 0x0d, 0xa1, 0x2e, 0x03, 0xc9, 0xc2, 0x95, - 0x63, 0x91, 0x1b, 0xd2, 0xb1, 0x71, 0x26, 0x36, 0x35, 0x89, 0x18, 0xd3, 0xbc, 0x48, 0x3e, 0x12, - 0x46, 0xe7, 0xc8, 0x37, 0x61, 0x5a, 0x98, 0xcd, 0x21, 0xde, 0x41, 0x38, 0x65, 0x3a, 0x28, 0x8f, - 0x40, 0xa6, 0xeb, 0x47, 0x4e, 0x23, 0x3d, 0x1b, 0xaf, 0x1f, 0x39, 0x8d, 0xb4, 0xbc, 0x7e, 0xe4, - 0x84, 0xd2, 0x91, 0xc5, 0x71, 0xee, 0x50, 0x8b, 0xe3, 0x1d, 0x98, 0x6b, 0xd9, 0x7e, 0xe8, 0x52, - 0xbd, 0xa7, 0x1f, 0x06, 0x2b, 0xf3, 0x8a, 0x91, 0x5e, 0x2a, 0x5a, 0xbf, 0xc0, 0x07, 0xf2, 0xe4, - 0x40, 0xc2, 0x57, 0x53, 0xf2, 0xc7, 0xf0, 0x6c, 0x7f, 0xeb, 0x85, 0xa7, 0xf1, 0xb7, 0xc6, 0x4e, - 0x45, 0xc3, 0xec, 0x62, 0x6c, 0x28, 0xc4, 0x83, 0x50, 0xc2, 0x3a, 0x1b, 0x21, 0x92, 0x2f, 0xc2, - 0x1c, 0xfd, 0xbb, 0xe5, 0xf5, 0xdc, 0x8e, 0xeb, 0x04, 0x2b, 0x45, 0x6c, 0xdc, 0x85, 0xcc, 0xd5, - 0x8f, 0x48, 0x07, 0xa6, 0x13, 0xb2, 0x05, 0x8c, 0x8c, 0x93, 0x37, 0x2e, 0x0a, 0x37, 0xf2, 0x3e, - 0xcc, 0xd1, 0xd9, 0x77, 0xdf, 0x0e, 0x98, 0xba, 0xbb, 0x14, 0x5b, 0x02, 0xbb, 0x1c, 0x9e, 0xca, - 0xc8, 0x21, 0x13, 0xd0, 0x6d, 0xbe, 0x34, 0x60, 0x02, 0x92, 0x48, 0xb3, 0x7d, 0x90, 0x12, 0x8e, - 0x02, 0x8d, 0x7c, 0x0e, 0xe6, 0x4a, 0x83, 0x41, 0x2c, 0x71, 0x8e, 0x4b, 0xf6, 0xd9, 0xc1, 0xc0, - 0xca, 0x94, 0x3a, 0x0a, 0x45, 0x52, 0x30, 0x2f, 0x3f, 0x91, 0x60, 0x26, 0xaf, 0x44, 0x27, 0x80, - 0x13, 0xf1, 0x15, 0x02, 0x3f, 0x8c, 0x2a, 0xc7, 0x09, 0x76, 0x18, 0x28, 0xc3, 0x3c, 0x33, 0x50, - 0x0a, 0x6d, 0xe6, 0x64, 0x6a, 0xf5, 0x64, 0x28, 0x35, 0x2a, 0x0d, 0x8b, 0x40, 0xe0, 0x86, 0xae, - 0xdd, 0xe3, 0xa9, 0x52, 0x56, 0x4e, 0xe1, 0xaa, 0xe5, 0x11, 0x08, 0xb0, 0x04, 0xf3, 0xf1, 0xd9, - 0x0a, 0x97, 0x04, 0x91, 0xfe, 0xc7, 0x1a, 0x9c, 0x1a, 0x31, 0xe2, 0x51, 0x22, 0x0d, 0x6d, 0x7c, - 0x22, 0x0d, 0x2a, 0x39, 0x54, 0xbb, 0x13, 0xb6, 0x3f, 0xfd, 0x60, 0x31, 0xd2, 0xb7, 0x3c, 0x20, - 0x3c, 0xf5, 0x26, 0xff, 0xf4, 0x2d, 0x0f, 0xef, 0x01, 0xf2, 0xe9, 0x4d, 0x88, 0xe3, 0xb1, 0x4a, - 0xb1, 0x28, 0xd1, 0x3c, 0xb3, 0x67, 0x34, 0xac, 0x1f, 0x7a, 0xca, 0x0a, 0xce, 0x60, 0xad, 0xff, - 0x54, 0x0e, 0x66, 0xa5, 0x75, 0x48, 0x2e, 0x4a, 0xcf, 0xe1, 0x8b, 0x2c, 0x37, 0xac, 0xc4, 0x21, - 0xc7, 0x76, 0x22, 0x5c, 0x54, 0xb9, 0xc3, 0x6f, 0x3b, 0x30, 0x02, 0xa0, 0x94, 0x6c, 0x24, 0x11, - 0xf2, 0x0f, 0xcb, 0xc9, 0x97, 0x00, 0xea, 0x76, 0x10, 0x96, 0x3a, 0xa1, 0xfb, 0xd0, 0x39, 0xc2, - 0xa6, 0x23, 0xa2, 0xe4, 0x9e, 0xc0, 0x2c, 0x60, 0x36, 0x92, 0x25, 0xf6, 0x08, 0x89, 0x21, 0x1d, - 0x02, 0x39, 0x5b, 0x01, 0x1f, 0x82, 0xb4, 0x08, 0x11, 0x58, 0xfa, 0x8f, 0x6a, 0x00, 0xdb, 0xb5, - 0x32, 0xa6, 0x17, 0x7a, 0x5a, 0x2d, 0x22, 0x3b, 0xb2, 0xbe, 0xe0, 0x3e, 0x46, 0x7f, 0xf8, 0x7d, - 0x0d, 0x16, 0x54, 0x34, 0xf2, 0x1e, 0x2c, 0x9a, 0x1d, 0xdf, 0xeb, 0xf5, 0xee, 0xdb, 0x9d, 0x07, - 0x75, 0xb7, 0xef, 0xb0, 0x68, 0xe3, 0x93, 0x6c, 0xf3, 0x0a, 0xa2, 0x22, 0xab, 0x47, 0xcb, 0x8c, - 0x24, 0x32, 0xf9, 0x31, 0x0d, 0xe6, 0xcd, 0x3d, 0xef, 0x51, 0x14, 0xbc, 0x9b, 0x8f, 0xe0, 0x97, - 0xa8, 0x30, 0x08, 0xf6, 0xbc, 0x47, 0x71, 0x26, 0x7a, 0xc5, 0x93, 0xf9, 0xdd, 0xa3, 0x39, 0x99, - 0x74, 0x3c, 0x3c, 0xc0, 0x84, 0xc1, 0x55, 0xe5, 0x23, 0x86, 0xfa, 0x4d, 0xfd, 0xcf, 0x35, 0x98, - 0xc5, 0xa3, 0x4e, 0xaf, 0x87, 0x4a, 0xda, 0x5f, 0xa5, 0xb4, 0xe6, 0x51, 0xbb, 0xc6, 0x0c, 0xec, - 0x1b, 0xb0, 0x98, 0x40, 0x23, 0x3a, 0x4c, 0x99, 0x18, 0x1a, 0x43, 0xb6, 0x92, 0xb0, 0x60, 0x19, - 0x06, 0x2f, 0xd1, 0xab, 0x12, 0xd9, 0x9d, 0x6b, 0xe8, 0x74, 0x70, 0x1d, 0xc0, 0x15, 0x20, 0x71, - 0x14, 0x22, 0xc9, 0x9a, 0xdc, 0xb9, 0x66, 0x48, 0x58, 0x7a, 0x03, 0xa6, 0x4c, 0xcf, 0x0f, 0xd7, - 0x0f, 0xd8, 0xe9, 0xa3, 0xe2, 0x04, 0x1d, 0xd9, 0xab, 0xc0, 0xc5, 0x3b, 0xbf, 0x8e, 0xc1, 0x8b, - 0xc8, 0x2a, 0x4c, 0x6e, 0xb8, 0x4e, 0xaf, 0x2b, 0x7b, 0x9b, 0xef, 0x50, 0x80, 0xc1, 0xe0, 0xf4, - 0x84, 0x76, 0x32, 0xce, 0xe9, 0x17, 0xbb, 0xb5, 0x3f, 0xed, 0xba, 0x29, 0x2b, 0xfd, 0x7b, 0x29, - 0xca, 0x7c, 0x95, 0xfe, 0xd2, 0x98, 0xae, 0xfe, 0x55, 0x0d, 0xce, 0x8c, 0x26, 0x91, 0x3d, 0xe5, - 0xb5, 0x31, 0x9e, 0xf2, 0x97, 0x93, 0xb7, 0xe0, 0x88, 0xc6, 0x6f, 0xc1, 0xe3, 0xbb, 0xef, 0x0a, - 0x3e, 0x54, 0xe8, 0x38, 0x22, 0x91, 0xdf, 0xc5, 0x31, 0x75, 0x46, 0x44, 0x36, 0xcc, 0x21, 0xd2, - 0x18, 0x9c, 0x56, 0xff, 0x97, 0x13, 0x70, 0x7a, 0x24, 0x05, 0xd9, 0x94, 0xd2, 0x83, 0x2e, 0x44, - 0x89, 0x09, 0x47, 0xe2, 0x5f, 0xc5, 0x7f, 0xd1, 0x17, 0x35, 0xf9, 0x26, 0xb2, 0x19, 0xa5, 0x85, - 0xcc, 0x21, 0xaf, 0x97, 0x0e, 0xe5, 0xc5, 0xd0, 0x91, 0x19, 0xa4, 0x33, 0x44, 0xe2, 0xf3, 0x5b, - 0x27, 0xb4, 0xdd, 0x5e, 0x20, 0x2f, 0xbb, 0x2e, 0x03, 0x19, 0xa2, 0x2c, 0x7e, 0xbe, 0x30, 0x91, - 0xfd, 0x7c, 0x41, 0xff, 0xdf, 0x1a, 0xcc, 0x44, 0xd5, 0x26, 0x67, 0xe0, 0x64, 0xdb, 0x28, 0x95, - 0xab, 0x56, 0xfb, 0x5e, 0xab, 0x6a, 0x6d, 0x37, 0xcc, 0x56, 0xb5, 0x5c, 0xdb, 0xa8, 0x55, 0x2b, - 0xc5, 0x63, 0x64, 0x09, 0xe6, 0xb7, 0x1b, 0xb7, 0x1b, 0xcd, 0xbb, 0x0d, 0xab, 0x6a, 0x18, 0x4d, - 0xa3, 0xa8, 0x91, 0x79, 0x98, 0x31, 0xd6, 0x4b, 0x65, 0xab, 0xd1, 0xac, 0x54, 0x8b, 0x39, 0x52, - 0x84, 0xb9, 0x72, 0xb3, 0xd1, 0xa8, 0x96, 0xdb, 0xb5, 0x3b, 0xb5, 0xf6, 0xbd, 0x62, 0x9e, 0x10, - 0x58, 0x40, 0x84, 0x96, 0x51, 0x6b, 0x94, 0x6b, 0xad, 0x52, 0xbd, 0x38, 0x41, 0x61, 0x14, 0x5f, - 0x82, 0x4d, 0x46, 0x8c, 0x6e, 0x6f, 0xaf, 0x57, 0x8b, 0x53, 0x14, 0x85, 0xfe, 0x25, 0xa1, 0x4c, - 0xd3, 0xcf, 0x23, 0x4a, 0xa5, 0xd4, 0x2e, 0xad, 0x97, 0xcc, 0x6a, 0xb1, 0x40, 0x4e, 0xc1, 0x71, - 0x05, 0x64, 0xd5, 0x9b, 0x37, 0x6b, 0x8d, 0xe2, 0x0c, 0x59, 0x86, 0x62, 0x04, 0xab, 0xac, 0x5b, - 0xdb, 0x66, 0xd5, 0x28, 0x42, 0x12, 0xda, 0x28, 0x6d, 0x55, 0x8b, 0xb3, 0xfa, 0xbb, 0xec, 0xb5, - 0x2a, 0xeb, 0x6a, 0x72, 0x12, 0x88, 0xd9, 0x2e, 0xb5, 0xb7, 0xcd, 0x44, 0xe3, 0x67, 0x61, 0xda, - 0xdc, 0x2e, 0x97, 0xab, 0xa6, 0x59, 0xd4, 0x08, 0xc0, 0xd4, 0x46, 0xa9, 0x56, 0xaf, 0x56, 0x8a, - 0x39, 0xfd, 0xeb, 0x1a, 0x2c, 0x09, 0x95, 0x51, 0xdc, 0xe3, 0x3d, 0xe5, 0x5a, 0x7c, 0x4f, 0x39, - 0x09, 0x8b, 0xa7, 0x87, 0x89, 0x8f, 0x8c, 0x59, 0x86, 0x3f, 0xaf, 0xc1, 0x89, 0x4c, 0x6c, 0x72, - 0x0f, 0x8a, 0xa2, 0x06, 0xd1, 0x33, 0x62, 0x4d, 0xd1, 0xb8, 0x05, 0x5d, 0x02, 0x8d, 0xd9, 0x56, - 0xa3, 0xed, 0xca, 0x48, 0xb1, 0x39, 0x7a, 0xee, 0x24, 0xfd, 0x63, 0x0d, 0x4e, 0x8d, 0xf8, 0x0c, - 0x29, 0xc3, 0x54, 0x94, 0x0a, 0x71, 0x8c, 0x3b, 0xe6, 0xf2, 0xb7, 0x3e, 0x59, 0xe5, 0x88, 0x74, - 0x69, 0xb0, 0x64, 0x87, 0xc6, 0x54, 0x94, 0xdb, 0x10, 0x13, 0x0c, 0xb2, 0xee, 0x3b, 0x9d, 0xe8, - 0x79, 0xfe, 0xa5, 0xd2, 0x5d, 0x73, 0x7d, 0x96, 0xf7, 0x5d, 0xde, 0x7e, 0x14, 0x60, 0x86, 0x41, - 0xfd, 0xe7, 0x34, 0xaa, 0x0d, 0x26, 0x11, 0xa9, 0x92, 0x1c, 0xe7, 0x12, 0x2b, 0x19, 0x0d, 0xbe, - 0x6d, 0xa0, 0x7a, 0xcb, 0xf2, 0x8f, 0xe1, 0x39, 0xc4, 0xb2, 0x7d, 0x25, 0x26, 0x86, 0x42, 0x43, - 0x6e, 0x00, 0x54, 0x1f, 0x87, 0x8e, 0xdf, 0xb7, 0x7b, 0x51, 0x74, 0x23, 0x16, 0xc5, 0x8f, 0x43, - 0x55, 0x05, 0x5d, 0x42, 0xd6, 0xbf, 0xaa, 0xc1, 0x9c, 0x48, 0xac, 0xd6, 0x73, 0xfc, 0xf0, 0xe9, - 0xa6, 0xd7, 0x0d, 0x65, 0x7a, 0x45, 0xaf, 0x8f, 0x24, 0xfe, 0xb4, 0x38, 0x73, 0x66, 0xfd, 0x5b, - 0x0d, 0x8a, 0x49, 0x44, 0xf2, 0x1e, 0x14, 0x4c, 0xe7, 0xa1, 0xe3, 0xbb, 0xe1, 0x01, 0x17, 0x94, - 0x22, 0x05, 0x35, 0xc3, 0xe1, 0x65, 0x6c, 0x3e, 0x04, 0xfc, 0x97, 0x11, 0xd1, 0x1c, 0x55, 0xde, - 0x4b, 0x76, 0x92, 0xfc, 0xb3, 0xb2, 0x93, 0xe8, 0x7f, 0x98, 0x83, 0x53, 0x37, 0x9d, 0x50, 0x6e, - 0x53, 0xe4, 0x26, 0xf3, 0xda, 0xd1, 0xda, 0x25, 0xb5, 0x64, 0x05, 0xa6, 0xb1, 0x48, 0x8c, 0xaf, - 0x21, 0x7e, 0x92, 0xf5, 0x68, 0x5e, 0xe7, 0x95, 0x1c, 0xb7, 0x23, 0xbe, 0x7d, 0x55, 0xca, 0x53, - 0x19, 0x4d, 0xeb, 0x2b, 0xb0, 0x80, 0x99, 0x6c, 0x86, 0x74, 0x39, 0x38, 0x5d, 0x6e, 0x2f, 0x2a, - 0x18, 0x09, 0x28, 0x59, 0x83, 0x22, 0x85, 0x94, 0x3a, 0x0f, 0xfa, 0xde, 0xa3, 0x9e, 0xd3, 0xdd, - 0x75, 0xba, 0xb8, 0xad, 0x17, 0x8c, 0x14, 0x5c, 0xf0, 0xdc, 0xee, 0xb3, 0xb3, 0x9e, 0xd3, 0x45, - 0xa3, 0x0e, 0xe7, 0x19, 0x43, 0xcf, 0xdc, 0x80, 0xd9, 0xef, 0x32, 0x83, 0xad, 0xfe, 0x5f, 0x34, - 0x58, 0xc6, 0xc6, 0x49, 0x1f, 0xc6, 0x6b, 0x8b, 0xd7, 0xe2, 0xde, 0x92, 0xd2, 0x30, 0xda, 0x14, - 0xa4, 0x2e, 0x85, 0xa8, 0x17, 0x63, 0x23, 0x52, 0xee, 0x08, 0x46, 0x24, 0xc9, 0x06, 0x36, 0xf1, - 0xac, 0x6c, 0x60, 0xb7, 0x26, 0x0a, 0xf9, 0xe2, 0x44, 0x3c, 0xe4, 0xfa, 0x8f, 0xe5, 0x60, 0xda, - 0x70, 0x7a, 0x8e, 0x1d, 0x38, 0xe4, 0x0a, 0x4c, 0x37, 0xbc, 0xd0, 0x09, 0xb6, 0x2a, 0xb2, 0x0b, - 0x7a, 0x9f, 0x82, 0xac, 0xfd, 0xae, 0x21, 0x0a, 0xe9, 0x84, 0x6f, 0xf9, 0x5e, 0x77, 0xd8, 0x09, - 0xe5, 0x09, 0x3f, 0x60, 0x20, 0x43, 0x94, 0x91, 0x97, 0x61, 0x86, 0x73, 0x8e, 0x6e, 0x96, 0xd1, - 0xb3, 0xde, 0x67, 0x40, 0xcc, 0x79, 0x15, 0x21, 0xa0, 0x4e, 0xcb, 0x14, 0x8c, 0x09, 0x49, 0xa7, - 0x4d, 0xe9, 0x0c, 0x42, 0x55, 0x9f, 0x1c, 0xa3, 0xaa, 0xbf, 0x06, 0x53, 0xa5, 0x20, 0x70, 0x42, - 0x11, 0xac, 0x63, 0x2e, 0xce, 0xa7, 0xe8, 0x84, 0x8c, 0xb1, 0x8d, 0xe5, 0x06, 0xc7, 0xd3, 0xff, - 0x34, 0x07, 0x93, 0xf8, 0x27, 0xde, 0xdb, 0xfa, 0x9d, 0x3d, 0xe5, 0xde, 0xd6, 0xef, 0xec, 0x19, - 0x08, 0x25, 0xd7, 0xd0, 0xb4, 0x21, 0x32, 0x86, 0xf2, 0xd6, 0xa3, 0xcd, 0xbe, 0x1b, 0x83, 0x0d, - 0x19, 0x27, 0xf2, 0x9a, 0xc8, 0x67, 0x7a, 0x4d, 0x9c, 0x84, 0x5c, 0xd3, 0xe4, 0x2d, 0xc6, 0x58, - 0x72, 0x5e, 0x60, 0xe4, 0x9a, 0x26, 0xf6, 0xc6, 0x66, 0xe9, 0xfa, 0x1b, 0x6f, 0xf2, 0x86, 0xb2, - 0xde, 0xd8, 0xb3, 0xaf, 0xbf, 0xf1, 0xa6, 0xc1, 0x4b, 0x68, 0xff, 0x62, 0x9d, 0xf1, 0xf6, 0x97, - 0x45, 0x92, 0xc0, 0xfe, 0xc5, 0xb6, 0xe1, 0x4d, 0xaf, 0x11, 0x23, 0x90, 0xeb, 0x30, 0xcb, 0x43, - 0x9a, 0x20, 0xbe, 0x14, 0x72, 0x84, 0x87, 0x3c, 0x61, 0x14, 0x32, 0x12, 0xbb, 0x07, 0xe4, 0x03, - 0xc4, 0xf3, 0xe3, 0x89, 0x7b, 0x40, 0x31, 0x84, 0x81, 0x21, 0xa1, 0xd0, 0x2a, 0xb1, 0x8b, 0xc4, - 0x38, 0x42, 0x04, 0x56, 0x89, 0xdf, 0x36, 0x62, 0xd6, 0xa0, 0x08, 0x41, 0xff, 0x95, 0x1c, 0x14, - 0x5a, 0xbd, 0xe1, 0xae, 0xdb, 0xbf, 0x73, 0x8d, 0x10, 0xc0, 0x63, 0x9c, 0x48, 0x2b, 0x45, 0xff, - 0x26, 0xa7, 0xa1, 0x20, 0x4e, 0x6e, 0x42, 0x20, 0x05, 0xfc, 0xd4, 0xb6, 0x02, 0x62, 0xdc, 0x79, - 0xd0, 0x40, 0xf1, 0x93, 0x5c, 0x83, 0xe8, 0xfc, 0x35, 0xea, 0xa0, 0x36, 0x41, 0x17, 0x8b, 0x11, - 0xa1, 0x91, 0x57, 0x00, 0x37, 0x09, 0x7e, 0x78, 0x10, 0x16, 0x70, 0x56, 0x35, 0xae, 0xa7, 0x30, - 0x12, 0x44, 0x43, 0xff, 0x0d, 0x39, 0x03, 0xe4, 0x09, 0x95, 0x80, 0xe5, 0x08, 0x14, 0x24, 0x22, - 0xfb, 0xe3, 0x3b, 0x6a, 0x1c, 0xc9, 0x69, 0xe5, 0x7e, 0x8b, 0x51, 0x4a, 0x81, 0x1c, 0xef, 0x5c, - 0x53, 0x43, 0x48, 0xfe, 0xf6, 0x0c, 0xcc, 0xc9, 0xf5, 0x21, 0x06, 0x1c, 0x0f, 0x7a, 0xf4, 0xec, - 0xce, 0x9d, 0x26, 0x07, 0x58, 0xc8, 0xb7, 0xd3, 0x8b, 0x6a, 0x85, 0x28, 0x1e, 0xf3, 0xa0, 0x14, - 0xb1, 0x58, 0x36, 0x8f, 0x19, 0x4b, 0x41, 0x0c, 0x66, 0x78, 0xa4, 0x04, 0x05, 0x6f, 0x10, 0xec, - 0x3a, 0x7d, 0x57, 0x5c, 0xd0, 0x3c, 0xa7, 0x30, 0x6a, 0xf2, 0xc2, 0x14, 0xaf, 0x88, 0x8c, 0xbc, - 0x01, 0x53, 0xde, 0xc0, 0xe9, 0xdb, 0x6e, 0xc2, 0x67, 0x46, 0x30, 0x70, 0xfa, 0xa5, 0x9a, 0x44, - 0xc8, 0x91, 0xc9, 0xab, 0x30, 0xe1, 0x3d, 0x88, 0xc6, 0xeb, 0xb4, 0x4a, 0xf4, 0x20, 0xb4, 0x25, - 0x12, 0x44, 0xa4, 0x04, 0x1f, 0xda, 0xfb, 0x3b, 0x7c, 0xc4, 0x54, 0x82, 0x5b, 0xf6, 0xfe, 0x8e, - 0x4c, 0x40, 0x11, 0xc9, 0xfb, 0x00, 0x03, 0x7b, 0xd7, 0xf1, 0xad, 0xee, 0x30, 0x3c, 0x48, 0xa4, - 0xc8, 0x66, 0x64, 0x2d, 0x5a, 0x5c, 0x19, 0x86, 0x07, 0x12, 0xed, 0xcc, 0x40, 0x00, 0x49, 0x09, - 0x60, 0xdf, 0x0e, 0x43, 0xc7, 0xdf, 0xf7, 0xb8, 0xd7, 0x6a, 0x1c, 0x06, 0x94, 0x31, 0xd8, 0x8a, - 0x8a, 0x25, 0x0e, 0x12, 0x11, 0x56, 0xda, 0xf5, 0x6d, 0xbc, 0x86, 0x4f, 0x55, 0xda, 0xf5, 0x95, - 0x56, 0x52, 0x44, 0xf2, 0x36, 0x4c, 0x63, 0xde, 0x57, 0xbf, 0xcb, 0x83, 0xf4, 0x9c, 0x53, 0x68, - 0x2a, 0xac, 0x4c, 0x22, 0x13, 0xe8, 0xb4, 0xb6, 0x3c, 0xe0, 0x73, 0xc3, 0x7b, 0x84, 0xf7, 0x02, - 0xc9, 0xda, 0x9a, 0x51, 0xb1, 0x5c, 0xdb, 0x98, 0x88, 0x0e, 0xe5, 0xae, 0x1b, 0xf6, 0xec, 0xfb, - 0xfc, 0xb2, 0x5d, 0x1d, 0xca, 0x9b, 0x58, 0x24, 0x0f, 0x25, 0x43, 0x26, 0x37, 0xa0, 0x20, 0xc2, - 0xb0, 0xf2, 0x27, 0xbf, 0x6a, 0xa5, 0x79, 0xf0, 0x55, 0xb9, 0xd2, 0x3c, 0xf0, 0x2a, 0xed, 0x9f, - 0xa0, 0xe3, 0xee, 0xf3, 0x97, 0xba, 0x6a, 0xff, 0x98, 0xe5, 0xda, 0x96, 0xdc, 0x3f, 0x14, 0x91, - 0xbc, 0x07, 0xd3, 0x74, 0xfd, 0x76, 0xbd, 0x5d, 0x1e, 0xe6, 0x44, 0x57, 0xfb, 0x87, 0x95, 0xa5, - 0xa6, 0xab, 0x20, 0xa2, 0x0b, 0xd9, 0x7e, 0x14, 0x58, 0x6e, 0x07, 0xe3, 0x00, 0x27, 0x97, 0x63, - 0xe9, 0xae, 0x59, 0x2b, 0x4b, 0x64, 0x93, 0xf6, 0xa3, 0xa0, 0xd6, 0x21, 0xd7, 0x61, 0x12, 0x33, - 0x2e, 0xf1, 0xa0, 0xbf, 0x2a, 0x0d, 0xe6, 0x5a, 0x92, 0x69, 0x10, 0x95, 0x0e, 0xe4, 0x7e, 0x80, - 0x8f, 0x9f, 0x78, 0xde, 0x23, 0xb5, 0x4f, 0xb6, 0x4c, 0x7c, 0x11, 0x25, 0x57, 0x91, 0xa3, 0xd3, - 0x2a, 0xf6, 0x9d, 0xd0, 0x72, 0x3f, 0xe2, 0x99, 0x8b, 0xd4, 0xcf, 0x35, 0x9c, 0xb0, 0xf6, 0x79, - 0xf9, 0x73, 0x7d, 0x27, 0xac, 0x7d, 0xc4, 0x87, 0x6e, 0x6f, 0x78, 0x1f, 0x8d, 0xef, 0x19, 0x43, - 0xb7, 0x37, 0x4c, 0x0e, 0xdd, 0xde, 0xf0, 0x3e, 0x25, 0x73, 0xfb, 0xe1, 0xb0, 0xef, 0xf0, 0xa7, - 0xb7, 0x2a, 0x59, 0x0d, 0x8b, 0x64, 0x32, 0x86, 0x4c, 0x2e, 0x00, 0xc4, 0xde, 0x0e, 0xec, 0x1e, - 0xc9, 0x90, 0x20, 0x9f, 0x99, 0xf8, 0xaf, 0xbf, 0xb0, 0xaa, 0xad, 0x03, 0x14, 0x44, 0xac, 0x28, - 0xaa, 0x86, 0x2f, 0x67, 0xd5, 0x85, 0x5c, 0x82, 0x39, 0x39, 0x92, 0x15, 0xdf, 0x0c, 0x66, 0xed, - 0x81, 0x2b, 0x62, 0x59, 0x8d, 0xcf, 0x69, 0xf3, 0x12, 0x2c, 0x29, 0x2f, 0xd4, 0x62, 0x37, 0x4c, - 0xa3, 0x28, 0x17, 0xe0, 0xde, 0x5b, 0x06, 0x08, 0x42, 0xdb, 0x0f, 0xad, 0xae, 0x1d, 0x1e, 0xc5, - 0x8c, 0x5c, 0xa0, 0xf2, 0x9c, 0x3d, 0x08, 0x40, 0xba, 0x8a, 0x1d, 0x3a, 0xac, 0x71, 0x7a, 0x1d, - 0x4e, 0x8f, 0x94, 0xb5, 0xe4, 0x45, 0x28, 0xee, 0xd8, 0xdc, 0xd2, 0xda, 0xd9, 0xb3, 0xfb, 0x7d, - 0xa7, 0xc7, 0x1b, 0xb6, 0x28, 0xe0, 0x65, 0x06, 0xe6, 0xdc, 0xde, 0x97, 0x7a, 0x47, 0x5a, 0x64, - 0x47, 0xe8, 0x1d, 0xce, 0xe0, 0x9b, 0x1a, 0x9c, 0x1b, 0x27, 0xb2, 0xc9, 0x19, 0x28, 0x0c, 0x7c, - 0xd7, 0xc3, 0xa3, 0x01, 0xef, 0x43, 0xf1, 0x1b, 0x53, 0xfe, 0xa0, 0x0e, 0x1b, 0xda, 0xbb, 0xfc, - 0xc9, 0x97, 0x31, 0x83, 0x90, 0xb6, 0xbd, 0x1b, 0xd0, 0x2e, 0xee, 0x3a, 0x3b, 0xf6, 0xb0, 0x17, - 0x5a, 0x41, 0x67, 0xcf, 0xe9, 0xe2, 0xa3, 0x4c, 0xf4, 0x5f, 0x35, 0x8a, 0xbc, 0xc0, 0x14, 0xf0, - 0x54, 0x8d, 0x27, 0x47, 0xd4, 0xf8, 0xd6, 0x44, 0x41, 0x2b, 0xe6, 0x0c, 0x74, 0x91, 0xd3, 0x7f, - 0x24, 0x07, 0x2b, 0xa3, 0x64, 0x14, 0x79, 0x37, 0xab, 0x0f, 0xd8, 0xed, 0x92, 0x0c, 0x97, 0x6f, - 0x97, 0xe4, 0xd9, 0x73, 0x1d, 0xa2, 0x27, 0x95, 0x87, 0x85, 0x47, 0x11, 0x30, 0x4a, 0x33, 0xb0, - 0x83, 0xe0, 0x11, 0x15, 0xc3, 0x79, 0x29, 0x60, 0x3b, 0x87, 0xc9, 0x34, 0x02, 0x46, 0xde, 0x02, - 0xe8, 0xf4, 0xbc, 0xc0, 0x41, 0x27, 0x0e, 0xae, 0xdf, 0xb1, 0x87, 0x22, 0x11, 0x54, 0xbe, 0xb5, - 0x47, 0x68, 0xd9, 0xeb, 0x8a, 0xf9, 0x64, 0xc3, 0xa9, 0x11, 0x9b, 0x12, 0x1d, 0x1e, 0x7c, 0x23, - 0xc9, 0x64, 0x10, 0x4f, 0x97, 0x49, 0x21, 0x2c, 0xcd, 0x5b, 0xb2, 0xc7, 0x73, 0xa3, 0xe6, 0xc8, - 0x01, 0x90, 0xf4, 0xce, 0x43, 0xb9, 0xf3, 0x87, 0x11, 0x43, 0x3f, 0xe2, 0xce, 0x20, 0xdb, 0x7e, - 0x8f, 0xac, 0xc2, 0xac, 0xc8, 0x18, 0x4f, 0xcf, 0x4f, 0x8c, 0x39, 0x70, 0xd0, 0x6d, 0x07, 0x27, - 0x0f, 0xc6, 0x57, 0x66, 0xe9, 0xbb, 0xd8, 0xca, 0x9b, 0x41, 0x48, 0xfb, 0x60, 0x20, 0x5a, 0x77, - 0x4e, 0xcc, 0x6f, 0x55, 0x1f, 0xe0, 0xa5, 0x7f, 0x47, 0x13, 0xc3, 0x9f, 0xde, 0x50, 0x0f, 0xab, - 0x1f, 0x01, 0x7c, 0xb7, 0xc8, 0x2b, 0x86, 0x7f, 0x53, 0x4d, 0x51, 0xac, 0x3a, 0xae, 0x29, 0xf2, - 0x9f, 0xe4, 0x0a, 0x2c, 0xfa, 0xcc, 0x9d, 0x3b, 0xf4, 0x78, 0x7f, 0xb2, 0x0c, 0x57, 0xf3, 0x0c, - 0xdc, 0xf6, 0xb0, 0x4f, 0x79, 0xbd, 0x6e, 0x45, 0x1d, 0x26, 0xe9, 0x17, 0xe4, 0x2a, 0xcc, 0x50, - 0xfd, 0x02, 0x63, 0x66, 0x25, 0x1e, 0x4c, 0x21, 0x1e, 0x6a, 0x6b, 0x46, 0xe1, 0x43, 0xfe, 0x37, - 0xe7, 0xf5, 0xa3, 0x91, 0x00, 0x54, 0xa5, 0x2a, 0x39, 0x09, 0x53, 0xa1, 0xd3, 0xb7, 0xa3, 0xc5, - 0xcd, 0x7f, 0x91, 0xcb, 0xb0, 0xc0, 0x5e, 0x39, 0x27, 0x06, 0x76, 0x1e, 0xa1, 0xd1, 0xf4, 0x3e, - 0x5a, 0x7a, 0x34, 0x5e, 0x89, 0x5f, 0xcf, 0x89, 0x16, 0xc9, 0x2a, 0x16, 0x39, 0x05, 0xd3, 0x9e, - 0xbf, 0x2b, 0xf5, 0xef, 0x94, 0xe7, 0xef, 0xd2, 0xce, 0x7d, 0x01, 0x8a, 0xec, 0x11, 0x21, 0x0b, - 0xe6, 0x12, 0x1c, 0xf4, 0x3b, 0xfc, 0xed, 0xc7, 0x02, 0x83, 0x6f, 0x07, 0x8e, 0x6f, 0x1e, 0xf4, - 0x3b, 0x14, 0x33, 0x08, 0x3c, 0x4b, 0x8e, 0xbf, 0xc7, 0x2b, 0xb2, 0x10, 0x04, 0x5e, 0x1c, 0x88, - 0xaf, 0x4b, 0xd6, 0x61, 0x9e, 0xf2, 0x89, 0xc2, 0x08, 0x72, 0x31, 0x7c, 0x3e, 0xad, 0x01, 0x1e, - 0xf4, 0x3b, 0xa2, 0x8a, 0xc6, 0x5c, 0x20, 0xfd, 0x22, 0xb7, 0xa1, 0x28, 0xa9, 0xca, 0xf8, 0xaa, - 0x3c, 0xf1, 0xbe, 0x21, 0x66, 0x23, 0xa9, 0xd8, 0xb5, 0xfe, 0x8e, 0x67, 0x2c, 0x76, 0x54, 0x40, - 0x24, 0x8e, 0xa6, 0x8a, 0xd3, 0xc6, 0x0a, 0x6f, 0x6e, 0x80, 0xae, 0xa2, 0x56, 0xcf, 0xdb, 0xb5, - 0x9c, 0xc7, 0x74, 0x62, 0xe8, 0xff, 0x40, 0x13, 0x02, 0x3f, 0x83, 0x29, 0xd1, 0x61, 0x7e, 0xcf, - 0x0e, 0xac, 0x20, 0xd8, 0x67, 0x1e, 0x8c, 0x3c, 0x56, 0xfa, 0xec, 0x9e, 0x1d, 0x98, 0xc1, 0xbe, - 0x48, 0xdb, 0x75, 0x82, 0xe2, 0x60, 0x3c, 0x77, 0x4b, 0x3e, 0x18, 0xb0, 0x1e, 0x3d, 0xbe, 0x67, - 0x07, 0x4d, 0x5a, 0x26, 0x07, 0x77, 0x7f, 0x1e, 0x16, 0x90, 0x6f, 0xc7, 0x15, 0x8c, 0x31, 0x60, - 0x8f, 0x31, 0x47, 0x19, 0x77, 0x5c, 0xc6, 0x99, 0x0f, 0xee, 0x7f, 0x9a, 0x82, 0x93, 0xd9, 0xbd, - 0x87, 0x6b, 0x88, 0xf6, 0x39, 0x3e, 0x2d, 0xe6, 0x75, 0x9b, 0xa1, 0x10, 0x16, 0x6c, 0x29, 0x6b, - 0xf0, 0x72, 0x99, 0x83, 0xb7, 0x06, 0x4b, 0xc8, 0x88, 0x1f, 0x41, 0x7a, 0x6e, 0x10, 0xf2, 0x18, - 0x42, 0xc6, 0x22, 0x2d, 0x60, 0x9b, 0x4e, 0x9d, 0x82, 0xe9, 0xcc, 0x14, 0xdb, 0x86, 0xf7, 0xa8, - 0x4f, 0x3f, 0xcc, 0xf6, 0x8c, 0x79, 0x0e, 0x6d, 0x22, 0x90, 0x9c, 0x80, 0x29, 0x7b, 0x30, 0xa0, - 0x9f, 0x64, 0x5b, 0xc5, 0xa4, 0x3d, 0x18, 0xb0, 0xc8, 0xf5, 0x2c, 0x2b, 0xe0, 0x0e, 0xfa, 0x9b, - 0x89, 0x97, 0x0d, 0x73, 0x08, 0x64, 0x3e, 0x68, 0xf8, 0x36, 0x82, 0xd2, 0x0a, 0x94, 0x69, 0x44, - 0x01, 0x7b, 0x10, 0x21, 0x9c, 0x86, 0x82, 0xf0, 0x7c, 0x60, 0x2f, 0xc7, 0x8c, 0x69, 0x9b, 0x7b, - 0x3d, 0xbc, 0x01, 0xa7, 0x78, 0x0a, 0x42, 0x8b, 0x35, 0x69, 0x30, 0xe0, 0x4f, 0xb7, 0x59, 0x9c, - 0x72, 0x63, 0x99, 0x17, 0xd3, 0x9e, 0x2c, 0x0d, 0x06, 0xd1, 0x03, 0xee, 0x33, 0x82, 0xec, 0xbe, - 0xcb, 0x22, 0x1d, 0x32, 0xef, 0x5f, 0x5c, 0x1c, 0x80, 0x94, 0x2b, 0x1c, 0x63, 0x5d, 0x46, 0x10, - 0xcb, 0x24, 0x5a, 0x49, 0x16, 0x7f, 0x11, 0xc0, 0xd4, 0x27, 0xbc, 0x1f, 0xc7, 0x41, 0x43, 0x28, - 0x79, 0x0b, 0x46, 0xce, 0x45, 0xd4, 0xce, 0x0b, 0xc6, 0x09, 0x56, 0xce, 0xbc, 0x9a, 0xeb, 0xde, - 0x6e, 0x15, 0x0b, 0xc9, 0xfb, 0x70, 0x4e, 0x54, 0xd0, 0x0e, 0x02, 0x77, 0xb7, 0x6f, 0x89, 0x51, - 0x40, 0xc7, 0x13, 0xd4, 0xd0, 0x0b, 0xc6, 0x69, 0x8e, 0x53, 0x42, 0x94, 0x0a, 0xc3, 0x60, 0x4f, - 0x7f, 0x5f, 0x83, 0xe5, 0xd0, 0xdd, 0x77, 0xac, 0xfb, 0x4e, 0xf8, 0xc8, 0x71, 0xfa, 0x96, 0xbb, - 0x4f, 0xf9, 0xb2, 0x68, 0x3a, 0x33, 0x06, 0xa1, 0x65, 0xeb, 0xac, 0xa8, 0xc6, 0x4a, 0x48, 0x0b, - 0x2e, 0x2b, 0x14, 0xec, 0xbb, 0xfb, 0x54, 0x63, 0x1b, 0xf8, 0x1e, 0x9b, 0x29, 0x9e, 0x37, 0x60, - 0x69, 0x3f, 0x66, 0x8c, 0x4b, 0x12, 0x8b, 0x52, 0x84, 0xda, 0x62, 0x98, 0x75, 0x8a, 0x48, 0xca, - 0x70, 0x81, 0xbb, 0x72, 0x70, 0x06, 0x6e, 0x7f, 0xd7, 0xa2, 0x6a, 0x93, 0xb7, 0xb3, 0x63, 0x05, - 0xa1, 0x33, 0x60, 0x69, 0x3c, 0x8c, 0xb3, 0x0c, 0xab, 0x15, 0x21, 0xad, 0x33, 0x1c, 0x33, 0x74, - 0x06, 0xa4, 0x04, 0xe7, 0x47, 0x33, 0xd9, 0xb7, 0x1f, 0x33, 0xdf, 0x1a, 0xe3, 0xcc, 0x08, 0x1e, - 0x5b, 0xf6, 0x63, 0xbe, 0xb2, 0xde, 0x86, 0x45, 0x7e, 0xf0, 0xe2, 0x5a, 0x1b, 0xce, 0x3c, 0xbe, - 0xa7, 0xe0, 0xb3, 0x1b, 0x96, 0x0b, 0x12, 0x38, 0xa8, 0xd6, 0x15, 0x3b, 0xdb, 0x1f, 0x68, 0x70, - 0x22, 0xf3, 0xe4, 0x46, 0x7e, 0x10, 0xd8, 0xbb, 0xe4, 0xd0, 0xb3, 0x7c, 0xa7, 0xe3, 0x0e, 0x5c, - 0x0c, 0xf4, 0xc4, 0x6e, 0x36, 0xae, 0x8f, 0x3b, 0xf3, 0xe1, 0x1b, 0xe7, 0xb6, 0x67, 0x44, 0x44, - 0xcc, 0xe4, 0x5a, 0xf4, 0x13, 0xe0, 0x33, 0x1f, 0xc0, 0x89, 0x4c, 0xd4, 0x0c, 0x53, 0xe8, 0xcb, - 0xb2, 0x29, 0x34, 0xbe, 0xab, 0x4e, 0x34, 0x5a, 0x32, 0x91, 0xf2, 0xe6, 0xfd, 0x66, 0xd4, 0xbc, - 0xc4, 0x19, 0x8f, 0x54, 0x93, 0x52, 0x3e, 0xcb, 0x4c, 0x21, 0x88, 0x46, 0x0b, 0xfa, 0x0f, 0xe0, - 0x04, 0x17, 0x35, 0x6c, 0x93, 0x8b, 0xd8, 0xb1, 0x8a, 0x7e, 0x2a, 0x8b, 0x1d, 0x93, 0x41, 0x37, - 0x29, 0x7e, 0xc4, 0xf5, 0xb8, 0x9d, 0x06, 0xf2, 0x36, 0x7c, 0x27, 0x2f, 0x04, 0x7b, 0x46, 0x75, - 0x32, 0x84, 0x98, 0x96, 0x25, 0xc4, 0x8e, 0x2e, 0x41, 0x1b, 0x40, 0xe4, 0xad, 0x2b, 0x90, 0x1f, - 0x50, 0xad, 0xaa, 0xd9, 0x4a, 0xa4, 0x8d, 0x80, 0xbf, 0x0c, 0x5a, 0xea, 0x24, 0x41, 0xf4, 0x70, - 0xc4, 0x14, 0x06, 0xfa, 0x49, 0xa6, 0xcb, 0x14, 0x18, 0xa0, 0xd6, 0x25, 0x17, 0x61, 0x8e, 0x9d, - 0xcc, 0x15, 0x09, 0x0b, 0x08, 0x2b, 0xa1, 0x98, 0x7d, 0x27, 0x4b, 0xcc, 0xc6, 0x57, 0x2c, 0x5c, - 0x29, 0x3f, 0xe8, 0x77, 0x98, 0x44, 0x4d, 0xc8, 0x5f, 0x0b, 0x4e, 0x4b, 0x3b, 0x01, 0xef, 0x21, - 0xd1, 0xa6, 0x69, 0x6c, 0xd3, 0xf3, 0x6a, 0x9b, 0xe2, 0x0d, 0x82, 0xf5, 0x1c, 0x6f, 0xd8, 0x49, - 0x3b, 0x13, 0x4e, 0x36, 0x61, 0x01, 0xa7, 0x91, 0xdb, 0x0f, 0x1d, 0xff, 0x21, 0xdd, 0x2c, 0x0b, - 0xca, 0x6d, 0x7f, 0x6a, 0xe0, 0x6a, 0x02, 0xd1, 0xc0, 0xf9, 0x17, 0xfd, 0x8c, 0x4e, 0x6d, 0x67, - 0x46, 0x93, 0x90, 0x65, 0x98, 0xec, 0x3a, 0xbd, 0xd0, 0xe6, 0x4b, 0x83, 0xfd, 0xa0, 0x1a, 0xe6, - 0xce, 0xb0, 0xd7, 0x13, 0x1a, 0x26, 0xfd, 0x9b, 0x73, 0xfb, 0x89, 0x1c, 0x14, 0x93, 0x3d, 0x44, - 0x74, 0xc8, 0x89, 0x44, 0x2c, 0x69, 0xe7, 0xa8, 0xcd, 0x63, 0x46, 0xce, 0xed, 0x92, 0x1b, 0x00, - 0x98, 0x7c, 0xd9, 0x77, 0x76, 0x9d, 0xc7, 0xfc, 0x54, 0x82, 0x67, 0x85, 0x18, 0xaa, 0xd0, 0xcc, - 0xa0, 0xfd, 0x98, 0x82, 0xc9, 0x35, 0x00, 0xe7, 0x31, 0x4b, 0x90, 0x23, 0x54, 0xac, 0x8c, 0xcf, - 0x68, 0xc6, 0x0c, 0xc7, 0xaa, 0x75, 0xc9, 0x26, 0x10, 0x41, 0x22, 0x7d, 0x75, 0xe2, 0x90, 0xaf, - 0x6a, 0x46, 0x91, 0x53, 0x35, 0xc4, 0xc7, 0xf9, 0xb9, 0x7e, 0x06, 0xa6, 0x79, 0x8e, 0x1e, 0xfa, - 0x27, 0x47, 0xd2, 0xbf, 0xa2, 0xc1, 0xc5, 0xc3, 0xd6, 0x21, 0xb9, 0x0b, 0x27, 0xd1, 0xa5, 0x30, - 0xf0, 0xa2, 0xa5, 0x6c, 0x75, 0xec, 0xce, 0x9e, 0xc3, 0x25, 0x9f, 0x9e, 0xb9, 0xa0, 0x07, 0x03, - 0xd3, 0x6c, 0x4a, 0x6b, 0x79, 0x30, 0x30, 0x03, 0x4f, 0xfc, 0x2e, 0x53, 0x72, 0x3e, 0x20, 0x5d, - 0x38, 0x3b, 0x86, 0x52, 0x52, 0x35, 0x34, 0x59, 0xd5, 0x78, 0x01, 0x8a, 0x3b, 0x4e, 0xd7, 0xf1, - 0xf1, 0x39, 0x3f, 0xad, 0xda, 0xc3, 0xeb, 0x38, 0x26, 0x73, 0xc6, 0x42, 0x04, 0x37, 0x03, 0xef, - 0xce, 0x75, 0xfe, 0x95, 0xaf, 0xe4, 0x85, 0x16, 0x2d, 0x9b, 0xa8, 0xc8, 0x75, 0x38, 0x9e, 0x88, - 0xc4, 0x26, 0x85, 0xf6, 0xc9, 0xad, 0x68, 0xc6, 0x12, 0x2d, 0x56, 0x63, 0x77, 0x5e, 0x86, 0x39, - 0x79, 0x77, 0xe6, 0x53, 0x81, 0x22, 0xcf, 0x76, 0xe3, 0x3d, 0x99, 0xdc, 0x87, 0x05, 0x49, 0xb4, - 0x50, 0x6d, 0x37, 0x9f, 0x21, 0xff, 0xe4, 0xda, 0x5c, 0x8d, 0x65, 0x4e, 0x7f, 0xc7, 0x63, 0x9e, - 0x75, 0x2a, 0x0b, 0x63, 0xbe, 0x23, 0xa3, 0x10, 0x13, 0x80, 0xb6, 0xd4, 0xea, 0xb9, 0xfb, 0x6e, - 0x1c, 0x44, 0x21, 0xc9, 0xdf, 0xb0, 0x43, 0xa7, 0x4e, 0x31, 0xd8, 0xcc, 0x89, 0x29, 0xe4, 0xb3, - 0xad, 0x2f, 0x90, 0xce, 0x7c, 0x5e, 0x8a, 0x6f, 0x8a, 0x5f, 0x39, 0x07, 0x13, 0xfd, 0xcc, 0xf4, - 0x05, 0x78, 0xee, 0x3e, 0x07, 0x13, 0x61, 0x66, 0x90, 0xee, 0x30, 0x3a, 0x4a, 0xf2, 0x31, 0xf8, - 0x5f, 0x1a, 0x1c, 0xcf, 0xa8, 0x15, 0xb9, 0x0c, 0xd3, 0xf6, 0x43, 0xc7, 0x17, 0xa1, 0x5c, 0x79, - 0xb4, 0x26, 0x0e, 0x32, 0xc4, 0x1f, 0x64, 0x15, 0x26, 0xef, 0x0f, 0xfd, 0x20, 0xe4, 0x21, 0x9d, - 0xd0, 0xc7, 0x04, 0x01, 0x06, 0xfb, 0x8f, 0xdc, 0x80, 0x85, 0x81, 0xe3, 0xbb, 0x5e, 0xd7, 0x0a, - 0x9c, 0x8e, 0xd7, 0xef, 0x32, 0x2d, 0x37, 0xcf, 0x5f, 0x52, 0x29, 0x25, 0xc6, 0x3c, 0xfb, 0x6d, - 0xb2, 0x9f, 0xe4, 0x1e, 0x9c, 0xde, 0xb7, 0x1f, 0xd3, 0x69, 0xd0, 0x19, 0xfa, 0x3e, 0x55, 0x82, - 0xbc, 0x01, 0x37, 0x8c, 0x31, 0xd7, 0x76, 0x16, 0xfe, 0x7b, 0x34, 0x92, 0x71, 0x6a, 0xdf, 0x7e, - 0x5c, 0x8e, 0x4a, 0x9a, 0x51, 0x01, 0x6f, 0xfb, 0x50, 0xcc, 0xf2, 0x4c, 0x6b, 0xe7, 0x51, 0x2c, - 0x6a, 0xaf, 0x00, 0x89, 0xec, 0x53, 0x91, 0xf6, 0xc1, 0x05, 0xdc, 0x92, 0x28, 0x89, 0xd4, 0x06, - 0xfe, 0xd9, 0x5f, 0x9b, 0x12, 0x5d, 0xae, 0x98, 0x49, 0xc9, 0x2b, 0x50, 0xa4, 0xe2, 0x79, 0xd7, - 0x97, 0x0c, 0x70, 0xf1, 0xa4, 0x5f, 0x94, 0xca, 0xf8, 0xfd, 0xd7, 0x94, 0xef, 0xec, 0x46, 0x77, - 0x69, 0x06, 0xff, 0x45, 0xb5, 0x12, 0xdb, 0x17, 0x57, 0x3b, 0xf4, 0x4f, 0x52, 0x83, 0x25, 0x4c, - 0xe7, 0x15, 0xb8, 0x1e, 0x66, 0xda, 0xc3, 0x03, 0xf7, 0x84, 0x62, 0x4c, 0xc5, 0x9a, 0xb4, 0x24, - 0x24, 0x7a, 0xe2, 0x36, 0x8a, 0x83, 0x04, 0x84, 0x7c, 0x16, 0xce, 0xc8, 0x1b, 0x55, 0x62, 0x4b, - 0xc7, 0xe7, 0x7c, 0xc6, 0xa9, 0x78, 0x0f, 0xaa, 0x28, 0x9b, 0xfb, 0x3a, 0x5c, 0xc0, 0x85, 0xed, - 0x76, 0x07, 0x56, 0x2a, 0xa7, 0x22, 0x36, 0x97, 0xa5, 0x94, 0x3a, 0x43, 0xb1, 0x6a, 0xdd, 0x41, - 0x22, 0xbd, 0x22, 0xb6, 0xba, 0x95, 0xb9, 0xed, 0xb3, 0x2d, 0xf2, 0xbc, 0xdc, 0x98, 0xd4, 0xa6, - 0xcf, 0x44, 0x47, 0x7a, 0xe3, 0xdf, 0x85, 0xa5, 0xf8, 0x28, 0x21, 0x76, 0xef, 0x02, 0x4a, 0xd1, - 0x33, 0x32, 0x43, 0x71, 0x44, 0x67, 0xdb, 0x13, 0x8b, 0x56, 0x95, 0x22, 0x94, 0x63, 0xb2, 0x0d, - 0x15, 0x82, 0x80, 0xd4, 0x61, 0xd9, 0x7e, 0x14, 0xd0, 0x23, 0x9f, 0x37, 0xec, 0x87, 0x41, 0xf4, - 0xad, 0x99, 0xf4, 0xb7, 0x84, 0x33, 0x07, 0x57, 0x16, 0x88, 0xfd, 0x28, 0x28, 0x71, 0x32, 0xc1, - 0xed, 0x43, 0x20, 0x4c, 0xe1, 0x50, 0xea, 0x0d, 0x87, 0xf1, 0x62, 0xfe, 0xfd, 0x69, 0x4a, 0x39, - 0xb2, 0x2c, 0x96, 0xca, 0x35, 0x6f, 0xab, 0x17, 0x70, 0xb3, 0x8a, 0xf7, 0x48, 0xb2, 0xb7, 0x99, - 0x67, 0x8b, 0x84, 0x2f, 0x1b, 0x14, 0x25, 0x30, 0xda, 0x90, 0x30, 0xf2, 0x24, 0xd6, 0x63, 0xdf, - 0xeb, 0x3a, 0xec, 0x45, 0x84, 0x31, 0x8f, 0x60, 0x5a, 0x81, 0xad, 0xd8, 0xae, 0xf7, 0xb1, 0x06, - 0xc5, 0xe4, 0xa7, 0xc8, 0x3b, 0x30, 0xc5, 0x4e, 0x75, 0x5c, 0x2d, 0xd6, 0xb3, 0xeb, 0xc4, 0x46, - 0x9a, 0x1d, 0xf0, 0x36, 0x8f, 0x19, 0x9c, 0x86, 0xbc, 0x09, 0x13, 0x9e, 0xdb, 0x15, 0xde, 0x30, - 0x17, 0xc7, 0xd1, 0x36, 0x6b, 0x95, 0x32, 0xde, 0xa0, 0xb9, 0x5d, 0x6e, 0x88, 0x5a, 0x2f, 0x88, - 0xa7, 0xe8, 0xfa, 0x87, 0x70, 0x76, 0xcc, 0x07, 0x49, 0x15, 0x16, 0x13, 0x9e, 0x42, 0x47, 0x74, - 0x22, 0xb2, 0x63, 0x27, 0x22, 0x5f, 0x18, 0x27, 0x7a, 0x70, 0x7a, 0x64, 0x05, 0x49, 0x6d, 0xa4, - 0x04, 0xc1, 0xd8, 0x68, 0xc9, 0x32, 0x79, 0xb2, 0x26, 0xa4, 0x0b, 0xff, 0xda, 0xd7, 0x72, 0x70, - 0x3c, 0x63, 0x12, 0xfd, 0x3f, 0xab, 0x9b, 0xfd, 0x13, 0x8d, 0xf7, 0x87, 0x2a, 0x0c, 0x48, 0x1b, - 0xb8, 0x23, 0x1a, 0x17, 0x1c, 0x57, 0x46, 0x0b, 0x0e, 0xd9, 0xbf, 0x87, 0x07, 0xc9, 0x43, 0x80, - 0xec, 0xc5, 0xc2, 0x20, 0x4f, 0xe1, 0x79, 0xc3, 0x87, 0xef, 0x03, 0x38, 0x91, 0x29, 0xd8, 0xc9, - 0x69, 0x28, 0xe0, 0x03, 0x98, 0xd8, 0x52, 0x39, 0x4d, 0x7f, 0x6f, 0xfb, 0x68, 0x05, 0xbf, 0xef, - 0xd8, 0xbe, 0xe3, 0x73, 0x3b, 0x19, 0xb7, 0x82, 0x33, 0x98, 0x6c, 0x26, 0xeb, 0xaa, 0xbb, 0x18, - 0xbf, 0xba, 0x27, 0x5b, 0x70, 0x9c, 0x49, 0x17, 0x66, 0xee, 0xb0, 0xf8, 0x75, 0xbf, 0x9a, 0xa3, - 0x11, 0x49, 0xd0, 0x10, 0xc4, 0x4c, 0x1f, 0x8c, 0xda, 0x58, 0xda, 0x4d, 0x82, 0xa8, 0x36, 0x7c, - 0x32, 0x1b, 0x9b, 0xac, 0xc3, 0x2c, 0x63, 0xce, 0x6e, 0x0a, 0x98, 0x9f, 0xd6, 0xa5, 0xb1, 0x5f, - 0x28, 0xe3, 0xbb, 0xd0, 0x20, 0xfa, 0x9b, 0x3c, 0x07, 0xf3, 0xe8, 0x12, 0x6b, 0xed, 0xcb, 0x6e, - 0x68, 0xc6, 0x1c, 0x02, 0xb9, 0xfb, 0x99, 0xfe, 0x1f, 0x22, 0x1d, 0x49, 0xb9, 0xa3, 0xa4, 0x3b, - 0x70, 0xe0, 0xf4, 0x85, 0x2b, 0xde, 0x8c, 0xc1, 0x7f, 0x3d, 0xa1, 0x56, 0x40, 0xde, 0x82, 0x39, - 0xca, 0x76, 0x77, 0xd8, 0x67, 0x3b, 0x73, 0x5e, 0x09, 0xde, 0xbb, 0xc5, 0x8a, 0xe8, 0xb0, 0x6d, - 0x1e, 0x33, 0x66, 0xf7, 0xe3, 0x9f, 0xe4, 0x2a, 0xcc, 0x04, 0xfb, 0xe1, 0x40, 0xde, 0xcf, 0x85, - 0xbf, 0x86, 0xb9, 0xd5, 0x6e, 0x71, 0x92, 0x02, 0xc5, 0x89, 0x0d, 0xe8, 0xeb, 0x53, 0xcc, 0x63, - 0x43, 0x7f, 0x09, 0x66, 0x25, 0xde, 0xb4, 0x31, 0x2c, 0x8a, 0x82, 0x68, 0x0c, 0xfb, 0xc5, 0x07, - 0xfb, 0x3e, 0x14, 0x04, 0x4b, 0x7a, 0x8e, 0xdb, 0xf3, 0x02, 0xa1, 0x0f, 0xe1, 0xdf, 0x14, 0x86, - 0x16, 0x35, 0xda, 0xc8, 0x49, 0x03, 0xff, 0xc6, 0xb3, 0x3c, 0x5e, 0x12, 0x62, 0x22, 0x08, 0x7c, - 0x39, 0x13, 0x99, 0xb2, 0x29, 0xbc, 0xdd, 0x0b, 0xd8, 0x7b, 0x1a, 0x61, 0x54, 0x8f, 0x8c, 0x20, - 0x89, 0x4b, 0xdd, 0x51, 0xe7, 0x0d, 0xe5, 0xc8, 0x9e, 0x4b, 0x1f, 0xd9, 0x59, 0x50, 0x56, 0x4e, - 0xc9, 0xbe, 0x0c, 0x08, 0x63, 0x47, 0xf6, 0x58, 0x81, 0x9a, 0x50, 0x14, 0x28, 0xe9, 0x9a, 0x2e, - 0x1e, 0x3d, 0x76, 0xe2, 0x17, 0xd7, 0x74, 0x49, 0x95, 0xee, 0x97, 0xa2, 0x19, 0xa2, 0x5c, 0x2b, - 0x93, 0xeb, 0x70, 0x82, 0x99, 0xa9, 0x59, 0xe6, 0xcc, 0xa4, 0x2e, 0x79, 0x1c, 0x0b, 0x59, 0xda, - 0xe8, 0x48, 0xa7, 0x3c, 0xfc, 0x1a, 0x8a, 0xbc, 0x06, 0xcb, 0x6e, 0x3f, 0x70, 0x3a, 0x43, 0xdf, - 0xb1, 0x82, 0x07, 0xee, 0x80, 0xe5, 0x70, 0x3f, 0xe0, 0x06, 0x64, 0x22, 0xca, 0xcc, 0x07, 0xee, - 0x00, 0xf3, 0xb9, 0x8b, 0x1e, 0xfe, 0xe5, 0x9c, 0xb8, 0xdc, 0x5c, 0xf7, 0xbc, 0x30, 0x08, 0x7d, - 0x7b, 0xa0, 0x38, 0xce, 0x90, 0x7d, 0x38, 0x8d, 0x55, 0xba, 0x8e, 0xa9, 0x42, 0x3d, 0x5f, 0x5c, - 0x06, 0x47, 0x0b, 0x6c, 0xf6, 0xfa, 0xab, 0xea, 0xc5, 0x40, 0x89, 0x62, 0x97, 0x64, 0x64, 0xba, - 0xae, 0x24, 0xae, 0x9b, 0xc7, 0x8c, 0x53, 0x8c, 0x67, 0x0a, 0x8b, 0x6c, 0x66, 0xc8, 0x9a, 0xa4, - 0xe7, 0xcc, 0x7a, 0x2c, 0x78, 0x54, 0xae, 0xb2, 0x48, 0x22, 0xef, 0xc1, 0x8c, 0xdb, 0x15, 0xb9, - 0x59, 0xf3, 0x19, 0x3e, 0x1b, 0xb5, 0x2e, 0x4b, 0xa1, 0x15, 0xf3, 0xa0, 0x4b, 0xc3, 0xe5, 0xd0, - 0xf5, 0x79, 0x45, 0xc3, 0xd1, 0x37, 0xc5, 0x3d, 0x5a, 0x9a, 0x8c, 0x2c, 0xc4, 0x7b, 0x1f, 0xee, - 0x73, 0x28, 0x05, 0xa4, 0x9c, 0xb0, 0xfc, 0x17, 0xef, 0xf2, 0x1f, 0x82, 0x17, 0x8e, 0xda, 0x53, - 0x54, 0x6e, 0x8c, 0xe8, 0xf6, 0x19, 0x96, 0xc0, 0x43, 0xed, 0xbd, 0x4b, 0x20, 0x27, 0x1e, 0x72, - 0xc5, 0x44, 0x11, 0xb0, 0x6d, 0xdf, 0xd5, 0xff, 0x24, 0x0f, 0x0b, 0xaa, 0x6b, 0x15, 0x79, 0x09, - 0x26, 0x24, 0x71, 0x79, 0x2a, 0xc3, 0xff, 0x0a, 0x85, 0x24, 0x22, 0x1d, 0x49, 0x3c, 0x92, 0x5b, - 0xb0, 0x80, 0xaf, 0xc3, 0x50, 0x8d, 0x0b, 0x5d, 0xee, 0x36, 0x70, 0x54, 0x87, 0x80, 0x39, 0x4a, - 0x4b, 0xb7, 0x47, 0x5a, 0x28, 0x79, 0xce, 0x4c, 0x8c, 0xf6, 0x9c, 0xe1, 0x4d, 0x19, 0xe1, 0x39, - 0x33, 0x39, 0xc6, 0x73, 0x26, 0xa6, 0x94, 0x3d, 0x67, 0xd0, 0x7f, 0x6a, 0x7a, 0x94, 0xff, 0x54, - 0x4c, 0xc3, 0xfc, 0xa7, 0x62, 0xcf, 0x97, 0xc2, 0x48, 0xcf, 0x97, 0x98, 0x86, 0x7b, 0xbe, 0xc4, - 0xbe, 0x28, 0x33, 0x23, 0x7d, 0x51, 0x24, 0x22, 0xe6, 0x8b, 0xf2, 0x3c, 0xef, 0x58, 0xdf, 0x7e, - 0x64, 0x61, 0x8f, 0xf3, 0xf3, 0x11, 0x76, 0x99, 0x61, 0x3f, 0xc2, 0x57, 0x1c, 0x54, 0x3d, 0xe1, - 0x4f, 0x3f, 0xf4, 0x6f, 0x26, 0xc4, 0x90, 0x18, 0xf3, 0xcb, 0xb0, 0xc0, 0x76, 0x63, 0x9e, 0x18, - 0x86, 0x6d, 0xc7, 0xf3, 0xc6, 0xbc, 0x80, 0xb2, 0xeb, 0xab, 0x4f, 0xc1, 0x62, 0x84, 0xc6, 0x6f, - 0x70, 0x30, 0x2c, 0x8d, 0x11, 0x51, 0xf3, 0xbb, 0x1b, 0x99, 0x9f, 0xcf, 0xc3, 0xdb, 0x2a, 0xfc, - 0xd8, 0x05, 0xc8, 0x2b, 0x40, 0x62, 0xb4, 0xe8, 0x21, 0xdc, 0x04, 0xa2, 0x2e, 0x45, 0xa8, 0xd1, - 0x6b, 0xb5, 0x9f, 0xd5, 0x12, 0xce, 0x1b, 0xdf, 0xab, 0xea, 0xbf, 0x04, 0xd1, 0xd7, 0x2d, 0x7e, - 0x01, 0x2f, 0x5a, 0x50, 0x14, 0x05, 0x2d, 0x0e, 0xd7, 0xff, 0x46, 0xca, 0x34, 0xff, 0xbd, 0xaa, - 0xd6, 0x59, 0x98, 0x89, 0x4f, 0x42, 0x6c, 0xbb, 0x2a, 0x04, 0xfc, 0x10, 0xa4, 0xff, 0xf2, 0x84, - 0x72, 0xe3, 0x2c, 0xea, 0x40, 0x75, 0xa0, 0xc0, 0xb3, 0xf8, 0x04, 0xe0, 0x22, 0xfa, 0xd2, 0x88, - 0x49, 0xcc, 0x1f, 0x06, 0x99, 0x66, 0xd3, 0x80, 0x20, 0xf0, 0xc4, 0x3b, 0x21, 0x8b, 0xd9, 0x12, - 0xa5, 0x33, 0xa1, 0x60, 0xc7, 0xe4, 0xf1, 0xda, 0x78, 0x76, 0xe2, 0x4a, 0x8f, 0xae, 0x61, 0xb4, - 0x29, 0x46, 0xbf, 0xc4, 0x07, 0xb6, 0x01, 0xbd, 0x44, 0x02, 0x95, 0x79, 0x96, 0xe5, 0x2d, 0xc5, - 0x1c, 0xbb, 0x10, 0x39, 0xe3, 0x7d, 0x5f, 0x20, 0xb3, 0xad, 0xc2, 0x1c, 0xde, 0xe7, 0x0a, 0x86, - 0x13, 0x19, 0x7e, 0x6c, 0xe9, 0xc6, 0x97, 0x6b, 0x5b, 0xc6, 0x2c, 0xa5, 0x13, 0x6c, 0xf6, 0x14, - 0xdb, 0x7b, 0xa2, 0x92, 0x93, 0x22, 0xd9, 0xd3, 0xd8, 0x1e, 0x88, 0x2f, 0x6b, 0xb1, 0xaa, 0x92, - 0x11, 0x5e, 0xa9, 0x70, 0x07, 0x4e, 0xa7, 0xee, 0x20, 0xa3, 0x2f, 0x31, 0xbf, 0xca, 0x17, 0x46, - 0x7c, 0x29, 0x71, 0x39, 0xe9, 0xf8, 0xc6, 0xc9, 0x40, 0x05, 0xf1, 0x8f, 0xe0, 0x43, 0xbc, 0xd1, - 0x03, 0x3f, 0x26, 0x0d, 0x79, 0xac, 0x64, 0xe5, 0x64, 0x25, 0x4b, 0xbe, 0xf9, 0xcd, 0xab, 0x37, - 0xbf, 0x1b, 0x70, 0x91, 0x4a, 0x44, 0x3e, 0x73, 0x9c, 0x87, 0x8e, 0x7f, 0xe0, 0xf5, 0x31, 0x42, - 0xf0, 0x20, 0x12, 0x0c, 0xec, 0xaa, 0xfa, 0x1c, 0xc5, 0xc3, 0x79, 0x51, 0xe5, 0x58, 0x5b, 0x88, - 0xc4, 0x22, 0x5f, 0xff, 0xa3, 0x3c, 0x3c, 0x77, 0x84, 0xc9, 0x35, 0xa6, 0xee, 0x9f, 0x53, 0x8f, - 0x02, 0x39, 0xe5, 0x16, 0x88, 0x32, 0x8d, 0x6f, 0x17, 0x46, 0x1c, 0x04, 0x7e, 0x10, 0x16, 0xd9, - 0x26, 0xc6, 0x5e, 0x22, 0xee, 0x0c, 0x7b, 0x47, 0xd8, 0xc5, 0xce, 0x8a, 0x38, 0x2b, 0x09, 0x52, - 0xdc, 0xd8, 0x50, 0x76, 0x9b, 0x11, 0x8c, 0xb4, 0x61, 0x16, 0xd1, 0x76, 0x6c, 0xb7, 0x77, 0xa4, - 0x80, 0x1f, 0x22, 0x8a, 0x8b, 0x4c, 0xc6, 0x5e, 0x5c, 0x53, 0xc0, 0x06, 0xfe, 0x26, 0x57, 0x60, - 0xb1, 0x3f, 0xdc, 0xa7, 0x4a, 0x2e, 0x9b, 0xb9, 0xfc, 0xc1, 0xc7, 0xa4, 0x31, 0xdf, 0x1f, 0xee, - 0x97, 0x06, 0x03, 0x9c, 0x80, 0xf8, 0x32, 0x64, 0x89, 0xe2, 0xf1, 0xec, 0xfe, 0x1c, 0x73, 0x0a, - 0x31, 0x29, 0x03, 0x26, 0x82, 0x38, 0xee, 0x32, 0xb0, 0x77, 0x82, 0x3c, 0x9d, 0x3b, 0xfb, 0xa1, - 0xff, 0xcf, 0x9c, 0xb8, 0x97, 0x18, 0xbd, 0x4a, 0xff, 0x7a, 0x88, 0x32, 0x86, 0xe8, 0x05, 0x28, - 0xd2, 0xae, 0x8f, 0x45, 0x60, 0x34, 0x46, 0x0b, 0xfd, 0xe1, 0x7e, 0xd4, 0x77, 0x72, 0xc7, 0x4f, - 0xc9, 0x1d, 0xff, 0x96, 0x30, 0x53, 0x67, 0x0a, 0xb3, 0xd1, 0x5d, 0x4e, 0xb5, 0xbf, 0x2b, 0x47, - 0x13, 0x59, 0x7f, 0x3d, 0x6e, 0x19, 0xe3, 0x96, 0x70, 0x8a, 0x99, 0x4c, 0x39, 0xc5, 0x64, 0xac, - 0xbd, 0xa9, 0xac, 0xb5, 0x97, 0x72, 0xc1, 0x99, 0xce, 0x70, 0xc1, 0xc9, 0x5c, 0xa0, 0x85, 0x43, - 0x16, 0xe8, 0x8c, 0x3c, 0x4f, 0x7e, 0x27, 0x07, 0x97, 0x0e, 0xdd, 0x37, 0xfe, 0x7a, 0xa4, 0x33, - 0x46, 0x3a, 0xbb, 0x3f, 0xff, 0x24, 0x27, 0xb4, 0x69, 0xf5, 0x90, 0xfc, 0x01, 0x1c, 0x17, 0x87, - 0x64, 0xa6, 0x37, 0xc4, 0x9e, 0x6a, 0xb3, 0xd7, 0x5f, 0xcc, 0x3a, 0x1e, 0x23, 0x5a, 0xc6, 0x11, - 0x76, 0x89, 0x1f, 0x8c, 0xe3, 0xf2, 0xff, 0x7b, 0x8e, 0xc4, 0xe4, 0x1e, 0x9c, 0xc4, 0x84, 0x8f, - 0x1d, 0xd9, 0xc7, 0xce, 0xf2, 0x9d, 0x1d, 0xde, 0xeb, 0x97, 0x52, 0x47, 0x47, 0xb7, 0x23, 0x55, - 0xc7, 0x70, 0x76, 0x36, 0x8f, 0x19, 0xcb, 0x41, 0x06, 0x9c, 0x1b, 0xa2, 0x12, 0x67, 0xee, 0x7f, - 0xae, 0x81, 0x7e, 0x78, 0xaf, 0xa1, 0x79, 0x24, 0xd9, 0xed, 0x33, 0xc6, 0xac, 0x2d, 0xf5, 0xe1, - 0x73, 0x30, 0xef, 0x3b, 0x3b, 0xbe, 0x13, 0xec, 0x29, 0x36, 0xcc, 0x39, 0x0e, 0x14, 0xdd, 0x23, - 0x72, 0xcf, 0x3c, 0xd1, 0x71, 0x55, 0x10, 0x45, 0x7e, 0x10, 0xe7, 0xc6, 0x8d, 0x09, 0x9d, 0x59, - 0x72, 0x35, 0xd9, 0x8f, 0xc8, 0x53, 0x32, 0x57, 0xcc, 0x1b, 0x3c, 0x5b, 0xce, 0x8e, 0xdb, 0x73, - 0xf4, 0xdf, 0x88, 0x74, 0xb7, 0xac, 0xee, 0x24, 0x1f, 0x48, 0x2f, 0xa4, 0xf3, 0x29, 0xb5, 0x34, - 0x8b, 0xe4, 0x28, 0xc6, 0xe6, 0xfa, 0x33, 0x32, 0x36, 0xdf, 0x10, 0xcf, 0xac, 0xe8, 0xae, 0x72, - 0xe7, 0x1a, 0x79, 0x11, 0xa6, 0xd9, 0xcb, 0x2a, 0x51, 0xdd, 0x45, 0xa5, 0xba, 0x77, 0xae, 0x19, - 0xa2, 0x5c, 0xff, 0x38, 0xf2, 0x09, 0x4d, 0x35, 0xe2, 0xce, 0x35, 0xf2, 0xd6, 0xd1, 0x5e, 0x3c, - 0x17, 0xc4, 0x8b, 0xe7, 0xe8, 0xb5, 0xf3, 0xdb, 0xca, 0x6b, 0xe7, 0xe7, 0xc7, 0xf7, 0x16, 0x77, - 0x37, 0x66, 0x19, 0x48, 0x0a, 0x1f, 0xff, 0x02, 0x8b, 0xfc, 0xae, 0xff, 0x58, 0x1e, 0xce, 0x8f, - 0xa5, 0x20, 0xe7, 0xa0, 0x50, 0x6a, 0xd5, 0xda, 0xf1, 0x28, 0xd3, 0x55, 0x24, 0x20, 0xe4, 0x26, - 0xcc, 0xac, 0xdb, 0x81, 0xdb, 0xa1, 0x53, 0x3a, 0xd3, 0xd9, 0x2a, 0xc5, 0x36, 0x42, 0xdf, 0x3c, - 0x66, 0xc4, 0xb4, 0xc4, 0x82, 0x25, 0x5c, 0x17, 0x4a, 0xda, 0xf8, 0x7c, 0x86, 0x49, 0x2e, 0xc5, - 0x30, 0x45, 0x46, 0x25, 0x4f, 0x0a, 0x48, 0xee, 0x03, 0x31, 0xcd, 0xcd, 0xb2, 0xe3, 0x87, 0xdc, - 0x48, 0x15, 0xba, 0xd1, 0xf3, 0xd9, 0xd7, 0x0e, 0xe9, 0xbb, 0x14, 0xdd, 0xe6, 0x31, 0x23, 0x83, - 0x1b, 0xb9, 0x04, 0xb3, 0x03, 0xdf, 0x7d, 0x68, 0x87, 0x0e, 0x3a, 0xc1, 0x53, 0x2d, 0x68, 0x6e, - 0xf3, 0x98, 0x01, 0x1c, 0x78, 0xdb, 0x39, 0xc8, 0x96, 0x0d, 0x3f, 0x24, 0x54, 0xcf, 0xd1, 0xbd, - 0xf5, 0x04, 0x69, 0xa0, 0x5e, 0x80, 0x42, 0x4b, 0xbc, 0x4a, 0x90, 0xe2, 0x15, 0x88, 0x17, 0x08, - 0x46, 0x54, 0xca, 0x67, 0xf6, 0xcf, 0x68, 0xc2, 0x88, 0x77, 0x78, 0xdf, 0x92, 0x17, 0x41, 0x64, - 0xf9, 0x17, 0x17, 0x64, 0x18, 0x3f, 0x21, 0x7a, 0x78, 0x63, 0x44, 0xc5, 0xe4, 0x0d, 0x50, 0xb2, - 0xfb, 0xf3, 0xba, 0x60, 0x9a, 0x6a, 0x8e, 0xce, 0xa4, 0xb7, 0xa1, 0xa0, 0xf1, 0x4a, 0x79, 0xf0, - 0xe2, 0x91, 0x47, 0x83, 0xbc, 0x03, 0x45, 0x4c, 0x92, 0x6e, 0x4b, 0x23, 0xab, 0xa6, 0x2b, 0x60, - 0xe1, 0x6b, 0x5b, 0xb6, 0xeb, 0x1b, 0x8b, 0x1d, 0x95, 0x9a, 0x7f, 0xf0, 0x57, 0x78, 0x86, 0xc8, - 0x5a, 0xb7, 0x95, 0xb8, 0x98, 0x7f, 0xda, 0xa0, 0x17, 0x55, 0x65, 0x9d, 0x8a, 0xbd, 0x2e, 0xfb, - 0x5b, 0xa3, 0x63, 0x5f, 0x48, 0x8b, 0xf6, 0xef, 0xe6, 0xe1, 0xdc, 0x38, 0x72, 0x52, 0x82, 0x62, - 0x95, 0x05, 0xd2, 0xe5, 0xcf, 0x8d, 0x3d, 0x5f, 0xce, 0x58, 0xcc, 0x82, 0xec, 0x5a, 0xdd, 0xa8, - 0xd0, 0x48, 0xa1, 0xd3, 0x71, 0x66, 0xb0, 0x28, 0xa2, 0x03, 0x8e, 0x33, 0x27, 0xa5, 0xe3, 0x2c, - 0x8a, 0xc9, 0x73, 0x30, 0x55, 0x2a, 0x9b, 0xdb, 0x46, 0x5d, 0x0e, 0xd7, 0x62, 0x77, 0x02, 0x7c, - 0xd4, 0xcb, 0x8b, 0xc8, 0x0f, 0x40, 0x31, 0x99, 0x99, 0x15, 0x0f, 0xcf, 0x52, 0x28, 0x32, 0x39, - 0x65, 0xa8, 0x48, 0xce, 0x83, 0xf5, 0x8d, 0x93, 0x8d, 0xf2, 0xfc, 0x7d, 0x46, 0x8a, 0x17, 0xd1, - 0x61, 0xaa, 0xe5, 0x3b, 0x81, 0x13, 0xca, 0xcf, 0xa2, 0x07, 0x08, 0x31, 0x78, 0x09, 0x7f, 0xb4, - 0x6c, 0x1f, 0xb0, 0xa0, 0x96, 0x53, 0x72, 0xf0, 0x62, 0x7c, 0xe5, 0x4c, 0xc1, 0x86, 0x84, 0x42, - 0x09, 0xea, 0xf6, 0xb0, 0xdf, 0xd9, 0xdb, 0x36, 0xea, 0x5c, 0xad, 0x65, 0x04, 0x3d, 0x84, 0xd2, - 0x06, 0x06, 0x86, 0x84, 0xa2, 0xff, 0xb8, 0x06, 0xcb, 0x59, 0xed, 0x38, 0xc4, 0x9f, 0xe9, 0x35, - 0x98, 0xc5, 0xfb, 0xdb, 0x1d, 0xcf, 0xdf, 0xb7, 0x43, 0xf9, 0xf1, 0xb8, 0x04, 0x36, 0xf0, 0xbe, - 0x79, 0x03, 0xff, 0x26, 0xab, 0x62, 0xb7, 0x92, 0xf2, 0x2e, 0x21, 0x80, 0x6f, 0x5c, 0x7a, 0x09, - 0xa0, 0xd6, 0x6d, 0x35, 0x07, 0x2c, 0x79, 0xe8, 0xeb, 0x30, 0x41, 0xab, 0x95, 0x98, 0xbd, 0x74, - 0xfe, 0x94, 0xb6, 0xea, 0x1c, 0x89, 0xd5, 0x2a, 0xb0, 0xf7, 0x7b, 0x06, 0x22, 0xeb, 0x77, 0x61, - 0x41, 0xc5, 0x20, 0x55, 0x35, 0x7f, 0xd4, 0xec, 0xf5, 0x22, 0xe7, 0xb4, 0xee, 0x79, 0x2c, 0x80, - 0xc9, 0xfa, 0xe9, 0x6f, 0x7d, 0xb2, 0x0a, 0xf4, 0x27, 0xa3, 0xc9, 0xca, 0x2f, 0xa5, 0xff, 0x54, - 0x0e, 0x96, 0xe3, 0x20, 0x8b, 0x62, 0x0d, 0x7d, 0xdf, 0x06, 0xf0, 0x2a, 0x29, 0x01, 0xa6, 0x84, - 0x12, 0x9a, 0x6e, 0xe0, 0x98, 0xb8, 0x36, 0x37, 0x61, 0x65, 0x14, 0x3e, 0x79, 0x09, 0x66, 0x30, - 0xd6, 0xf7, 0xc0, 0xee, 0x38, 0xb2, 0xc8, 0xed, 0x0b, 0xa0, 0x11, 0x97, 0xeb, 0xbf, 0xa3, 0xc1, - 0x19, 0x1e, 0x76, 0x63, 0xcb, 0x76, 0xfb, 0x78, 0x5d, 0xd8, 0x71, 0x9e, 0x4d, 0x00, 0xba, 0x9b, - 0x8a, 0x1c, 0xbb, 0xac, 0x46, 0x57, 0x49, 0x7d, 0x6d, 0x74, 0x6b, 0xc9, 0x8b, 0x18, 0xbf, 0x9e, - 0xbb, 0x33, 0x4f, 0xb0, 0x08, 0xa1, 0x7d, 0x0a, 0x90, 0x23, 0x84, 0x22, 0x86, 0xfe, 0xc3, 0x70, - 0x61, 0xfc, 0x07, 0xc8, 0x97, 0x60, 0x1e, 0xb3, 0xfd, 0x6f, 0x0f, 0x76, 0x7d, 0xbb, 0xeb, 0x08, - 0x23, 0xb1, 0xb8, 0xe9, 0x90, 0xcb, 0x58, 0x38, 0x7e, 0x1e, 0xb1, 0x92, 0xc2, 0xad, 0x21, 0x27, - 0x52, 0x62, 0xdb, 0xc8, 0xdc, 0xf4, 0x1f, 0xd1, 0x80, 0xa4, 0x79, 0x90, 0x37, 0x61, 0x6e, 0xbb, - 0x5d, 0x36, 0x43, 0xdb, 0x0f, 0x37, 0xbd, 0xa1, 0xcf, 0x63, 0xe1, 0xb3, 0x00, 0x86, 0x61, 0xc7, - 0x62, 0x17, 0xc3, 0x7b, 0xde, 0xd0, 0x37, 0x14, 0x3c, 0xcc, 0x08, 0xef, 0x38, 0x0f, 0xba, 0xf6, - 0x81, 0x9a, 0x11, 0x9e, 0xc3, 0x94, 0x8c, 0xf0, 0x1c, 0xa6, 0xff, 0x92, 0x06, 0x67, 0xc5, 0xc3, - 0xc9, 0x6e, 0x46, 0x5d, 0xca, 0x18, 0xa6, 0xd7, 0x17, 0x59, 0xb9, 0xc6, 0x29, 0xfa, 0x4b, 0x22, - 0x92, 0x35, 0x56, 0x10, 0x35, 0x7e, 0x46, 0x4b, 0x3e, 0x07, 0x13, 0x66, 0xe8, 0x0d, 0x8e, 0x10, - 0xca, 0xba, 0x18, 0x8d, 0x68, 0xe8, 0x0d, 0x90, 0x05, 0x52, 0xea, 0x0e, 0x2c, 0xcb, 0x95, 0x13, - 0x35, 0x26, 0x5b, 0x30, 0xcd, 0xf3, 0x20, 0x24, 0x9c, 0x77, 0xc7, 0xb4, 0x69, 0x7d, 0x51, 0xc4, - 0xcb, 0xe6, 0xf9, 0xa3, 0x0c, 0xc1, 0x43, 0xff, 0x49, 0x0d, 0x66, 0xa9, 0xaa, 0x83, 0x16, 0x83, - 0xa7, 0x9d, 0xd2, 0xaa, 0x0a, 0x2d, 0xde, 0x33, 0x44, 0xec, 0x8f, 0xb4, 0x1b, 0xbf, 0x01, 0x8b, - 0x09, 0x02, 0xa2, 0x63, 0xa4, 0xd4, 0x9e, 0xdb, 0xe1, 0xde, 0x9c, 0xec, 0x2d, 0x80, 0x02, 0xd3, - 0x7f, 0x42, 0x83, 0xe5, 0xe6, 0x83, 0xd0, 0x66, 0xfe, 0x1b, 0xc6, 0xb0, 0x27, 0xd6, 0x3b, 0x55, - 0xdf, 0xc4, 0x0b, 0x5c, 0x16, 0x94, 0x91, 0xa9, 0x6f, 0x1c, 0x66, 0x44, 0xa5, 0x64, 0x13, 0x0a, - 0x7c, 0x7f, 0x09, 0x78, 0x06, 0xa3, 0x0b, 0x92, 0x39, 0x23, 0x66, 0xcc, 0x91, 0x68, 0x4b, 0x50, - 0x84, 0x71, 0x1a, 0x23, 0xa2, 0xd6, 0xff, 0x54, 0x83, 0x53, 0x23, 0x68, 0xc8, 0xbb, 0x30, 0x89, - 0x01, 0xa3, 0xf8, 0xe8, 0x9d, 0x1b, 0xf1, 0x89, 0xb0, 0xb3, 0x77, 0xe7, 0x1a, 0xdb, 0x88, 0xf6, - 0xe9, 0x0f, 0x83, 0x51, 0x91, 0x0f, 0x60, 0xa6, 0xd4, 0xed, 0xf2, 0x83, 0x5d, 0x4e, 0x39, 0xd8, - 0x8d, 0xf8, 0xe2, 0xd5, 0x08, 0x9f, 0x1d, 0xec, 0x58, 0xe8, 0x92, 0x6e, 0xd7, 0xe2, 0xc1, 0xb0, - 0x62, 0x7e, 0x67, 0xde, 0x81, 0x05, 0x15, 0xf9, 0x89, 0xe2, 0xf7, 0x7c, 0xac, 0x41, 0x51, 0xad, - 0xc3, 0xf7, 0x26, 0xd2, 0x77, 0xd6, 0x30, 0x1f, 0x32, 0xa9, 0x7e, 0x3a, 0x07, 0x27, 0x32, 0x7b, - 0x98, 0xbc, 0x02, 0x53, 0xa5, 0xc1, 0xa0, 0x56, 0xe1, 0xb3, 0x8a, 0x6b, 0x48, 0x78, 0xb3, 0xa1, - 0x9c, 0x7b, 0x19, 0x12, 0x79, 0x1d, 0x0a, 0xcc, 0x4d, 0xa8, 0x22, 0x04, 0x0e, 0x86, 0x2e, 0xe6, - 0x3e, 0x4c, 0x6a, 0x2e, 0x21, 0x81, 0x48, 0x36, 0x60, 0x81, 0x07, 0xfd, 0x45, 0x9f, 0xb1, 0x28, - 0xbf, 0x27, 0xba, 0xd9, 0x89, 0xeb, 0x12, 0xe6, 0x6d, 0xa6, 0xc8, 0xce, 0x04, 0x15, 0xa9, 0x43, - 0x11, 0x79, 0xca, 0x9c, 0x58, 0x42, 0x23, 0xc9, 0x4d, 0x73, 0x04, 0xaf, 0x14, 0x65, 0x34, 0x5c, - 0xf1, 0x5b, 0xb0, 0xef, 0xdd, 0x70, 0xc5, 0xdf, 0x38, 0xd2, 0x70, 0xfd, 0xdc, 0x04, 0x5b, 0xcc, - 0x49, 0x32, 0xaa, 0xd1, 0x48, 0xe9, 0xfc, 0x50, 0xa3, 0xa1, 0x27, 0x36, 0x1e, 0xd6, 0xb6, 0x02, - 0xd3, 0x6d, 0x9e, 0x5d, 0x8d, 0xad, 0x8c, 0xf3, 0x99, 0x55, 0x60, 0x38, 0x77, 0xae, 0x31, 0xf5, - 0x85, 0x67, 0x5b, 0x33, 0x04, 0x29, 0xb9, 0x03, 0xb3, 0xe5, 0x9e, 0x63, 0xf7, 0x87, 0x83, 0xf6, - 0xd1, 0xbc, 0x13, 0x56, 0x78, 0x5b, 0xe6, 0x3a, 0x8c, 0x0c, 0xbd, 0x1a, 0x50, 0x92, 0xcb, 0x8c, - 0x48, 0x3b, 0x0a, 0x66, 0x33, 0x81, 0xb6, 0xd2, 0xd7, 0xc6, 0xf4, 0x4f, 0x12, 0x38, 0x32, 0xf5, - 0x11, 0xb1, 0x60, 0xa1, 0x6e, 0x07, 0x61, 0xdb, 0xb7, 0xfb, 0x01, 0xa6, 0x3e, 0x39, 0x42, 0x18, - 0xf7, 0xb3, 0xbc, 0xc2, 0xcc, 0x86, 0x1a, 0x46, 0xa4, 0xcc, 0x86, 0xaa, 0xb2, 0xa3, 0xfa, 0xd2, - 0x86, 0xdb, 0xb7, 0x7b, 0xee, 0x97, 0x45, 0xcc, 0x2f, 0xa6, 0x2f, 0xed, 0x08, 0xa0, 0x11, 0x97, - 0xeb, 0x5f, 0x4c, 0x8d, 0x1b, 0xab, 0xe5, 0x2c, 0x4c, 0xf3, 0x88, 0x90, 0x2c, 0x42, 0x62, 0xab, - 0xda, 0xa8, 0xd4, 0x1a, 0x37, 0x8b, 0x1a, 0x59, 0x00, 0x68, 0x19, 0xcd, 0x72, 0xd5, 0x34, 0xe9, - 0xef, 0x1c, 0xfd, 0xcd, 0xc3, 0x27, 0x6e, 0x6c, 0xd7, 0x8b, 0x79, 0x29, 0x82, 0xe2, 0x84, 0xfe, - 0xd5, 0x1c, 0x9c, 0xcc, 0x1e, 0x4a, 0xd2, 0xe6, 0x4f, 0x18, 0x98, 0x9f, 0xca, 0x9b, 0x63, 0xc7, - 0x3d, 0x13, 0x9c, 0x8c, 0xc5, 0x19, 0xb2, 0x18, 0x8f, 0x39, 0x71, 0xc1, 0xc9, 0x82, 0x46, 0xb9, - 0x5d, 0xee, 0xac, 0xaa, 0x26, 0xac, 0xba, 0x34, 0xe6, 0x7b, 0xa6, 0x9a, 0xaa, 0xaa, 0x0c, 0x2b, - 0xa3, 0x3e, 0xaf, 0xf6, 0xd2, 0x22, 0xcc, 0x96, 0x5a, 0xad, 0x7a, 0xad, 0x5c, 0x6a, 0xd7, 0x9a, - 0x8d, 0xa2, 0x46, 0x66, 0x60, 0xf2, 0xa6, 0xd1, 0xdc, 0x6e, 0x15, 0x73, 0xfa, 0x7f, 0xd4, 0xe0, - 0xcc, 0xe8, 0x6f, 0x91, 0x15, 0x98, 0xf6, 0x86, 0x61, 0xc7, 0x13, 0x07, 0x24, 0x43, 0xfc, 0x24, - 0x0b, 0x90, 0xe3, 0x4a, 0xc9, 0x8c, 0x91, 0xf3, 0x06, 0x64, 0x97, 0xfb, 0x8c, 0xf0, 0x97, 0x9a, - 0x47, 0x8a, 0x98, 0xf7, 0x3c, 0x9f, 0x3d, 0x2b, 0x2a, 0x65, 0x22, 0x0e, 0xda, 0x3c, 0x2d, 0x6d, - 0x89, 0x42, 0xf2, 0x1c, 0xcc, 0xef, 0xd8, 0x6e, 0x6f, 0xe8, 0x3b, 0x16, 0x3a, 0xa6, 0xe3, 0x1a, - 0x98, 0x34, 0xe6, 0x38, 0xb0, 0x4c, 0x61, 0xfa, 0xef, 0x6b, 0x30, 0x5f, 0x8b, 0x3d, 0x8b, 0x9f, - 0x56, 0x1c, 0x7d, 0x46, 0x11, 0x47, 0x2b, 0x51, 0xfc, 0xdd, 0xe8, 0x03, 0x63, 0x74, 0xea, 0xf5, - 0x28, 0x48, 0x5a, 0x5e, 0xf1, 0xc0, 0x91, 0xa9, 0x45, 0xf8, 0xa9, 0x28, 0x49, 0xab, 0x1a, 0x44, - 0x4d, 0x92, 0x67, 0xff, 0x2c, 0x0f, 0x4b, 0xa9, 0xef, 0x12, 0x13, 0xa6, 0x4b, 0x77, 0xcd, 0x66, - 0xad, 0x52, 0xe6, 0xad, 0x5b, 0x8d, 0xbd, 0x51, 0x29, 0x34, 0x5d, 0x53, 0x16, 0xf7, 0xee, 0x51, - 0x60, 0x79, 0x6e, 0xb7, 0xa3, 0xb8, 0x43, 0x0b, 0x4e, 0xa8, 0x1f, 0x7c, 0x79, 0xe8, 0xa3, 0x87, - 0x37, 0x6f, 0x79, 0xe4, 0xe4, 0x2a, 0xe0, 0x69, 0xc6, 0xe8, 0xf3, 0x6c, 0xd3, 0xf2, 0x34, 0xeb, - 0x98, 0x1f, 0x69, 0xc0, 0xd4, 0x4d, 0x37, 0xdc, 0x1c, 0xde, 0xe7, 0xbd, 0x72, 0x21, 0xce, 0x40, - 0xbf, 0x39, 0xbc, 0x9f, 0x66, 0x8b, 0x36, 0x64, 0x16, 0xf3, 0x46, 0x61, 0xc9, 0xb9, 0x90, 0xdb, - 0x30, 0x59, 0xba, 0x6b, 0x1a, 0x25, 0x2e, 0xb3, 0x24, 0x7f, 0x5f, 0xa3, 0x34, 0x82, 0x1b, 0x6d, - 0xbd, 0x6f, 0x2b, 0xdc, 0x18, 0x8f, 0x64, 0xdc, 0xaf, 0x89, 0x27, 0x8a, 0xfb, 0xb5, 0x3e, 0x0f, - 0xb3, 0xfc, 0x98, 0x8b, 0x27, 0xc8, 0xc7, 0x70, 0x3c, 0x63, 0xa8, 0x89, 0x8d, 0xaf, 0x0a, 0xd0, - 0x0f, 0xa1, 0xd4, 0x3f, 0x78, 0xb4, 0xe7, 0xf8, 0x4e, 0x7a, 0xec, 0xd4, 0xba, 0x8b, 0x59, 0x92, - 0x59, 0x7b, 0x23, 0xc5, 0x4e, 0xff, 0x4d, 0x0d, 0x56, 0x46, 0x4d, 0x00, 0x7a, 0x66, 0x57, 0x23, - 0x8b, 0x9e, 0x8c, 0x32, 0x2d, 0xab, 0xaf, 0x01, 0x04, 0x1a, 0x79, 0x1f, 0x66, 0x99, 0xc7, 0xa8, - 0xf9, 0xfa, 0xb6, 0x51, 0xe3, 0xb2, 0xec, 0xfc, 0x77, 0x3e, 0x59, 0x3d, 0xc5, 0x9d, 0x4c, 0x83, - 0xd7, 0xad, 0xa1, 0xef, 0xc6, 0xa4, 0x2b, 0x9a, 0x21, 0x53, 0xd0, 0x23, 0x96, 0x3d, 0xec, 0xba, - 0x8e, 0x38, 0x60, 0x8a, 0xe8, 0x8b, 0x1c, 0x26, 0x2b, 0x3c, 0x02, 0xa6, 0x7f, 0x55, 0x83, 0x33, - 0xa3, 0x67, 0x1b, 0x55, 0xa2, 0xda, 0xcc, 0xf1, 0x56, 0xc4, 0x3f, 0x44, 0x25, 0x2a, 0xf2, 0xce, - 0x95, 0x79, 0x0a, 0x44, 0x4a, 0xc4, 0x4d, 0xa1, 0xc2, 0x82, 0x86, 0x44, 0x91, 0xa5, 0x54, 0x26, - 0x12, 0x88, 0xfa, 0x3d, 0x38, 0x35, 0x62, 0x6e, 0x92, 0xf7, 0x32, 0xf3, 0xb7, 0x63, 0x7c, 0x1b, - 0x39, 0x80, 0x91, 0x9c, 0x7c, 0x41, 0xc6, 0xd7, 0xff, 0x1d, 0x73, 0x35, 0xcf, 0x98, 0xa8, 0x54, - 0xdf, 0xc3, 0x7c, 0xe1, 0xa5, 0x7e, 0x67, 0xcf, 0xf3, 0xe3, 0xc1, 0x42, 0x7d, 0x2f, 0xa4, 0x25, - 0x96, 0x8d, 0x45, 0x89, 0x41, 0x4b, 0x50, 0x11, 0x0f, 0x96, 0x5a, 0xbe, 0xb7, 0xe3, 0xb2, 0x60, - 0x08, 0xec, 0x98, 0xce, 0xd7, 0xf4, 0x0b, 0xd2, 0x74, 0x93, 0xa7, 0x4f, 0x0a, 0x3f, 0xca, 0x75, - 0x49, 0xc1, 0xcc, 0x5b, 0xa9, 0x83, 0x05, 0x46, 0x9a, 0xb7, 0xfe, 0xad, 0x1c, 0x5c, 0x3a, 0x94, - 0xe3, 0x51, 0xd3, 0x9e, 0xbf, 0x0a, 0xc0, 0x69, 0x69, 0x0f, 0x48, 0x46, 0x38, 0x51, 0x19, 0xdb, - 0xef, 0x1b, 0x12, 0x0a, 0x79, 0x00, 0xe7, 0xc5, 0xaf, 0x4e, 0xc7, 0x19, 0x84, 0x01, 0xad, 0x07, - 0x4f, 0x88, 0x10, 0x45, 0x76, 0x2c, 0xac, 0x5f, 0xfe, 0xce, 0x27, 0xab, 0x97, 0x22, 0x1e, 0x0c, - 0x93, 0x3d, 0x88, 0x11, 0xb9, 0x15, 0xd0, 0x14, 0x38, 0x9e, 0x17, 0xb9, 0x12, 0xaf, 0xa4, 0x89, - 0xd8, 0xa8, 0x2f, 0x56, 0x52, 0xbc, 0x7e, 0x36, 0x81, 0x70, 0x46, 0x94, 0x6c, 0x43, 0xbe, 0xdd, - 0x67, 0x52, 0x53, 0xd4, 0x84, 0x99, 0x16, 0x59, 0xb9, 0x91, 0x41, 0xa3, 0xff, 0x2c, 0x5b, 0xd8, - 0x99, 0xd2, 0x81, 0x3c, 0x82, 0xc5, 0x3a, 0xdb, 0x30, 0x45, 0x67, 0x73, 0xb9, 0x72, 0xfd, 0xf0, - 0x81, 0xae, 0x85, 0xfc, 0xa5, 0xa2, 0x39, 0xdc, 0xdf, 0xb7, 0xfd, 0x83, 0xf5, 0xd3, 0x22, 0x7f, - 0xb7, 0xd8, 0xa1, 0xa3, 0xb1, 0x37, 0x92, 0x5f, 0xd1, 0xff, 0x38, 0x07, 0x2f, 0x3d, 0x01, 0x6f, - 0xd2, 0x82, 0x19, 0xb4, 0x70, 0xa0, 0x6e, 0x7c, 0xb8, 0x85, 0xe4, 0x24, 0xdf, 0x1b, 0x79, 0x00, - 0xb0, 0x48, 0x33, 0x8e, 0x99, 0x90, 0x5b, 0x74, 0x3a, 0x75, 0x91, 0xdf, 0xe1, 0xd6, 0x92, 0x65, - 0x61, 0x38, 0x74, 0xfa, 0xdd, 0x98, 0x9b, 0x60, 0x20, 0xc5, 0x36, 0xcd, 0x8f, 0x8c, 0x6d, 0xfa, - 0x06, 0xcc, 0x55, 0x25, 0x87, 0x64, 0x3e, 0xfc, 0x78, 0x8f, 0xa2, 0x78, 0x2f, 0x1b, 0x0a, 0x1a, - 0xf9, 0x2c, 0x2c, 0x30, 0x7f, 0x0a, 0xde, 0x3b, 0xcc, 0xdb, 0x6f, 0x92, 0x67, 0x6c, 0xc2, 0x12, - 0xd1, 0xd5, 0x81, 0x91, 0x40, 0xa5, 0x0b, 0xeb, 0x24, 0xd5, 0x4a, 0x7a, 0x4e, 0x10, 0x94, 0x86, - 0xe1, 0x1e, 0xdd, 0x75, 0x98, 0xe5, 0x82, 0xbc, 0x05, 0x53, 0x7b, 0x4f, 0x76, 0x5f, 0xc9, 0xd0, - 0x09, 0x01, 0x3c, 0xfb, 0x88, 0x07, 0xf2, 0xf4, 0x6f, 0xf2, 0x36, 0x4c, 0xa2, 0xd9, 0x9d, 0x1f, - 0x31, 0x84, 0x69, 0x28, 0xfb, 0xd3, 0x68, 0x94, 0x37, 0x18, 0x01, 0x5d, 0xad, 0x71, 0x62, 0x72, - 0xbe, 0x1f, 0x0b, 0x73, 0x74, 0x94, 0x9b, 0xdc, 0x98, 0xd9, 0xdf, 0xb1, 0x79, 0xb6, 0xef, 0x35, - 0x58, 0x12, 0xb2, 0x77, 0x20, 0xb2, 0x1f, 0x71, 0x4f, 0xa4, 0x45, 0x1e, 0x26, 0x6e, 0x20, 0x32, - 0x20, 0x3d, 0x0f, 0x0b, 0x41, 0xb0, 0x67, 0xf1, 0xe0, 0xa4, 0x0f, 0x44, 0x62, 0x45, 0x63, 0x2e, - 0x08, 0xf6, 0x58, 0x94, 0xd2, 0xdb, 0xce, 0x01, 0xc5, 0xc2, 0xb7, 0x1f, 0x31, 0x56, 0x81, 0x61, - 0x85, 0xbd, 0x20, 0xc2, 0xe2, 0x71, 0x75, 0x21, 0xc6, 0xd2, 0x3f, 0x0f, 0xb3, 0x66, 0xc7, 0x1b, - 0x88, 0xd7, 0x66, 0xcb, 0x30, 0x19, 0xd0, 0x9f, 0xe2, 0x12, 0x1d, 0x7f, 0x90, 0x97, 0x61, 0x62, - 0x3f, 0xf6, 0x53, 0x59, 0xb9, 0x2a, 0x12, 0x64, 0x5c, 0xc5, 0xe2, 0xe0, 0xea, 0xc3, 0x6b, 0x57, - 0xb7, 0xd0, 0x93, 0x9d, 0x62, 0xe9, 0xff, 0x22, 0x0f, 0x33, 0x77, 0xed, 0xb0, 0xb3, 0x87, 0x76, - 0xef, 0xf1, 0x76, 0xf4, 0xeb, 0x30, 0x5b, 0xf7, 0x6c, 0xee, 0x07, 0xc1, 0xc3, 0x0a, 0xb1, 0xf7, - 0x67, 0x3d, 0xcf, 0x16, 0x2e, 0x15, 0x81, 0x21, 0x23, 0x1d, 0x12, 0xab, 0xf6, 0x16, 0x4c, 0xb1, - 0xb6, 0xf0, 0x2b, 0x1d, 0x61, 0x23, 0x8a, 0x6a, 0x74, 0x95, 0x15, 0x4b, 0x17, 0xf5, 0x4c, 0xf0, - 0xc8, 0x06, 0x0b, 0xde, 0x1b, 0x92, 0x95, 0x7f, 0xf2, 0x68, 0x56, 0x7e, 0x29, 0x31, 0xce, 0xd4, - 0x91, 0x12, 0xe3, 0xb4, 0x94, 0xfe, 0xe7, 0xae, 0xf1, 0x24, 0xb2, 0x4a, 0x46, 0x25, 0x6c, 0x73, - 0xc5, 0xfe, 0xb6, 0x52, 0xf5, 0x95, 0x59, 0x9c, 0xb9, 0x01, 0xb3, 0x52, 0x0b, 0x9f, 0xc8, 0x08, - 0xf5, 0x95, 0x1c, 0xcc, 0x63, 0x3f, 0x45, 0xa2, 0xf5, 0xfb, 0xf3, 0x16, 0xe4, 0x33, 0xca, 0x2d, - 0xc8, 0x8a, 0x3c, 0x03, 0xb8, 0xaf, 0xd6, 0xe8, 0xeb, 0x8f, 0x5b, 0xb0, 0x94, 0x42, 0x24, 0x6f, - 0xc0, 0x24, 0xad, 0xbe, 0xb0, 0x1a, 0x17, 0x93, 0x73, 0x2a, 0x4e, 0xcb, 0x48, 0x1b, 0x1e, 0x18, - 0x0c, 0x5b, 0xff, 0x1f, 0x1a, 0xcc, 0xf1, 0x64, 0xfd, 0xfd, 0x1d, 0xef, 0xd0, 0xee, 0xbc, 0x92, - 0xec, 0x4e, 0x16, 0xcb, 0x9d, 0x77, 0xe7, 0x5f, 0x74, 0x27, 0xde, 0x50, 0x3a, 0xf1, 0x54, 0x94, - 0xa4, 0x49, 0x34, 0x67, 0x4c, 0x1f, 0x7e, 0x13, 0xd3, 0x16, 0xaa, 0x88, 0xe4, 0x07, 0x60, 0xa6, - 0xe1, 0x3c, 0x52, 0x8c, 0xaf, 0x57, 0x46, 0x30, 0xbd, 0x1a, 0x21, 0xb2, 0x55, 0xca, 0x5e, 0x95, - 0x3a, 0x8f, 0xac, 0x94, 0x4b, 0x4d, 0xcc, 0xf2, 0xcc, 0x3b, 0xb0, 0xa0, 0x92, 0x3d, 0xc9, 0xd4, - 0xe7, 0xa1, 0x1d, 0x31, 0x3d, 0xc1, 0x37, 0xf2, 0x00, 0x71, 0x54, 0x3c, 0xba, 0xa4, 0x15, 0x2f, - 0x3e, 0x71, 0x6f, 0x8d, 0x20, 0x79, 0x8e, 0x0b, 0xe7, 0xbe, 0x2b, 0xfc, 0x7e, 0x35, 0x37, 0x3a, - 0x89, 0x16, 0xde, 0xb4, 0xde, 0xe1, 0x11, 0xce, 0xba, 0x4e, 0xcf, 0x3e, 0xe0, 0xb1, 0x1a, 0xde, - 0xc2, 0x9c, 0x89, 0x11, 0x54, 0x49, 0x58, 0x74, 0xb6, 0x32, 0x14, 0x8a, 0x8f, 0xef, 0xf6, 0x77, - 0x37, 0x3c, 0x5f, 0x8a, 0xdd, 0x87, 0xef, 0x19, 0x2a, 0x94, 0x26, 0x15, 0x7c, 0x72, 0xe2, 0xc9, - 0x82, 0x4f, 0xb6, 0x60, 0xc6, 0xed, 0x3f, 0x74, 0xfa, 0xa1, 0xe7, 0x1f, 0xe0, 0x3d, 0x73, 0x7c, - 0x81, 0x45, 0xbf, 0x57, 0x13, 0x65, 0x6c, 0x68, 0x50, 0x43, 0x8e, 0xf0, 0xe5, 0x91, 0x89, 0x80, - 0x91, 0x0f, 0xd6, 0x64, 0x71, 0x8a, 0xc5, 0xac, 0xbb, 0x35, 0x51, 0x28, 0x14, 0x67, 0x6e, 0x4d, - 0x14, 0x66, 0x8a, 0x60, 0x48, 0x6e, 0x23, 0x91, 0x5b, 0x88, 0xe4, 0xc3, 0xa1, 0xfa, 0x67, 0xe8, - 0x7f, 0x90, 0x07, 0x92, 0xae, 0x06, 0xf9, 0x0c, 0xcc, 0x32, 0xa9, 0x68, 0xf9, 0xc1, 0x47, 0xfc, - 0xa1, 0x2d, 0x7b, 0x1d, 0x2f, 0x81, 0xe5, 0xbc, 0x0f, 0x0c, 0x6c, 0x04, 0x1f, 0xf5, 0xc8, 0x00, - 0x8e, 0x63, 0x8f, 0xf3, 0x10, 0x19, 0x98, 0xd5, 0xcf, 0xee, 0xf1, 0x30, 0x1b, 0x9f, 0xfb, 0xce, - 0x27, 0xab, 0xe7, 0x33, 0x8a, 0x8f, 0x3e, 0x32, 0x18, 0x7d, 0xae, 0x85, 0xc4, 0x2d, 0x46, 0x4b, - 0x76, 0xa1, 0x28, 0xb3, 0xc4, 0x50, 0x3d, 0x6c, 0xfc, 0xdf, 0xa5, 0xa7, 0x9c, 0x64, 0xd9, 0xd1, - 0xbf, 0xb5, 0x10, 0x7f, 0x6b, 0x63, 0xd8, 0xeb, 0x91, 0xb7, 0x00, 0xbc, 0xbe, 0xb5, 0xef, 0x62, - 0x8c, 0x30, 0xf9, 0x89, 0x76, 0x0c, 0x95, 0x87, 0xcc, 0xeb, 0x6f, 0x31, 0x20, 0xf9, 0x34, 0x60, - 0x50, 0x69, 0x8c, 0xb6, 0xce, 0x75, 0x35, 0x76, 0x16, 0x12, 0x40, 0x35, 0x9e, 0xe8, 0xae, 0x83, - 0x91, 0xd8, 0x3f, 0x03, 0xb3, 0x4c, 0x07, 0x62, 0x81, 0x35, 0xa7, 0xe2, 0x51, 0x90, 0xc0, 0xf2, - 0x28, 0x30, 0xb0, 0x14, 0x74, 0xf3, 0x0b, 0xb0, 0xc4, 0x75, 0xc6, 0xbb, 0x6e, 0xb8, 0xc7, 0x6d, - 0x51, 0x4f, 0x63, 0xc8, 0x92, 0x0c, 0x49, 0xbf, 0x31, 0x09, 0x50, 0xba, 0x6b, 0x8a, 0x24, 0x28, - 0x2f, 0xc2, 0x24, 0xfd, 0xb0, 0xb8, 0xbb, 0x40, 0x55, 0x14, 0xf9, 0xca, 0x37, 0xbf, 0x88, 0x41, - 0x45, 0x80, 0x81, 0x0f, 0x56, 0xc5, 0xbd, 0x05, 0x8a, 0x00, 0xf6, 0x86, 0x55, 0xc9, 0x5a, 0xc9, - 0xb1, 0x48, 0x1d, 0x20, 0x4e, 0x4b, 0xc2, 0xed, 0x3d, 0x4b, 0x71, 0x7c, 0x7f, 0x5e, 0xc0, 0xb3, - 0x71, 0xc7, 0x51, 0x09, 0xe4, 0xae, 0x89, 0xd1, 0xc8, 0x6d, 0x98, 0x68, 0xdb, 0x51, 0xd4, 0xc9, - 0x11, 0xc9, 0x5a, 0x2e, 0xd2, 0xd6, 0x2b, 0x09, 0x5b, 0x16, 0x42, 0x7b, 0x57, 0xae, 0x1d, 0x32, - 0x21, 0x55, 0x98, 0x6a, 0xd9, 0xbe, 0xbd, 0x1f, 0x8c, 0x4a, 0xf2, 0xc5, 0x4a, 0x45, 0x2e, 0x50, - 0x04, 0xca, 0xaa, 0x11, 0x2b, 0x26, 0xd7, 0x21, 0x6f, 0x9a, 0x5b, 0xfc, 0x29, 0xcd, 0x7c, 0x7c, - 0xce, 0x32, 0xcd, 0x2d, 0x76, 0x1c, 0x08, 0x82, 0x7d, 0x89, 0x8c, 0x22, 0x93, 0xcf, 0xc2, 0xac, - 0x74, 0x7c, 0xe3, 0xc1, 0xfd, 0xb1, 0x0f, 0xa4, 0x90, 0x08, 0xb2, 0x58, 0x92, 0xb0, 0x49, 0x1d, - 0x8a, 0xb7, 0x87, 0xf7, 0x9d, 0xd2, 0x60, 0x80, 0x21, 0xde, 0x1e, 0x3a, 0x3e, 0x53, 0x71, 0x0b, - 0x71, 0x1a, 0x4d, 0x7c, 0x7f, 0xdc, 0x15, 0xa5, 0xb2, 0xa1, 0x28, 0x49, 0x49, 0x5a, 0xb0, 0x64, - 0x3a, 0xe1, 0x70, 0xc0, 0x3c, 0x5f, 0x37, 0x98, 0xa1, 0x81, 0xa5, 0x02, 0xc0, 0x8c, 0x83, 0x01, - 0x2d, 0x14, 0x4e, 0xc7, 0x3b, 0x29, 0x63, 0x43, 0x9a, 0x98, 0x7c, 0x29, 0x61, 0x12, 0x81, 0xa4, - 0x55, 0x4e, 0x2e, 0x15, 0xf9, 0x7e, 0x8e, 0x6e, 0x31, 0xf9, 0xcf, 0xcc, 0x62, 0x92, 0xc1, 0x84, - 0x54, 0x61, 0x41, 0x06, 0x47, 0x76, 0x21, 0x8c, 0x79, 0xa1, 0x04, 0x9a, 0x56, 0x0c, 0x3d, 0x09, - 0x22, 0xf2, 0x18, 0x8e, 0xcb, 0x10, 0xbb, 0xb7, 0xdd, 0x77, 0x43, 0xf1, 0x30, 0x4d, 0xcf, 0x6e, - 0x07, 0xa2, 0x88, 0xc6, 0x60, 0xc7, 0x79, 0x0a, 0x0b, 0x6b, 0x48, 0x11, 0xa4, 0x8f, 0x66, 0x7d, - 0x42, 0xff, 0x61, 0x0c, 0xea, 0x31, 0x8a, 0x2f, 0x4b, 0x53, 0x8b, 0x41, 0x21, 0xe4, 0x6b, 0x46, - 0x1e, 0x32, 0x42, 0x4d, 0x53, 0x8b, 0x20, 0x4a, 0x50, 0x65, 0x51, 0x24, 0xe4, 0xe5, 0xca, 0x03, - 0x4b, 0xc8, 0x04, 0x1c, 0x4b, 0xff, 0xba, 0x26, 0xaf, 0x57, 0xd9, 0xbc, 0xa1, 0x8d, 0x33, 0x6f, - 0xbc, 0x9a, 0x91, 0x6b, 0x08, 0x8d, 0x34, 0x52, 0xae, 0x21, 0x39, 0xc3, 0x10, 0x79, 0x11, 0x0a, - 0x94, 0x56, 0x3a, 0xbd, 0xb0, 0x84, 0x4f, 0x1e, 0x37, 0x81, 0x18, 0x51, 0xb1, 0xfe, 0xe7, 0x93, - 0x52, 0x66, 0x3c, 0xbe, 0xe6, 0xde, 0x05, 0xb8, 0xe5, 0xb9, 0xfd, 0x2d, 0x27, 0xdc, 0xf3, 0xba, - 0xd2, 0x20, 0xcf, 0x7e, 0xe8, 0xb9, 0x7d, 0x6b, 0x1f, 0xc1, 0x7f, 0xf6, 0xc9, 0xaa, 0x84, 0x64, - 0x48, 0x7f, 0x93, 0x97, 0x61, 0x86, 0xfe, 0x6a, 0xc7, 0x7e, 0xda, 0xec, 0x3a, 0x1b, 0xa9, 0x59, - 0x12, 0xfb, 0x18, 0x81, 0xdc, 0x00, 0x60, 0x39, 0xfa, 0xa4, 0xda, 0xe2, 0x5a, 0x65, 0x6e, 0x79, - 0xc9, 0xa8, 0x26, 0x12, 0x32, 0xd9, 0x8c, 0xaa, 0xde, 0xe6, 0x07, 0x45, 0x96, 0x43, 0x47, 0x84, - 0x46, 0xc1, 0x22, 0x4b, 0x1c, 0x22, 0xe5, 0xd0, 0x28, 0x09, 0x32, 0xac, 0x84, 0xb9, 0x59, 0xe1, - 0xd6, 0xbb, 0x49, 0xa9, 0x12, 0xc1, 0x5e, 0x97, 0xdb, 0xe2, 0x94, 0x4a, 0x44, 0xc8, 0x64, 0x1d, - 0x16, 0xd9, 0xb9, 0xb7, 0xe5, 0x7b, 0x8f, 0x0f, 0xe8, 0x61, 0x9a, 0xef, 0x47, 0xc2, 0xf0, 0xf4, - 0xf8, 0x00, 0x4f, 0xde, 0xf2, 0xe7, 0x13, 0x04, 0x64, 0x03, 0x26, 0xd1, 0x12, 0xcb, 0x8f, 0x65, - 0x67, 0xe5, 0xbb, 0x80, 0xa4, 0xbc, 0xc4, 0xfd, 0x03, 0x6f, 0x01, 0xe4, 0xfd, 0x03, 0x51, 0xc9, - 0x17, 0x00, 0xaa, 0x7d, 0xdf, 0xeb, 0xf5, 0x30, 0x71, 0x68, 0x41, 0x89, 0xa0, 0xc4, 0xf9, 0x20, - 0x97, 0x18, 0x09, 0xb3, 0xe7, 0x9e, 0x70, 0xf0, 0xb7, 0xa5, 0xa6, 0x17, 0x5d, 0xd1, 0x0c, 0x89, - 0x1b, 0x79, 0x19, 0xa6, 0xcc, 0xe1, 0xce, 0x8e, 0xfb, 0x98, 0x8b, 0x2f, 0x96, 0x35, 0x13, 0x21, - 0xb2, 0xd8, 0x66, 0x38, 0xe4, 0x1d, 0x98, 0xdd, 0x1e, 0x74, 0xed, 0xd0, 0xc1, 0x1b, 0x6d, 0x9e, - 0x8a, 0x19, 0xa5, 0xd0, 0x10, 0xc1, 0xec, 0x51, 0x8c, 0x2c, 0x83, 0x25, 0x74, 0xe2, 0xc2, 0xd2, - 0x66, 0xbb, 0xdd, 0xc2, 0x0e, 0x12, 0x81, 0x17, 0x78, 0x88, 0x22, 0x71, 0xc8, 0x4a, 0x95, 0xaf, - 0x5f, 0xa2, 0x1a, 0xd4, 0x5e, 0x18, 0x0e, 0x2c, 0xd6, 0xe9, 0x22, 0x88, 0x9e, 0x2c, 0x4e, 0x53, - 0x54, 0xfa, 0xaf, 0x6b, 0x19, 0xdf, 0x22, 0x6f, 0xc2, 0x4c, 0x04, 0x94, 0xb3, 0x8c, 0xc7, 0xec, - 0x65, 0x75, 0x26, 0x42, 0xa5, 0xf3, 0x88, 0xfe, 0x30, 0x19, 0xa1, 0x94, 0x15, 0x8c, 0x12, 0x06, - 0x29, 0x4a, 0x09, 0x99, 0x9e, 0x71, 0x1b, 0x1e, 0xa3, 0x93, 0x2c, 0xf8, 0x7d, 0x2f, 0x45, 0x24, - 0xd0, 0xf4, 0x1a, 0x4c, 0xb1, 0x8d, 0x10, 0x13, 0x23, 0x7b, 0x9d, 0xe1, 0xbe, 0xd3, 0x0f, 0xa5, - 0xb4, 0xba, 0x2c, 0x31, 0x32, 0x87, 0xa7, 0x13, 0x23, 0x4b, 0x04, 0xfa, 0x6d, 0x58, 0xce, 0x9a, - 0x6c, 0x8a, 0x3d, 0x5f, 0x3b, 0xaa, 0x3d, 0xff, 0x0f, 0xf2, 0x30, 0x87, 0xdc, 0x84, 0x60, 0x2d, - 0xc1, 0xbc, 0x39, 0xbc, 0x1f, 0x25, 0x01, 0x12, 0x9a, 0x10, 0xd6, 0x2f, 0x90, 0x0b, 0x64, 0x97, - 0x34, 0x85, 0x82, 0xee, 0x3d, 0x42, 0x0b, 0xbb, 0x29, 0x1e, 0x5c, 0x47, 0x39, 0x89, 0xc5, 0x43, - 0x74, 0xfe, 0xd2, 0x4a, 0xde, 0x7b, 0x54, 0xa2, 0x58, 0x17, 0xcb, 0x3f, 0x89, 0x2e, 0x36, 0x71, - 0x24, 0x5d, 0xec, 0x03, 0x98, 0x13, 0x5f, 0x43, 0x2d, 0x6a, 0xf2, 0xe9, 0xb4, 0x28, 0x85, 0x19, - 0xa9, 0x47, 0xda, 0xd4, 0xd4, 0x58, 0x6d, 0x0a, 0xfd, 0xfc, 0x84, 0xe4, 0x1b, 0x20, 0x2c, 0x43, - 0xa9, 0x7a, 0x1a, 0x05, 0x49, 0xff, 0xa3, 0x3c, 0xc0, 0xcd, 0x72, 0xeb, 0xbb, 0x50, 0x6f, 0xdf, - 0x80, 0x99, 0xba, 0x27, 0xfc, 0xc3, 0x24, 0xc7, 0x9c, 0x9e, 0x00, 0xca, 0x8b, 0x2a, 0xc2, 0x8c, - 0xd4, 0xd2, 0xfc, 0xb3, 0x50, 0x4b, 0x6f, 0xe0, 0x85, 0xc7, 0x87, 0x4e, 0x27, 0xac, 0x55, 0xc4, - 0xc8, 0x62, 0xcb, 0x45, 0x24, 0x7f, 0xd5, 0x3f, 0x48, 0x42, 0xa6, 0xdb, 0x0d, 0x77, 0x3d, 0x17, - 0x81, 0xdb, 0xf8, 0x15, 0x03, 0x6e, 0x37, 0x22, 0xfa, 0x9d, 0x88, 0x05, 0x27, 0xcb, 0xfb, 0x04, - 0xd9, 0x33, 0x1e, 0xcd, 0xcf, 0x47, 0x6f, 0x88, 0xa6, 0xc7, 0xf5, 0x90, 0x9e, 0xea, 0xa1, 0x91, - 0x2f, 0x87, 0xf4, 0xdf, 0xd6, 0xe4, 0x6c, 0xf2, 0xdf, 0xc5, 0x50, 0xbf, 0x0d, 0x10, 0x39, 0xe8, - 0x8a, 0xb1, 0x8e, 0x62, 0x76, 0x31, 0xa8, 0xdc, 0xcb, 0x31, 0xae, 0xd4, 0x9a, 0xfc, 0xb3, 0x6a, - 0x4d, 0x1b, 0x66, 0x9b, 0x0f, 0x42, 0x3b, 0xf6, 0xe8, 0x06, 0x33, 0x3a, 0xbe, 0xf2, 0x50, 0x98, - 0x97, 0x71, 0xb3, 0x8f, 0xcf, 0xc3, 0xca, 0x51, 0xb8, 0x20, 0x8e, 0xc2, 0x86, 0x44, 0xa8, 0xff, - 0xb9, 0x06, 0x8b, 0x72, 0x1c, 0xd7, 0x83, 0x7e, 0x87, 0xbc, 0xc7, 0x72, 0x55, 0x6a, 0x8a, 0x35, - 0x43, 0x42, 0xa2, 0xf2, 0xfa, 0xa0, 0xdf, 0x61, 0x27, 0x17, 0xfb, 0x91, 0x5c, 0x59, 0x4a, 0x48, - 0xee, 0xc3, 0x5c, 0xcb, 0xeb, 0xf5, 0x44, 0xd8, 0xdd, 0x28, 0xe9, 0x65, 0xf2, 0xae, 0x45, 0x54, - 0x68, 0xfd, 0x39, 0x6e, 0x15, 0x3b, 0x35, 0xa0, 0x1b, 0xb8, 0x08, 0x05, 0x1c, 0xb3, 0xfd, 0x18, - 0xe3, 0xb1, 0xc8, 0x3c, 0x63, 0x65, 0x43, 0xcd, 0x8a, 0x2e, 0xd7, 0x92, 0x16, 0x63, 0x3d, 0xc7, - 0x28, 0x1b, 0xfa, 0xdf, 0xd4, 0xe0, 0x62, 0xba, 0x69, 0xe5, 0x9e, 0x37, 0xec, 0xb6, 0x7d, 0xdb, - 0xed, 0xd5, 0xbd, 0xdd, 0x80, 0xe5, 0xf8, 0xdb, 0x8d, 0xaf, 0x6e, 0x79, 0x8e, 0xbf, 0x5d, 0x37, - 0x99, 0xe3, 0x0f, 0xc3, 0x34, 0xbd, 0x0e, 0x05, 0xf3, 0xf3, 0xe6, 0xe7, 0x87, 0x8e, 0xb0, 0x9c, - 0x31, 0xf9, 0x10, 0x7c, 0x14, 0x58, 0x1f, 0x51, 0xa0, 0xbc, 0xdd, 0x08, 0x44, 0x7d, 0x1f, 0x2e, - 0xa4, 0xab, 0x51, 0xbd, 0x6d, 0x96, 0x86, 0x5d, 0x37, 0xc4, 0x4a, 0x08, 0x01, 0xa2, 0x3d, 0x03, - 0x01, 0xa2, 0xff, 0xd3, 0x3c, 0x90, 0xf4, 0xf7, 0xe4, 0xed, 0x42, 0xfb, 0x4b, 0x38, 0xba, 0x27, - 0x24, 0xfa, 0xc4, 0x13, 0x1d, 0x79, 0x3f, 0x82, 0x62, 0x87, 0x0e, 0x9b, 0x15, 0xd2, 0x71, 0xb3, - 0x7a, 0x5e, 0xb4, 0x7b, 0x7d, 0x6a, 0xe4, 0x14, 0x56, 0xc7, 0x99, 0x89, 0xc0, 0x24, 0x13, 0x79, - 0x23, 0xee, 0xa8, 0xf3, 0xc2, 0x85, 0x05, 0xe7, 0x41, 0x60, 0xd9, 0x74, 0x8c, 0xd8, 0x07, 0xa7, - 0x14, 0x0f, 0xfb, 0xf1, 0x23, 0xca, 0x04, 0xa3, 0xca, 0x40, 0xde, 0x3a, 0x9d, 0x07, 0x41, 0x84, - 0xab, 0x7f, 0x43, 0x83, 0xe5, 0xac, 0xc9, 0x4d, 0x75, 0x0a, 0x59, 0xc9, 0x50, 0xcf, 0xb3, 0xb2, - 0x5e, 0x92, 0x38, 0xcf, 0xaa, 0x44, 0xc9, 0xae, 0xcf, 0x3d, 0xd1, 0x66, 0xfa, 0xc7, 0x79, 0x98, - 0x63, 0x6e, 0x61, 0x9b, 0x8e, 0xdd, 0x0b, 0xf7, 0xe8, 0x3c, 0xe2, 0x17, 0x72, 0xf2, 0xeb, 0x25, - 0x7e, 0x75, 0xa7, 0x64, 0xd1, 0xe4, 0xd7, 0x76, 0xd7, 0xa1, 0xd0, 0xa2, 0x62, 0xa1, 0xe3, 0xf5, - 0xe4, 0xcb, 0x91, 0x01, 0x87, 0xc9, 0x4b, 0x46, 0xe0, 0xa1, 0x2e, 0x2f, 0xdf, 0xc0, 0x32, 0x5d, - 0x1e, 0x21, 0x8a, 0x2e, 0xcf, 0xee, 0x62, 0x1f, 0xc3, 0xf1, 0xd8, 0xd5, 0x30, 0xba, 0xe5, 0x3d, - 0xc2, 0x43, 0xed, 0x35, 0x7e, 0xc5, 0x7d, 0x21, 0xf6, 0x5e, 0xc4, 0xeb, 0x60, 0x2c, 0x4d, 0xb8, - 0xa2, 0x65, 0x7d, 0x82, 0xdc, 0x86, 0x62, 0x0c, 0xe6, 0xb9, 0x42, 0xd9, 0xe1, 0x0c, 0x43, 0xb5, - 0x4a, 0x6c, 0x53, 0x69, 0x43, 0x53, 0x84, 0x74, 0xfb, 0x8e, 0x61, 0xd5, 0x38, 0x26, 0x83, 0xf0, - 0xf8, 0x88, 0x78, 0xe1, 0x05, 0xb3, 0xbc, 0x7d, 0x27, 0xc8, 0xe8, 0x18, 0x89, 0x7b, 0xe9, 0xe9, - 0x78, 0x8c, 0xf8, 0x8d, 0xb4, 0x3c, 0x46, 0x1c, 0x6b, 0xed, 0xeb, 0x1a, 0x2c, 0xd6, 0x4a, 0x5b, - 0x2c, 0x34, 0x1e, 0xef, 0xd5, 0x4b, 0x70, 0xbe, 0x56, 0xda, 0xb2, 0x5a, 0xcd, 0x7a, 0xad, 0x7c, - 0xcf, 0xca, 0xcc, 0x52, 0x7d, 0x1e, 0x4e, 0xa7, 0x51, 0x62, 0xaf, 0xcc, 0x73, 0xb0, 0x92, 0x2e, - 0x16, 0x99, 0xac, 0xb3, 0x89, 0x45, 0xd2, 0xeb, 0xfc, 0xda, 0xfb, 0xb0, 0x28, 0xb2, 0x36, 0xb7, - 0xeb, 0x26, 0x9e, 0xf0, 0x16, 0x61, 0xf6, 0x4e, 0xd5, 0xa8, 0x6d, 0xdc, 0xb3, 0x36, 0xb6, 0xeb, - 0xf5, 0xe2, 0x31, 0x32, 0x0f, 0x33, 0x1c, 0x50, 0x2e, 0x15, 0x35, 0x32, 0x07, 0x85, 0x5a, 0xc3, - 0xac, 0x96, 0xb7, 0x8d, 0x6a, 0x31, 0xb7, 0xf6, 0xf7, 0x34, 0x98, 0x67, 0x67, 0x36, 0x9f, 0xb7, - 0xe8, 0x02, 0x9c, 0xd9, 0x6e, 0x55, 0x4a, 0xed, 0xaa, 0x91, 0xdd, 0x9c, 0x13, 0xb0, 0x94, 0x28, - 0x6f, 0xde, 0x2e, 0x6a, 0xe4, 0x2c, 0x9c, 0x4a, 0x80, 0x2b, 0x35, 0xb3, 0xb4, 0xce, 0x5a, 0x71, - 0x1a, 0x4e, 0x24, 0x0a, 0x5b, 0xb5, 0x46, 0xa3, 0x5a, 0x29, 0xe6, 0x69, 0x03, 0x53, 0x9f, 0x33, - 0xaa, 0xa5, 0x0a, 0x25, 0x2d, 0x4e, 0xac, 0xbd, 0x0f, 0x0b, 0xad, 0xe8, 0x09, 0x2a, 0x7a, 0x6e, - 0x4e, 0x43, 0xde, 0x28, 0xdd, 0x2d, 0x1e, 0x23, 0x00, 0x53, 0xad, 0xdb, 0x65, 0xf3, 0xda, 0xb5, - 0xa2, 0x46, 0x66, 0x61, 0xfa, 0x66, 0xb9, 0x65, 0xdd, 0xde, 0x32, 0x8b, 0x39, 0xfa, 0xa3, 0x74, - 0xd7, 0xc4, 0x1f, 0xf9, 0xb5, 0xd7, 0xd0, 0xb1, 0xe7, 0xf1, 0x41, 0xdd, 0x0d, 0x42, 0xa7, 0xef, - 0xf8, 0xd8, 0x47, 0x73, 0x50, 0x30, 0x1d, 0xaa, 0x89, 0x85, 0x0e, 0xeb, 0xa0, 0xad, 0x61, 0x2f, - 0x74, 0x07, 0x3d, 0xe7, 0x71, 0x51, 0x5b, 0xbb, 0x01, 0x8b, 0x86, 0x37, 0xa4, 0x27, 0x48, 0x33, - 0xa4, 0x18, 0xbb, 0x07, 0xd8, 0xe6, 0x46, 0x69, 0x6b, 0xbd, 0x76, 0x73, 0xbb, 0xb9, 0x6d, 0x5a, - 0x5b, 0xa5, 0x76, 0x79, 0x93, 0xf9, 0x8d, 0x6e, 0x35, 0xcd, 0xb6, 0x65, 0x54, 0xcb, 0xd5, 0x46, - 0xbb, 0xa8, 0xad, 0xfd, 0x14, 0xde, 0x64, 0x75, 0xbc, 0x7e, 0x77, 0xc3, 0xee, 0x84, 0x9e, 0x8f, - 0x15, 0xd6, 0xe1, 0x82, 0x59, 0x2d, 0x37, 0x1b, 0x15, 0x6b, 0xa3, 0x54, 0x6e, 0x37, 0x8d, 0xac, - 0x34, 0xee, 0x67, 0xe0, 0x64, 0x06, 0x4e, 0xb3, 0xdd, 0x2a, 0x6a, 0x64, 0x15, 0xce, 0x66, 0x94, - 0xdd, 0xad, 0xae, 0x97, 0xb6, 0xdb, 0x9b, 0x8d, 0x62, 0x6e, 0x04, 0xb1, 0x69, 0x36, 0x8b, 0xf9, - 0xb5, 0xbf, 0xad, 0xc1, 0xc2, 0x76, 0xc0, 0xdf, 0xbf, 0x6f, 0xa3, 0x77, 0xc5, 0x45, 0x38, 0xb7, - 0x6d, 0x56, 0x0d, 0xab, 0xdd, 0xbc, 0x5d, 0x6d, 0x58, 0xdb, 0x66, 0xe9, 0x66, 0xb2, 0x36, 0xab, - 0x70, 0x56, 0xc2, 0x30, 0xaa, 0xe5, 0xe6, 0x9d, 0xaa, 0x61, 0xb5, 0x4a, 0xa6, 0x79, 0xb7, 0x69, - 0x54, 0x8a, 0x1a, 0xfd, 0x62, 0x06, 0xc2, 0xd6, 0x46, 0x89, 0xd5, 0x46, 0x29, 0x6b, 0x54, 0xef, - 0x96, 0xea, 0xd6, 0x7a, 0xb3, 0x5d, 0xcc, 0xaf, 0x6d, 0xd1, 0xc3, 0x15, 0x26, 0x53, 0x66, 0xaf, - 0x14, 0x0b, 0x30, 0xd1, 0x68, 0x36, 0xaa, 0x49, 0x47, 0xe5, 0x39, 0x28, 0x94, 0x5a, 0x2d, 0xa3, - 0x79, 0x07, 0x27, 0x0f, 0xc0, 0x54, 0xa5, 0xda, 0xa8, 0xe1, 0x6c, 0x99, 0x83, 0x42, 0xcb, 0x68, - 0x6e, 0x35, 0xdb, 0xd5, 0x4a, 0x71, 0x62, 0xad, 0x04, 0x4b, 0x6c, 0x4b, 0xe0, 0x4c, 0xf1, 0xba, - 0x73, 0x1e, 0x66, 0xb6, 0x1b, 0x95, 0xea, 0x46, 0xad, 0x81, 0x6d, 0x59, 0x00, 0x30, 0x37, 0x9b, - 0x46, 0xdb, 0x6a, 0x57, 0x8d, 0x2d, 0x96, 0x1d, 0xbf, 0xde, 0x6c, 0xdc, 0x64, 0x3f, 0x73, 0x6b, - 0x86, 0x50, 0x03, 0x44, 0xbd, 0xd0, 0x83, 0x61, 0x16, 0xa6, 0x2b, 0xd5, 0x8d, 0xd2, 0x76, 0xbd, - 0xcd, 0x47, 0xf9, 0x9e, 0x65, 0x54, 0x3f, 0xbf, 0x5d, 0x35, 0xdb, 0x66, 0x51, 0x23, 0x45, 0x98, - 0x6b, 0x54, 0xab, 0x15, 0xd3, 0x32, 0xaa, 0x77, 0x6a, 0xd5, 0xbb, 0xc5, 0x1c, 0xad, 0x16, 0xfb, - 0x9b, 0x56, 0x72, 0xed, 0x97, 0x34, 0x20, 0x2c, 0x97, 0xf5, 0xa6, 0x17, 0x84, 0xb4, 0xf7, 0x71, - 0xd2, 0x5d, 0x80, 0x33, 0x9b, 0x74, 0xb6, 0x60, 0xef, 0x6c, 0x35, 0x2b, 0xc9, 0x5e, 0x3f, 0x09, - 0x24, 0x51, 0xde, 0xdc, 0xd8, 0xc0, 0x95, 0x75, 0x3c, 0x01, 0xaf, 0x18, 0xcd, 0x56, 0x31, 0x77, - 0x26, 0x57, 0xd0, 0xc8, 0xa9, 0x54, 0xe1, 0xed, 0x6a, 0xb5, 0x55, 0xcc, 0xd3, 0x51, 0x4e, 0x14, - 0x88, 0x55, 0xcf, 0xc8, 0x27, 0xd6, 0xbe, 0xaa, 0xc1, 0x49, 0x56, 0x4d, 0x21, 0x42, 0xa2, 0xaa, - 0x9e, 0x83, 0x15, 0x9e, 0xa1, 0x3f, 0xab, 0xa2, 0xcb, 0x50, 0x54, 0x4a, 0x59, 0x35, 0x4f, 0xc0, - 0x92, 0x02, 0xc5, 0x7a, 0xe4, 0xa8, 0x80, 0x54, 0xc0, 0xeb, 0x55, 0xb3, 0x6d, 0x55, 0x37, 0x36, - 0xe8, 0x90, 0x60, 0x45, 0xf2, 0x6b, 0x7f, 0x5f, 0x83, 0x95, 0xbb, 0xce, 0xfd, 0xb6, 0xe3, 0xef, - 0xbb, 0x7d, 0xbb, 0x57, 0xee, 0xb9, 0x83, 0xfb, 0x9e, 0xed, 0x77, 0xb1, 0x2a, 0x2f, 0xc1, 0xa7, - 0xee, 0x56, 0xd7, 0x71, 0xb8, 0x6a, 0x8d, 0x52, 0xdd, 0x2a, 0xd7, 0x6b, 0xad, 0xf5, 0x66, 0xc9, - 0xa8, 0x64, 0xd5, 0xec, 0x65, 0x78, 0x61, 0x3c, 0xb2, 0x51, 0x35, 0xdb, 0x46, 0xad, 0x4c, 0xa7, - 0x8f, 0x46, 0x3e, 0x05, 0xcf, 0x8d, 0xc3, 0x6e, 0x34, 0xad, 0x72, 0xb3, 0x75, 0xaf, 0x98, 0x5b, - 0xd3, 0x61, 0xa9, 0xec, 0xf8, 0x61, 0xf5, 0x71, 0xe8, 0xf4, 0x03, 0xd7, 0xeb, 0x63, 0xc5, 0xe6, - 0x61, 0xa6, 0xfa, 0xff, 0xb5, 0xab, 0x0d, 0xb3, 0xd6, 0x6c, 0x14, 0x8f, 0xad, 0x9d, 0x4b, 0xe0, - 0x08, 0x59, 0x65, 0x9a, 0x9b, 0xc5, 0x63, 0x6b, 0x36, 0xcc, 0x8b, 0xa7, 0xeb, 0x6c, 0xe6, 0x5f, - 0x80, 0x33, 0x62, 0x3d, 0xa1, 0xdc, 0x4b, 0xb6, 0x64, 0x05, 0x96, 0xd3, 0xe5, 0xd5, 0x76, 0x51, - 0xa3, 0xd3, 0x24, 0x51, 0x42, 0xe1, 0xb9, 0xb5, 0x9f, 0xd7, 0x60, 0x3e, 0xf2, 0x1e, 0xc2, 0x95, - 0xb0, 0x0a, 0x67, 0xb7, 0x36, 0x4a, 0x56, 0xa5, 0x7a, 0xa7, 0x56, 0xae, 0x5a, 0xb7, 0x6b, 0x8d, - 0x4a, 0xe2, 0x23, 0xa7, 0xe1, 0x44, 0x06, 0x02, 0x7e, 0x65, 0x05, 0x96, 0x93, 0x45, 0x6d, 0x2a, - 0x8e, 0x72, 0x74, 0x6e, 0x24, 0x4b, 0x22, 0x59, 0x94, 0xa7, 0xf3, 0x31, 0x59, 0x4a, 0x05, 0xd1, - 0xc4, 0xda, 0x17, 0x61, 0xf1, 0xae, 0x73, 0x9f, 0xbb, 0x05, 0x32, 0x41, 0x74, 0x09, 0xce, 0xd3, - 0xfe, 0x37, 0xab, 0x26, 0xed, 0xc3, 0x4c, 0x49, 0x74, 0x05, 0xf4, 0x34, 0x4a, 0x09, 0x77, 0x3f, - 0xeb, 0xa6, 0x51, 0x6a, 0x6d, 0x5a, 0xa5, 0x56, 0xad, 0xa8, 0xad, 0xdd, 0x81, 0x05, 0xb3, 0xb4, - 0x55, 0xdf, 0xf0, 0xfc, 0x8e, 0x53, 0x1a, 0x86, 0x7b, 0x7d, 0xba, 0x1f, 0x6d, 0x34, 0x8d, 0x72, - 0xd5, 0xc2, 0x9a, 0x25, 0xd8, 0x1e, 0x87, 0x45, 0xb9, 0xf0, 0x5e, 0x95, 0x2e, 0x6b, 0x02, 0x0b, - 0x32, 0xb0, 0xd1, 0x2c, 0xe6, 0xd6, 0x2c, 0x58, 0xe6, 0xaf, 0xf7, 0x2a, 0x4e, 0xf0, 0x20, 0xf4, - 0x06, 0x3c, 0x34, 0x3d, 0xad, 0x57, 0xad, 0x51, 0x69, 0xde, 0x35, 0xad, 0x4a, 0xd5, 0xbc, 0xdd, - 0x6e, 0xb6, 0x2c, 0xb3, 0xb9, 0x4d, 0x89, 0x53, 0x92, 0x74, 0x04, 0x5e, 0xbd, 0x52, 0x6a, 0x15, - 0xb5, 0xb5, 0x2f, 0xc2, 0x1c, 0xef, 0x13, 0x36, 0x2f, 0x4e, 0xc1, 0x71, 0xf9, 0x77, 0xcb, 0xe9, - 0x77, 0xdd, 0xfe, 0x6e, 0xf1, 0x58, 0xb2, 0xc0, 0x18, 0xf6, 0xfb, 0xb4, 0x00, 0x65, 0xb1, 0x5c, - 0xc0, 0x57, 0x51, 0xe8, 0x74, 0x8b, 0xb9, 0xb5, 0xab, 0x30, 0xaf, 0xa4, 0xc5, 0xa7, 0x13, 0xb2, - 0xde, 0xe4, 0x9b, 0xe7, 0x56, 0xb5, 0x52, 0xdb, 0xde, 0x2a, 0x4e, 0x52, 0x29, 0xbc, 0x59, 0xbb, - 0xb9, 0x59, 0x84, 0xb5, 0x9f, 0xd1, 0x60, 0x81, 0x0a, 0x42, 0xd7, 0x77, 0xb6, 0x36, 0x4a, 0x62, - 0x0a, 0xd3, 0xf5, 0x8d, 0x12, 0x9a, 0x0f, 0x03, 0x53, 0x5a, 0xc4, 0x98, 0x94, 0x1a, 0x15, 0x6b, - 0xb3, 0x64, 0x54, 0xee, 0x96, 0x0c, 0xba, 0xe8, 0xef, 0x15, 0x73, 0x28, 0xc9, 0x24, 0x88, 0xd5, - 0x6e, 0x6e, 0x97, 0x37, 0x8b, 0x79, 0x2a, 0x38, 0x14, 0x78, 0xab, 0xd6, 0x28, 0x4e, 0xa0, 0x5c, - 0x4c, 0x61, 0x23, 0x5b, 0x5a, 0x3e, 0xb9, 0xf6, 0x6d, 0x0d, 0x4e, 0x99, 0xee, 0x6e, 0xdf, 0x0e, - 0x87, 0xbe, 0x53, 0xea, 0xed, 0x7a, 0xbe, 0x1b, 0xee, 0xed, 0x9b, 0x43, 0x37, 0x74, 0xc8, 0x8b, - 0x70, 0xd9, 0xac, 0xdd, 0x6c, 0x94, 0xda, 0x54, 0xae, 0x95, 0xea, 0x37, 0x9b, 0x46, 0xad, 0xbd, - 0xb9, 0x65, 0x99, 0xdb, 0xb5, 0xd4, 0x8a, 0x7a, 0x1e, 0x2e, 0x8e, 0x46, 0xad, 0x57, 0x6f, 0x96, - 0xca, 0xf7, 0x8a, 0xda, 0x78, 0x86, 0xeb, 0xa5, 0x7a, 0xa9, 0x51, 0xae, 0x56, 0xac, 0x3b, 0xd7, - 0x8a, 0x39, 0x72, 0x19, 0x2e, 0x8d, 0x46, 0xdd, 0xa8, 0xb5, 0x4c, 0x8a, 0x96, 0x1f, 0xff, 0xdd, - 0x4d, 0x73, 0x8b, 0x62, 0x4d, 0xac, 0x7d, 0x43, 0x83, 0x95, 0x51, 0x49, 0x91, 0xe8, 0x6c, 0xab, - 0x36, 0xda, 0x46, 0xa9, 0x56, 0xb1, 0xca, 0x46, 0xb5, 0x52, 0x6d, 0xb4, 0x6b, 0xa5, 0xba, 0x19, - 0xcf, 0x38, 0xf1, 0x8e, 0xe5, 0x39, 0x58, 0x1d, 0x83, 0xd7, 0xac, 0x55, 0xca, 0x45, 0x8d, 0x5c, - 0x83, 0x57, 0xc6, 0x20, 0x99, 0xf7, 0xcc, 0x76, 0x75, 0x4b, 0x2e, 0x29, 0xe6, 0xd6, 0xfe, 0x9b, - 0x06, 0xe7, 0xc7, 0x66, 0x37, 0x92, 0x99, 0xf2, 0xd5, 0x59, 0xaf, 0x99, 0x6d, 0xab, 0x79, 0xb7, - 0x51, 0x35, 0xcc, 0xec, 0xa5, 0xf1, 0x32, 0xbc, 0x70, 0x38, 0x49, 0xab, 0xbe, 0x7d, 0xb3, 0x46, - 0x27, 0xdc, 0x2b, 0xf0, 0xe2, 0xe1, 0xd8, 0x1c, 0xa3, 0x98, 0x23, 0x6f, 0xc3, 0xa7, 0x8f, 0xca, - 0x1c, 0x67, 0x9a, 0xa0, 0xcc, 0xe3, 0xae, 0x98, 0x9d, 0xa6, 0x82, 0x8e, 0x24, 0x96, 0x8c, 0x1f, - 0x84, 0x4b, 0x70, 0x7e, 0x24, 0x16, 0x1f, 0x82, 0xe7, 0x60, 0x75, 0x24, 0x0a, 0x1b, 0x80, 0x62, - 0x6e, 0xed, 0x03, 0x38, 0x33, 0x3a, 0x54, 0x3a, 0x55, 0x4a, 0xd4, 0xee, 0x2c, 0xc0, 0x44, 0x85, - 0xaa, 0x52, 0x9a, 0xf4, 0x8c, 0x0b, 0x57, 0xa2, 0x51, 0xad, 0x6d, 0xb5, 0xe8, 0x6e, 0xcb, 0x35, - 0x18, 0x54, 0x51, 0x7e, 0x44, 0x8b, 0xf2, 0x3a, 0xc5, 0x3c, 0x93, 0x2f, 0xc7, 0x8c, 0xed, 0x46, - 0x83, 0x29, 0x64, 0x8b, 0x30, 0xdb, 0x6c, 0x6f, 0x56, 0x0d, 0xab, 0x6a, 0x18, 0x4d, 0xa3, 0x98, - 0xa3, 0x2a, 0xd0, 0x76, 0x83, 0xca, 0xc9, 0xa6, 0x51, 0xfb, 0x02, 0x6a, 0x66, 0x2b, 0xb0, 0x6c, - 0xd6, 0x4b, 0xe5, 0xdb, 0x56, 0xa3, 0xd9, 0xb6, 0x6a, 0x0d, 0xab, 0xbc, 0x59, 0x6a, 0x34, 0xaa, - 0xf5, 0x22, 0x50, 0xc5, 0xa0, 0x79, 0xbb, 0x5d, 0xb2, 0xca, 0xcd, 0xc6, 0x46, 0xed, 0x26, 0x67, - 0xb1, 0x8c, 0x33, 0x7e, 0x54, 0xec, 0x32, 0x3a, 0x39, 0x90, 0x86, 0x0f, 0x91, 0x79, 0xaf, 0x51, - 0x16, 0xc7, 0x83, 0x72, 0x7a, 0xdb, 0x7f, 0x01, 0x9e, 0x1f, 0x8b, 0x2d, 0xce, 0x4b, 0x1a, 0x5d, - 0x49, 0x63, 0x31, 0x79, 0xfb, 0xd6, 0x7e, 0x47, 0x83, 0xb3, 0x63, 0x3c, 0x79, 0xe9, 0x74, 0xdc, - 0xac, 0x96, 0x2a, 0x75, 0x3a, 0xb3, 0x68, 0x37, 0xd0, 0x41, 0x64, 0xaf, 0xc7, 0x32, 0x77, 0xf3, - 0x17, 0xe1, 0xf2, 0x78, 0xf4, 0x58, 0xf7, 0x7d, 0x01, 0x9e, 0x1f, 0x8f, 0xca, 0x75, 0xe1, 0x1c, - 0x59, 0x83, 0x2b, 0xe3, 0x31, 0x23, 0x1d, 0x3a, 0xbf, 0xf6, 0x35, 0x0d, 0x4e, 0x66, 0x5f, 0x1d, - 0xd2, 0xba, 0xd5, 0x1a, 0x66, 0xbb, 0x54, 0xaf, 0x5b, 0xad, 0x92, 0x51, 0xda, 0xb2, 0xaa, 0x0d, - 0xa3, 0x59, 0xaf, 0x67, 0xa9, 0x57, 0xcf, 0xc3, 0xc5, 0xd1, 0xa8, 0x66, 0xd9, 0xa8, 0xb5, 0xa8, - 0xea, 0xa0, 0xc3, 0x85, 0xd1, 0x58, 0xd5, 0x5a, 0xb9, 0x5a, 0xcc, 0xad, 0xbf, 0xfb, 0x5b, 0x7f, - 0x74, 0xe1, 0xd8, 0x6f, 0x7d, 0xfb, 0x82, 0xf6, 0x7b, 0xdf, 0xbe, 0xa0, 0xfd, 0xe1, 0xb7, 0x2f, - 0x68, 0x5f, 0x78, 0x89, 0xbd, 0x5d, 0xba, 0xda, 0xf1, 0xf6, 0x5f, 0xdd, 0xf5, 0xed, 0x87, 0x6e, - 0xc8, 0x2f, 0xfd, 0x5f, 0x15, 0x37, 0xbf, 0xaf, 0xda, 0x03, 0xf7, 0x55, 0xb4, 0x2b, 0xdd, 0x9f, - 0x42, 0x33, 0xc5, 0xeb, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0x90, 0x61, 0x4b, 0x71, 0x5b, 0x22, - 0x02, 0x00, + // 37751 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6d, 0x90, 0x1c, 0x49, + 0x76, 0x18, 0x76, 0xdd, 0x3d, 0x1f, 0x3d, 0x6f, 0xbe, 0x7a, 0x12, 0x5f, 0x83, 0xd9, 0x8f, 0xc1, + 0xd6, 0xee, 0x62, 0x81, 0xbd, 0x5d, 0x60, 0x77, 0x70, 0x8b, 0xbb, 0xdb, 0xdd, 0xbb, 0xdd, 0x9e, + 0x0f, 0x00, 0x03, 0xcc, 0x00, 0xbd, 0xd5, 0x03, 0xcc, 0x1d, 0xef, 0xa3, 0xae, 0xa6, 0x3b, 0x67, + 0xa6, 0x16, 0xdd, 0x55, 0xbd, 0x55, 0xd5, 0x03, 0x0c, 0x8f, 0x14, 0x65, 0xea, 0x4e, 0x12, 0x49, + 0xd1, 0xfc, 0x10, 0x29, 0x92, 0x0e, 0x93, 0xa1, 0x70, 0x84, 0x42, 0x1f, 0x34, 0x1d, 0x61, 0x8a, + 0xb2, 0xe9, 0x90, 0x45, 0x93, 0x0e, 0x59, 0xb2, 0x1d, 0x36, 0xa5, 0x08, 0xfa, 0x43, 0x66, 0x5c, + 0x0c, 0xc3, 0x26, 0xed, 0x90, 0xe1, 0x08, 0x85, 0xec, 0x1f, 0x8c, 0xf0, 0x29, 0x1c, 0x76, 0xe4, + 0xcb, 0x97, 0x55, 0x99, 0x55, 0xd5, 0x33, 0x83, 0x5d, 0xac, 0x44, 0x5c, 0xf8, 0x0f, 0x30, 0xfd, + 0xf2, 0xbd, 0x97, 0x59, 0xf9, 0xf1, 0xf2, 0xe5, 0xcb, 0xf7, 0x5e, 0xc2, 0x0b, 0x31, 0xef, 0xf0, + 0x5e, 0x10, 0xc6, 0x97, 0x3b, 0x7c, 0xc7, 0x6d, 0xed, 0x5f, 0x8e, 0xf7, 0x7b, 0x3c, 0x92, 0xff, + 0x5e, 0xea, 0x85, 0x41, 0x1c, 0xb0, 0x61, 0xfc, 0x31, 0x77, 0x72, 0x27, 0xd8, 0x09, 0x10, 0x72, + 0x59, 0xfc, 0x25, 0x0b, 0xe7, 0x9e, 0xdf, 0x09, 0x82, 0x9d, 0x0e, 0xbf, 0x8c, 0xbf, 0xb6, 0xfa, + 0xdb, 0x97, 0xdb, 0xfd, 0xd0, 0x8d, 0xbd, 0xc0, 0xa7, 0xf2, 0x67, 0xb3, 0xe5, 0x51, 0x1c, 0xf6, + 0x5b, 0x31, 0x95, 0xce, 0x67, 0x4b, 0x63, 0xaf, 0xcb, 0xa3, 0xd8, 0xed, 0xf6, 0x08, 0xe1, 0x62, + 0xd2, 0x3c, 0x37, 0x8e, 0x45, 0x89, 0x60, 0x7d, 0x79, 0xef, 0x4d, 0xfd, 0x27, 0xa1, 0x5e, 0x4d, + 0x50, 0x5b, 0x41, 0xb7, 0x17, 0xf8, 0xdc, 0x8f, 0xb7, 0xb9, 0x1b, 0xf7, 0x43, 0x1e, 0x09, 0x82, + 0x04, 0xe8, 0x28, 0x28, 0xd1, 0xbd, 0x54, 0xdc, 0x03, 0x5d, 0x1e, 0xbb, 0x6d, 0x37, 0x76, 0x09, + 0xeb, 0xfc, 0x00, 0xac, 0x6d, 0xd7, 0x69, 0xf3, 0x3d, 0xaf, 0xc5, 0x09, 0xef, 0xe5, 0x62, 0xbc, + 0x90, 0x47, 0x41, 0x3f, 0x6c, 0x25, 0x95, 0x5e, 0x1d, 0xd0, 0xed, 0x61, 0x3f, 0x8a, 0x79, 0x9b, + 0x58, 0x3a, 0x21, 0xff, 0xa8, 0xef, 0x85, 0xbc, 0xcb, 0x7d, 0xd5, 0x61, 0xaf, 0x17, 0xd3, 0x3d, + 0x08, 0xdd, 0x5e, 0x8f, 0x87, 0xe9, 0x1f, 0xaa, 0x7f, 0x13, 0xf4, 0xa8, 0x15, 0xf4, 0x64, 0x47, + 0xc8, 0xbf, 0x24, 0x82, 0xf5, 0x5b, 0x15, 0x18, 0xbb, 0xc5, 0x79, 0xaf, 0xde, 0xf1, 0xf6, 0x38, + 0x7b, 0x11, 0x86, 0x6e, 0xbb, 0x5d, 0x3e, 0x5b, 0x3a, 0x57, 0xba, 0x30, 0xb6, 0x38, 0xfd, 0xe8, + 0x60, 0x7e, 0x3c, 0xe2, 0xe1, 0x1e, 0x0f, 0x1d, 0xdf, 0xed, 0x72, 0x1b, 0x0b, 0xd9, 0x67, 0x61, + 0x4c, 0xfc, 0x1f, 0xf5, 0xdc, 0x16, 0x9f, 0x2d, 0x23, 0xe6, 0xe4, 0xa3, 0x83, 0xf9, 0x31, 0x5f, + 0x01, 0xed, 0xb4, 0x9c, 0xad, 0xc2, 0xe8, 0xca, 0xc3, 0x9e, 0x17, 0xf2, 0x68, 0x76, 0xe8, 0x5c, + 0xe9, 0xc2, 0xf8, 0xc2, 0xdc, 0x25, 0x39, 0xe4, 0x97, 0xd4, 0x90, 0x5f, 0xda, 0x50, 0x43, 0xbe, + 0x78, 0xe2, 0xbf, 0x3c, 0x98, 0xff, 0xcc, 0xa3, 0x83, 0xf9, 0x51, 0x2e, 0x49, 0x7e, 0xf6, 0x8f, + 0xe6, 0x4b, 0xb6, 0xa2, 0x67, 0xef, 0xc2, 0xd0, 0xc6, 0x7e, 0x8f, 0xcf, 0x8e, 0x9d, 0x2b, 0x5d, + 0x98, 0x5a, 0x78, 0xfe, 0x92, 0x9c, 0xa2, 0x49, 0xe3, 0xd3, 0xbf, 0x04, 0xd6, 0x62, 0xf5, 0xd1, + 0xc1, 0xfc, 0x90, 0x40, 0xb1, 0x91, 0x8a, 0xbd, 0x0e, 0x23, 0x37, 0x82, 0x28, 0x5e, 0x5d, 0x9e, + 0x05, 0x6c, 0xf2, 0xa9, 0x47, 0x07, 0xf3, 0x33, 0xbb, 0x41, 0x14, 0x3b, 0x5e, 0xfb, 0xb5, 0xa0, + 0xeb, 0xc5, 0xbc, 0xdb, 0x8b, 0xf7, 0x6d, 0x42, 0x62, 0x27, 0x61, 0x18, 0xfb, 0x69, 0x76, 0x5c, + 0x60, 0xdb, 0xf2, 0x87, 0xf5, 0x10, 0x26, 0x8d, 0x5a, 0xd8, 0x38, 0x8c, 0xde, 0xbd, 0x7d, 0xeb, + 0xf6, 0x9d, 0xcd, 0xdb, 0xb5, 0xcf, 0xb0, 0x2a, 0x0c, 0xdd, 0xbe, 0xb3, 0xbc, 0x52, 0x2b, 0xb1, + 0x51, 0xa8, 0xd4, 0x1b, 0x8d, 0x5a, 0x99, 0x4d, 0x40, 0x75, 0xb9, 0xbe, 0x51, 0x5f, 0xac, 0x37, + 0x57, 0x6a, 0x15, 0x76, 0x02, 0xa6, 0x37, 0x57, 0x6f, 0x2f, 0xdf, 0xd9, 0x6c, 0x3a, 0xcb, 0x2b, + 0xcd, 0x5b, 0x1b, 0x77, 0x1a, 0xb5, 0x21, 0x36, 0x05, 0x70, 0xeb, 0xee, 0xe2, 0x8a, 0x7d, 0x7b, + 0x65, 0x63, 0xa5, 0x59, 0x1b, 0x66, 0x27, 0xa1, 0xa6, 0x48, 0x9c, 0xe6, 0x8a, 0x7d, 0x6f, 0x75, + 0x69, 0xa5, 0x36, 0x72, 0x73, 0xa8, 0x5a, 0xa9, 0x0d, 0xd9, 0xa3, 0x6b, 0xdc, 0x8d, 0xf8, 0xea, + 0xb2, 0xf5, 0xf3, 0x43, 0x50, 0xb5, 0x03, 0x39, 0xfd, 0xd9, 0x45, 0x18, 0x6e, 0xc6, 0x6e, 0xac, + 0x86, 0xed, 0xc4, 0xa3, 0x83, 0xf9, 0x69, 0xb1, 0x34, 0xb8, 0xf6, 0x5d, 0x12, 0x43, 0xa0, 0x36, + 0x76, 0xdd, 0x48, 0x8d, 0x1b, 0xa2, 0xf6, 0x04, 0x40, 0x47, 0x45, 0x0c, 0x76, 0x1e, 0x86, 0xd6, + 0x83, 0x36, 0x9f, 0xad, 0x20, 0x26, 0x7b, 0x74, 0x30, 0x3f, 0xd5, 0x0d, 0xda, 0x3a, 0x22, 0x96, + 0xb3, 0xd7, 0x60, 0x6c, 0xa9, 0x1f, 0x86, 0xdc, 0x17, 0x7d, 0x3b, 0x84, 0xc8, 0x53, 0x8f, 0x0e, + 0xe6, 0xa1, 0x25, 0x81, 0x8e, 0xd7, 0xb6, 0x53, 0x04, 0xd6, 0x84, 0xd1, 0x66, 0xec, 0x86, 0x31, + 0x6f, 0xcf, 0x0e, 0x1f, 0x39, 0x1f, 0x9e, 0xa3, 0xf9, 0x30, 0x13, 0x49, 0x92, 0xb4, 0x6e, 0x39, + 0x33, 0x88, 0x13, 0xbb, 0x01, 0xe3, 0xd7, 0x43, 0xb7, 0xc5, 0x1b, 0x3c, 0xf4, 0x82, 0xf6, 0xec, + 0xc8, 0xb9, 0xd2, 0x85, 0xca, 0xe2, 0xf9, 0x47, 0x07, 0xf3, 0xa7, 0x77, 0x04, 0xd8, 0xe9, 0x21, + 0x3c, 0xa5, 0xfe, 0xfe, 0xc1, 0x7c, 0x75, 0x99, 0xc4, 0x94, 0xad, 0x93, 0xb2, 0x6f, 0xc1, 0xf8, + 0x9a, 0x1b, 0xc5, 0xd8, 0xb5, 0xbc, 0x3d, 0x3b, 0x7a, 0x64, 0x13, 0x2d, 0x6a, 0xe2, 0xe9, 0x8e, + 0x1b, 0xc5, 0x4e, 0x28, 0xe9, 0x32, 0xed, 0xd4, 0x59, 0xb2, 0x3b, 0x50, 0x6d, 0xb6, 0x76, 0x79, + 0xbb, 0xdf, 0xe1, 0xb3, 0x55, 0x64, 0x7f, 0x86, 0x66, 0xb2, 0x1a, 0x4f, 0x55, 0xbc, 0x38, 0x47, + 0xbc, 0x59, 0x44, 0x10, 0xad, 0xef, 0x13, 0x26, 0x6f, 0x57, 0x7f, 0xf9, 0xaf, 0xcf, 0x7f, 0xe6, + 0xcf, 0x7f, 0xef, 0xdc, 0x67, 0xac, 0xff, 0xb8, 0x0c, 0xb5, 0x2c, 0x13, 0xb6, 0x0d, 0x93, 0x77, + 0x7b, 0x6d, 0x37, 0xe6, 0x4b, 0x1d, 0x8f, 0xfb, 0x71, 0x84, 0x93, 0xe4, 0xf0, 0x6f, 0x7a, 0x89, + 0xea, 0x9d, 0xed, 0x23, 0xa1, 0xd3, 0x92, 0x94, 0x99, 0xaf, 0x32, 0xd9, 0xa6, 0xf5, 0x34, 0x51, + 0x60, 0x44, 0x38, 0xc3, 0x1e, 0xaf, 0x1e, 0x29, 0x6a, 0x06, 0xd4, 0x43, 0x6c, 0x69, 0x02, 0xf9, + 0xed, 0xad, 0x7d, 0x9c, 0x99, 0xc7, 0x9f, 0x40, 0x82, 0xa4, 0x60, 0x02, 0x09, 0xb0, 0xf5, 0x27, + 0x25, 0x98, 0xb2, 0x49, 0x42, 0xdf, 0xe0, 0x6e, 0x9b, 0x87, 0x62, 0xfa, 0xdf, 0xf2, 0xfc, 0x36, + 0xad, 0x29, 0x9c, 0xfe, 0xf7, 0x3d, 0x5f, 0x17, 0x15, 0x58, 0xce, 0xde, 0x80, 0xd1, 0x66, 0x7f, + 0x0b, 0x51, 0xe5, 0x9a, 0x3a, 0x8d, 0x23, 0xd6, 0xdf, 0x72, 0x32, 0xe8, 0x0a, 0x8d, 0x5d, 0x86, + 0xd1, 0x7b, 0x3c, 0x8c, 0xbc, 0xc0, 0xa7, 0xb5, 0x85, 0xa2, 0x68, 0x4f, 0x82, 0x74, 0x02, 0xc2, + 0x62, 0xd7, 0xa1, 0xba, 0x4e, 0x9b, 0x11, 0x09, 0xd1, 0x69, 0x9a, 0x32, 0x0a, 0x9c, 0x4e, 0x15, + 0xb5, 0x6b, 0xe9, 0x53, 0x45, 0x61, 0x59, 0xff, 0xa2, 0x0c, 0xb5, 0x65, 0x37, 0x76, 0xb7, 0xdc, + 0x88, 0xfa, 0xf3, 0xde, 0x15, 0xf6, 0xac, 0xf1, 0xa1, 0x28, 0x36, 0x45, 0xcb, 0x3f, 0xf6, 0xe7, + 0xbd, 0x9c, 0xfd, 0xbc, 0x71, 0x21, 0xd1, 0xe9, 0xf3, 0xd2, 0x8f, 0xfa, 0xd2, 0xd1, 0x1f, 0x55, + 0xa3, 0x8f, 0xaa, 0xaa, 0x8f, 0x4a, 0x3f, 0x85, 0x7d, 0x09, 0x86, 0x9a, 0x3d, 0xde, 0x22, 0x21, + 0xf2, 0x0c, 0x91, 0x9a, 0x1f, 0x27, 0x10, 0xee, 0x5d, 0x59, 0x9c, 0x20, 0x36, 0x43, 0x51, 0x8f, + 0xb7, 0x6c, 0x24, 0x63, 0x2b, 0x30, 0x22, 0x04, 0x62, 0x3f, 0x42, 0x61, 0x31, 0xbe, 0xf0, 0x5c, + 0x31, 0x03, 0x44, 0xb9, 0x77, 0x65, 0x71, 0x8a, 0x58, 0x8c, 0x44, 0x08, 0xb1, 0x89, 0x38, 0xdd, + 0x25, 0x46, 0xb5, 0x5d, 0x42, 0x5b, 0x91, 0x7f, 0x5a, 0x81, 0x93, 0x45, 0x6d, 0xd2, 0x3b, 0x69, + 0xe4, 0x90, 0x4e, 0xba, 0x00, 0x55, 0xb1, 0x1f, 0x89, 0x9d, 0x55, 0x56, 0xb1, 0x38, 0x21, 0xfa, + 0x63, 0x97, 0x60, 0x76, 0x52, 0xca, 0x5e, 0x4c, 0xb6, 0xb7, 0x6a, 0xca, 0x8f, 0xb6, 0xb7, 0x64, + 0x53, 0xbb, 0x9e, 0x6e, 0x1a, 0xb8, 0x0b, 0xa6, 0x7d, 0xae, 0xc0, 0xe9, 0x44, 0x0a, 0x09, 0xa2, + 0x4f, 0xa4, 0x64, 0xc7, 0x59, 0x81, 0xaa, 0xfa, 0xac, 0xd9, 0x09, 0x64, 0x34, 0x93, 0xe9, 0xc0, + 0x7b, 0x57, 0xe4, 0x4c, 0x69, 0xd3, 0x6f, 0x9d, 0x8d, 0xc2, 0x61, 0x57, 0xa0, 0xda, 0x08, 0x83, + 0x87, 0xfb, 0xab, 0xcb, 0xd1, 0xec, 0xe4, 0xb9, 0xca, 0x85, 0xb1, 0xc5, 0x33, 0x8f, 0x0e, 0xe6, + 0x4f, 0xf4, 0x04, 0xcc, 0xf1, 0xda, 0x91, 0x4e, 0xa4, 0x10, 0xd9, 0x3c, 0x8c, 0x87, 0xbc, 0xe3, + 0xee, 0x3b, 0x3b, 0x61, 0xd0, 0xef, 0xcd, 0x4e, 0x61, 0xcf, 0x03, 0x82, 0xae, 0x0b, 0x08, 0x7b, + 0x06, 0xc6, 0x24, 0x82, 0xd7, 0x8e, 0x66, 0xa7, 0x05, 0x5b, 0xbb, 0x8a, 0x80, 0xd5, 0x76, 0x74, + 0x73, 0xa8, 0x5a, 0xaa, 0x95, 0x6f, 0x0e, 0x55, 0xcb, 0xb5, 0x8a, 0xdc, 0x53, 0x6f, 0x0e, 0x55, + 0x87, 0x6a, 0xc3, 0x37, 0x87, 0xaa, 0xc3, 0xb8, 0xcb, 0x8e, 0xd5, 0xe0, 0xe6, 0x50, 0x75, 0xbc, + 0x36, 0x61, 0x8f, 0x2f, 0xf3, 0xa8, 0x15, 0x7a, 0x3d, 0xdc, 0x24, 0x44, 0xf5, 0x71, 0xd0, 0x0a, + 0x3a, 0x76, 0xe5, 0xae, 0xbd, 0x6a, 0x8f, 0x2c, 0xd5, 0x97, 0x78, 0x18, 0xdb, 0x95, 0xfa, 0x66, + 0xd3, 0x9e, 0x5c, 0xde, 0xf7, 0xdd, 0xae, 0xd7, 0x5a, 0x73, 0xb7, 0x78, 0x27, 0xb2, 0x2b, 0xd7, + 0x97, 0x1a, 0x96, 0x0f, 0xa7, 0x8b, 0xa7, 0x12, 0xdb, 0x80, 0x89, 0x0d, 0x37, 0xdc, 0xe1, 0xf1, + 0x0d, 0xee, 0x76, 0xe2, 0x5d, 0x6c, 0xff, 0xf8, 0xc2, 0x09, 0xea, 0x3e, 0xbd, 0x68, 0xf1, 0x99, + 0x47, 0x07, 0xf3, 0x67, 0x62, 0x84, 0x38, 0xbb, 0x08, 0xd2, 0x3a, 0xc4, 0xe0, 0x62, 0xd5, 0x61, + 0x2a, 0xed, 0xf9, 0x35, 0x2f, 0x8a, 0xd9, 0x65, 0x18, 0x53, 0x10, 0x21, 0xf3, 0x2b, 0x85, 0x63, + 0x64, 0xa7, 0x38, 0xd6, 0x3f, 0x2a, 0x03, 0xa4, 0x25, 0x4f, 0xa9, 0x58, 0xf8, 0xbc, 0x21, 0x16, + 0x4e, 0x65, 0x57, 0xf5, 0x60, 0x81, 0xf0, 0x5e, 0x46, 0x20, 0x9c, 0xc9, 0x92, 0x1e, 0x21, 0x0a, + 0xb4, 0x45, 0xff, 0x6b, 0xa3, 0xe9, 0x60, 0xd0, 0x72, 0xbf, 0x00, 0xc9, 0x04, 0xa2, 0x0e, 0xc5, + 0x75, 0xdc, 0x53, 0x93, 0x2a, 0x29, 0x65, 0x67, 0x41, 0x4c, 0x30, 0xea, 0xd4, 0xd1, 0x47, 0x07, + 0xf3, 0x95, 0x7e, 0xe8, 0xe1, 0xa4, 0x63, 0x97, 0x81, 0xa6, 0x1d, 0x75, 0xa0, 0x58, 0x2b, 0x33, + 0x2d, 0xd7, 0x69, 0xf1, 0x30, 0x4e, 0x7b, 0x7c, 0xb6, 0xa4, 0x66, 0x27, 0xeb, 0x81, 0x39, 0x35, + 0x67, 0x87, 0x70, 0x1a, 0x5c, 0x28, 0xec, 0x95, 0x4b, 0x06, 0xea, 0x8a, 0x1f, 0x87, 0xfb, 0x8b, + 0xe7, 0xd4, 0x06, 0xdd, 0x96, 0x65, 0x4e, 0x07, 0x0b, 0xb5, 0xe1, 0x35, 0x2b, 0x60, 0x57, 0x40, + 0xac, 0x08, 0xea, 0x7d, 0xa0, 0x7a, 0xea, 0x9b, 0xcd, 0xc5, 0x53, 0xc4, 0x69, 0xd2, 0x7d, 0xa0, + 0x93, 0x0b, 0x6c, 0xf6, 0x0e, 0x88, 0x25, 0x43, 0xfd, 0xce, 0x88, 0xe8, 0xfa, 0x52, 0x63, 0xa9, + 0x13, 0xf4, 0xdb, 0xcd, 0x0f, 0xd6, 0x52, 0xe2, 0x9d, 0x56, 0x4f, 0x27, 0xbe, 0xbe, 0xd4, 0x60, + 0xef, 0xc0, 0x70, 0xfd, 0x87, 0xfb, 0x21, 0x27, 0x55, 0x6d, 0x42, 0xd5, 0x29, 0x60, 0x8b, 0x67, + 0x88, 0x70, 0xda, 0x15, 0x3f, 0x75, 0x15, 0x17, 0xcb, 0x45, 0xcd, 0x1b, 0x6b, 0x4d, 0x52, 0xc3, + 0x58, 0xa6, 0x5b, 0x36, 0xd6, 0xb4, 0x66, 0xc7, 0xc6, 0x57, 0x0b, 0x2a, 0x76, 0x19, 0xca, 0xf5, + 0x65, 0x3c, 0x8c, 0x8c, 0x2f, 0x8c, 0xa9, 0x6a, 0x97, 0x17, 0x4f, 0x12, 0xc9, 0x84, 0xab, 0x2f, + 0x83, 0x72, 0x7d, 0x99, 0x2d, 0xc2, 0xf0, 0xfa, 0x7e, 0xf3, 0x83, 0x35, 0x12, 0xbd, 0x6a, 0xc9, + 0x23, 0xec, 0x0e, 0x8a, 0x99, 0x28, 0x6d, 0x71, 0x77, 0x3f, 0xfa, 0xa8, 0xa3, 0xb7, 0x18, 0xd1, + 0x58, 0x03, 0xc6, 0xea, 0xed, 0xae, 0xe7, 0xdf, 0x8d, 0x78, 0x88, 0x47, 0x93, 0xf1, 0x85, 0xd9, + 0x4c, 0xbb, 0x93, 0xf2, 0xc5, 0xd9, 0x47, 0x07, 0xf3, 0x27, 0x5d, 0xf1, 0xd3, 0xe9, 0x47, 0x3c, + 0xd4, 0xb8, 0xa5, 0x4c, 0x58, 0x03, 0x60, 0x3d, 0xf0, 0x77, 0x82, 0x7a, 0xdc, 0x71, 0xa3, 0x8c, + 0x30, 0x4f, 0x0b, 0x12, 0x4d, 0xea, 0x54, 0x57, 0xc0, 0x1c, 0x57, 0x00, 0x35, 0x86, 0x1a, 0x0f, + 0x76, 0x0d, 0x46, 0xee, 0x84, 0x6e, 0xab, 0xc3, 0x67, 0x27, 0x91, 0xdb, 0x49, 0xe2, 0x26, 0x81, + 0xea, 0x4b, 0x67, 0x89, 0x61, 0x2d, 0x40, 0xb0, 0x7e, 0x04, 0x93, 0x88, 0x73, 0x9b, 0xc0, 0xf2, + 0x73, 0x92, 0xd5, 0xa0, 0x72, 0x9f, 0xef, 0xcb, 0x55, 0x64, 0x8b, 0x3f, 0xd9, 0x67, 0x61, 0x78, + 0xcf, 0xed, 0xf4, 0x39, 0x69, 0x9c, 0x6a, 0xd1, 0x2f, 0x05, 0xdd, 0xae, 0xeb, 0xb7, 0x91, 0xf6, + 0xde, 0x82, 0x2d, 0x71, 0xde, 0x2e, 0x7f, 0xa1, 0x64, 0x7d, 0x04, 0x33, 0xb9, 0xce, 0x12, 0xf2, + 0x4e, 0x3b, 0xfa, 0xa2, 0xbc, 0xd3, 0xce, 0xbc, 0x5f, 0x86, 0xe9, 0x65, 0xbe, 0xed, 0xf6, 0x3b, + 0x71, 0xb2, 0xef, 0xc9, 0x25, 0x7a, 0x52, 0x7c, 0x42, 0x5b, 0x16, 0x39, 0x6a, 0xb3, 0xb3, 0xb3, + 0xc8, 0xd6, 0x3f, 0x29, 0xc1, 0xa4, 0xf1, 0xfd, 0xec, 0x2a, 0x8c, 0xd5, 0xfb, 0x6d, 0x2f, 0xc6, + 0x91, 0x94, 0x95, 0xca, 0xf1, 0x12, 0xc0, 0xfc, 0x78, 0x29, 0x54, 0xf6, 0x45, 0x00, 0x9b, 0xc7, + 0xe1, 0xfe, 0x52, 0xd0, 0xf7, 0x63, 0x6c, 0xc4, 0xf0, 0xe2, 0x59, 0x31, 0x30, 0xa1, 0x80, 0x3a, + 0x2d, 0x01, 0xd6, 0x07, 0x26, 0x45, 0x66, 0xb7, 0xa0, 0xd6, 0xdc, 0xed, 0x6f, 0x6f, 0x77, 0xb8, + 0x52, 0x1b, 0x22, 0x14, 0x25, 0xd5, 0xc5, 0xf9, 0x47, 0x07, 0xf3, 0xcf, 0x44, 0xb2, 0xcc, 0x51, + 0xda, 0x85, 0x3e, 0xbe, 0x39, 0x42, 0xeb, 0x8f, 0x75, 0x5d, 0x52, 0x6d, 0x6e, 0xaf, 0x27, 0x22, + 0xaa, 0x94, 0x6a, 0xb6, 0x39, 0x11, 0x95, 0x08, 0xa8, 0x8b, 0x52, 0x5c, 0x94, 0x73, 0xe2, 0x62, + 0x9c, 0x26, 0x47, 0xc5, 0x7d, 0x10, 0x49, 0x21, 0x91, 0x2c, 0x9e, 0xca, 0xc7, 0x5f, 0x3c, 0xef, + 0xc1, 0xc4, 0xba, 0xeb, 0xbb, 0x3b, 0xbc, 0x2d, 0x7a, 0x52, 0x8a, 0xc3, 0x31, 0xb9, 0xcb, 0x76, + 0x25, 0x1c, 0xfb, 0x5d, 0xff, 0x6e, 0x83, 0x80, 0xbd, 0xa9, 0x84, 0xcd, 0x70, 0x81, 0xb0, 0x99, + 0xa4, 0xda, 0x87, 0x51, 0xd8, 0x28, 0x11, 0xf3, 0x25, 0x18, 0xbf, 0x77, 0x9b, 0xc7, 0xcb, 0xb7, + 0x9b, 0x38, 0xbb, 0xa4, 0xb2, 0x87, 0x55, 0xee, 0xf9, 0x3c, 0x76, 0xda, 0x7e, 0x84, 0xa6, 0x15, + 0xad, 0x4a, 0x1d, 0xdf, 0xfa, 0x1d, 0xc0, 0x2e, 0x62, 0xaf, 0xc1, 0x88, 0xcd, 0x77, 0xc4, 0xe6, + 0x59, 0x4a, 0xa7, 0x5d, 0x88, 0x10, 0xbd, 0x5f, 0x25, 0x0e, 0xea, 0x79, 0xbc, 0x1d, 0xed, 0x7a, + 0xdb, 0x31, 0x75, 0x6e, 0xa2, 0xe7, 0x11, 0x58, 0xd3, 0xf3, 0x08, 0x62, 0xe8, 0x79, 0x04, 0x13, + 0xf2, 0xdc, 0x5e, 0x6e, 0x52, 0x9f, 0xab, 0x01, 0xb2, 0x97, 0x35, 0xc1, 0x18, 0x1a, 0x5a, 0x9a, + 0xc0, 0xc6, 0x99, 0xdd, 0xc2, 0x79, 0x98, 0x18, 0x04, 0xe4, 0xcc, 0x96, 0x40, 0xd3, 0xde, 0x92, + 0xa2, 0xb2, 0x26, 0x8c, 0xaf, 0x88, 0x53, 0xb4, 0xb7, 0xe4, 0xb6, 0x76, 0x55, 0x1f, 0x2b, 0xa9, + 0xac, 0x95, 0xa4, 0xb2, 0x88, 0x23, 0xb0, 0x25, 0x80, 0x7a, 0x07, 0x6a, 0xb8, 0x6c, 0x03, 0xc6, + 0x9b, 0xbc, 0x15, 0xf2, 0xb8, 0x19, 0x07, 0x21, 0xcf, 0x6c, 0x32, 0x5a, 0xc9, 0xe2, 0xf3, 0xea, + 0x20, 0x1f, 0x21, 0xd0, 0x89, 0x04, 0x54, 0xe7, 0xaa, 0x21, 0xcb, 0x13, 0x59, 0x37, 0x08, 0xf7, + 0x97, 0x17, 0x69, 0xe3, 0x49, 0xb5, 0x14, 0x09, 0xd6, 0x4f, 0x64, 0x02, 0xd2, 0xde, 0x32, 0x4f, + 0x64, 0x12, 0x0b, 0x47, 0x6a, 0xb9, 0x89, 0xba, 0x2d, 0x6d, 0x43, 0xd3, 0x69, 0x2f, 0x23, 0x58, + 0x1b, 0xa9, 0x76, 0x84, 0x9a, 0xb1, 0x31, 0x52, 0x84, 0xc5, 0x7a, 0xc0, 0xd4, 0xa8, 0x49, 0x85, + 0xb3, 0xc3, 0xa3, 0x88, 0x76, 0xa7, 0xb3, 0x99, 0xc1, 0x4f, 0x11, 0x16, 0x5f, 0x26, 0xe6, 0xcf, + 0xa9, 0x69, 0x40, 0x87, 0x70, 0x51, 0xa8, 0xd5, 0x53, 0xc0, 0x5b, 0x08, 0xa2, 0x95, 0x87, 0x31, + 0x0f, 0x7d, 0xb7, 0x93, 0x18, 0xd5, 0x50, 0x10, 0x71, 0x82, 0x9a, 0x03, 0xad, 0x21, 0xb3, 0x25, + 0x98, 0xac, 0x47, 0x51, 0xbf, 0xcb, 0xed, 0xa0, 0xc3, 0xeb, 0xf6, 0x6d, 0x69, 0x64, 0x5b, 0x7c, + 0xee, 0xd1, 0xc1, 0xfc, 0x59, 0x17, 0x0b, 0x9c, 0x30, 0xe8, 0x70, 0xc7, 0x0d, 0xf5, 0xd9, 0x6d, + 0xd2, 0xb0, 0x3b, 0x00, 0x77, 0x7a, 0xdc, 0x6f, 0x72, 0x37, 0x6c, 0xed, 0x66, 0x36, 0xae, 0xb4, + 0x60, 0xf1, 0x59, 0xfa, 0xc2, 0x93, 0x41, 0x8f, 0xfb, 0x11, 0xc2, 0xf4, 0x56, 0xa5, 0x98, 0x6c, + 0x13, 0xa6, 0x57, 0xeb, 0xeb, 0x8d, 0xa0, 0xe3, 0xb5, 0xf6, 0x49, 0x17, 0x9c, 0x42, 0x53, 0xe3, + 0x69, 0xe2, 0x9a, 0x29, 0x95, 0xd2, 0xcd, 0x73, 0xbb, 0x4e, 0x0f, 0xa1, 0x0e, 0x69, 0x84, 0x59, + 0x2e, 0xec, 0xab, 0x62, 0x0e, 0x46, 0x42, 0xbd, 0xdd, 0x70, 0x77, 0xe4, 0x91, 0x24, 0x3d, 0xb2, + 0xd6, 0x37, 0x9b, 0x97, 0xb4, 0x52, 0xa9, 0x78, 0xcd, 0xc9, 0x89, 0x88, 0x50, 0x27, 0x76, 0x77, + 0x22, 0x73, 0x22, 0x26, 0xd8, 0xec, 0x26, 0xc0, 0x72, 0xd0, 0xea, 0x77, 0xb9, 0x1f, 0x2f, 0x2f, + 0xce, 0xd6, 0xcc, 0xa3, 0x58, 0x52, 0x90, 0x4a, 0xc6, 0x76, 0xd0, 0x32, 0x66, 0xa2, 0x46, 0xcd, + 0x7e, 0x04, 0x4e, 0x69, 0x2b, 0x47, 0x9b, 0x45, 0x33, 0xc8, 0xf6, 0xd9, 0xfc, 0x4a, 0xd4, 0x26, + 0xd2, 0x05, 0xaa, 0xe1, 0x9c, 0xb6, 0x26, 0x8b, 0xe7, 0x52, 0x71, 0x25, 0x73, 0x5f, 0x86, 0x5a, + 0xb6, 0x1b, 0x0a, 0xf6, 0xfa, 0x93, 0xfa, 0x5e, 0x3f, 0xa6, 0x6d, 0xea, 0x37, 0x87, 0xaa, 0x93, + 0xb5, 0x29, 0xad, 0xef, 0x57, 0x1e, 0x7a, 0x51, 0x1c, 0x59, 0xdf, 0x36, 0xd6, 0xbf, 0x90, 0x4d, + 0xb7, 0xf8, 0x7e, 0x23, 0xe4, 0xdb, 0xde, 0x43, 0x7d, 0xd7, 0xbd, 0xcf, 0xf7, 0x9d, 0x1e, 0x42, + 0x75, 0xd9, 0x94, 0xa0, 0xb2, 0xcf, 0x41, 0xf5, 0xd6, 0x7a, 0xf3, 0x16, 0xdf, 0x5f, 0x5d, 0xa6, + 0x8d, 0x5f, 0x92, 0x75, 0x23, 0x47, 0x90, 0x1a, 0x33, 0x3d, 0xc1, 0xb4, 0x16, 0x53, 0x39, 0x2c, + 0x6a, 0x5e, 0xea, 0xf4, 0xa3, 0x98, 0x87, 0xab, 0xcb, 0x7a, 0xcd, 0x2d, 0x09, 0xcc, 0x48, 0xc5, + 0x04, 0xd5, 0xfa, 0x7f, 0xcb, 0x28, 0x83, 0xc5, 0x72, 0x5b, 0xf5, 0xa3, 0xd8, 0xf5, 0x5b, 0x3c, + 0x61, 0x80, 0xcb, 0xcd, 0x23, 0x68, 0x66, 0xb9, 0xa5, 0xc8, 0x66, 0xd5, 0xe5, 0x63, 0x57, 0x2d, + 0x55, 0x0d, 0x69, 0x14, 0x5b, 0x5d, 0xa6, 0x43, 0x07, 0xa9, 0x1a, 0x12, 0x9a, 0xa9, 0x32, 0x45, + 0x66, 0xe7, 0x61, 0x74, 0xb5, 0xbe, 0x5e, 0xef, 0xc7, 0xbb, 0xb8, 0x03, 0x54, 0xe5, 0x79, 0x47, + 0xac, 0x15, 0xb7, 0x1f, 0xef, 0xda, 0xaa, 0x90, 0x5d, 0xc6, 0x73, 0xa4, 0xcf, 0xe3, 0x68, 0x76, + 0x18, 0x77, 0x63, 0x5c, 0x53, 0x91, 0x04, 0x65, 0x8e, 0x91, 0x02, 0xc4, 0x5e, 0x85, 0xe1, 0x7b, + 0x8d, 0xa5, 0xd5, 0x65, 0xda, 0x49, 0x71, 0x1f, 0xdc, 0xeb, 0xb5, 0xcc, 0x96, 0x48, 0x14, 0xb6, + 0x02, 0x53, 0x4d, 0xde, 0xea, 0x87, 0x5e, 0x2c, 0x2d, 0x03, 0xd1, 0xec, 0x28, 0xd6, 0x81, 0x72, + 0x26, 0xa2, 0x12, 0x69, 0x46, 0xd0, 0xeb, 0xca, 0x10, 0x59, 0xbf, 0x57, 0x4a, 0x85, 0x34, 0x3b, + 0x6f, 0xa8, 0x89, 0x68, 0x16, 0xcc, 0xec, 0xdf, 0x52, 0x61, 0xb4, 0x81, 0x2d, 0xf5, 0xa3, 0x38, + 0xe8, 0xae, 0xf8, 0xed, 0x5e, 0xe0, 0xf9, 0x31, 0x52, 0xc9, 0xce, 0xb7, 0x1e, 0x1d, 0xcc, 0x3f, + 0xdf, 0xc2, 0x52, 0x87, 0x53, 0x71, 0x56, 0x0b, 0x28, 0xa0, 0xfe, 0x04, 0xe3, 0x61, 0xfd, 0xd7, + 0x65, 0x63, 0x73, 0x15, 0xcd, 0xb3, 0x79, 0xaf, 0xe3, 0xb5, 0xd0, 0x9e, 0x83, 0x1f, 0x9a, 0xcc, + 0x2a, 0x6c, 0x5e, 0x98, 0x96, 0xca, 0x1e, 0x32, 0x79, 0x17, 0x50, 0xb3, 0xf7, 0x61, 0x42, 0xa8, + 0x49, 0xf4, 0x33, 0x9a, 0x2d, 0x63, 0x67, 0x3f, 0x8b, 0x06, 0xde, 0x88, 0x87, 0x09, 0x1b, 0x43, + 0xbf, 0xd2, 0x29, 0x58, 0x1b, 0x66, 0x37, 0x42, 0xd7, 0x8f, 0xbc, 0x78, 0xc5, 0x6f, 0x85, 0xfb, + 0xa8, 0xd6, 0xad, 0xf8, 0xee, 0x56, 0x87, 0xb7, 0x49, 0x51, 0xbd, 0xf0, 0xe8, 0x60, 0xfe, 0xa5, + 0x58, 0xe2, 0x38, 0x3c, 0x41, 0x72, 0xb8, 0xc4, 0xd2, 0x38, 0x0f, 0xe4, 0x24, 0xd4, 0x40, 0xd5, + 0xad, 0x78, 0x9f, 0x34, 0x94, 0xea, 0x64, 0xc9, 0x68, 0x08, 0x51, 0xa7, 0x37, 0x53, 0x27, 0xb0, + 0xee, 0x0c, 0x10, 0x94, 0xb8, 0xd0, 0x04, 0x48, 0x9b, 0x21, 0x72, 0xa1, 0xa1, 0x34, 0xcc, 0x8c, + 0x70, 0x8a, 0x6a, 0xfd, 0x69, 0x29, 0xd5, 0x27, 0xd8, 0xbb, 0x30, 0x4e, 0x4b, 0x50, 0x63, 0x83, + 0x1b, 0x82, 0x5a, 0xaf, 0x59, 0x85, 0x51, 0x43, 0x67, 0x6f, 0xc0, 0x68, 0x7d, 0x69, 0x4d, 0x9b, + 0x6c, 0x68, 0x98, 0x71, 0x5b, 0x9d, 0x2c, 0x95, 0x42, 0x13, 0xb3, 0x6a, 0x63, 0xad, 0x69, 0x76, + 0x33, 0xce, 0xaa, 0xb8, 0x13, 0x15, 0xf4, 0xab, 0x86, 0xfc, 0xc9, 0x7b, 0xf2, 0x7f, 0x2c, 0x15, + 0xa9, 0x2d, 0x6c, 0x11, 0x26, 0x37, 0x83, 0xf0, 0x3e, 0x4e, 0x18, 0xad, 0x13, 0x70, 0x2a, 0x3d, + 0x50, 0x05, 0xd9, 0x0f, 0x32, 0x49, 0xf4, 0xb6, 0x69, 0xbd, 0x61, 0xb6, 0x2d, 0xc3, 0xc1, 0x20, + 0x10, 0xe3, 0x90, 0x70, 0x4c, 0x96, 0x1b, 0x8e, 0x43, 0xda, 0x04, 0x63, 0x4d, 0xe8, 0xe8, 0xd6, + 0x3f, 0x28, 0xe9, 0xea, 0x89, 0xe8, 0xe4, 0xe5, 0xa0, 0xeb, 0x7a, 0xbe, 0xf6, 0x39, 0xd8, 0xc9, + 0x6d, 0x84, 0x66, 0x5b, 0xa2, 0x21, 0xb3, 0x2b, 0x50, 0x95, 0xbf, 0x12, 0xe1, 0x8d, 0x46, 0x52, + 0x22, 0x34, 0x77, 0x1e, 0x85, 0x98, 0x1b, 0x99, 0xca, 0xe3, 0x8e, 0xcc, 0xef, 0x96, 0x74, 0xcd, + 0xe2, 0xe3, 0xee, 0x5e, 0x99, 0x5d, 0xab, 0xfc, 0x38, 0xbb, 0xd6, 0x27, 0xfe, 0x84, 0xff, 0xa2, + 0x04, 0xe3, 0x9a, 0x19, 0x49, 0x7c, 0x43, 0x23, 0x0c, 0x3e, 0xe4, 0xad, 0xd8, 0xfc, 0x86, 0x9e, + 0x04, 0x66, 0xbe, 0x21, 0x41, 0xfd, 0x24, 0xdf, 0xb0, 0x04, 0xa3, 0xf5, 0x4e, 0x27, 0x10, 0xc7, + 0x04, 0x79, 0x86, 0x9a, 0x52, 0x5a, 0x9f, 0x84, 0x2e, 0x9e, 0x55, 0x17, 0x54, 0xae, 0x00, 0x18, + 0xaa, 0x99, 0xa2, 0xb4, 0x7e, 0xa9, 0x94, 0x70, 0xc9, 0x75, 0x4a, 0xe9, 0x31, 0x3b, 0x85, 0xdd, + 0x82, 0x9a, 0xfa, 0x7d, 0x67, 0x8f, 0x87, 0xa1, 0xd7, 0x56, 0x4b, 0x03, 0x6d, 0x00, 0x09, 0x93, + 0x80, 0x0a, 0x75, 0x1b, 0x40, 0x96, 0xd0, 0xfa, 0x3f, 0x4b, 0x74, 0x20, 0x3e, 0xf6, 0xb6, 0x68, + 0x6e, 0x61, 0xe5, 0xc7, 0x51, 0x29, 0xde, 0x87, 0x61, 0x9b, 0xb7, 0xbd, 0x88, 0x7a, 0x72, 0x46, + 0x3f, 0x7c, 0x63, 0x41, 0xaa, 0xe5, 0x86, 0xe2, 0xa7, 0xae, 0x0f, 0x60, 0xb9, 0x38, 0x76, 0xac, + 0x46, 0xd7, 0x3a, 0xfc, 0xa1, 0x27, 0x65, 0x0d, 0xa9, 0x26, 0xa8, 0x0e, 0x78, 0x91, 0xb3, 0x2d, + 0x4a, 0x48, 0x67, 0xd5, 0xe5, 0x8a, 0x41, 0x63, 0x7d, 0x15, 0x20, 0xad, 0x52, 0x74, 0x27, 0x4d, + 0x76, 0xcf, 0xdf, 0x91, 0x8a, 0x27, 0xf5, 0x01, 0x76, 0x67, 0x2b, 0x29, 0xa3, 0x33, 0x82, 0xde, + 0x9d, 0x59, 0x42, 0xeb, 0x7f, 0xab, 0x40, 0xb9, 0x8e, 0xf3, 0xed, 0x16, 0xdf, 0x8f, 0xdd, 0xad, + 0x6b, 0x5e, 0xc7, 0x90, 0x15, 0xf7, 0x11, 0xea, 0x6c, 0x7b, 0x86, 0xb9, 0x4c, 0x43, 0x16, 0xb2, + 0xe2, 0x56, 0xb8, 0xf5, 0x16, 0x12, 0x6a, 0xb2, 0xe2, 0x7e, 0xb8, 0xf5, 0x56, 0x96, 0x2c, 0x41, + 0x64, 0x16, 0x8c, 0x48, 0xb9, 0x41, 0x4b, 0x0c, 0x1e, 0x1d, 0xcc, 0x8f, 0x48, 0xf1, 0x62, 0x53, + 0x09, 0x3b, 0x0b, 0x95, 0x66, 0xe3, 0x36, 0x09, 0x78, 0x34, 0x4b, 0x47, 0x3d, 0xdf, 0x16, 0x30, + 0x51, 0xe7, 0xda, 0x72, 0xbd, 0x81, 0x56, 0x9f, 0xe1, 0xb4, 0xce, 0x4e, 0xdb, 0xed, 0x65, 0xed, + 0x3e, 0x09, 0x22, 0xfb, 0x12, 0x8c, 0xdf, 0x5a, 0x5e, 0xba, 0x11, 0x44, 0x71, 0xd6, 0x2c, 0x72, + 0xbf, 0xdd, 0x42, 0x0b, 0x54, 0x6e, 0x97, 0xd3, 0xf0, 0x99, 0x03, 0xa7, 0x05, 0x2b, 0x31, 0x24, + 0x5e, 0x8b, 0x93, 0x09, 0xe1, 0x76, 0x7a, 0x4b, 0xf6, 0xca, 0xa3, 0x83, 0xf9, 0x17, 0xb1, 0x05, + 0x91, 0x44, 0x71, 0x94, 0xf1, 0x21, 0xc3, 0x75, 0x00, 0x1b, 0xf6, 0x75, 0x38, 0x95, 0x2f, 0x69, + 0x26, 0xb7, 0x6b, 0xe7, 0x1f, 0x1d, 0xcc, 0x5b, 0x85, 0xfc, 0x23, 0x63, 0xfe, 0x16, 0x33, 0xb1, + 0xbe, 0x5b, 0x86, 0x71, 0xcd, 0xcc, 0xcc, 0x3e, 0x47, 0xae, 0x16, 0x25, 0xe3, 0xb8, 0xa9, 0x61, + 0x88, 0x52, 0x69, 0x93, 0xec, 0x06, 0x6d, 0x4e, 0x8e, 0x17, 0xa9, 0xb1, 0xad, 0x7c, 0x1c, 0x63, + 0xdb, 0x17, 0x01, 0xe4, 0x14, 0xc6, 0x7e, 0xd2, 0xb4, 0x47, 0xcd, 0xc3, 0x47, 0x9f, 0x56, 0x29, + 0x32, 0xbb, 0x07, 0x27, 0x36, 0xc2, 0x7e, 0x14, 0x37, 0xf7, 0xa3, 0x98, 0x77, 0x05, 0xb7, 0x46, + 0x10, 0x74, 0x68, 0xf9, 0xbc, 0x24, 0x4e, 0x7d, 0xe8, 0xb7, 0xe4, 0x44, 0x58, 0x8e, 0x0d, 0x70, + 0x7a, 0x41, 0xa0, 0x9b, 0xe0, 0x8a, 0x18, 0x58, 0x36, 0x4c, 0xe8, 0x06, 0x3c, 0xb1, 0xef, 0xd3, + 0xb5, 0x34, 0xdd, 0x14, 0x69, 0xfb, 0x3e, 0xb5, 0x32, 0x7f, 0x4d, 0x6e, 0x92, 0x58, 0x9f, 0xd3, + 0xed, 0xd9, 0xc7, 0x95, 0x4b, 0xd6, 0x8f, 0x97, 0x52, 0x21, 0x7f, 0xef, 0x4d, 0xf6, 0x0e, 0x8c, + 0x48, 0x37, 0x00, 0xf2, 0x96, 0x38, 0x95, 0x58, 0x50, 0x74, 0x1f, 0x01, 0x79, 0x91, 0xf4, 0x4f, + 0x0f, 0xe6, 0x4b, 0x8f, 0x0e, 0xe6, 0x3f, 0x63, 0x13, 0x49, 0x72, 0x07, 0x65, 0x9a, 0xa3, 0x15, + 0x77, 0xbc, 0x6d, 0x79, 0xb3, 0xe8, 0x0e, 0xca, 0xfa, 0x2b, 0x23, 0x30, 0x65, 0xa2, 0xe9, 0xbe, + 0x02, 0xa5, 0x63, 0xf9, 0x0a, 0xbc, 0x0f, 0x55, 0x9a, 0x6f, 0x4a, 0x01, 0x7f, 0x09, 0x6f, 0xe6, + 0x08, 0x66, 0xf8, 0xc0, 0x80, 0x1c, 0x0e, 0x3b, 0xe8, 0x70, 0x3b, 0xa1, 0x62, 0x0b, 0xda, 0x9d, + 0x73, 0x25, 0x55, 0x21, 0x95, 0x55, 0x58, 0x5f, 0xce, 0xc9, 0xed, 0xf3, 0xeb, 0x30, 0x22, 0x8e, + 0x73, 0x89, 0xbd, 0x0f, 0x5b, 0x29, 0x4e, 0x7a, 0x19, 0xe7, 0x2a, 0x89, 0xc4, 0x36, 0xa1, 0xba, + 0xe6, 0x46, 0x71, 0x93, 0x73, 0xff, 0x18, 0x5e, 0x40, 0xf3, 0xd4, 0x55, 0x27, 0xd0, 0xc5, 0x26, + 0xe2, 0xdc, 0xcf, 0xb8, 0x71, 0x24, 0xcc, 0xd8, 0x37, 0x00, 0x96, 0x02, 0x3f, 0x0e, 0x83, 0xce, + 0x5a, 0xb0, 0x33, 0x3b, 0x82, 0x86, 0x96, 0xe7, 0x33, 0x03, 0x90, 0x22, 0x48, 0x5b, 0x4b, 0x62, + 0x4d, 0x6c, 0xc9, 0x02, 0xa7, 0x13, 0xec, 0xe8, 0xeb, 0x20, 0xc5, 0x67, 0xd7, 0xa0, 0xa6, 0xac, + 0x58, 0x77, 0x7b, 0x3b, 0x21, 0x4e, 0x90, 0xd1, 0x54, 0x2f, 0xe4, 0x0f, 0x63, 0xa7, 0x4f, 0x70, + 0x63, 0xdf, 0xcc, 0xd0, 0xb0, 0xaf, 0xc3, 0x99, 0x2c, 0x4c, 0x8d, 0x72, 0x35, 0x3d, 0x82, 0xe9, + 0xec, 0x0a, 0xe6, 0xfd, 0x20, 0x16, 0xec, 0x3a, 0x4c, 0x8b, 0x0e, 0x59, 0xe7, 0x6e, 0xd4, 0x97, + 0xbe, 0x83, 0x64, 0x07, 0x54, 0x4e, 0x0e, 0xb4, 0x0a, 0x3b, 0x41, 0xeb, 0xbe, 0x86, 0x64, 0x67, + 0xa9, 0xd8, 0x55, 0x18, 0x97, 0xbe, 0x37, 0xe1, 0xaa, 0xbf, 0x1d, 0xd0, 0xb5, 0x95, 0xba, 0xcd, + 0xa1, 0x92, 0x7b, 0x0b, 0xa2, 0xcc, 0xd6, 0x11, 0xd9, 0x0a, 0x30, 0x79, 0x8b, 0xf1, 0x51, 0x9f, + 0xf7, 0x39, 0x99, 0xc5, 0xe8, 0xb6, 0x4a, 0xdd, 0xab, 0xe2, 0x85, 0xc6, 0x07, 0xa2, 0x5c, 0x1a, + 0xc9, 0xec, 0x9a, 0x9b, 0x81, 0x58, 0x3f, 0x51, 0x86, 0x5a, 0x16, 0x8d, 0xbd, 0x08, 0x93, 0x3d, + 0xee, 0xb7, 0xc5, 0x8e, 0x8a, 0xe2, 0x14, 0x97, 0x45, 0xc5, 0x9e, 0x20, 0xa0, 0xbc, 0xe8, 0x78, + 0x15, 0x66, 0xda, 0xdc, 0x6d, 0x3b, 0x1d, 0x1e, 0x0b, 0xe5, 0xb4, 0x95, 0x5c, 0x95, 0x54, 0xec, + 0x69, 0x51, 0xb0, 0x86, 0x70, 0x89, 0xfb, 0x22, 0x4c, 0xb6, 0x82, 0x30, 0xec, 0xf7, 0x62, 0xc2, + 0xab, 0x48, 0x86, 0x04, 0x94, 0x48, 0xef, 0xc0, 0x5c, 0xd0, 0x69, 0xf3, 0x28, 0x76, 0x54, 0xe5, + 0xee, 0x0e, 0x77, 0x22, 0xde, 0x0a, 0xfc, 0xb6, 0x74, 0x6c, 0xac, 0xd8, 0x67, 0x24, 0x46, 0x43, + 0x22, 0xd4, 0x77, 0x78, 0x53, 0x16, 0xb3, 0x45, 0x78, 0x9e, 0x88, 0xf5, 0x46, 0xe9, 0x0c, 0x86, + 0x91, 0x01, 0x55, 0xb1, 0x9c, 0x34, 0x30, 0xe5, 0x61, 0x1d, 0x94, 0xe1, 0x74, 0xf1, 0xb0, 0xb1, + 0x1f, 0x83, 0x53, 0x34, 0x45, 0x3b, 0x3c, 0xd4, 0x70, 0x8e, 0xe1, 0xe8, 0xf5, 0x3a, 0x4d, 0xfd, + 0x17, 0x5a, 0x09, 0x83, 0x44, 0x86, 0x0b, 0x16, 0x99, 0x75, 0x56, 0x5c, 0x0f, 0xfb, 0x16, 0x8c, + 0xeb, 0xd5, 0x96, 0x8f, 0xef, 0x33, 0x77, 0x48, 0x5d, 0x3a, 0x4b, 0xe6, 0xc2, 0xb4, 0xcd, 0x3f, + 0xea, 0x8b, 0xce, 0x21, 0xaf, 0x3d, 0x52, 0x02, 0xcf, 0xe6, 0x6a, 0x51, 0x08, 0x89, 0xdd, 0xb7, + 0x16, 0x4a, 0x4a, 0x47, 0xf9, 0x25, 0xff, 0xb2, 0x60, 0x9f, 0xe5, 0x67, 0x7d, 0xbf, 0x0c, 0x67, + 0x06, 0x48, 0x08, 0xb1, 0x89, 0x68, 0xca, 0x36, 0x6e, 0x22, 0x19, 0x1d, 0x5b, 0xba, 0x98, 0x9e, + 0x83, 0x32, 0x29, 0xb5, 0x43, 0x8b, 0xb5, 0x47, 0x07, 0xf3, 0x13, 0x86, 0xf0, 0x2b, 0xaf, 0x2e, + 0xb3, 0x9b, 0x30, 0x24, 0xba, 0xe1, 0x18, 0x9e, 0x6b, 0xca, 0xea, 0x3f, 0x15, 0x7b, 0xba, 0xcc, + 0xc5, 0xbe, 0x41, 0x1e, 0xec, 0x73, 0x50, 0xd9, 0xd8, 0x58, 0x93, 0x93, 0x0f, 0x05, 0xc6, 0x64, + 0x1c, 0x77, 0x0c, 0xf9, 0x3e, 0x29, 0x68, 0x93, 0x1e, 0xb1, 0x05, 0x3a, 0xfb, 0x0a, 0x8c, 0x90, + 0x33, 0xc0, 0x30, 0x4a, 0xc7, 0x57, 0x0f, 0x97, 0x8e, 0x97, 0x74, 0x77, 0x00, 0x34, 0xb6, 0xe5, + 0x5c, 0x00, 0x88, 0xdf, 0xdc, 0x17, 0x61, 0xfc, 0xf0, 0x7b, 0xda, 0x81, 0xb6, 0x5b, 0xeb, 0xa7, + 0x4b, 0xca, 0x79, 0x90, 0xe4, 0x09, 0x3b, 0xa7, 0x44, 0x0f, 0xda, 0x86, 0x88, 0x8b, 0x0e, 0x62, + 0xcf, 0x03, 0xc8, 0x9f, 0x77, 0xef, 0x52, 0xa7, 0x4f, 0xd8, 0x1a, 0x84, 0xbd, 0x9d, 0xb0, 0x24, + 0x5b, 0x7e, 0x05, 0x95, 0xab, 0x8c, 0xf8, 0x22, 0xe1, 0x63, 0xa2, 0x5a, 0xbf, 0x53, 0x4e, 0x37, + 0xe2, 0x6b, 0x5e, 0x27, 0xe6, 0x21, 0x9b, 0x93, 0xfb, 0x6a, 0x7a, 0xfe, 0xb5, 0x93, 0xdf, 0x6c, + 0x36, 0xdd, 0xa4, 0xe5, 0xa7, 0x25, 0xbb, 0xf1, 0xab, 0xda, 0x6e, 0x5c, 0xc1, 0xdd, 0x78, 0x6a, + 0xe0, 0xbe, 0xfb, 0x6a, 0xc1, 0xe6, 0x82, 0xbb, 0x69, 0xc1, 0x06, 0xf2, 0x12, 0x4c, 0xde, 0x0e, + 0x56, 0x1e, 0xc6, 0x09, 0xa2, 0x90, 0x20, 0x55, 0xdb, 0x04, 0x0a, 0x8e, 0x77, 0x3a, 0x6d, 0x1e, + 0x6e, 0xec, 0xba, 0xbe, 0xe1, 0x6d, 0x66, 0xe7, 0xe0, 0x02, 0xf7, 0x36, 0x7f, 0x60, 0xe2, 0x4a, + 0xa7, 0xb6, 0x1c, 0x3c, 0x3b, 0x38, 0xd5, 0xdc, 0xe0, 0x58, 0xbf, 0x54, 0x56, 0xdd, 0x75, 0x6f, + 0xe1, 0x29, 0xf5, 0x24, 0x7a, 0xcb, 0xf0, 0x24, 0x3a, 0x91, 0xdc, 0x18, 0x26, 0x4e, 0x7c, 0x0b, + 0x85, 0x7e, 0x44, 0x89, 0x47, 0xe0, 0x48, 0xb1, 0x47, 0xe0, 0xf7, 0xaa, 0x30, 0xa1, 0x33, 0x11, + 0xbd, 0x53, 0x6f, 0xb7, 0x43, 0xbd, 0x77, 0xdc, 0x76, 0x3b, 0xb4, 0x11, 0x6a, 0x38, 0x00, 0x56, + 0x0e, 0x75, 0x00, 0xfc, 0x26, 0x8c, 0x2d, 0x75, 0xdb, 0x86, 0xa3, 0x8f, 0x55, 0xd0, 0xe8, 0x4b, + 0x09, 0x92, 0x5c, 0xd3, 0xc9, 0xf5, 0x58, 0xab, 0xdb, 0xce, 0xbb, 0xf7, 0xa4, 0x2c, 0x0d, 0xdf, + 0xc1, 0xe1, 0x4f, 0xe2, 0x3b, 0x78, 0x15, 0xc6, 0xee, 0x46, 0x7c, 0xa3, 0xef, 0xfb, 0xbc, 0x83, + 0xbd, 0x54, 0x95, 0x66, 0x98, 0x7e, 0xc4, 0x9d, 0x18, 0xa1, 0x7a, 0x03, 0x12, 0x54, 0x7d, 0xd8, + 0x47, 0x0f, 0x19, 0xf6, 0x2b, 0x50, 0x6d, 0x70, 0x1e, 0x62, 0x9f, 0x8e, 0xa7, 0xc7, 0xd1, 0x1e, + 0x17, 0x7b, 0x6e, 0xbb, 0x1d, 0x1a, 0x3e, 0x85, 0x84, 0x68, 0x38, 0x22, 0x4e, 0x1c, 0xd7, 0x11, + 0xf1, 0x05, 0x98, 0xe8, 0xf5, 0xb7, 0x3a, 0x5e, 0x0b, 0xf9, 0x92, 0x07, 0xa3, 0x3d, 0x2e, 0x61, + 0x82, 0x6d, 0xc4, 0xbe, 0x02, 0x93, 0x68, 0x7e, 0x4a, 0x26, 0xe2, 0x94, 0xa1, 0x43, 0x19, 0x65, + 0xf2, 0x98, 0xd3, 0x12, 0x20, 0xa7, 0xc0, 0x8b, 0xd7, 0x64, 0xc4, 0x6e, 0xc2, 0xe8, 0x8e, 0x17, + 0x3b, 0xbb, 0xfd, 0xad, 0xd9, 0x69, 0xc3, 0x05, 0xf6, 0xba, 0x17, 0xdf, 0xe8, 0x6f, 0xc9, 0x21, + 0x4f, 0x58, 0xa3, 0xe4, 0xde, 0xf1, 0xe2, 0xdd, 0xbe, 0x6e, 0x61, 0x1a, 0xd9, 0x41, 0xdc, 0xac, + 0x47, 0x65, 0xed, 0x70, 0x8f, 0xca, 0x19, 0xd3, 0xa3, 0x92, 0x39, 0xc0, 0xf2, 0xa1, 0x35, 0xb3, + 0x0c, 0x1b, 0xf5, 0xc6, 0x25, 0x15, 0x7f, 0x72, 0x29, 0x17, 0x93, 0x73, 0x69, 0xef, 0xcd, 0x4b, + 0x4b, 0x0a, 0x78, 0x8d, 0x80, 0xf6, 0x4c, 0x2b, 0x0b, 0x62, 0x4d, 0xa8, 0x79, 0xdd, 0x6e, 0x3f, + 0x76, 0xb7, 0x3a, 0x9c, 0xa6, 0xe8, 0xec, 0x09, 0xc3, 0x93, 0xcd, 0x98, 0xe0, 0xab, 0x0a, 0x57, + 0x9b, 0xe6, 0xf6, 0xb4, 0x67, 0x42, 0xe7, 0x9a, 0x30, 0x65, 0xae, 0x84, 0x27, 0xe0, 0x58, 0x34, + 0xb7, 0x08, 0x27, 0x8b, 0x6a, 0x7f, 0xcc, 0x7b, 0x4c, 0xe9, 0x9a, 0x5a, 0xad, 0x8d, 0xdd, 0x1c, + 0xaa, 0x42, 0x6d, 0x5c, 0x3a, 0xa5, 0xda, 0xd0, 0x48, 0xe6, 0x95, 0xcd, 0x6e, 0xf5, 0xb7, 0x78, + 0xe8, 0xf3, 0x98, 0x47, 0x64, 0x44, 0x8a, 0xec, 0xa1, 0x7a, 0xaf, 0x17, 0x59, 0xbf, 0x55, 0x86, + 0xd1, 0xfa, 0x66, 0x13, 0xf7, 0xd0, 0xd7, 0x74, 0x3f, 0x8c, 0x52, 0x1a, 0x98, 0x91, 0xfa, 0x61, + 0xe8, 0xde, 0x17, 0x97, 0x0b, 0xac, 0x9c, 0x18, 0x00, 0xa4, 0x59, 0x39, 0x0d, 0xdb, 0x66, 0xea, + 0x92, 0x52, 0x39, 0x86, 0x4b, 0x4a, 0x72, 0x6f, 0x37, 0x74, 0xf4, 0xbd, 0xdd, 0x3b, 0x30, 0xbe, + 0xea, 0xc7, 0x7c, 0x27, 0x4c, 0xa5, 0x4d, 0x62, 0x71, 0x4d, 0xc0, 0xba, 0x69, 0x48, 0xc3, 0x16, + 0x4b, 0x59, 0xde, 0x15, 0x26, 0x77, 0x84, 0xb8, 0x94, 0xe5, 0x95, 0x62, 0xc6, 0x5c, 0xae, 0x10, + 0xad, 0xe5, 0xcc, 0x3a, 0x55, 0x8e, 0x8c, 0x25, 0xd3, 0x68, 0x2b, 0x3b, 0x76, 0x71, 0xa6, 0xd8, + 0x91, 0xd1, 0xfa, 0xab, 0x25, 0x38, 0x59, 0xb4, 0xfc, 0xd8, 0x97, 0x61, 0x22, 0x08, 0x77, 0x5c, + 0xdf, 0xfb, 0x61, 0xf9, 0x45, 0xda, 0x9d, 0x8e, 0x0e, 0xd7, 0x8d, 0xb6, 0x3a, 0x5c, 0x74, 0x88, + 0xf6, 0xe5, 0xa6, 0x09, 0xba, 0xb0, 0x43, 0x34, 0xb0, 0xf5, 0xab, 0x65, 0x18, 0xaf, 0xf7, 0x7a, + 0x4f, 0xb9, 0xbf, 0xff, 0x17, 0x8c, 0xed, 0x58, 0x99, 0xc8, 0x92, 0xef, 0x3a, 0xc4, 0xb3, 0xf7, + 0xa8, 0x1d, 0xf9, 0xbf, 0xad, 0xc0, 0x74, 0x86, 0x8f, 0xfe, 0x4d, 0xa5, 0x63, 0xba, 0xe7, 0x97, + 0x8f, 0xe9, 0x9e, 0x5f, 0x39, 0x9e, 0x7b, 0xfe, 0xd0, 0x27, 0xd9, 0x62, 0x5f, 0x81, 0x4a, 0xbd, + 0xd7, 0xcb, 0x7a, 0xa9, 0xf5, 0x7a, 0xf7, 0xae, 0x48, 0xdb, 0xad, 0xdb, 0xeb, 0xd9, 0x02, 0xc3, + 0xd8, 0xf7, 0x46, 0x3e, 0xa6, 0x03, 0xfe, 0xe8, 0xe1, 0xdb, 0x45, 0xf5, 0x58, 0xdb, 0xc5, 0xd8, + 0x13, 0xdb, 0x2e, 0xac, 0xd7, 0x61, 0x0c, 0x3f, 0x15, 0xbd, 0xe0, 0xcf, 0x01, 0xca, 0x45, 0x72, + 0x80, 0x37, 0xba, 0x82, 0x24, 0xe6, 0xcf, 0x95, 0x61, 0x18, 0x7f, 0x3f, 0xa5, 0x0b, 0x63, 0xc1, + 0x58, 0x18, 0x35, 0x6d, 0x61, 0x7c, 0xfc, 0x25, 0xf1, 0x77, 0x2a, 0x00, 0x4b, 0x77, 0xec, 0xa6, + 0xbc, 0x97, 0x60, 0xd7, 0x60, 0xda, 0xed, 0x74, 0x82, 0x07, 0xbc, 0xed, 0x04, 0xa1, 0xb7, 0xe3, + 0xf9, 0xb2, 0x3f, 0x95, 0xc3, 0x96, 0x59, 0xa4, 0x3b, 0x52, 0x50, 0xd1, 0x1d, 0x59, 0xa2, 0xf3, + 0xe9, 0xf2, 0x78, 0x37, 0x68, 0x2b, 0x13, 0xa5, 0xc1, 0x87, 0x8a, 0x0a, 0xf8, 0xac, 0xcb, 0x12, + 0x9d, 0xcf, 0x2e, 0x9a, 0x5c, 0xd5, 0xe1, 0xca, 0xe0, 0x43, 0x45, 0x05, 0x7c, 0xa4, 0x9d, 0x36, + 0x62, 0x6b, 0x80, 0xd7, 0x6e, 0x0f, 0x9c, 0x56, 0xc8, 0xdb, 0xdc, 0x8f, 0x3d, 0xb7, 0x13, 0x91, + 0x51, 0x1b, 0x6f, 0x6f, 0x72, 0x85, 0xba, 0x51, 0x0f, 0x0b, 0x97, 0xd2, 0x32, 0x76, 0x09, 0x46, + 0xbb, 0xee, 0x43, 0xc7, 0xdd, 0x91, 0xae, 0x8b, 0x93, 0xd2, 0x08, 0x4a, 0x20, 0x7d, 0x47, 0xec, + 0xba, 0x0f, 0xeb, 0x3b, 0x5c, 0x7c, 0x05, 0x7f, 0xd8, 0x0b, 0x22, 0xed, 0x2b, 0x46, 0xd2, 0xaf, + 0xc8, 0x14, 0xe9, 0x5f, 0x41, 0x45, 0xf4, 0x15, 0xd6, 0x6f, 0x94, 0xe0, 0x99, 0x55, 0x6c, 0x45, + 0xbc, 0xbf, 0xc4, 0xfd, 0x98, 0x87, 0x0d, 0x1e, 0x76, 0x3d, 0x74, 0xa5, 0x6a, 0xf2, 0x98, 0xbd, + 0x08, 0x95, 0xba, 0x7d, 0x9b, 0x66, 0xb5, 0xdc, 0xba, 0x0c, 0xb7, 0x3a, 0x51, 0x9a, 0xd8, 0xc9, + 0xcb, 0x47, 0xdc, 0xdf, 0xd5, 0x61, 0xa2, 0x1e, 0x45, 0xde, 0x8e, 0xdf, 0x95, 0xf1, 0x9e, 0x15, + 0xc3, 0x71, 0x8f, 0xe0, 0xb9, 0x7b, 0x75, 0x9d, 0xc4, 0xfa, 0x8f, 0x4a, 0x30, 0x53, 0xef, 0xf5, + 0xcc, 0x26, 0x9b, 0x4e, 0xa3, 0xa5, 0xe3, 0x3b, 0x8d, 0x7a, 0x30, 0x65, 0x7c, 0xae, 0x9c, 0x52, + 0xe9, 0xd9, 0xe7, 0x90, 0x9e, 0x91, 0xcd, 0xee, 0x25, 0x20, 0x27, 0x32, 0x7d, 0x8e, 0x32, 0x8c, + 0xad, 0x7f, 0x67, 0x0c, 0x25, 0x0b, 0x6d, 0x11, 0x14, 0xa8, 0x51, 0x2a, 0x08, 0xd4, 0xf8, 0x22, + 0x68, 0xca, 0x9a, 0xbe, 0x5b, 0x6b, 0xc7, 0x05, 0xdd, 0xc2, 0x9c, 0x22, 0xb3, 0xfb, 0xd9, 0x90, + 0x8d, 0x0a, 0x7e, 0xcd, 0x8b, 0xd9, 0x65, 0xfd, 0x44, 0xa2, 0x35, 0x6e, 0x00, 0x5b, 0xf5, 0xd1, + 0x0f, 0x8a, 0x37, 0xef, 0x7b, 0xbd, 0x7b, 0x3c, 0xf4, 0xb6, 0xf7, 0x69, 0x01, 0x60, 0xe7, 0x7b, + 0x54, 0xea, 0x44, 0xf7, 0xbd, 0x9e, 0xb3, 0x87, 0xe5, 0x76, 0x01, 0x0d, 0x7b, 0x0f, 0x46, 0x6d, + 0xfe, 0x20, 0xf4, 0x62, 0xe5, 0xb6, 0x3b, 0x95, 0x5c, 0x98, 0x20, 0x54, 0xae, 0x85, 0x50, 0xfe, + 0xd0, 0x65, 0x25, 0x95, 0xb3, 0x05, 0xa9, 0x6f, 0x49, 0xf7, 0xdc, 0xc9, 0xf4, 0x6b, 0xeb, 0x9b, + 0xcd, 0x41, 0xea, 0x16, 0xbb, 0x08, 0xc3, 0xa8, 0xb4, 0xd1, 0x71, 0x10, 0x63, 0x99, 0xf1, 0xf8, + 0xa4, 0x6b, 0x94, 0x88, 0x81, 0xc6, 0x22, 0xe5, 0x68, 0xa4, 0xb6, 0x24, 0x0d, 0x92, 0xd5, 0x38, + 0xc7, 0x1e, 0x4b, 0xe3, 0x5c, 0x83, 0x9a, 0x2d, 0xe3, 0xf4, 0xdb, 0xf5, 0x1e, 0x3a, 0x9f, 0x44, + 0xb3, 0x80, 0x2b, 0xf9, 0xdc, 0xa3, 0x83, 0xf9, 0x67, 0x29, 0x86, 0xbf, 0xed, 0xb8, 0x3d, 0xe9, + 0xb3, 0x62, 0x88, 0x91, 0x2c, 0x25, 0xfb, 0x22, 0x0c, 0x09, 0xd1, 0x4b, 0xe6, 0x72, 0x75, 0xcb, + 0x9d, 0x4a, 0x63, 0xb9, 0x38, 0x5b, 0x81, 0x21, 0x13, 0x90, 0x84, 0x39, 0x30, 0x65, 0x4e, 0x77, + 0xf2, 0x8a, 0x9d, 0x4d, 0xfb, 0xd3, 0x2c, 0xa7, 0xab, 0x6f, 0x82, 0x39, 0x2d, 0x04, 0xea, 0x2b, + 0x20, 0xb3, 0x48, 0x57, 0xa0, 0xba, 0xb1, 0xd4, 0x68, 0x04, 0x61, 0x2c, 0x4f, 0xbb, 0xe9, 0x7e, + 0x23, 0x60, 0xb6, 0xeb, 0xef, 0x70, 0xa9, 0x40, 0xc4, 0xad, 0x9e, 0x23, 0xb6, 0x71, 0x43, 0x81, + 0x50, 0xa4, 0xec, 0x1b, 0x70, 0xea, 0x6e, 0xc4, 0xeb, 0xfe, 0x3e, 0xaa, 0x14, 0xda, 0x52, 0x99, + 0xc2, 0xa9, 0x87, 0x97, 0xb7, 0xfd, 0x88, 0x3b, 0xae, 0xbf, 0xef, 0x48, 0x55, 0xa4, 0x78, 0xe1, + 0x14, 0x73, 0x61, 0x97, 0xa1, 0xb2, 0xbe, 0xd4, 0xa0, 0x63, 0xb1, 0x72, 0x5a, 0x5f, 0x5f, 0x6a, + 0xc8, 0x89, 0xd4, 0x35, 0x63, 0x88, 0xd6, 0x97, 0x1a, 0xec, 0x2d, 0xa8, 0xac, 0xad, 0xad, 0x93, + 0xf7, 0xac, 0x22, 0x58, 0x5b, 0x5b, 0x97, 0xdf, 0xe2, 0xf9, 0xdb, 0x3c, 0xe4, 0x7e, 0x4b, 0x9f, + 0xb7, 0x02, 0x5f, 0xcc, 0xd9, 0x8d, 0xb5, 0x26, 0x79, 0xc7, 0x6a, 0x73, 0x76, 0x63, 0x8d, 0xe6, + 0x6c, 0x3e, 0x68, 0xe8, 0xd3, 0x8b, 0x69, 0xf9, 0x3a, 0x7e, 0x34, 0x9b, 0x85, 0xd1, 0x96, 0xc4, + 0x21, 0x6e, 0xea, 0x27, 0x63, 0x30, 0xe4, 0x86, 0x3b, 0xb4, 0xe3, 0xda, 0xf8, 0x37, 0x7b, 0x05, + 0x6a, 0x61, 0xdf, 0x77, 0xdc, 0x48, 0xde, 0xb8, 0xf7, 0x23, 0x1e, 0x4a, 0x89, 0x6e, 0x4f, 0x86, + 0x7d, 0xbf, 0x1e, 0x09, 0xbd, 0x54, 0xac, 0x12, 0xeb, 0xef, 0x95, 0x40, 0x5b, 0xaa, 0x55, 0x9b, + 0xb7, 0xbd, 0x90, 0xb7, 0x62, 0x52, 0x03, 0x4e, 0x53, 0x1c, 0x01, 0xc2, 0x32, 0x71, 0x04, 0x08, + 0x63, 0x5f, 0x86, 0x51, 0xda, 0xae, 0x48, 0x3c, 0xab, 0xee, 0xa2, 0x8b, 0x54, 0x99, 0x8c, 0x21, + 0xb7, 0xd5, 0x29, 0x22, 0xb1, 0x3b, 0xdc, 0xdc, 0xdc, 0x58, 0xea, 0xb8, 0x5e, 0x37, 0xa2, 0x3d, + 0x07, 0x05, 0xd4, 0x87, 0x0f, 0x62, 0xa7, 0x85, 0x50, 0x7d, 0x77, 0x48, 0x50, 0xad, 0xeb, 0xea, + 0x1e, 0xf7, 0x88, 0xf0, 0x9e, 0x79, 0x18, 0xbe, 0x97, 0x1e, 0xc7, 0x17, 0xc7, 0x1e, 0x1d, 0xcc, + 0xcb, 0xbe, 0xb5, 0x25, 0xdc, 0xe2, 0x30, 0x96, 0x4c, 0x71, 0xc1, 0x4b, 0xfc, 0x40, 0x5e, 0x93, + 0x92, 0x97, 0x98, 0xec, 0x36, 0x42, 0x85, 0xa2, 0xb8, 0xe2, 0xb7, 0x11, 0xa1, 0x8c, 0x08, 0xd8, + 0x3d, 0xdc, 0x6f, 0xe3, 0x8a, 0xd0, 0xbf, 0x8e, 0xd0, 0x34, 0xc5, 0xeb, 0xbf, 0x29, 0xe1, 0x54, + 0x64, 0xa7, 0x61, 0x64, 0x3b, 0x08, 0xbb, 0x2e, 0xc5, 0xd1, 0xd8, 0xf4, 0x8b, 0xcd, 0x41, 0xb5, + 0x17, 0x06, 0x7b, 0x5e, 0x9b, 0xd3, 0xbe, 0x62, 0x27, 0xbf, 0xd9, 0x05, 0x18, 0xe9, 0x06, 0xed, + 0x74, 0xcf, 0xa8, 0xa5, 0x13, 0xf9, 0xd2, 0xba, 0x28, 0xb0, 0xa9, 0x9c, 0xbd, 0x0c, 0x53, 0xdb, + 0x6e, 0xa7, 0xb3, 0xe5, 0xb6, 0xee, 0x3b, 0x08, 0x22, 0x3b, 0xf3, 0xa4, 0x82, 0x22, 0xfa, 0xdc, + 0xfb, 0x30, 0x8c, 0x7f, 0x88, 0xa9, 0xe3, 0x27, 0x7d, 0x67, 0x0f, 0xd1, 0x81, 0x66, 0x52, 0xd5, + 0xec, 0xa4, 0xe7, 0x1f, 0x7b, 0x42, 0x01, 0xf1, 0x56, 0xfd, 0x37, 0x4b, 0x30, 0x22, 0x17, 0x84, + 0xe0, 0xd1, 0x55, 0x2e, 0x0e, 0x63, 0x36, 0xfe, 0x2d, 0x0e, 0x12, 0x9a, 0xef, 0x01, 0x71, 0x80, + 0x28, 0xf5, 0x3b, 0xb8, 0x00, 0x35, 0x42, 0x88, 0x7a, 0xde, 0xf6, 0x36, 0x77, 0xbc, 0x36, 0xcd, + 0xcf, 0xa9, 0x88, 0x8e, 0x6b, 0x02, 0xbc, 0xda, 0x16, 0xac, 0x94, 0xde, 0xd7, 0x72, 0x29, 0xb2, + 0xc7, 0x06, 0x02, 0x2d, 0xb9, 0x91, 0x60, 0x25, 0xd3, 0x18, 0x48, 0xef, 0x04, 0x6c, 0xcb, 0xb0, + 0x64, 0x25, 0xe1, 0x4b, 0x3c, 0x8c, 0xc5, 0xe7, 0x5a, 0x3f, 0x55, 0x82, 0x29, 0x73, 0x9d, 0xb1, + 0x4b, 0x30, 0x42, 0xa9, 0x25, 0xf0, 0xb6, 0x11, 0x47, 0x74, 0x44, 0x26, 0x95, 0x30, 0x52, 0x49, + 0x10, 0x96, 0xd0, 0xfc, 0x89, 0x03, 0x29, 0xb8, 0xa8, 0xf9, 0xd3, 0x4a, 0xb4, 0x55, 0x19, 0xb3, + 0x60, 0xc4, 0xe6, 0x51, 0xbf, 0x13, 0xeb, 0x8e, 0x37, 0x21, 0x42, 0x6c, 0x2a, 0xb1, 0xfe, 0x99, + 0xec, 0x42, 0xb1, 0xdf, 0x99, 0x01, 0x17, 0xa5, 0xc7, 0x09, 0xb8, 0xf8, 0x36, 0x9c, 0xb4, 0x83, + 0x0e, 0x8f, 0xea, 0xfe, 0xfe, 0x83, 0x5d, 0x1e, 0xf2, 0x46, 0x18, 0x6c, 0x2b, 0x1f, 0xa1, 0xf1, + 0x85, 0x17, 0x8c, 0xfd, 0xb6, 0x08, 0x51, 0x3a, 0x79, 0x84, 0xa2, 0x44, 0x48, 0x65, 0x2c, 0x12, + 0xa2, 0x39, 0xe3, 0x53, 0x54, 0x58, 0x89, 0x98, 0xcc, 0xa1, 0x66, 0x28, 0xb2, 0xe9, 0x97, 0xf5, + 0xeb, 0x25, 0x98, 0x1b, 0x5c, 0x25, 0x6a, 0x51, 0xf2, 0xcf, 0x54, 0x7d, 0x95, 0x5a, 0x94, 0x84, + 0x66, 0xa2, 0x43, 0x34, 0x64, 0x66, 0xc3, 0xa9, 0x7a, 0xab, 0xc5, 0x7b, 0xb1, 0x60, 0x4c, 0x51, + 0x05, 0x89, 0x7a, 0x5b, 0x95, 0x86, 0x56, 0x17, 0x11, 0x64, 0x9c, 0x89, 0x8a, 0xb4, 0x40, 0x89, + 0x50, 0x4c, 0x6a, 0xfd, 0x49, 0x09, 0xa0, 0xd9, 0xbc, 0x71, 0x8b, 0xef, 0x37, 0x5c, 0x0f, 0xf5, + 0x55, 0xb9, 0xe5, 0xdc, 0x22, 0xc1, 0x3d, 0x41, 0xce, 0x84, 0x72, 0xa7, 0xba, 0xcf, 0xf7, 0x0d, + 0x67, 0x42, 0x85, 0x2a, 0xbf, 0xca, 0xdb, 0x73, 0x63, 0x2e, 0x08, 0xf1, 0xda, 0x4a, 0x7d, 0x15, + 0x42, 0x33, 0x94, 0x1a, 0x32, 0xfb, 0x06, 0x4c, 0xa5, 0xbf, 0x12, 0x97, 0xc8, 0xa9, 0x64, 0x73, + 0x30, 0x0b, 0x17, 0x9f, 0x7f, 0x74, 0x30, 0x3f, 0xa7, 0x71, 0xcd, 0xfa, 0x05, 0x66, 0x98, 0xbd, + 0x3d, 0xf4, 0xcf, 0xff, 0xfa, 0x7c, 0x09, 0xfd, 0x56, 0x37, 0xd6, 0x9a, 0xea, 0x33, 0xcf, 0xc3, + 0x50, 0x12, 0xce, 0x37, 0x41, 0xaa, 0x87, 0xe9, 0x5e, 0x84, 0xe5, 0xe2, 0x90, 0x91, 0x7e, 0x0f, + 0x6e, 0x7e, 0xe6, 0x77, 0x88, 0x52, 0x76, 0x1d, 0x46, 0x8f, 0xd5, 0x72, 0x14, 0x98, 0x05, 0x2d, + 0x56, 0xd4, 0x42, 0xf7, 0x5e, 0xb2, 0xe5, 0xd5, 0xe7, 0x84, 0xd4, 0xbd, 0x5b, 0x61, 0xc7, 0x16, + 0x30, 0xeb, 0xa0, 0x04, 0x70, 0x73, 0x73, 0xe3, 0x07, 0x76, 0x98, 0xac, 0x9f, 0x2b, 0xc3, 0x4c, + 0xea, 0xd3, 0xae, 0xbe, 0xf3, 0xf3, 0x00, 0xe9, 0x27, 0x1d, 0xfd, 0xa1, 0xbd, 0xe4, 0x43, 0xdf, + 0x86, 0x71, 0xad, 0xf2, 0x63, 0x7c, 0x69, 0x2f, 0xfd, 0x52, 0x07, 0x6a, 0xd9, 0x86, 0x7f, 0xc2, + 0x6f, 0xed, 0x19, 0xf8, 0x62, 0xf6, 0xed, 0xba, 0x91, 0x0c, 0x21, 0x99, 0x94, 0xb3, 0x4f, 0xfc, + 0xd6, 0x67, 0x9f, 0xf8, 0x6d, 0xd5, 0xa1, 0x56, 0xdf, 0xe1, 0x46, 0xaf, 0xb0, 0xd7, 0x0b, 0x7a, + 0x04, 0xcd, 0xdf, 0x29, 0x54, 0xeb, 0x07, 0xeb, 0x67, 0xca, 0x30, 0x2d, 0x66, 0x72, 0xbd, 0x1f, + 0xef, 0x06, 0xa1, 0x17, 0xef, 0x3f, 0xb5, 0x17, 0x97, 0xef, 0x1a, 0x06, 0xa1, 0x39, 0xa5, 0x39, + 0xea, 0xdf, 0x36, 0xf8, 0xfe, 0x52, 0xd7, 0x45, 0x86, 0xe1, 0x44, 0x01, 0x15, 0x7b, 0xcd, 0xf0, + 0x86, 0x98, 0x55, 0x69, 0xb6, 0xbe, 0x7f, 0x30, 0x3f, 0xa1, 0xd0, 0x37, 0xd2, 0xb4, 0x5b, 0x0b, + 0x66, 0x34, 0x83, 0xec, 0x29, 0x74, 0x8e, 0xd0, 0xa3, 0x19, 0xcc, 0x18, 0x86, 0x8b, 0x30, 0x8c, + 0x3b, 0x02, 0x85, 0x04, 0xe1, 0xc1, 0x0e, 0xf7, 0x1e, 0xc3, 0xa5, 0x57, 0x00, 0xd8, 0x0d, 0x18, + 0x15, 0x7f, 0xac, 0xbb, 0x3d, 0xf2, 0xf6, 0x62, 0x89, 0xc5, 0x14, 0xa1, 0x3d, 0xcf, 0xdf, 0xd1, + 0x8d, 0xa6, 0x1d, 0xee, 0x74, 0xdd, 0x9e, 0x71, 0x02, 0x95, 0x88, 0x86, 0xf1, 0xb5, 0x3a, 0xd8, + 0xf8, 0x5a, 0x3a, 0xd2, 0xf8, 0xba, 0x0d, 0xd0, 0xf4, 0x76, 0x7c, 0xcf, 0xdf, 0xa9, 0x77, 0x76, + 0x28, 0x59, 0xd9, 0xc5, 0xc1, 0xa3, 0x70, 0x29, 0x45, 0xc6, 0x35, 0xf2, 0x0c, 0xba, 0x64, 0x4a, + 0x98, 0xe3, 0x76, 0x76, 0x8c, 0xcc, 0x0e, 0x1a, 0x67, 0x76, 0x1b, 0xa0, 0xde, 0x8a, 0xbd, 0x3d, + 0xb1, 0x5a, 0x94, 0x77, 0x95, 0x6a, 0xf2, 0x52, 0xfd, 0x16, 0xdf, 0x47, 0x13, 0x87, 0x72, 0x6e, + 0x73, 0x11, 0x55, 0xcc, 0x7a, 0x23, 0x6c, 0x3f, 0xe5, 0xc0, 0x7a, 0x70, 0xaa, 0xde, 0x6e, 0x7b, + 0xe2, 0x1b, 0xdc, 0xce, 0x86, 0xcc, 0x43, 0x87, 0xac, 0x27, 0x8a, 0x59, 0x5f, 0x54, 0xce, 0x43, + 0x6e, 0x42, 0xe5, 0xa8, 0xf4, 0x75, 0x99, 0x6a, 0x8a, 0x19, 0x5b, 0x4d, 0x98, 0x32, 0x3f, 0xde, + 0x4c, 0xa7, 0x36, 0x01, 0x55, 0xbb, 0x59, 0x77, 0x9a, 0x37, 0xea, 0x6f, 0xd6, 0x4a, 0xac, 0x06, + 0x13, 0xf4, 0x6b, 0xc1, 0x59, 0x78, 0xeb, 0x6a, 0xad, 0x6c, 0x40, 0xde, 0x7a, 0x73, 0xa1, 0x56, + 0x99, 0x2b, 0xcf, 0x96, 0x32, 0x49, 0x5d, 0x46, 0x6b, 0x55, 0x79, 0x8b, 0x26, 0x74, 0xd1, 0xaa, + 0x6a, 0x3b, 0xbb, 0x0a, 0x95, 0x66, 0xf3, 0x46, 0x26, 0x2d, 0x4a, 0xba, 0xbb, 0xcb, 0x1d, 0x2c, + 0x32, 0x24, 0x8d, 0x20, 0x10, 0x74, 0xe2, 0xc8, 0x57, 0x36, 0xe8, 0xd2, 0xed, 0x72, 0xd0, 0xb1, + 0x4f, 0xd0, 0xdd, 0xdc, 0xdc, 0x20, 0xc5, 0x5c, 0xd1, 0xa5, 0xdb, 0x94, 0xa4, 0xfb, 0xf0, 0x81, + 0xbe, 0xaf, 0x0a, 0x02, 0xcb, 0x86, 0x71, 0x6d, 0x22, 0x4b, 0x85, 0xb1, 0x1b, 0x24, 0x99, 0xde, + 0x48, 0x61, 0x14, 0x10, 0x9b, 0x4a, 0xc4, 0x51, 0x66, 0x2d, 0x68, 0xb9, 0x1d, 0xd2, 0x3c, 0xf1, + 0x28, 0xd3, 0x11, 0x00, 0x5b, 0xc2, 0xad, 0xdf, 0x2b, 0x41, 0xad, 0x21, 0xb4, 0x74, 0x8c, 0xb5, + 0x0c, 0xee, 0x73, 0xff, 0xde, 0x9b, 0xec, 0x75, 0xb5, 0xe4, 0x4a, 0xc9, 0x2d, 0xc0, 0x30, 0x2e, + 0xb9, 0x8c, 0xbb, 0x0a, 0x2d, 0xbb, 0x66, 0x9a, 0xd5, 0xaf, 0x7c, 0xfc, 0x24, 0x5c, 0x94, 0xd5, + 0x2f, 0x9b, 0x84, 0x4b, 0xe5, 0xf7, 0x9b, 0x87, 0x61, 0x6c, 0x0e, 0x09, 0x47, 0x6c, 0x79, 0x2c, + 0x00, 0xb6, 0x84, 0x6b, 0xb2, 0xe9, 0xa0, 0x9c, 0xfb, 0x86, 0x85, 0x1f, 0xa8, 0x44, 0x56, 0xe6, + 0xc7, 0x1d, 0xe2, 0x6f, 0x72, 0x6b, 0x40, 0x22, 0xab, 0x0c, 0x03, 0x99, 0xa0, 0x61, 0x41, 0xde, + 0xbb, 0x4a, 0x87, 0x4c, 0xdd, 0x24, 0x9d, 0xcb, 0x61, 0xf3, 0x55, 0x38, 0x99, 0xed, 0x5f, 0xbc, + 0x4f, 0xa9, 0xc3, 0xb4, 0x09, 0x57, 0x57, 0x2b, 0x67, 0x0a, 0xeb, 0xbd, 0xb7, 0x60, 0x67, 0xf1, + 0xad, 0xdf, 0xae, 0xc0, 0x18, 0xfe, 0x69, 0xf7, 0xa5, 0x96, 0x5f, 0xdf, 0x6c, 0x92, 0x3d, 0x57, + 0xd7, 0xf2, 0xdd, 0x07, 0x91, 0x72, 0xaa, 0x37, 0x04, 0x56, 0x82, 0x4c, 0xa4, 0xf2, 0x7e, 0x59, + 0xdd, 0x24, 0x24, 0xa4, 0xf2, 0x8c, 0x11, 0x65, 0x48, 0x09, 0x19, 0xa3, 0xe4, 0xe4, 0xb1, 0x43, + 0x77, 0x71, 0x46, 0xba, 0xa0, 0x63, 0x46, 0xc9, 0x49, 0x34, 0xf4, 0x70, 0xde, 0x6c, 0x8a, 0x93, + 0x88, 0xee, 0xe1, 0x2c, 0xda, 0x68, 0x9c, 0x42, 0x08, 0x89, 0xdd, 0x81, 0x99, 0xfa, 0x66, 0xf3, + 0x8e, 0x76, 0x89, 0xbb, 0xba, 0x4c, 0x17, 0xd9, 0x2f, 0x3c, 0x3a, 0x98, 0x7f, 0x4e, 0x50, 0xea, + 0x57, 0xbc, 0xe6, 0xd1, 0x2d, 0x4f, 0xcb, 0x7e, 0xbc, 0x04, 0xa7, 0x33, 0x50, 0xb7, 0x73, 0xd7, + 0xf7, 0x62, 0x35, 0xf0, 0x56, 0x7a, 0x49, 0xad, 0x23, 0x21, 0xca, 0xba, 0x1b, 0xb7, 0x76, 0x79, + 0x28, 0xc3, 0x16, 0xb2, 0x55, 0xbb, 0x1d, 0xa7, 0x2f, 0x90, 0xf4, 0xa8, 0x88, 0xe2, 0x9a, 0xac, + 0xbf, 0x3d, 0x9d, 0x9d, 0x16, 0xa4, 0x13, 0x3c, 0xa6, 0xf8, 0x78, 0x07, 0x86, 0xeb, 0xe2, 0x68, + 0x4e, 0x82, 0x54, 0x59, 0x2a, 0x92, 0x59, 0x21, 0xb7, 0x7c, 0x3c, 0xbd, 0x1b, 0x49, 0x7b, 0x04, + 0x80, 0x2d, 0xc1, 0x58, 0x7d, 0xb3, 0xb9, 0xba, 0xba, 0xbc, 0xb1, 0x21, 0xb3, 0x81, 0x54, 0x16, + 0x5f, 0x56, 0xa3, 0xee, 0x79, 0x6d, 0x27, 0xeb, 0x05, 0x99, 0x1e, 0xcf, 0x53, 0x3a, 0xf6, 0x25, + 0x80, 0x9b, 0x81, 0xe7, 0xcb, 0x1b, 0x25, 0x1a, 0xd2, 0xe7, 0x1e, 0x1d, 0xcc, 0x8f, 0x7f, 0x18, + 0x78, 0x3e, 0x5d, 0x41, 0x89, 0xb6, 0xa7, 0x48, 0xb6, 0xf6, 0xb7, 0x98, 0x3f, 0x8b, 0x81, 0x0c, + 0x38, 0x19, 0x4e, 0xe7, 0xcf, 0x56, 0x90, 0xbb, 0xfa, 0x50, 0x68, 0xac, 0x0b, 0xd3, 0xcd, 0xfe, + 0xce, 0x0e, 0x17, 0x9b, 0x1f, 0x99, 0xf6, 0x47, 0xc8, 0xb4, 0x97, 0xa4, 0x6c, 0x95, 0xe6, 0x06, + 0xb7, 0xd3, 0xe7, 0xd1, 0xe2, 0x6b, 0x62, 0xad, 0xff, 0xe1, 0xc1, 0x3c, 0x79, 0x57, 0x0a, 0xc5, + 0x39, 0x52, 0xf4, 0x79, 0xc3, 0x7e, 0x96, 0x37, 0xbb, 0x03, 0x23, 0xd2, 0x13, 0x81, 0xd2, 0x53, + 0xbc, 0x70, 0x88, 0x5c, 0x91, 0x88, 0x83, 0x7c, 0x84, 0x64, 0x29, 0xdb, 0x84, 0xea, 0x92, 0x17, + 0xb6, 0x3a, 0x7c, 0x69, 0x95, 0xd4, 0xa3, 0x17, 0x0f, 0x61, 0xa9, 0x50, 0x65, 0xbf, 0xb4, 0xf0, + 0x57, 0xcb, 0xd3, 0xd5, 0x25, 0x85, 0xc1, 0xfe, 0x6a, 0x09, 0x9e, 0x49, 0x5a, 0x5f, 0xdf, 0xe1, + 0x7e, 0x4c, 0xb3, 0x95, 0x7a, 0x69, 0xec, 0xb0, 0x5e, 0x7a, 0x3b, 0xd7, 0x4b, 0x17, 0xd2, 0x5e, + 0x72, 0x05, 0x33, 0xa7, 0x2b, 0xb9, 0xe5, 0xfb, 0xec, 0xb0, 0x5a, 0x99, 0x03, 0x90, 0xfa, 0xd8, + 0x90, 0xe7, 0xfb, 0xcb, 0x87, 0x7c, 0x70, 0x8a, 0x4c, 0x89, 0x01, 0x92, 0xdf, 0x46, 0xa4, 0x56, + 0x02, 0x65, 0xb7, 0x54, 0x2a, 0x19, 0xa9, 0xb8, 0x9d, 0x3b, 0x84, 0xb7, 0x4c, 0x2f, 0x73, 0xe2, + 0x90, 0x3c, 0x56, 0x72, 0xb4, 0xd7, 0xdc, 0x2d, 0xd2, 0xd5, 0x8e, 0x18, 0xed, 0x35, 0x37, 0x1d, + 0xed, 0x8e, 0x9b, 0x1d, 0xed, 0x35, 0x77, 0x8b, 0x2d, 0xc9, 0x94, 0x5c, 0x32, 0x7f, 0xd3, 0xf3, + 0x87, 0x71, 0x53, 0x66, 0xf5, 0x82, 0xd4, 0x5c, 0x5f, 0x83, 0xb1, 0x66, 0xcf, 0x6d, 0xf1, 0x8e, + 0xb7, 0x1d, 0x93, 0xe3, 0xdb, 0x4b, 0x87, 0xb0, 0x4a, 0x70, 0xc9, 0x61, 0x47, 0xfd, 0xd4, 0xcf, + 0xad, 0x09, 0x8e, 0x68, 0xe1, 0x46, 0x63, 0x9d, 0x8c, 0xfc, 0x87, 0xb5, 0x70, 0xa3, 0xb1, 0x4e, + 0x6a, 0x59, 0xaf, 0x6b, 0xa8, 0x65, 0x8d, 0x75, 0xd6, 0x83, 0xa9, 0x0d, 0x1e, 0x86, 0xee, 0x76, + 0x10, 0x76, 0xe5, 0x55, 0x92, 0xbc, 0x03, 0xb8, 0x78, 0x18, 0x3f, 0x83, 0x40, 0xde, 0xa0, 0xc4, + 0x0a, 0xe6, 0x64, 0xef, 0x9f, 0x32, 0xfc, 0x45, 0x9f, 0x2c, 0x7a, 0xf1, 0x56, 0xbf, 0x75, 0x9f, + 0xc7, 0x74, 0x73, 0x70, 0x58, 0x9f, 0x24, 0xb8, 0xb2, 0x4f, 0xb6, 0xd4, 0x4f, 0xbd, 0x4f, 0x12, + 0x1c, 0x31, 0x0d, 0x28, 0xf1, 0x16, 0x3b, 0x72, 0x1a, 0x48, 0x44, 0x39, 0x0d, 0x06, 0x65, 0xe0, + 0x62, 0xbb, 0x30, 0xb1, 0x18, 0xf4, 0x7d, 0xa1, 0xad, 0xf7, 0x5c, 0x2f, 0x9c, 0x3d, 0x81, 0x6c, + 0x5f, 0x39, 0xac, 0xc1, 0x1a, 0xba, 0x8c, 0xe7, 0xdb, 0x12, 0x10, 0x71, 0x20, 0x10, 0x20, 0xfd, + 0x52, 0x58, 0x47, 0x65, 0x6d, 0x18, 0xc7, 0xa9, 0xbc, 0xcc, 0xf7, 0x82, 0x5e, 0x34, 0x7b, 0x12, + 0x2b, 0x3a, 0x7f, 0xd4, 0xa2, 0x90, 0xd8, 0xd2, 0x91, 0x0a, 0x97, 0x86, 0xd3, 0x46, 0x88, 0x7e, + 0x53, 0xa7, 0x21, 0xb2, 0xeb, 0x30, 0xb4, 0xe2, 0xef, 0xbd, 0x31, 0x7b, 0x0a, 0xd9, 0xcf, 0x1f, + 0xc2, 0x5e, 0xa0, 0x49, 0x83, 0x03, 0xf7, 0xf7, 0xde, 0xd0, 0x0d, 0x0e, 0xa2, 0x24, 0x7b, 0x5f, + 0x78, 0xfa, 0xb1, 0xee, 0x0b, 0xdb, 0x30, 0x7e, 0x9d, 0xfb, 0x3c, 0xf4, 0x5a, 0x77, 0x56, 0x97, + 0x97, 0x66, 0xcf, 0x1c, 0xf9, 0xad, 0x1a, 0xb6, 0xfc, 0xd6, 0x1d, 0x09, 0x70, 0x02, 0xaf, 0xdd, + 0xd2, 0x6b, 0xd1, 0x10, 0xad, 0xdf, 0x1a, 0x86, 0xf9, 0x23, 0x3a, 0x8e, 0xdd, 0x53, 0xfb, 0xb0, + 0xd4, 0xe1, 0x3e, 0x7b, 0xbc, 0xfe, 0xbe, 0x74, 0xe4, 0x16, 0xfd, 0x0e, 0x4c, 0x65, 0x54, 0x1f, + 0x2d, 0xdd, 0x74, 0x46, 0xed, 0xb1, 0x33, 0xa8, 0x73, 0xff, 0xaa, 0x02, 0x43, 0xa8, 0x1a, 0xbe, + 0x08, 0x95, 0x66, 0x7f, 0x4b, 0x77, 0x5c, 0x88, 0x8c, 0xad, 0x49, 0x94, 0xb2, 0x77, 0x61, 0x9c, + 0x22, 0xb5, 0x35, 0xfb, 0x02, 0x76, 0x92, 0x0a, 0xeb, 0xce, 0xc6, 0x91, 0x6a, 0xe8, 0xec, 0x3d, + 0x98, 0x68, 0x78, 0x3d, 0xde, 0xf1, 0x7c, 0xae, 0x45, 0x45, 0xe2, 0xbc, 0xed, 0x11, 0x3c, 0xe7, + 0xcc, 0xa0, 0x13, 0x98, 0x31, 0xe5, 0x43, 0xc7, 0x8f, 0x29, 0x7f, 0x0f, 0x26, 0x96, 0xf9, 0xb6, + 0xe7, 0x7b, 0x86, 0x6a, 0x88, 0x15, 0xb7, 0x13, 0xb8, 0x49, 0x6d, 0x10, 0xb0, 0x45, 0x98, 0xb4, + 0x79, 0x2f, 0x88, 0xbc, 0x38, 0x08, 0xf7, 0xef, 0xda, 0xab, 0xe4, 0xeb, 0x88, 0xa6, 0xed, 0x30, + 0x29, 0x70, 0xfa, 0xa1, 0xbe, 0xeb, 0x9a, 0x24, 0xec, 0x36, 0xcc, 0xa4, 0x00, 0xd3, 0xb7, 0x9a, + 0x6e, 0xae, 0x13, 0x3e, 0xf9, 0xf0, 0xb3, 0x3c, 0xa9, 0xd9, 0x26, 0x9b, 0x6f, 0x53, 0x30, 0x5b, + 0xb6, 0x4d, 0x21, 0xdf, 0x2e, 0x6e, 0x93, 0xcd, 0xb7, 0xad, 0xbf, 0x5f, 0x81, 0x33, 0x03, 0xc4, + 0x38, 0xbb, 0x6d, 0x4e, 0xd7, 0x17, 0x0f, 0x97, 0xfa, 0x47, 0x4f, 0xd3, 0x35, 0xa8, 0xad, 0xdc, + 0x42, 0x93, 0x0c, 0xdd, 0x13, 0xd5, 0xd5, 0x31, 0x02, 0x3f, 0x9f, 0xdf, 0xc7, 0xab, 0x22, 0xed, + 0x5e, 0xc9, 0x08, 0xea, 0xcb, 0x50, 0xce, 0xfd, 0x5b, 0x65, 0x9a, 0xb7, 0xef, 0x80, 0x9e, 0x3a, + 0xd6, 0x48, 0xf6, 0x90, 0x82, 0xf5, 0x19, 0xa9, 0x61, 0xb3, 0xf7, 0x61, 0x62, 0xe5, 0x96, 0xb4, + 0x43, 0xdf, 0x50, 0xa6, 0x4f, 0xea, 0x42, 0x7e, 0x5f, 0xdd, 0x83, 0x67, 0x8c, 0xa0, 0x06, 0x05, + 0xbb, 0x0b, 0x27, 0x64, 0xdb, 0xbc, 0x6d, 0xaf, 0x25, 0x13, 0x67, 0x7b, 0x6e, 0x87, 0x66, 0xd8, + 0x8b, 0x8f, 0x0e, 0xe6, 0xe7, 0xf9, 0x7d, 0xbc, 0x04, 0xa3, 0x72, 0x27, 0x42, 0x04, 0x3d, 0x56, + 0xb7, 0x80, 0x5e, 0x4f, 0x99, 0x6b, 0x8f, 0x61, 0x85, 0xa2, 0x36, 0x51, 0xb7, 0xc0, 0x95, 0x48, + 0xd6, 0xff, 0x31, 0x02, 0x73, 0x83, 0x55, 0x4c, 0xf6, 0x81, 0x39, 0x80, 0xe7, 0x8f, 0x54, 0x4a, + 0x8f, 0x1e, 0xc3, 0xaf, 0xc0, 0xc9, 0x15, 0x3f, 0xe6, 0x61, 0x2f, 0xf4, 0x54, 0xaa, 0xdd, 0x1b, + 0x41, 0xa4, 0x42, 0xa2, 0xf1, 0xda, 0x8a, 0x27, 0xe5, 0x14, 0xdc, 0x8f, 0xb7, 0xdd, 0xfa, 0xb5, + 0x55, 0x11, 0x07, 0xb6, 0x02, 0x53, 0x1a, 0xbc, 0xd3, 0xdf, 0xd1, 0x9d, 0x9d, 0x74, 0x9e, 0x9d, + 0xbe, 0x1e, 0x2f, 0x9a, 0x21, 0xc2, 0xb0, 0xeb, 0xd8, 0x8d, 0xbd, 0xd6, 0xcd, 0xcd, 0x5b, 0x4d, + 0x1a, 0x4e, 0x19, 0x76, 0x8d, 0x50, 0xe7, 0xc3, 0x07, 0xf7, 0x0d, 0x1d, 0x31, 0x45, 0x9e, 0xfb, + 0xd5, 0xa1, 0xc7, 0x91, 0x84, 0x5f, 0x00, 0x48, 0x97, 0x92, 0x9e, 0xe6, 0x29, 0x5d, 0x7a, 0x66, + 0x66, 0x05, 0x05, 0x65, 0x37, 0x60, 0x3a, 0xfd, 0x75, 0xe7, 0x81, 0xaf, 0xbc, 0x00, 0xa4, 0xbd, + 0x5e, 0x5b, 0xb9, 0x81, 0x28, 0xd3, 0x8f, 0x1d, 0x19, 0x32, 0xb6, 0x00, 0xd5, 0xcd, 0x20, 0xbc, + 0xbf, 0x2d, 0xc6, 0x78, 0x28, 0x3d, 0x18, 0x3d, 0x20, 0x98, 0x7e, 0x00, 0x50, 0x78, 0x62, 0xb9, + 0xac, 0xf8, 0x7b, 0x5e, 0x18, 0xa0, 0x83, 0x98, 0xee, 0xed, 0xcd, 0x53, 0xb0, 0x91, 0xde, 0x2f, + 0x05, 0xb3, 0x8b, 0x30, 0x5c, 0x6f, 0xc5, 0x41, 0x48, 0xe2, 0x4f, 0xce, 0x14, 0x01, 0x30, 0x66, + 0x8a, 0x00, 0x88, 0x4e, 0x14, 0x32, 0x69, 0x34, 0xed, 0x44, 0x53, 0x10, 0x89, 0x52, 0x71, 0xb0, + 0xb3, 0xf9, 0x36, 0xda, 0xb7, 0xab, 0x69, 0xfb, 0x43, 0xbe, 0x9d, 0xbb, 0x8b, 0x22, 0x34, 0xd1, + 0xed, 0xe9, 0x88, 0x93, 0xe7, 0x10, 0x76, 0x7b, 0x3a, 0x45, 0x8c, 0x4b, 0xd9, 0x04, 0x2a, 0x73, + 0x79, 0xa8, 0x5f, 0x49, 0x0a, 0x3d, 0xca, 0xe5, 0x91, 0x4c, 0x2f, 0x73, 0x0f, 0xd0, 0x09, 0xac, + 0xbf, 0x00, 0x03, 0x57, 0x9b, 0x50, 0xe2, 0x1f, 0x6f, 0xb5, 0xad, 0xb9, 0xc7, 0x58, 0x6d, 0xaf, + 0x25, 0xa9, 0x22, 0xf4, 0xec, 0xa7, 0x08, 0xd1, 0xd5, 0x47, 0x4a, 0x1a, 0x61, 0x4e, 0xfd, 0xca, + 0xe3, 0x4c, 0xfd, 0xbf, 0x55, 0x7d, 0x9c, 0xa9, 0x4f, 0x43, 0x5b, 0x3e, 0xee, 0xd0, 0x56, 0x8e, + 0x37, 0xb4, 0x8b, 0x30, 0x99, 0x3c, 0x64, 0xd2, 0x70, 0x63, 0x43, 0x18, 0x27, 0x8f, 0x9d, 0x38, + 0x3d, 0xd7, 0xc8, 0xcb, 0x6d, 0x92, 0x68, 0xfa, 0x09, 0x72, 0x18, 0xce, 0xeb, 0x27, 0x19, 0x7a, + 0x1d, 0x5d, 0xc8, 0x20, 0xa5, 0x6e, 0x34, 0x31, 0xf1, 0x00, 0xcd, 0x73, 0xe9, 0xb9, 0xa8, 0x34, + 0x14, 0x99, 0x93, 0xc0, 0xb8, 0x33, 0x34, 0x88, 0xb2, 0x4b, 0x6c, 0xf4, 0xb1, 0x96, 0x98, 0x8c, + 0xd7, 0x0a, 0xd7, 0x82, 0x1d, 0x4f, 0x85, 0xa7, 0xab, 0x78, 0xad, 0xd0, 0xe9, 0x08, 0x68, 0x26, + 0x5e, 0x4b, 0xa2, 0xb2, 0xd7, 0x61, 0x44, 0xfc, 0x58, 0x5d, 0xa6, 0x45, 0x81, 0x16, 0x33, 0x24, + 0x32, 0x73, 0x02, 0x48, 0x24, 0x55, 0xcd, 0x4a, 0xd7, 0xf5, 0x3a, 0xb4, 0x14, 0xd2, 0x6a, 0xb8, + 0x80, 0x66, 0xab, 0x41, 0x54, 0xd6, 0x82, 0x09, 0x9b, 0x6f, 0x37, 0xc2, 0x20, 0xe6, 0xad, 0x98, + 0xb7, 0xe9, 0x3c, 0xad, 0x4c, 0x4a, 0x8b, 0x41, 0x20, 0x6d, 0x05, 0x18, 0xeb, 0x5c, 0xfa, 0xc3, + 0x83, 0x79, 0x10, 0x20, 0x99, 0x70, 0x42, 0xac, 0x34, 0x31, 0xfe, 0x3d, 0x45, 0xac, 0xaf, 0x34, + 0x9d, 0x29, 0xfb, 0xb6, 0xd8, 0x65, 0x92, 0x2e, 0x49, 0x2b, 0x9b, 0x18, 0x50, 0xd9, 0x5b, 0x85, + 0x95, 0xcd, 0x6b, 0xbd, 0x5d, 0x58, 0x69, 0x61, 0x25, 0xec, 0x4b, 0x30, 0xbe, 0xb4, 0xba, 0x14, + 0xf8, 0xdb, 0xde, 0x4e, 0xf3, 0x46, 0x1d, 0x0f, 0xe5, 0x24, 0x26, 0x5a, 0x9e, 0xd3, 0x42, 0xb8, + 0x13, 0xed, 0xba, 0x46, 0x46, 0xb0, 0x14, 0x9f, 0x5d, 0x87, 0x29, 0xf5, 0xd3, 0xe6, 0xdb, 0x42, + 0x55, 0x9c, 0xd2, 0x12, 0xd4, 0x24, 0x1c, 0x44, 0x47, 0x98, 0xda, 0x62, 0x86, 0x4c, 0x4c, 0xc6, + 0x65, 0xde, 0xeb, 0x04, 0xfb, 0xa2, 0x79, 0x1b, 0x1e, 0x0f, 0xf1, 0xf4, 0x4d, 0x93, 0xb1, 0x9d, + 0x94, 0x38, 0xb1, 0x67, 0x3a, 0x11, 0x9a, 0x44, 0x42, 0xeb, 0xa4, 0x29, 0x7e, 0xcf, 0x8b, 0xbc, + 0x2d, 0xaf, 0xe3, 0xc5, 0xfb, 0x32, 0xe6, 0x4c, 0xaa, 0x5d, 0x6a, 0x5d, 0xec, 0x25, 0xa5, 0xba, + 0xd6, 0x99, 0x23, 0xb5, 0x7e, 0xb3, 0x0c, 0xcf, 0x1e, 0x66, 0x83, 0x62, 0x4d, 0x53, 0x0e, 0x5e, + 0x38, 0x86, 0xdd, 0xea, 0x68, 0x49, 0xb8, 0x32, 0xe0, 0x88, 0x83, 0x9d, 0x31, 0xd8, 0xb2, 0x9b, + 0x3d, 0xec, 0xec, 0x91, 0x98, 0xfb, 0xb8, 0xb9, 0xa9, 0xae, 0xc2, 0xd8, 0x52, 0xe0, 0xc7, 0xfc, + 0x61, 0x9c, 0x49, 0xed, 0x28, 0x81, 0xd9, 0xbc, 0x5c, 0x0a, 0xd5, 0xfa, 0x83, 0x0a, 0x3c, 0x77, + 0xa8, 0x11, 0x86, 0x6d, 0x98, 0xbd, 0x76, 0xf1, 0x38, 0x96, 0x9b, 0xa3, 0xbb, 0x6d, 0x21, 0x17, + 0x31, 0x73, 0x74, 0x72, 0x11, 0x1b, 0x98, 0x4c, 0x38, 0x77, 0xbd, 0x13, 0x6c, 0xa1, 0x99, 0xce, + 0xf3, 0x77, 0x28, 0x51, 0x9d, 0x4c, 0x94, 0x81, 0xa5, 0xce, 0x4e, 0x27, 0xd8, 0x92, 0xe6, 0x3e, + 0xcf, 0xd7, 0x35, 0xb2, 0x02, 0xea, 0xb9, 0x3f, 0x28, 0x51, 0xc7, 0xbf, 0x01, 0xa3, 0xd8, 0xfc, + 0xa4, 0xdb, 0xe5, 0xb5, 0x10, 0x4a, 0x76, 0xcf, 0xbc, 0x16, 0x92, 0x68, 0xec, 0x0a, 0x54, 0x97, + 0xdc, 0x4e, 0x47, 0x4b, 0xa6, 0x89, 0xb6, 0x99, 0x16, 0xc2, 0x32, 0x01, 0x66, 0x0a, 0x51, 0x6c, + 0x85, 0xf2, 0x6f, 0x6d, 0xff, 0x41, 0x01, 0x4c, 0x64, 0x99, 0x2d, 0x48, 0x43, 0xc6, 0xd7, 0x9d, + 0x30, 0x58, 0x63, 0x48, 0x7b, 0xdd, 0x49, 0x00, 0x8c, 0xd7, 0x9d, 0xf0, 0x79, 0xaa, 0xef, 0x8d, + 0xc0, 0xf3, 0x87, 0x5b, 0x27, 0xd9, 0x5d, 0x73, 0x58, 0x5f, 0x3d, 0x96, 0x4d, 0xf3, 0xe8, 0x71, + 0x55, 0x6f, 0x73, 0xc9, 0x0e, 0xb9, 0x90, 0x4f, 0x91, 0xf0, 0xfd, 0x83, 0x79, 0x2d, 0x6a, 0xf1, + 0x66, 0xe0, 0xf9, 0x9a, 0x93, 0xc0, 0x47, 0x39, 0x45, 0x61, 0x7c, 0xe1, 0xea, 0xf1, 0x5a, 0x96, + 0xd2, 0x49, 0x59, 0x75, 0x4c, 0x05, 0x83, 0x7d, 0x05, 0x86, 0xd0, 0xfa, 0x32, 0xa4, 0x02, 0x8f, + 0x8e, 0x53, 0x99, 0xa0, 0xa0, 0x6a, 0xd0, 0x36, 0x94, 0xb1, 0xbf, 0x20, 0xc7, 0xb9, 0xb7, 0xa1, + 0x96, 0x6d, 0x14, 0x3b, 0x0f, 0x43, 0xf8, 0x69, 0x5a, 0x06, 0x89, 0x4c, 0xdb, 0xb0, 0x7c, 0xee, + 0x17, 0x4a, 0x00, 0x69, 0x25, 0x42, 0xdd, 0x5a, 0x8d, 0xa2, 0x7e, 0x92, 0x20, 0x1e, 0xd5, 0x2d, + 0x0f, 0x21, 0xfa, 0x0e, 0x2a, 0x71, 0xd8, 0x57, 0x30, 0x83, 0x05, 0xba, 0xe6, 0xe3, 0xa0, 0xdc, + 0xd8, 0xd8, 0x68, 0x10, 0xb9, 0xf4, 0x7b, 0x43, 0x75, 0x3e, 0xf1, 0xe9, 0x97, 0xd1, 0x2d, 0xbb, + 0x71, 0xdc, 0x73, 0x24, 0x4b, 0x7b, 0x10, 0xf9, 0xdc, 0x77, 0xd4, 0xd1, 0x16, 0x33, 0xaa, 0xea, + 0xd9, 0xac, 0xa8, 0x61, 0x94, 0x51, 0xd5, 0x48, 0x85, 0x65, 0x66, 0x54, 0xd5, 0x89, 0xc4, 0x8a, + 0x2e, 0x48, 0xdd, 0xa5, 0x25, 0x47, 0x3d, 0x22, 0x6b, 0x57, 0x01, 0x35, 0x73, 0xe1, 0x4c, 0x1e, + 0x2a, 0xdf, 0xa8, 0xab, 0xa4, 0x39, 0xc1, 0x8a, 0x18, 0x23, 0x92, 0x9e, 0x58, 0x67, 0x00, 0x1f, + 0xeb, 0xd7, 0xcb, 0x70, 0x76, 0xa0, 0x6d, 0x8c, 0x35, 0xcc, 0x95, 0xf5, 0xf2, 0x51, 0xc6, 0xb4, + 0x23, 0x17, 0xd5, 0xdc, 0xdf, 0x57, 0x42, 0xea, 0xcb, 0x30, 0xd1, 0xec, 0x6f, 0x65, 0x4d, 0x0a, + 0x32, 0x49, 0xb4, 0x06, 0xd7, 0xd5, 0x17, 0x1d, 0x5f, 0x0c, 0x9b, 0xca, 0x5c, 0x45, 0x21, 0x10, + 0x5a, 0xdc, 0x55, 0x92, 0x79, 0x2f, 0x9f, 0x08, 0xd7, 0x24, 0x12, 0xd3, 0x31, 0xe6, 0xbe, 0xeb, + 0xc7, 0x7a, 0xc4, 0xaf, 0x84, 0xe8, 0xd3, 0x51, 0x42, 0xac, 0xff, 0xa0, 0x5c, 0x6c, 0xc9, 0xb9, + 0xbe, 0xd4, 0x78, 0x1c, 0x4b, 0xce, 0xf5, 0xa5, 0xc6, 0xd1, 0x3d, 0xf5, 0x9f, 0xab, 0x9e, 0x92, + 0x4e, 0xa3, 0x72, 0x73, 0x54, 0xb7, 0x91, 0xca, 0x69, 0x94, 0x36, 0xd2, 0x28, 0xe3, 0x34, 0x4a, + 0xc8, 0xec, 0x2d, 0x18, 0x5b, 0x0b, 0x64, 0x46, 0x5b, 0xd5, 0x3f, 0x32, 0x91, 0x9d, 0x02, 0xea, + 0x3b, 0x69, 0x82, 0x29, 0x0e, 0xcf, 0xe6, 0x7c, 0x51, 0xc1, 0x68, 0xb8, 0xda, 0x32, 0xf3, 0xcd, + 0xbc, 0xb3, 0x33, 0xc9, 0xac, 0xff, 0x70, 0x18, 0xac, 0xa3, 0x6f, 0x1c, 0xd8, 0x57, 0xcd, 0xbe, + 0xbb, 0x74, 0xec, 0xbb, 0x8a, 0x63, 0xed, 0xce, 0xf5, 0x7e, 0xdb, 0xe3, 0x7e, 0xcb, 0xcc, 0x1e, + 0x4b, 0x30, 0x7d, 0x67, 0x53, 0x78, 0x1f, 0x27, 0x5d, 0xd8, 0xdc, 0x3f, 0xae, 0xa4, 0xf2, 0x24, + 0xa3, 0x45, 0x95, 0x3e, 0x86, 0x16, 0xc5, 0x6e, 0x41, 0x4d, 0x87, 0x68, 0xd2, 0x04, 0x95, 0x5c, + 0x83, 0x51, 0xa6, 0x51, 0x39, 0x42, 0x53, 0x15, 0xab, 0x1c, 0x5f, 0x15, 0xcb, 0x58, 0xa2, 0x87, + 0x1e, 0xcf, 0x12, 0x4d, 0xd9, 0x66, 0x23, 0xd2, 0x45, 0x86, 0xcd, 0x6c, 0xb3, 0x05, 0xfa, 0x88, + 0x8e, 0xae, 0x12, 0xe6, 0xe2, 0x4f, 0x2d, 0xa1, 0x62, 0x92, 0x30, 0x57, 0xd2, 0x17, 0x25, 0xcc, + 0x4d, 0x48, 0x84, 0x5e, 0x63, 0xf7, 0x7d, 0xf9, 0x3a, 0xe4, 0x68, 0xaa, 0xd7, 0x84, 0x7d, 0xdf, + 0xc9, 0xbe, 0x10, 0x99, 0x20, 0x5a, 0xbf, 0x3d, 0x54, 0xac, 0x47, 0xa6, 0x97, 0x52, 0x8f, 0xa1, + 0x47, 0x26, 0x44, 0x9f, 0xce, 0x4c, 0xbd, 0x0b, 0x27, 0x54, 0x3c, 0x53, 0x83, 0xa2, 0x14, 0xee, + 0xda, 0x6b, 0x34, 0xc4, 0x68, 0x18, 0x4d, 0x22, 0xa1, 0x92, 0xd0, 0x86, 0x7e, 0x68, 0x18, 0x46, + 0x0b, 0xe8, 0xe7, 0xfe, 0x9e, 0xda, 0x1c, 0xf5, 0x41, 0xc0, 0xa4, 0x44, 0xa5, 0xa2, 0x41, 0xe8, + 0xf7, 0x8d, 0x61, 0x34, 0x49, 0xa4, 0xa4, 0x4e, 0x6c, 0xf4, 0x77, 0xcd, 0x63, 0x85, 0x6e, 0xd7, + 0x37, 0xb9, 0x64, 0x88, 0xd8, 0x0e, 0x9c, 0x4d, 0x4f, 0x5d, 0xda, 0xa1, 0x12, 0x39, 0xca, 0x0f, + 0xbe, 0xf8, 0xe8, 0x60, 0xfe, 0x65, 0xed, 0xd4, 0xa6, 0x9f, 0x4d, 0x33, 0xdc, 0x07, 0xf3, 0x12, + 0xf2, 0x76, 0x31, 0x74, 0xfd, 0xd6, 0xae, 0x36, 0xe7, 0x51, 0xde, 0x6e, 0x21, 0x34, 0x97, 0x54, + 0x32, 0x45, 0xb6, 0xfe, 0xb0, 0x5c, 0x6c, 0xbd, 0xa2, 0xbb, 0xc7, 0xc7, 0xb0, 0x5e, 0x49, 0x8a, + 0xa3, 0x77, 0x89, 0x7f, 0xae, 0x76, 0x89, 0x97, 0x61, 0x74, 0x43, 0x6c, 0x52, 0x49, 0xb2, 0x56, + 0xf4, 0xec, 0x8a, 0x25, 0xc8, 0x56, 0x65, 0xec, 0x03, 0x60, 0x0d, 0x37, 0xe4, 0x7e, 0xbc, 0x14, + 0x74, 0x7b, 0x6e, 0x18, 0x77, 0xf1, 0xfd, 0x4c, 0xb9, 0x35, 0xa0, 0x17, 0x4f, 0x0f, 0x4b, 0x9d, + 0x96, 0x56, 0xac, 0x6b, 0x29, 0x79, 0x62, 0x76, 0x19, 0x46, 0x95, 0xc3, 0x52, 0x25, 0xcd, 0x77, + 0x9f, 0x77, 0x56, 0x52, 0x58, 0x62, 0x57, 0x52, 0x39, 0x37, 0xd4, 0x83, 0x35, 0x14, 0x95, 0x46, + 0x40, 0x5d, 0x18, 0x25, 0x98, 0xd6, 0x7f, 0x3a, 0x02, 0xb3, 0x83, 0xee, 0x35, 0xd9, 0x1d, 0xb3, + 0x6b, 0x5f, 0x3a, 0xe2, 0x1e, 0xf4, 0xe8, 0x8e, 0xfd, 0xfd, 0xe1, 0x27, 0x2b, 0xcf, 0x0d, 0x11, + 0x5c, 0xfe, 0xd8, 0x22, 0xb8, 0xf2, 0x78, 0x22, 0xf8, 0x8b, 0x00, 0x1b, 0xbc, 0xdb, 0xeb, 0xb8, + 0x31, 0x4f, 0x2e, 0xf3, 0x64, 0x22, 0x74, 0x82, 0x66, 0xe2, 0x6b, 0x52, 0x64, 0xf6, 0x1e, 0x4c, + 0xa8, 0x5f, 0x9a, 0x53, 0x90, 0x7c, 0xbf, 0x4f, 0x11, 0x67, 0xef, 0x11, 0x75, 0x02, 0x21, 0x3b, + 0xb4, 0xa5, 0x95, 0xa4, 0x2e, 0x91, 0xf7, 0x3e, 0xda, 0xca, 0x34, 0x65, 0x87, 0x41, 0x22, 0x34, + 0x11, 0x0d, 0xa0, 0x65, 0xc3, 0x45, 0x4d, 0x44, 0xe7, 0x92, 0x69, 0x4a, 0x96, 0x2c, 0xbf, 0x9d, + 0x54, 0x1f, 0x7f, 0x3b, 0x31, 0xad, 0x45, 0xea, 0xdd, 0xe6, 0x02, 0x6b, 0x51, 0x26, 0x04, 0xc0, + 0x24, 0x12, 0x4d, 0x91, 0x10, 0xd3, 0x34, 0x88, 0x4d, 0x69, 0x53, 0x41, 0xce, 0x3c, 0x68, 0x92, + 0xc8, 0x88, 0xbc, 0xbd, 0x37, 0x36, 0xdc, 0x1d, 0xca, 0x08, 0x45, 0x11, 0x79, 0x7b, 0x6f, 0x38, + 0xb1, 0xbb, 0x63, 0x46, 0xe4, 0x21, 0x9a, 0xf5, 0xa7, 0x63, 0xc5, 0x97, 0xe7, 0xda, 0x05, 0x3b, + 0xb3, 0x32, 0x87, 0x33, 0x74, 0xc6, 0xa5, 0x93, 0xd4, 0xa7, 0x7e, 0x24, 0x33, 0x36, 0xc0, 0xca, + 0x31, 0x37, 0xc0, 0x8f, 0x7f, 0x15, 0xc5, 0x5e, 0x85, 0xe1, 0x8d, 0xb5, 0xe6, 0x52, 0x9d, 0x66, + 0xb6, 0xd4, 0xfc, 0x3b, 0x91, 0xd3, 0xd2, 0xcd, 0x8e, 0x12, 0x85, 0x6d, 0xc1, 0xf4, 0x7a, 0x3f, + 0x92, 0x0e, 0x55, 0xd7, 0x3c, 0xde, 0x69, 0xa7, 0x6f, 0x2a, 0x66, 0x9d, 0x84, 0x9b, 0xf8, 0x16, + 0x3c, 0x6a, 0x69, 0x33, 0xdd, 0x7e, 0x44, 0xde, 0x5a, 0xce, 0x36, 0x52, 0x7d, 0xff, 0x60, 0x7e, + 0x44, 0x22, 0xd8, 0x59, 0x86, 0xec, 0x1e, 0x54, 0xb1, 0x47, 0xea, 0xfe, 0x3e, 0x3e, 0xea, 0x71, + 0xb8, 0xf3, 0x82, 0x36, 0x6c, 0x52, 0x98, 0xe1, 0x7b, 0xe5, 0xb2, 0xb7, 0x5d, 0x5f, 0x74, 0x11, + 0xf1, 0x9a, 0xbb, 0x04, 0xe3, 0x4b, 0x81, 0x2f, 0x9d, 0xd5, 0x57, 0x3e, 0x4a, 0xe3, 0x42, 0x4b, + 0xc5, 0x71, 0xa1, 0x73, 0x6f, 0xc2, 0x54, 0x82, 0x7f, 0x3b, 0x88, 0x8f, 0x47, 0x92, 0x56, 0xb1, + 0xea, 0x8b, 0x69, 0x24, 0x9d, 0xd9, 0xe8, 0xb4, 0x82, 0xd3, 0x08, 0x09, 0x22, 0x9b, 0x4a, 0xe6, + 0x3e, 0x67, 0xd6, 0x72, 0x4c, 0xaa, 0x1f, 0xaf, 0xa0, 0x71, 0x50, 0x92, 0xb1, 0xcf, 0xc2, 0x58, + 0x3d, 0x8e, 0x43, 0x6f, 0xab, 0x9f, 0xb8, 0x8f, 0xcb, 0x5e, 0x50, 0x40, 0x3b, 0x2d, 0x67, 0x37, + 0xa1, 0xbc, 0xf2, 0x11, 0xb9, 0x76, 0x2f, 0x1c, 0xb3, 0x63, 0xb5, 0x7e, 0x5b, 0x1c, 0x79, 0x74, + 0x30, 0x5f, 0xe6, 0x1f, 0xd9, 0xe5, 0x95, 0x8f, 0xd8, 0x26, 0x0c, 0x63, 0xcf, 0x90, 0x5d, 0xe7, + 0xad, 0xc7, 0x65, 0x87, 0xc4, 0xf2, 0x03, 0xfd, 0x20, 0x76, 0xf8, 0x47, 0xb6, 0xe4, 0x27, 0x1a, + 0xb9, 0xaa, 0x12, 0xe5, 0x3c, 0x76, 0x23, 0x57, 0x7d, 0xd9, 0x48, 0xcf, 0xb7, 0xcb, 0xab, 0x3e, + 0x35, 0x72, 0x55, 0xa5, 0xb6, 0xfb, 0x38, 0x8d, 0x5c, 0xf5, 0xd3, 0x46, 0x7a, 0xbe, 0x2d, 0xf9, + 0xcd, 0xfd, 0xfb, 0x4a, 0xe7, 0xf8, 0x10, 0x53, 0x13, 0x4b, 0x6c, 0xe5, 0x34, 0xfd, 0xc6, 0xe3, + 0x56, 0x93, 0x18, 0x77, 0x89, 0x4f, 0x26, 0x4f, 0x31, 0x41, 0xf1, 0x4e, 0xf2, 0x61, 0x2f, 0x94, + 0xd1, 0x8b, 0xfa, 0x06, 0xca, 0x13, 0xa8, 0x19, 0x28, 0xaa, 0xa0, 0xd6, 0x3f, 0x1e, 0x86, 0x73, + 0x47, 0xf9, 0x75, 0xb1, 0x6d, 0x80, 0x3b, 0xfe, 0x56, 0xe0, 0x86, 0x6d, 0xcf, 0xdf, 0xa1, 0x1c, + 0x59, 0x57, 0x8f, 0xe9, 0x14, 0x76, 0x29, 0xa5, 0x14, 0x85, 0x32, 0x24, 0x2b, 0x48, 0x60, 0xb6, + 0xc6, 0x99, 0x7d, 0x13, 0xaa, 0x36, 0x6f, 0x05, 0x7b, 0x9c, 0xee, 0xb3, 0xc7, 0x17, 0x3e, 0x77, + 0xdc, 0x5a, 0x14, 0x1d, 0xd6, 0x81, 0x49, 0x99, 0x42, 0x82, 0xd8, 0x09, 0x4f, 0xf6, 0x2d, 0x18, + 0x97, 0xaf, 0xb2, 0xd7, 0xb7, 0x63, 0xba, 0xf3, 0x3e, 0x3a, 0x71, 0x6d, 0x49, 0xa8, 0x13, 0xf2, + 0x9d, 0x77, 0xc7, 0xdd, 0x36, 0xf2, 0x25, 0xc8, 0xc4, 0xb5, 0x1a, 0xcb, 0xb9, 0xbf, 0x5d, 0x86, + 0x29, 0xf3, 0x83, 0xd9, 0x1a, 0xd4, 0x56, 0x7d, 0x2f, 0xf6, 0xdc, 0x8e, 0x19, 0x98, 0x48, 0xb7, + 0x1f, 0x9e, 0x2c, 0x73, 0x0a, 0xe3, 0xf6, 0x72, 0x94, 0x42, 0x45, 0x15, 0x9a, 0x62, 0x14, 0x4b, + 0xdf, 0x32, 0xf9, 0xd4, 0x15, 0x8d, 0xf8, 0x0b, 0xf2, 0x5d, 0xb7, 0xb4, 0xd4, 0x91, 0x4f, 0xdb, + 0x99, 0xcf, 0xf8, 0x64, 0x89, 0xd9, 0x1e, 0x30, 0x21, 0x6d, 0x65, 0x09, 0x0f, 0x17, 0xf9, 0x76, + 0x10, 0x1e, 0x27, 0x63, 0xed, 0xab, 0xd4, 0x39, 0xcf, 0xa3, 0x20, 0x0f, 0x89, 0xdc, 0xd9, 0x42, + 0xfa, 0x4c, 0x27, 0x15, 0xd4, 0x30, 0xb7, 0x0e, 0x13, 0xfa, 0xa8, 0x61, 0x20, 0x8b, 0xd7, 0xf5, + 0x54, 0x98, 0xbd, 0x0c, 0x64, 0x11, 0x00, 0x5b, 0xc2, 0xd9, 0xb3, 0x94, 0x35, 0xbf, 0x9c, 0xc6, + 0x7b, 0xa4, 0xd9, 0xf1, 0xad, 0xef, 0x94, 0xe0, 0x74, 0x71, 0x10, 0x04, 0xfb, 0x30, 0xe3, 0xd6, + 0x58, 0x3a, 0xcc, 0xe9, 0x53, 0x45, 0x4e, 0x7c, 0x3c, 0xc7, 0x46, 0xeb, 0x1f, 0x0c, 0xe5, 0x8c, + 0x3a, 0x05, 0x1c, 0xd9, 0xf5, 0xc2, 0x71, 0x2c, 0x69, 0xc7, 0xf0, 0xfc, 0x38, 0x16, 0x8e, 0xde, + 0xbb, 0x30, 0x85, 0x8c, 0xd3, 0xc9, 0xa5, 0xdd, 0xd4, 0xcb, 0x26, 0x6b, 0x01, 0x90, 0x19, 0x5c, + 0xb6, 0x0a, 0x0c, 0x21, 0x8b, 0x42, 0x76, 0x25, 0xc9, 0x00, 0xb5, 0xeb, 0x0a, 0xc9, 0x61, 0x0b, + 0x45, 0x5c, 0x9a, 0x16, 0xb0, 0x80, 0x88, 0x7d, 0x01, 0x26, 0xd5, 0x70, 0xca, 0xa7, 0x4a, 0xb5, + 0x20, 0x4e, 0xb5, 0x16, 0x65, 0xc6, 0x6d, 0xdb, 0x44, 0x64, 0x5d, 0x99, 0xbf, 0x9c, 0x80, 0xbc, + 0x5d, 0x8f, 0x8f, 0x91, 0x24, 0xfe, 0x15, 0x9a, 0x7d, 0xcf, 0x60, 0x92, 0xf8, 0x50, 0xd1, 0x3a, + 0x6e, 0x9c, 0x99, 0x7a, 0x59, 0xde, 0x6c, 0x07, 0x26, 0x11, 0x84, 0xcb, 0x56, 0x54, 0x36, 0x72, + 0x64, 0x65, 0x2f, 0x53, 0x65, 0x67, 0x65, 0x65, 0x92, 0x32, 0x5f, 0x95, 0xc9, 0x97, 0x5d, 0x81, + 0x71, 0xec, 0x27, 0x4a, 0x04, 0xa7, 0x39, 0xa2, 0xc8, 0x5e, 0x55, 0xe9, 0xe0, 0x74, 0x2c, 0xeb, + 0xa7, 0xcb, 0x30, 0x25, 0xf5, 0x30, 0x19, 0x3e, 0xf3, 0xd4, 0xc6, 0x39, 0xbd, 0x63, 0xc4, 0x39, + 0xa9, 0x27, 0x29, 0xf5, 0x4f, 0x3b, 0x56, 0x54, 0xea, 0x2e, 0xb0, 0x3c, 0x0d, 0xb3, 0x61, 0x42, + 0x87, 0x1e, 0x1e, 0x93, 0xf4, 0x66, 0xfa, 0x7a, 0x29, 0x29, 0xbe, 0x18, 0x65, 0x16, 0xd9, 0x06, + 0x0f, 0xeb, 0xa7, 0xca, 0x30, 0xa9, 0x45, 0xa5, 0x3e, 0xb5, 0x1d, 0xff, 0xb6, 0xd1, 0xf1, 0xb3, + 0x49, 0xea, 0xd9, 0xe4, 0xcb, 0x8e, 0xd5, 0xef, 0x7d, 0x98, 0xc9, 0x91, 0x64, 0x83, 0x7b, 0x4b, + 0xc7, 0x09, 0xee, 0x7d, 0x2d, 0xff, 0x18, 0x21, 0xea, 0x00, 0xe9, 0x4b, 0x52, 0xfa, 0xeb, 0x87, + 0x3f, 0x53, 0x86, 0x93, 0xf4, 0x0b, 0x9f, 0x02, 0x90, 0x17, 0x63, 0x4f, 0xed, 0x58, 0xd4, 0x8d, + 0xb1, 0x98, 0x37, 0xc7, 0x42, 0xfb, 0xc0, 0xc1, 0x43, 0x62, 0x7d, 0x07, 0x60, 0x76, 0x10, 0xc1, + 0xb1, 0x33, 0xd5, 0xa7, 0xb9, 0x5b, 0xcb, 0xc7, 0xc8, 0xdd, 0xba, 0x46, 0xef, 0x30, 0x50, 0x3a, + 0x8c, 0xe8, 0xae, 0xbd, 0x4a, 0x9d, 0x84, 0x2a, 0x8b, 0x7c, 0xeb, 0x81, 0x72, 0x68, 0x44, 0x19, + 0x27, 0x92, 0x1c, 0x25, 0xfb, 0xf7, 0x4a, 0x30, 0x85, 0xc0, 0x95, 0x3d, 0xee, 0xc7, 0xc8, 0x6c, + 0x88, 0x92, 0x7a, 0x26, 0x31, 0x3e, 0xcd, 0x38, 0xf4, 0xfc, 0x1d, 0x0a, 0xf2, 0xd9, 0xa2, 0x20, + 0x9f, 0x77, 0x65, 0x70, 0xd2, 0xa5, 0x56, 0xd0, 0xbd, 0xbc, 0x13, 0xba, 0x7b, 0x5e, 0x4c, 0xc1, + 0x69, 0x97, 0x55, 0xd6, 0xc8, 0xcb, 0x6e, 0xcf, 0xbb, 0x8c, 0x7d, 0x7a, 0x39, 0xc3, 0x0a, 0x03, + 0xa8, 0x64, 0x43, 0x39, 0x56, 0x9b, 0xf5, 0x75, 0x31, 0x5b, 0xc4, 0x7e, 0x08, 0xce, 0x48, 0xaf, + 0x81, 0xa5, 0xc0, 0x8f, 0x3d, 0xbf, 0x1f, 0xf4, 0xa3, 0x45, 0xb7, 0x75, 0xbf, 0xdf, 0x8b, 0x28, + 0x65, 0x35, 0x7e, 0x79, 0x2b, 0x29, 0x74, 0xb6, 0x64, 0xa9, 0xf1, 0x3e, 0x47, 0x31, 0x03, 0x76, + 0x03, 0x66, 0x64, 0x51, 0xbd, 0x1f, 0x07, 0xcd, 0x96, 0xdb, 0x11, 0x5a, 0xf4, 0x28, 0x72, 0x95, + 0x91, 0x0c, 0xfd, 0x38, 0x70, 0x22, 0x09, 0xd7, 0x5d, 0x5f, 0x72, 0x44, 0x6c, 0x15, 0xa6, 0x6d, + 0xee, 0xb6, 0xd7, 0xdd, 0x87, 0x4b, 0x6e, 0xcf, 0x6d, 0x79, 0xb1, 0x7c, 0x44, 0xb8, 0x22, 0xaf, + 0x3d, 0x42, 0xee, 0xb6, 0x9d, 0xae, 0xfb, 0xd0, 0x69, 0x51, 0xa1, 0xe9, 0xb9, 0x69, 0xd0, 0x25, + 0xac, 0x3c, 0x3f, 0x61, 0x35, 0x96, 0x65, 0xe5, 0xf9, 0x83, 0x59, 0xa5, 0x74, 0x8a, 0xd5, 0x86, + 0x1b, 0xee, 0xf0, 0x58, 0x9e, 0x85, 0xe1, 0x5c, 0xe9, 0x42, 0x49, 0x63, 0x15, 0x63, 0x99, 0x83, + 0x07, 0xd6, 0x2c, 0x2b, 0x8d, 0x4e, 0xcc, 0xbc, 0xcd, 0xd0, 0x8b, 0xb9, 0xfe, 0x85, 0xe3, 0xd8, + 0x2c, 0xec, 0x7f, 0x4c, 0x48, 0x35, 0xe8, 0x13, 0x73, 0x94, 0x29, 0x37, 0xed, 0x23, 0x27, 0x72, + 0xdc, 0x8a, 0xbf, 0x32, 0x47, 0x99, 0x70, 0xd3, 0xbf, 0x73, 0x12, 0xbf, 0x53, 0xe3, 0x36, 0xe0, + 0x43, 0x73, 0x94, 0xec, 0xb6, 0xe8, 0xb4, 0x98, 0xfb, 0x62, 0x46, 0x53, 0xae, 0xa1, 0x29, 0x6c, + 0xda, 0x4b, 0xa4, 0x6b, 0xd4, 0x42, 0x55, 0xec, 0x14, 0x64, 0x1e, 0xca, 0x12, 0xb3, 0x1f, 0x81, + 0xe9, 0xbb, 0x11, 0xbf, 0xb6, 0xda, 0x68, 0xaa, 0x57, 0xdb, 0xd0, 0x5b, 0x6b, 0x6a, 0xe1, 0xcd, + 0x23, 0x84, 0xce, 0x25, 0x9d, 0x46, 0x6c, 0x88, 0x5c, 0x8e, 0x5b, 0x3f, 0xe2, 0xce, 0xb6, 0xd7, + 0x8b, 0x92, 0x17, 0x4b, 0xf5, 0x71, 0xcb, 0x54, 0x65, 0xdd, 0x80, 0x99, 0x1c, 0x1b, 0x36, 0x05, + 0x20, 0x80, 0xce, 0xdd, 0xdb, 0xcd, 0x95, 0x8d, 0xda, 0x67, 0x58, 0x0d, 0x26, 0xf0, 0xf7, 0xca, + 0xed, 0xfa, 0xe2, 0xda, 0xca, 0x72, 0xad, 0xc4, 0x66, 0x60, 0x12, 0x21, 0xcb, 0xab, 0x4d, 0x09, + 0x2a, 0xdf, 0x1c, 0xaa, 0x0e, 0xd7, 0x46, 0xe8, 0x71, 0x18, 0x47, 0xa6, 0x00, 0x17, 0x7b, 0x8a, + 0xf5, 0x2b, 0x65, 0x38, 0xab, 0xb6, 0x15, 0x1e, 0x3f, 0x08, 0xc2, 0xfb, 0xf8, 0xfa, 0xcb, 0x53, + 0xbd, 0x3b, 0x5c, 0x33, 0x76, 0x87, 0x97, 0x32, 0x3b, 0x75, 0xe6, 0x2b, 0x0f, 0xd9, 0x22, 0x7e, + 0x77, 0x0c, 0x9e, 0x3b, 0x94, 0x8a, 0x7d, 0x20, 0x76, 0x73, 0x8f, 0xfb, 0xf1, 0x6a, 0xbb, 0xc3, + 0x85, 0x5e, 0x1b, 0xf4, 0xe9, 0x25, 0x1d, 0xbc, 0x08, 0x3b, 0x41, 0xa9, 0xb2, 0xbc, 0x76, 0x87, + 0x3b, 0xb1, 0x2c, 0x36, 0xa6, 0x5b, 0x9e, 0x5a, 0xb0, 0xbc, 0xc5, 0x79, 0xaf, 0xde, 0xf1, 0xf6, + 0xf8, 0xaa, 0x1f, 0xf3, 0x70, 0x0f, 0x73, 0x10, 0x24, 0x2c, 0xef, 0x73, 0xde, 0x73, 0x5c, 0x51, + 0xea, 0x78, 0x54, 0x6c, 0xb2, 0xcc, 0x51, 0xb3, 0x6b, 0x1a, 0x4b, 0x3c, 0x1a, 0xac, 0xbb, 0x0f, + 0x29, 0xe2, 0x97, 0x1e, 0x6d, 0x4e, 0x58, 0x4a, 0x6f, 0x8e, 0xae, 0xfb, 0xd0, 0xce, 0x93, 0xb0, + 0x6f, 0xc0, 0x29, 0xda, 0x80, 0xe8, 0x75, 0x13, 0xf5, 0xc5, 0xf2, 0xed, 0x94, 0x57, 0xc4, 0x69, + 0x4e, 0xa5, 0x80, 0x52, 0x8f, 0x40, 0x15, 0x7d, 0x75, 0x31, 0x17, 0xb6, 0x21, 0x36, 0xe4, 0x4c, + 0x77, 0xac, 0xf3, 0x28, 0x52, 0x99, 0x60, 0xc9, 0xf6, 0xa2, 0x77, 0xa6, 0xd3, 0x95, 0xe5, 0xf6, + 0x40, 0x4a, 0x76, 0x03, 0xa6, 0x36, 0xf9, 0x96, 0x3e, 0x3e, 0x23, 0x89, 0xa8, 0xaa, 0x3d, 0xe0, + 0x5b, 0x83, 0x07, 0x27, 0x43, 0xc7, 0x3c, 0x74, 0xb8, 0x7c, 0xb8, 0xbf, 0x26, 0x4e, 0xdb, 0x3e, + 0x0f, 0xf1, 0xd0, 0x3c, 0x8a, 0xc2, 0x60, 0x36, 0xd5, 0x90, 0xcd, 0x72, 0xba, 0xdf, 0xc2, 0xe4, + 0x8b, 0x1d, 0x82, 0x63, 0xd2, 0xb4, 0x8c, 0x2f, 0xa6, 0x49, 0xc5, 0xbe, 0x05, 0xd3, 0x76, 0xd0, + 0x8f, 0x3d, 0x7f, 0xa7, 0x29, 0x8e, 0xa5, 0x7c, 0x47, 0x6e, 0x48, 0xe9, 0x9b, 0x76, 0x99, 0x52, + 0x8a, 0x30, 0x90, 0x40, 0x27, 0x22, 0xa8, 0xb1, 0x23, 0x98, 0x04, 0xec, 0x9b, 0x30, 0x25, 0xdf, + 0x83, 0x48, 0x2a, 0x18, 0x33, 0xde, 0x95, 0x32, 0x0b, 0xef, 0xbd, 0x49, 0x01, 0x9a, 0x08, 0x2d, + 0xaa, 0x20, 0xc3, 0x8d, 0x7d, 0x8d, 0x3a, 0xab, 0xe1, 0xf9, 0x3b, 0xc9, 0x34, 0x06, 0xec, 0xf9, + 0xd7, 0xd3, 0x2e, 0xe9, 0x89, 0xe6, 0xaa, 0x69, 0x3c, 0x20, 0xda, 0x3c, 0xcf, 0x87, 0xc5, 0xf0, + 0x5c, 0x3d, 0x8a, 0xbc, 0x28, 0xa6, 0x0c, 0x70, 0x2b, 0x0f, 0x79, 0xab, 0x2f, 0x90, 0x37, 0x83, + 0xf0, 0x3e, 0x0f, 0x65, 0x16, 0x97, 0xe1, 0xc5, 0x4b, 0x8f, 0x0e, 0xe6, 0x5f, 0x75, 0x11, 0xd1, + 0xa1, 0xa4, 0x71, 0x0e, 0x57, 0xa8, 0xce, 0x03, 0x89, 0xab, 0x7d, 0xc3, 0xe1, 0x4c, 0xd9, 0x37, + 0xe1, 0xf4, 0x92, 0x1b, 0xf1, 0x55, 0x3f, 0xe2, 0x7e, 0xe4, 0xc5, 0xde, 0x1e, 0xa7, 0x4e, 0xc5, + 0xcd, 0xaf, 0x2a, 0xb3, 0x02, 0xb4, 0xdc, 0x48, 0x2c, 0xcc, 0x04, 0xc5, 0xa1, 0x41, 0xd1, 0xb3, + 0x02, 0x14, 0x73, 0x61, 0x36, 0x4c, 0x35, 0x9b, 0x37, 0x96, 0x3d, 0x37, 0x59, 0x57, 0x93, 0xd8, + 0x5f, 0xaf, 0xa2, 0x03, 0x4c, 0xb4, 0xeb, 0xb4, 0x3d, 0x37, 0x59, 0x50, 0x03, 0x3a, 0x2b, 0xc3, + 0xc1, 0x3a, 0x28, 0x41, 0x2d, 0x3b, 0x94, 0xec, 0x2b, 0x30, 0x26, 0x43, 0xb5, 0x79, 0xb4, 0x4b, + 0x46, 0x1b, 0x15, 0xf9, 0x9b, 0xc0, 0x4d, 0x22, 0xca, 0x21, 0x2c, 0x03, 0xc1, 0xb9, 0x1e, 0x34, + 0x75, 0xe3, 0x33, 0x76, 0xca, 0x8c, 0xb5, 0x61, 0x42, 0x8e, 0x16, 0xc7, 0xf7, 0x38, 0x33, 0x79, + 0xf1, 0xf4, 0xa2, 0x0c, 0x7f, 0x19, 0x2a, 0x28, 0xe7, 0x84, 0x44, 0x30, 0xaa, 0x30, 0xb8, 0x2e, + 0x02, 0x54, 0x15, 0xa1, 0x75, 0x16, 0xce, 0x0c, 0x68, 0xb3, 0xf5, 0xdb, 0x25, 0xbc, 0x18, 0x1f, + 0x50, 0x25, 0xfb, 0x0a, 0x9c, 0x44, 0xca, 0xa5, 0xc0, 0xf7, 0x79, 0x2b, 0x46, 0x79, 0x94, 0x3c, + 0x84, 0x26, 0x23, 0x9e, 0xe4, 0x07, 0xb7, 0x12, 0x04, 0x27, 0xeb, 0x90, 0x57, 0xc8, 0x81, 0xbd, + 0x0f, 0xcf, 0xb6, 0xbd, 0x88, 0xc8, 0x9c, 0x78, 0x37, 0xe4, 0xd1, 0x6e, 0xd0, 0x69, 0x27, 0xcf, + 0x94, 0x61, 0x3a, 0x4b, 0x7b, 0x2e, 0xc5, 0xd9, 0x50, 0x28, 0xea, 0x99, 0xb2, 0x7f, 0x51, 0x86, + 0x59, 0x12, 0x92, 0x36, 0x6f, 0x05, 0x61, 0xfb, 0xe9, 0xdf, 0x94, 0x57, 0x8c, 0x4d, 0xf9, 0xc5, + 0xe4, 0xc5, 0x91, 0xa2, 0x8f, 0x3c, 0x24, 0x4f, 0xcb, 0xdd, 0x4c, 0x9e, 0x96, 0x23, 0x18, 0x21, + 0xea, 0xe2, 0xac, 0xd2, 0xfd, 0x06, 0x65, 0x6c, 0xb1, 0x56, 0xa1, 0x7a, 0x8b, 0xef, 0x63, 0xd2, + 0x02, 0xd1, 0xbf, 0x71, 0x7a, 0xf8, 0xab, 0xaa, 0xc4, 0x5c, 0x36, 0xfe, 0xcb, 0xe6, 0x61, 0x18, + 0x53, 0x20, 0xe8, 0x29, 0x4e, 0x11, 0x60, 0xcb, 0xff, 0xac, 0xff, 0xbd, 0x04, 0x67, 0xd7, 0x5d, + 0xbf, 0xef, 0x76, 0x6e, 0xf1, 0xfd, 0x75, 0xd7, 0x77, 0x77, 0xf0, 0x7d, 0x39, 0x72, 0x45, 0xbd, + 0x0c, 0xa3, 0xf4, 0x16, 0x38, 0xf2, 0xaf, 0x4a, 0xc7, 0x85, 0xfc, 0xf3, 0xe0, 0x0a, 0x8b, 0xdd, + 0x82, 0x71, 0x2d, 0xe7, 0x14, 0x65, 0xfa, 0x50, 0x3d, 0xaf, 0xda, 0x4c, 0xf9, 0x5a, 0x8a, 0x73, + 0x53, 0xb9, 0x69, 0x6e, 0xaa, 0xdb, 0x30, 0xa1, 0xac, 0x6c, 0xc8, 0xad, 0x52, 0xcc, 0x6d, 0x2e, + 0xb5, 0xcd, 0xe7, 0x72, 0x50, 0x8d, 0x13, 0x1c, 0x33, 0x4f, 0xfd, 0x7e, 0x09, 0xce, 0x65, 0x7b, + 0x3e, 0x4d, 0x1f, 0xf7, 0x71, 0x3f, 0x79, 0x1f, 0x4e, 0x75, 0xb1, 0x03, 0x31, 0xb7, 0x5d, 0x37, + 0xe9, 0x42, 0x12, 0x27, 0x2a, 0xc7, 0xc3, 0xc0, 0x4e, 0x96, 0x4e, 0x48, 0x85, 0x2c, 0x74, 0x27, + 0xa4, 0x6e, 0x9e, 0xde, 0xfa, 0xc5, 0x32, 0x3c, 0x7b, 0xd8, 0x9c, 0x4c, 0x8c, 0xe5, 0xa5, 0x22, + 0x63, 0x39, 0xeb, 0xc1, 0x09, 0x94, 0x38, 0x4b, 0xbb, 0xbc, 0x75, 0x1f, 0xd3, 0xfe, 0xde, 0x92, + 0x83, 0x56, 0x1c, 0xde, 0xf2, 0x5a, 0x61, 0x78, 0xcb, 0x69, 0x29, 0x07, 0x5b, 0xc8, 0x43, 0x5a, + 0x33, 0xc5, 0x18, 0xd8, 0x45, 0xac, 0x19, 0x07, 0x48, 0x9f, 0xef, 0xa7, 0xdb, 0x85, 0x57, 0x06, + 0xac, 0x89, 0xec, 0xc8, 0xe0, 0xa3, 0x55, 0x25, 0x19, 0x63, 0xa7, 0x4a, 0xf4, 0x89, 0x93, 0x42, + 0xad, 0xfe, 0xc0, 0x6e, 0x91, 0xcf, 0x49, 0xde, 0x85, 0xe9, 0x14, 0x5b, 0xce, 0x2d, 0xd3, 0x76, + 0x98, 0xcd, 0x2b, 0x28, 0xfd, 0x60, 0x32, 0x34, 0xf6, 0x14, 0xd7, 0x71, 0xf0, 0x51, 0x89, 0x5a, + 0xbd, 0x1f, 0xef, 0x36, 0x42, 0x4e, 0xc9, 0xa3, 0x7f, 0xc0, 0xf2, 0x53, 0x99, 0x1f, 0x77, 0x2c, + 0x0b, 0xe2, 0xef, 0x4d, 0xc3, 0xc9, 0x22, 0x32, 0xd1, 0x2f, 0x1b, 0x85, 0x72, 0x0b, 0x4d, 0x55, + 0xdf, 0x2d, 0xc1, 0x84, 0xdc, 0x5e, 0xae, 0x61, 0x60, 0x27, 0xf5, 0x8e, 0x2b, 0x95, 0x76, 0x01, + 0x77, 0xb6, 0x33, 0x11, 0x9f, 0xdf, 0x3f, 0x98, 0x7f, 0xff, 0x78, 0xb6, 0xa2, 0x56, 0x80, 0x17, + 0x16, 0xb1, 0x98, 0x73, 0x69, 0x15, 0xb2, 0x35, 0xb6, 0x51, 0x2d, 0x5b, 0x84, 0x49, 0xda, 0x2f, + 0x03, 0xfd, 0x05, 0x65, 0xf9, 0x68, 0x97, 0x2a, 0xc8, 0xf9, 0xc4, 0x18, 0x24, 0xec, 0x0a, 0x54, + 0xee, 0x2e, 0x5c, 0xa3, 0x51, 0x50, 0x89, 0xc6, 0xef, 0x2e, 0x5c, 0x43, 0x83, 0xb4, 0x98, 0xd0, + 0x93, 0xfd, 0x05, 0x23, 0x6e, 0xf1, 0xee, 0xc2, 0x35, 0xf6, 0x63, 0x70, 0x6a, 0x39, 0xd9, 0x71, + 0x65, 0xae, 0xb4, 0x36, 0xe6, 0x64, 0x1d, 0x19, 0xb0, 0x3a, 0x3f, 0x5f, 0xb8, 0x3a, 0x5f, 0xd0, + 0xb6, 0x76, 0x99, 0x88, 0xad, 0x9d, 0x7d, 0x29, 0xba, 0xb8, 0x1e, 0xf6, 0x21, 0x4c, 0xa1, 0x7f, + 0x04, 0xa6, 0x8f, 0x13, 0x23, 0x48, 0x39, 0x85, 0xf2, 0x35, 0xbf, 0x51, 0x58, 0xf3, 0x9c, 0x74, + 0xb4, 0xc0, 0x24, 0x74, 0x8e, 0xdb, 0x37, 0xa2, 0x1d, 0x33, 0x9c, 0xd9, 0x4d, 0x98, 0xa6, 0x03, + 0xd0, 0x9d, 0xed, 0x8d, 0x5d, 0xbe, 0xec, 0xee, 0x93, 0xef, 0x11, 0xda, 0x54, 0xe8, 0xd4, 0xe4, + 0x04, 0xdb, 0x4e, 0xbc, 0xcb, 0x9d, 0xb6, 0x6b, 0x1c, 0x15, 0x32, 0x84, 0xec, 0xdb, 0x30, 0xbe, + 0x16, 0xb4, 0xc4, 0xd9, 0x17, 0x25, 0x9f, 0x74, 0x3f, 0xfa, 0xaa, 0x10, 0x54, 0x1d, 0x09, 0xce, + 0x1c, 0x68, 0xbe, 0x7f, 0x30, 0xff, 0xce, 0xe3, 0x4e, 0x1b, 0xad, 0x02, 0x5b, 0xaf, 0x8d, 0x2d, + 0x41, 0x75, 0x93, 0x6f, 0x89, 0xaf, 0xf5, 0x29, 0x7d, 0x90, 0x5a, 0x75, 0x0a, 0x4c, 0xa1, 0xd1, + 0xf4, 0xcb, 0x08, 0x8d, 0x26, 0x18, 0x0b, 0x61, 0x06, 0xfb, 0xa7, 0xe1, 0x46, 0xd1, 0x83, 0x20, + 0x6c, 0x77, 0x78, 0x14, 0x0d, 0x0c, 0x70, 0x5c, 0x28, 0xec, 0xfc, 0x67, 0x65, 0xe7, 0xf7, 0x34, + 0x0e, 0x46, 0xa2, 0xb1, 0x2c, 0x7b, 0xf6, 0x2d, 0x98, 0xa2, 0x37, 0x09, 0xd6, 0xaf, 0xd5, 0x71, + 0x25, 0x4c, 0x18, 0x29, 0x5d, 0xcd, 0x42, 0x75, 0xc9, 0x8c, 0xb0, 0x24, 0x8f, 0x72, 0x77, 0xdb, + 0x35, 0x9d, 0x53, 0x75, 0x12, 0xd6, 0x80, 0xf1, 0x65, 0xbe, 0xe7, 0xb5, 0x38, 0xe6, 0x82, 0xa4, + 0x24, 0x43, 0x2a, 0x6f, 0xa7, 0x56, 0x22, 0xb7, 0xf3, 0x36, 0x02, 0x64, 0x66, 0x49, 0x33, 0x69, + 0x42, 0x82, 0xc8, 0xae, 0x42, 0x65, 0x75, 0xb9, 0x41, 0x39, 0x86, 0x66, 0x92, 0x97, 0x3f, 0x1a, + 0xf4, 0xae, 0xb8, 0xbc, 0x64, 0xf3, 0xda, 0x46, 0x86, 0xa2, 0xd5, 0xe5, 0x06, 0xdb, 0x86, 0x49, + 0xe9, 0x57, 0xc5, 0x5d, 0xd9, 0xb7, 0xd3, 0x03, 0xfa, 0xf6, 0x52, 0x61, 0xdf, 0xce, 0x92, 0xbf, + 0x16, 0x51, 0xeb, 0xeb, 0xde, 0x60, 0x2b, 0x0e, 0x95, 0xcb, 0x7c, 0xdb, 0xed, 0x77, 0x94, 0x21, + 0x7c, 0x63, 0x63, 0x0d, 0x43, 0x1e, 0xe9, 0x50, 0xd9, 0x96, 0x85, 0x49, 0xff, 0x0d, 0x4e, 0x61, + 0x96, 0xe7, 0xc3, 0xde, 0x86, 0xa1, 0x3b, 0xf7, 0x63, 0x97, 0xb2, 0x09, 0xa9, 0x7e, 0x14, 0x20, + 0xf5, 0xf9, 0x32, 0x56, 0xe9, 0xbe, 0xf1, 0x9c, 0x20, 0xd2, 0x88, 0xa1, 0xb8, 0xe1, 0x86, 0xed, + 0x07, 0x6e, 0x88, 0x79, 0x8e, 0x4f, 0x18, 0x2c, 0xb4, 0x12, 0x39, 0x14, 0xbb, 0x04, 0xc8, 0x78, + 0x25, 0xe8, 0x2c, 0xd8, 0x0f, 0xc1, 0xd9, 0xc8, 0xdb, 0xf1, 0xf1, 0x2d, 0x26, 0xc7, 0xed, 0xec, + 0x04, 0xa1, 0x17, 0xef, 0x76, 0x9d, 0xa8, 0xef, 0xc5, 0x1c, 0xd3, 0xfa, 0x4c, 0x25, 0x67, 0xb6, + 0xa6, 0xc2, 0xab, 0x2b, 0xb4, 0xa6, 0xc0, 0xb2, 0xcf, 0x44, 0xc5, 0x05, 0xec, 0x6b, 0x30, 0xa9, + 0x8b, 0xe4, 0x68, 0xf6, 0xd4, 0xb9, 0xca, 0x85, 0xa9, 0x64, 0xab, 0xce, 0x8a, 0x70, 0xf5, 0x6e, + 0xbc, 0xb6, 0x47, 0x44, 0xe6, 0xbb, 0xf1, 0x1a, 0x2f, 0x66, 0xc3, 0x99, 0x48, 0xda, 0x17, 0xfb, + 0xbe, 0xf7, 0x10, 0xdf, 0x42, 0xa0, 0xd0, 0x58, 0x4c, 0xef, 0x93, 0x6e, 0x7d, 0x4d, 0xc4, 0xba, + 0x7b, 0x7b, 0xf5, 0x2b, 0x77, 0x23, 0x1e, 0x52, 0x84, 0xec, 0x49, 0x49, 0x7b, 0xd7, 0xf7, 0x1e, + 0xa6, 0x50, 0xf6, 0x9d, 0x12, 0x9c, 0xc6, 0x99, 0xb0, 0xb4, 0xb6, 0x2a, 0xc4, 0xdb, 0x3d, 0xcf, + 0x5d, 0x0c, 0x83, 0x07, 0x11, 0x0f, 0x67, 0x67, 0x07, 0xcc, 0xb4, 0xb7, 0x0b, 0x67, 0xda, 0x4b, + 0xf4, 0x14, 0x51, 0xc7, 0x43, 0x01, 0xea, 0xec, 0x79, 0xae, 0xb3, 0x25, 0x39, 0x19, 0x69, 0xfb, + 0x0a, 0xeb, 0x92, 0x36, 0xd4, 0x9b, 0x43, 0x55, 0x56, 0x3b, 0x71, 0x73, 0xa8, 0x7a, 0xa6, 0x36, + 0x6b, 0xcf, 0xd0, 0x92, 0xa4, 0x69, 0xb4, 0x7e, 0xad, 0x6e, 0x8f, 0x36, 0x56, 0xef, 0x35, 0x3b, + 0x41, 0x6c, 0xcf, 0x21, 0x17, 0xa2, 0x15, 0x9c, 0xb8, 0x1f, 0x7b, 0x32, 0xda, 0xc5, 0xda, 0x85, + 0x93, 0x45, 0x9f, 0xcf, 0x66, 0x33, 0xaa, 0x72, 0xaa, 0x13, 0x3f, 0x03, 0x63, 0xdb, 0x5e, 0x18, + 0xc5, 0x4e, 0xdf, 0x93, 0x8a, 0xcd, 0xb0, 0x5d, 0x45, 0xc0, 0x5d, 0xaf, 0xcd, 0xce, 0x42, 0x15, + 0x6f, 0xd0, 0xfb, 0x94, 0xd7, 0x7f, 0xd8, 0x1e, 0x15, 0xbf, 0xef, 0x7a, 0x6d, 0xeb, 0x3f, 0x29, + 0xe1, 0x5e, 0xc9, 0x5e, 0xc5, 0xd7, 0xb9, 0x12, 0x47, 0x62, 0xbc, 0xa8, 0x72, 0x7b, 0xbd, 0xcc, + 0xbb, 0x81, 0x88, 0xc2, 0x5e, 0x83, 0x91, 0x6b, 0x6e, 0x8b, 0x27, 0x3e, 0xda, 0x88, 0xbc, 0x8d, + 0x10, 0xfd, 0xe8, 0x24, 0x71, 0xc4, 0x39, 0x5a, 0xca, 0x90, 0x7a, 0x1c, 0xf3, 0x48, 0x0a, 0xfa, + 0xa5, 0xba, 0xf2, 0xcb, 0xc6, 0x73, 0x34, 0xc9, 0x1e, 0x37, 0x45, 0xc8, 0x64, 0x81, 0x29, 0xe4, + 0x60, 0xfd, 0xcb, 0x52, 0x2a, 0xfc, 0xd9, 0x2b, 0x30, 0x64, 0x37, 0x92, 0xf6, 0xcb, 0x5c, 0xc6, + 0x99, 0xe6, 0x23, 0x02, 0xfb, 0x1a, 0x9c, 0xd2, 0xf8, 0xe4, 0x52, 0xd2, 0xbc, 0x8c, 0xa9, 0x76, + 0xb5, 0x96, 0x14, 0xe7, 0xa5, 0x29, 0xe6, 0x81, 0x46, 0x83, 0xb4, 0x60, 0x99, 0xfb, 0x9e, 0xe4, + 0xad, 0x7d, 0xac, 0xce, 0xbb, 0x8d, 0x08, 0xd9, 0x8f, 0x2d, 0xe2, 0x20, 0x33, 0xed, 0x5a, 0xbf, + 0x53, 0x32, 0x84, 0x3a, 0x3b, 0x6f, 0x1c, 0x37, 0x50, 0x00, 0x65, 0xac, 0x87, 0xf2, 0xe0, 0xf1, + 0x45, 0x80, 0x7a, 0x3f, 0x0e, 0x56, 0xfc, 0x30, 0xe8, 0x74, 0xc8, 0x27, 0x56, 0x9e, 0x09, 0xfb, + 0x71, 0xe0, 0x70, 0x04, 0x1b, 0x39, 0x3c, 0x13, 0xe4, 0xc2, 0xec, 0x3d, 0x95, 0x8f, 0x9b, 0xbd, + 0x47, 0x1c, 0xa0, 0x0c, 0x39, 0xf6, 0x06, 0xa8, 0x05, 0xa1, 0xc7, 0x05, 0xf7, 0xbc, 0x3d, 0x27, + 0xea, 0x04, 0xc6, 0x2b, 0x1e, 0x84, 0xc6, 0xfe, 0x72, 0x09, 0x4e, 0xcb, 0x34, 0x38, 0xb7, 0xfb, + 0xdd, 0x2d, 0x1e, 0xde, 0x73, 0x3b, 0x5e, 0x3b, 0x7d, 0xb0, 0x31, 0x0d, 0x3c, 0xd7, 0xaa, 0x29, + 0xc6, 0x97, 0x16, 0x39, 0x99, 0x96, 0xc7, 0xf1, 0xb1, 0xd0, 0xd9, 0x4b, 0x4a, 0xf5, 0x05, 0x5f, + 0x4c, 0xcf, 0x56, 0x61, 0xbc, 0xe1, 0xf9, 0x4b, 0x6e, 0x6b, 0x97, 0xa7, 0x49, 0x32, 0x5f, 0x91, + 0x59, 0xad, 0xc4, 0x14, 0x6e, 0xed, 0xf2, 0x43, 0xf6, 0x18, 0x9d, 0xd6, 0xfa, 0xcd, 0x12, 0xbc, + 0x70, 0x64, 0x83, 0xc5, 0x51, 0x79, 0xe5, 0x58, 0x47, 0x65, 0xc2, 0x62, 0x5f, 0x87, 0x53, 0x3a, + 0xab, 0x8d, 0xd0, 0xf5, 0xf4, 0x20, 0xd0, 0x82, 0x0e, 0x88, 0x05, 0x4a, 0x56, 0xbf, 0x2e, 0x66, + 0x62, 0xfd, 0xdf, 0x25, 0x18, 0x4b, 0xc2, 0x36, 0x9f, 0xd2, 0x73, 0xd7, 0x55, 0xe3, 0xdc, 0xa5, + 0x5e, 0x0c, 0x4e, 0xbe, 0x4a, 0x3a, 0x36, 0x16, 0x5c, 0xfe, 0x4c, 0x6b, 0x49, 0x4b, 0x10, 0xf0, + 0x97, 0xca, 0x30, 0x2e, 0x44, 0xb5, 0x74, 0x7e, 0xf9, 0xc1, 0x7a, 0xff, 0x33, 0xf9, 0xae, 0xc1, + 0x8f, 0x1d, 0x6a, 0x27, 0xd0, 0xff, 0xb9, 0x84, 0x97, 0xa2, 0x3a, 0x85, 0xe8, 0x0d, 0x01, 0xd2, + 0x7b, 0x43, 0x6c, 0xfd, 0x36, 0x42, 0xe5, 0x23, 0x6f, 0x6b, 0xd4, 0x13, 0xf4, 0xc8, 0x5b, 0xc7, + 0x16, 0x30, 0xf6, 0x3e, 0x0c, 0xdf, 0xc5, 0x2b, 0x1e, 0xf3, 0xc5, 0x83, 0x84, 0x3f, 0x16, 0x4a, + 0x79, 0xdf, 0x8f, 0xcc, 0x17, 0x00, 0x25, 0x21, 0x6b, 0xc2, 0xe8, 0x52, 0xc8, 0xdd, 0x98, 0xb7, + 0xa9, 0x43, 0x8e, 0x95, 0x44, 0xbb, 0x25, 0x49, 0xb2, 0x49, 0xb4, 0x89, 0x93, 0x90, 0x63, 0x2c, + 0xfd, 0x46, 0xf4, 0x09, 0x8c, 0x9e, 0xda, 0x41, 0x7f, 0xcf, 0x18, 0xf4, 0xe7, 0x72, 0x83, 0x2e, + 0x3f, 0xef, 0x58, 0x63, 0xff, 0x7b, 0x25, 0x38, 0x5d, 0x4c, 0xc8, 0x5e, 0x84, 0x91, 0x3b, 0x1b, + 0x8d, 0xd4, 0x0f, 0x17, 0x3f, 0x25, 0xe8, 0xa1, 0xf5, 0xca, 0xa6, 0x22, 0xf6, 0x3a, 0x8c, 0x7c, + 0x60, 0x2f, 0xa5, 0xee, 0xa6, 0x28, 0xe0, 0x3e, 0x12, 0x2a, 0xa2, 0xb1, 0xab, 0x11, 0x92, 0x3e, + 0xb6, 0x95, 0x27, 0x36, 0xb6, 0x3f, 0x53, 0x86, 0xe9, 0x7a, 0xab, 0xc5, 0xa3, 0x48, 0xa8, 0x6f, + 0x3c, 0x8a, 0x9f, 0xda, 0x81, 0x2d, 0x7e, 0xa3, 0xc2, 0xf8, 0xb6, 0x63, 0x8d, 0xea, 0x3f, 0x2a, + 0xc9, 0x97, 0x7e, 0x04, 0xd5, 0x9e, 0xc7, 0x1f, 0x24, 0x17, 0x12, 0xc9, 0x83, 0x96, 0xa5, 0x23, + 0x1e, 0xb4, 0x14, 0x3a, 0xa3, 0xd7, 0x89, 0xd5, 0x7b, 0x5a, 0xa4, 0x33, 0x22, 0xc4, 0xd0, 0x19, + 0x11, 0x22, 0xf6, 0xb9, 0x7a, 0xaf, 0x17, 0x06, 0x7b, 0x72, 0xd9, 0xd3, 0x1b, 0x9f, 0xae, 0x04, + 0x19, 0x69, 0xc3, 0x25, 0x48, 0x34, 0x63, 0x99, 0xfb, 0xfb, 0xba, 0xf3, 0x6b, 0x9b, 0xfb, 0xfa, + 0xe9, 0x09, 0xcb, 0xad, 0x26, 0xb0, 0x46, 0x18, 0x74, 0x83, 0x98, 0xb7, 0xe5, 0xf7, 0x60, 0xb6, + 0xf5, 0x23, 0x1f, 0x2d, 0xdb, 0xf0, 0xe2, 0x8e, 0xf1, 0x68, 0x59, 0x2c, 0x00, 0xb6, 0x84, 0x5b, + 0x3f, 0x57, 0x81, 0xe7, 0x8c, 0x3e, 0x5d, 0x0e, 0xf7, 0xed, 0xbe, 0xbf, 0xe2, 0x87, 0x5e, 0x6b, + 0x17, 0x73, 0x3b, 0xdd, 0x06, 0xb0, 0xb9, 0x1b, 0x05, 0xbe, 0xa6, 0xac, 0x5d, 0x12, 0xea, 0x57, + 0x88, 0xd0, 0xbc, 0x81, 0x64, 0x86, 0x38, 0xa5, 0x54, 0xb6, 0xc6, 0x81, 0xd5, 0x61, 0x52, 0xfe, + 0x12, 0x1f, 0xd3, 0x4b, 0x14, 0xf1, 0x67, 0x64, 0xa6, 0x25, 0x64, 0xd9, 0xc3, 0x12, 0x33, 0x01, + 0xa4, 0x46, 0xc1, 0xbe, 0x0d, 0x13, 0x62, 0x9d, 0x2e, 0x7b, 0x51, 0xaf, 0xe3, 0x26, 0xa6, 0xfe, + 0xab, 0x45, 0x53, 0x24, 0xfb, 0x39, 0x97, 0x74, 0x42, 0xf9, 0x26, 0xe6, 0x3c, 0x4d, 0x9f, 0x33, + 0x78, 0x9e, 0x6b, 0x53, 0x99, 0xee, 0xad, 0xad, 0xd3, 0xcc, 0x7d, 0x0b, 0x66, 0x72, 0x3c, 0x0a, + 0x5e, 0xe7, 0x7b, 0xcb, 0x7c, 0x9d, 0x6f, 0xbe, 0xa8, 0x71, 0x1a, 0x1f, 0xfd, 0x9d, 0xbe, 0x1f, + 0x2f, 0xc1, 0xec, 0x20, 0x3c, 0x31, 0xbd, 0x1a, 0xa1, 0xd7, 0x75, 0x43, 0x25, 0x8a, 0x70, 0x7a, + 0xf5, 0x24, 0xc8, 0xd0, 0x39, 0x25, 0x88, 0xbd, 0x05, 0x63, 0xf2, 0x14, 0xeb, 0x26, 0x29, 0xff, + 0x64, 0xf2, 0x64, 0x05, 0x34, 0x92, 0x27, 0x2b, 0xa0, 0xf5, 0x7f, 0x8d, 0xc0, 0x84, 0xbe, 0x6c, + 0x98, 0x05, 0x23, 0xf2, 0x59, 0x12, 0x3d, 0x96, 0xcd, 0x45, 0x88, 0x4d, 0x25, 0xe9, 0xab, 0x2c, + 0xe5, 0x23, 0x5f, 0x65, 0xd9, 0x84, 0xc9, 0x46, 0x18, 0xe0, 0x2b, 0xb5, 0xe8, 0xfa, 0x43, 0x7b, + 0xe4, 0x09, 0xcd, 0x84, 0x24, 0x56, 0x38, 0x3a, 0x17, 0xe1, 0x21, 0xbd, 0x47, 0xd8, 0x8e, 0x38, + 0x5e, 0x18, 0x06, 0x54, 0x83, 0x8f, 0xf4, 0x5b, 0x14, 0xb3, 0x45, 0x7f, 0x46, 0x5e, 0x4e, 0x2c, + 0xd3, 0x6f, 0x51, 0x40, 0x74, 0x21, 0x3c, 0xfc, 0xa4, 0x84, 0x30, 0xfb, 0xc5, 0x12, 0x8c, 0xd7, + 0x7d, 0x9f, 0x5e, 0x7b, 0x39, 0x22, 0x8b, 0xfb, 0xd7, 0xc9, 0x75, 0xf1, 0x9d, 0x8f, 0xe5, 0xba, + 0x88, 0x0a, 0x6d, 0x84, 0xa7, 0xa1, 0xb4, 0x42, 0x23, 0xb7, 0x71, 0x0a, 0x66, 0xef, 0x40, 0x2d, + 0x91, 0x7e, 0xab, 0x7e, 0x9b, 0x3f, 0xe4, 0x11, 0x46, 0xc6, 0x4d, 0xd2, 0x2b, 0xfe, 0xfa, 0xe9, + 0x27, 0x8b, 0xc8, 0x36, 0x00, 0xdc, 0x44, 0xec, 0x90, 0x47, 0xc5, 0xd9, 0xf4, 0xf6, 0x3b, 0x23, + 0x97, 0xd4, 0xad, 0x9d, 0xf8, 0x8d, 0xde, 0x21, 0xe6, 0xad, 0x5d, 0x22, 0xbe, 0xba, 0x30, 0x5d, + 0x8f, 0xa2, 0x7e, 0x97, 0x37, 0x63, 0x37, 0x8c, 0x45, 0x57, 0x93, 0x29, 0xf4, 0x58, 0x8e, 0xfc, + 0x2e, 0x92, 0x8a, 0x19, 0x11, 0xc6, 0xe8, 0x3d, 0x90, 0x75, 0xe4, 0xcf, 0xf0, 0x66, 0xef, 0xc2, + 0x78, 0xb3, 0xbf, 0xd5, 0xf5, 0xe2, 0x98, 0xb7, 0x17, 0xf7, 0x29, 0xd4, 0x53, 0x25, 0x49, 0x91, + 0x60, 0x67, 0xcb, 0x30, 0x5c, 0x69, 0xe8, 0xec, 0x7d, 0x78, 0x2e, 0x45, 0x0b, 0x7c, 0x67, 0x8b, + 0xef, 0xba, 0x9d, 0x6d, 0x27, 0xd8, 0x76, 0xe4, 0x3a, 0x90, 0xce, 0x12, 0xf6, 0xd9, 0x04, 0xe9, + 0x8e, 0xbf, 0x88, 0x28, 0x77, 0xb6, 0xe5, 0x02, 0xb9, 0x39, 0x54, 0xad, 0xd6, 0xc6, 0xec, 0x33, + 0xf9, 0xfe, 0x92, 0xd2, 0xf8, 0x67, 0x4a, 0x70, 0x5a, 0x5f, 0x74, 0x58, 0x39, 0x9a, 0x5f, 0xd8, + 0x25, 0x18, 0xa3, 0x35, 0x91, 0x18, 0x0a, 0xd0, 0x8b, 0xda, 0x8c, 0x73, 0x4e, 0x50, 0xd8, 0x8a, + 0x58, 0x06, 0x82, 0x07, 0x09, 0xa0, 0x13, 0x19, 0x01, 0x24, 0x8a, 0xf0, 0xf2, 0xf8, 0x33, 0x72, + 0x7d, 0x88, 0xdf, 0xe6, 0xfa, 0x10, 0x10, 0xeb, 0xcb, 0x30, 0x63, 0xce, 0x84, 0x26, 0x8f, 0xd9, + 0x45, 0x18, 0x55, 0xd3, 0xa7, 0x54, 0x3c, 0x7d, 0x54, 0xb9, 0xb5, 0x09, 0x2c, 0x47, 0x1f, 0xa1, + 0x8f, 0x33, 0x8f, 0xd5, 0x3d, 0x9a, 0xf2, 0x30, 0xca, 0x21, 0x2e, 0x9e, 0xa0, 0xf6, 0x8d, 0x1b, + 0x69, 0x90, 0xf0, 0xa5, 0xe5, 0x9f, 0x3e, 0x01, 0x27, 0x0a, 0x94, 0x81, 0x23, 0x94, 0xf5, 0x79, + 0x53, 0x40, 0x8d, 0x25, 0x8f, 0x50, 0x28, 0xb1, 0xf4, 0x65, 0x18, 0x3e, 0x52, 0x1c, 0xc9, 0xf4, + 0x5a, 0x19, 0x29, 0x24, 0xc9, 0x3e, 0x15, 0x85, 0x5d, 0x7f, 0x4a, 0x67, 0xf8, 0x89, 0x3d, 0xa5, + 0x83, 0x99, 0x98, 0xb5, 0x8d, 0xda, 0xcc, 0x0e, 0x8d, 0x05, 0x4e, 0x4e, 0x6c, 0x9a, 0x24, 0x92, + 0x47, 0x14, 0x74, 0xf6, 0x38, 0xf1, 0x18, 0xd5, 0x79, 0x60, 0x41, 0x21, 0x0f, 0x8d, 0x84, 0xfd, + 0x7a, 0x09, 0x18, 0x41, 0x74, 0x99, 0x59, 0x3d, 0x4c, 0x66, 0xb6, 0x9f, 0x8c, 0xcc, 0x7c, 0x4e, + 0xb5, 0xb1, 0x58, 0x76, 0x16, 0x34, 0x8b, 0xfd, 0xad, 0x12, 0xcc, 0xc8, 0xc7, 0x4a, 0xf4, 0xc6, + 0x1e, 0xfa, 0x00, 0x45, 0xeb, 0xc9, 0x34, 0xf6, 0xd9, 0x08, 0xab, 0x1d, 0xd0, 0xd6, 0x7c, 0xa3, + 0xd8, 0x0f, 0x01, 0x24, 0x2b, 0x4a, 0xbe, 0x36, 0x3d, 0xbe, 0xf0, 0x6c, 0x81, 0x14, 0x48, 0x90, + 0xf0, 0xce, 0x5c, 0x4c, 0x9d, 0x93, 0x89, 0x5f, 0x8f, 0xe1, 0x6c, 0x91, 0x72, 0x63, 0x3f, 0x26, + 0x1f, 0x0b, 0x4d, 0x20, 0xf4, 0xfa, 0xd4, 0xec, 0x38, 0xd6, 0xf2, 0xb9, 0xc1, 0xca, 0xfa, 0xa5, + 0x22, 0x32, 0xa9, 0x87, 0x25, 0x11, 0x37, 0x61, 0xdc, 0xcd, 0x3e, 0x18, 0x9a, 0xa5, 0xc0, 0x47, + 0xdd, 0xb0, 0xf5, 0xd1, 0xec, 0x04, 0xd6, 0x59, 0x28, 0xdf, 0xce, 0xaa, 0xb5, 0x20, 0xe5, 0x5b, + 0x26, 0x7b, 0x06, 0x82, 0xd8, 0x07, 0xc0, 0x92, 0x57, 0x3e, 0x24, 0x8c, 0x87, 0xf2, 0xe9, 0x6a, + 0x0a, 0x8f, 0x4c, 0x5f, 0x0b, 0x09, 0x55, 0xb1, 0x91, 0x67, 0x2c, 0x47, 0xcc, 0x38, 0x9c, 0xa4, + 0x8f, 0x16, 0x50, 0x99, 0x1f, 0x6b, 0x75, 0x39, 0x9a, 0x9d, 0x32, 0xde, 0xf6, 0x4a, 0x4b, 0x16, + 0x9f, 0xa7, 0x76, 0x9e, 0x4e, 0x12, 0x6d, 0x99, 0x39, 0xa8, 0x0a, 0xd9, 0xb1, 0xab, 0x30, 0x86, + 0x69, 0x54, 0x6f, 0x28, 0xcf, 0x6d, 0xf2, 0x22, 0xc5, 0x84, 0xab, 0xce, 0xae, 0xe9, 0x7f, 0x9d, + 0xa2, 0x8a, 0x73, 0xaa, 0x54, 0x8b, 0xf1, 0x7e, 0x89, 0x0c, 0x71, 0xed, 0x70, 0xdf, 0x09, 0xfb, + 0x66, 0x8a, 0x5e, 0x44, 0x62, 0xdf, 0x82, 0xf1, 0x75, 0xf7, 0xa1, 0x32, 0xfd, 0xd1, 0x1d, 0xd2, + 0x51, 0x21, 0xb0, 0xf8, 0x35, 0x5d, 0xf7, 0xa1, 0xd3, 0xee, 0x67, 0x5f, 0x3a, 0x90, 0x21, 0xb0, + 0x1a, 0x4b, 0xf6, 0x0d, 0x00, 0xed, 0xd2, 0x8b, 0x1d, 0x59, 0xc1, 0x0b, 0xea, 0xc5, 0xba, 0xc2, + 0xcb, 0x30, 0xe4, 0xaf, 0x31, 0xcc, 0x68, 0x2e, 0x27, 0x3f, 0x3d, 0xcd, 0xe5, 0xd4, 0xa7, 0xa8, + 0xb9, 0x7c, 0x2b, 0xcd, 0xcf, 0x86, 0x12, 0x7c, 0x9f, 0xae, 0x9c, 0x0e, 0xab, 0x4d, 0xf9, 0xc0, + 0xd4, 0x70, 0x2b, 0xd8, 0xcf, 0x54, 0x91, 0xe1, 0xc7, 0x42, 0xa8, 0x65, 0x4f, 0x4b, 0xf4, 0xf0, + 0xc4, 0x4b, 0xc7, 0x39, 0x59, 0x49, 0x93, 0x3a, 0x4d, 0x23, 0x87, 0x27, 0x70, 0x5d, 0xa9, 0xcc, + 0x1d, 0x2e, 0xef, 0xc1, 0x38, 0xb1, 0x43, 0x03, 0xc4, 0xac, 0xe1, 0x71, 0x6c, 0x54, 0x27, 0xca, + 0xc9, 0x79, 0x8b, 0x36, 0xa7, 0x8c, 0x79, 0x42, 0x67, 0xc4, 0xba, 0x50, 0x5b, 0x0b, 0xfc, 0x9d, + 0x0d, 0x1e, 0x76, 0x31, 0x31, 0x9d, 0x90, 0x4d, 0x67, 0x8d, 0x83, 0x98, 0x2a, 0x36, 0xf2, 0xd7, + 0x79, 0xfe, 0x8e, 0xfc, 0x8c, 0x4e, 0xe0, 0xef, 0x38, 0x31, 0x0f, 0xbb, 0x32, 0xe3, 0x9d, 0xe9, + 0xe4, 0x9a, 0x63, 0xcd, 0xfe, 0x1c, 0xcc, 0xe5, 0x56, 0xa8, 0x6c, 0xbd, 0x58, 0xf6, 0x73, 0x86, + 0xb7, 0x50, 0xb6, 0x1c, 0x23, 0x45, 0x3f, 0xa3, 0x76, 0x23, 0x5c, 0xfc, 0x34, 0xf9, 0x4c, 0x19, + 0x70, 0x48, 0x0d, 0x73, 0x5b, 0x70, 0x76, 0xa0, 0x5c, 0x2d, 0x38, 0x9b, 0x5e, 0x36, 0xcf, 0xa6, + 0x67, 0x07, 0xe9, 0x5f, 0x91, 0x76, 0x2a, 0xbd, 0x39, 0x54, 0x3d, 0x51, 0x3b, 0x39, 0x58, 0x75, + 0xfd, 0xc3, 0x72, 0x46, 0x1f, 0x23, 0x73, 0xc8, 0x39, 0x28, 0x1f, 0xa2, 0xb0, 0x96, 0x57, 0x97, + 0xd9, 0x79, 0xd2, 0xd8, 0xca, 0xa9, 0x19, 0xa6, 0x6f, 0x5e, 0x5a, 0x4a, 0xdd, 0xed, 0x93, 0xaa, + 0x66, 0xef, 0xc2, 0x54, 0x93, 0xbb, 0x61, 0x6b, 0xf7, 0x16, 0xdf, 0x7f, 0x10, 0x84, 0x6d, 0x95, + 0x2b, 0x49, 0xbe, 0x77, 0x87, 0x25, 0x66, 0x3e, 0x4a, 0x1d, 0x97, 0x2d, 0xab, 0x14, 0xab, 0xc3, + 0x58, 0xfb, 0xd9, 0xc2, 0x2d, 0x4e, 0x20, 0x1c, 0x96, 0x7d, 0x55, 0x1c, 0xc6, 0xd5, 0x30, 0xaa, + 0x7c, 0xf4, 0x14, 0xba, 0x4d, 0xc0, 0x02, 0x65, 0x9e, 0x87, 0xd6, 0xf7, 0x2a, 0xc0, 0x64, 0x4d, + 0x4b, 0x6e, 0xcf, 0xc5, 0xa4, 0xc6, 0x1e, 0x3e, 0x10, 0x55, 0x23, 0x1c, 0x77, 0xab, 0xc3, 0xf5, + 0xd7, 0xd5, 0x28, 0x8c, 0x2a, 0x29, 0x73, 0xb2, 0xa7, 0xf0, 0x1c, 0xe1, 0x80, 0x7d, 0xb0, 0xfc, + 0x49, 0xf6, 0xc1, 0x6f, 0xc1, 0x33, 0xf5, 0x5e, 0xaf, 0xe3, 0xb5, 0x92, 0x5a, 0xae, 0x05, 0xa1, + 0x9a, 0xbd, 0x46, 0x0e, 0x44, 0x37, 0x41, 0xcb, 0xb5, 0xf4, 0x30, 0x16, 0x9a, 0x12, 0x2b, 0x6d, + 0x43, 0x7a, 0xfa, 0x75, 0x25, 0x27, 0x8a, 0xac, 0x49, 0x1a, 0x89, 0xe2, 0xe1, 0x85, 0x4a, 0x89, + 0x1d, 0x4e, 0x5f, 0x00, 0x57, 0x5e, 0x2b, 0xc5, 0x8a, 0x70, 0x42, 0x22, 0xce, 0x95, 0xf5, 0x7e, + 0x1c, 0x10, 0x63, 0x8a, 0xff, 0x4b, 0x23, 0xf5, 0xa8, 0x29, 0xc6, 0xb9, 0x3c, 0x45, 0xb7, 0xfe, + 0x60, 0x08, 0xce, 0xe6, 0x87, 0x97, 0x4a, 0x93, 0xf5, 0x51, 0x3a, 0x62, 0x7d, 0x14, 0xcd, 0x06, + 0x79, 0x5b, 0xfa, 0xc4, 0x66, 0x83, 0x4c, 0xa8, 0xfc, 0x31, 0x67, 0x43, 0x53, 0xc8, 0xfa, 0x54, + 0x19, 0x1a, 0xfa, 0xb8, 0xca, 0x90, 0xce, 0x85, 0x5d, 0x84, 0x61, 0x99, 0x75, 0x7e, 0x38, 0xbd, + 0x3b, 0xcf, 0x26, 0x9c, 0x97, 0x18, 0xec, 0xcf, 0xc1, 0x39, 0x29, 0x93, 0xb2, 0x1f, 0xbb, 0xb8, + 0xaf, 0x38, 0xd2, 0xc0, 0x2d, 0x3c, 0x3a, 0x98, 0xbf, 0x24, 0x0d, 0xbc, 0x4e, 0xae, 0xdb, 0x9c, + 0xad, 0x7d, 0x47, 0xb5, 0x4c, 0xab, 0xe4, 0x48, 0xde, 0xcc, 0x87, 0x13, 0x05, 0x12, 0x9e, 0x52, + 0x13, 0x7d, 0xd2, 0xdd, 0x61, 0x26, 0x34, 0x09, 0xdb, 0x91, 0xf5, 0x23, 0x30, 0x2b, 0xdf, 0x7b, + 0x2d, 0x90, 0x1c, 0x47, 0xac, 0xcc, 0xd2, 0x27, 0x5e, 0x99, 0x62, 0x3f, 0x98, 0x1f, 0x54, 0xfd, + 0xe3, 0xce, 0xec, 0x9b, 0x30, 0x29, 0xa5, 0x71, 0x3d, 0xd2, 0x4f, 0xef, 0xe8, 0x92, 0x20, 0x9f, + 0xb6, 0x75, 0xa4, 0xfc, 0x76, 0xdc, 0x28, 0xd7, 0x4a, 0x93, 0x34, 0x3b, 0x0b, 0x2b, 0x4f, 0x64, + 0x16, 0x0e, 0x18, 0xda, 0xa1, 0x4f, 0x6b, 0x68, 0x1f, 0xc2, 0x59, 0xa5, 0xed, 0x24, 0x99, 0xa7, + 0x93, 0x79, 0x76, 0x1e, 0xf0, 0x7a, 0x47, 0xef, 0xd5, 0x8c, 0xaa, 0x84, 0xe5, 0xec, 0x0a, 0x54, + 0xeb, 0x8d, 0x55, 0xd4, 0x61, 0x74, 0xbb, 0xb0, 0xdb, 0xf3, 0xa4, 0xd2, 0x63, 0xe4, 0x07, 0x23, + 0x44, 0xeb, 0x27, 0xca, 0x30, 0xd1, 0xe8, 0xf4, 0x77, 0x3c, 0x7f, 0xd9, 0x8d, 0xdd, 0xa7, 0xf6, + 0x72, 0xe9, 0x8b, 0xc6, 0xe5, 0x52, 0x92, 0x04, 0x22, 0xf9, 0xb0, 0x63, 0xdd, 0x2c, 0xfd, 0x42, + 0x09, 0xa6, 0x53, 0x12, 0xa9, 0x6c, 0xdd, 0x80, 0x21, 0xf1, 0x83, 0x2c, 0x5b, 0xe7, 0x72, 0x8c, + 0x11, 0xeb, 0x52, 0xf2, 0x17, 0x5d, 0xf7, 0xb8, 0xa6, 0xdf, 0x9d, 0x28, 0x9e, 0xfb, 0x3c, 0x8c, + 0xa5, 0x6c, 0xf3, 0x3a, 0xdc, 0x49, 0x5d, 0x87, 0x1b, 0xd3, 0xaf, 0x0f, 0xfe, 0x6e, 0x09, 0x6a, + 0xd9, 0x2f, 0x61, 0xb7, 0x60, 0x54, 0x70, 0xf2, 0x78, 0x94, 0xcd, 0xe8, 0x98, 0xc1, 0xbc, 0x44, + 0x68, 0xb2, 0x79, 0xd8, 0xf9, 0x5c, 0x42, 0x6c, 0xc5, 0x61, 0xce, 0xc6, 0x57, 0x69, 0x12, 0xac, + 0x82, 0xd6, 0xbd, 0x66, 0x6a, 0x98, 0xa7, 0x8b, 0xfb, 0x41, 0x6f, 0xf5, 0xbf, 0x6b, 0xb4, 0x9a, + 0x94, 0xc7, 0xf3, 0xc6, 0xe4, 0x2a, 0x9c, 0xca, 0x38, 0x69, 0x16, 0xa0, 0x9a, 0x88, 0xf0, 0xb2, + 0xfe, 0x70, 0x4b, 0x4e, 0x4c, 0x27, 0x78, 0xec, 0x35, 0x18, 0x91, 0xf5, 0xe9, 0x99, 0xaa, 0x7b, + 0x08, 0xd1, 0x0f, 0xc1, 0x12, 0xc7, 0xfa, 0xd5, 0x0a, 0x9c, 0x4e, 0x9b, 0x77, 0xb7, 0xd7, 0x76, + 0x63, 0xde, 0x70, 0x43, 0xb7, 0x1b, 0x1d, 0xb1, 0x02, 0x2e, 0xe4, 0x9a, 0x46, 0xa9, 0xa6, 0x24, + 0x4c, 0x6b, 0x90, 0x95, 0x69, 0x10, 0x5e, 0xb0, 0xc8, 0x06, 0xa9, 0x66, 0xb0, 0x5b, 0x50, 0x69, + 0xf2, 0x98, 0x04, 0xcb, 0xf9, 0x5c, 0xaf, 0xea, 0xed, 0xba, 0xd4, 0xe4, 0xb1, 0x1c, 0x44, 0xf9, + 0x24, 0x8e, 0x91, 0xd4, 0x49, 0x70, 0x61, 0x9b, 0x30, 0xb2, 0xf2, 0xb0, 0xc7, 0x5b, 0x31, 0x3e, + 0xa2, 0xaf, 0x65, 0x37, 0x2a, 0xe6, 0x27, 0x71, 0x25, 0xcb, 0x93, 0x74, 0xaa, 0xe4, 0x2d, 0x23, + 0xad, 0xb7, 0x44, 0x99, 0xbb, 0x0a, 0x55, 0x55, 0xf9, 0xe3, 0xcc, 0xdc, 0xb9, 0x2f, 0xc2, 0xb8, + 0x56, 0xc9, 0x63, 0x4d, 0xfa, 0x5f, 0x2b, 0x01, 0x08, 0x01, 0x4f, 0x13, 0x67, 0x25, 0xa7, 0xeb, + 0x97, 0xd2, 0x6c, 0xe6, 0xb4, 0x57, 0xdc, 0xa7, 0xa2, 0x43, 0x94, 0xfe, 0x55, 0x98, 0x6e, 0xde, + 0xf7, 0x7a, 0xe9, 0xeb, 0xc4, 0x86, 0x46, 0x15, 0xdd, 0xf7, 0x7a, 0x0e, 0x59, 0xe5, 0xb2, 0xbb, + 0x4e, 0x96, 0xce, 0xfa, 0x7f, 0x4a, 0x30, 0x22, 0xfe, 0xba, 0x77, 0xf5, 0x29, 0x15, 0x99, 0x57, + 0x0c, 0x91, 0x99, 0xec, 0x98, 0x41, 0x47, 0xc6, 0x68, 0x5c, 0x3d, 0x42, 0x58, 0x1e, 0xd0, 0x00, + 0x49, 0x64, 0x76, 0x1d, 0x46, 0xc9, 0xf5, 0x98, 0xa2, 0x34, 0x99, 0xc6, 0x50, 0x39, 0x25, 0x27, + 0xe6, 0xbb, 0xa0, 0x97, 0xb5, 0x77, 0x2a, 0x6a, 0x71, 0x2e, 0x53, 0xef, 0x44, 0x4b, 0x1b, 0x6c, + 0xca, 0x26, 0xcd, 0x76, 0xb7, 0x78, 0x86, 0x38, 0x0d, 0xca, 0x15, 0x5d, 0xa7, 0x3b, 0xf8, 0xca, + 0x61, 0x4c, 0x4e, 0x13, 0x93, 0xe2, 0xeb, 0xf9, 0x2e, 0x9c, 0x6e, 0x36, 0x6f, 0x60, 0x98, 0x42, + 0x23, 0x08, 0xe3, 0x6b, 0x41, 0xf8, 0x80, 0x72, 0xd4, 0x35, 0x4d, 0xcf, 0xb7, 0x22, 0x97, 0xde, + 0x57, 0x0a, 0x5d, 0x7a, 0x0f, 0xf1, 0x8e, 0xb3, 0x7c, 0x38, 0xd3, 0x6c, 0xde, 0x90, 0x1a, 0xd6, + 0xbf, 0x8e, 0xfa, 0xfe, 0x6e, 0x09, 0x66, 0x9a, 0xcd, 0x1b, 0x99, 0xaa, 0xd6, 0x84, 0xfa, 0xdd, + 0x72, 0x3b, 0x54, 0x91, 0x72, 0xbf, 0x29, 0xee, 0x08, 0x1c, 0x85, 0x92, 0xd4, 0xd0, 0x5b, 0xc6, + 0x6b, 0x82, 0x92, 0x09, 0x6b, 0xc0, 0x88, 0xfc, 0x20, 0x1a, 0xcc, 0xe7, 0x53, 0x76, 0x45, 0x1f, + 0x9a, 0xc6, 0x3e, 0x4a, 0x25, 0xd0, 0xbc, 0xbe, 0x12, 0x10, 0xeb, 0xf7, 0xcf, 0xc0, 0xb8, 0x36, + 0xa3, 0xd8, 0x97, 0x60, 0x82, 0xe8, 0x31, 0xba, 0x95, 0x3c, 0x11, 0xcf, 0x0a, 0x01, 0xb9, 0x2d, + 0xe1, 0xf2, 0x49, 0xe8, 0xef, 0x1f, 0xcc, 0x0f, 0x89, 0xae, 0xb1, 0x0d, 0x74, 0x76, 0x07, 0x26, + 0xd7, 0xdd, 0x87, 0x9a, 0xad, 0x52, 0x26, 0x2f, 0xb8, 0x28, 0xa4, 0x4a, 0xd7, 0x7d, 0x78, 0x0c, + 0xe7, 0x7c, 0x93, 0x9e, 0xdd, 0x87, 0x29, 0xf3, 0x9b, 0x68, 0x06, 0xe6, 0x47, 0xec, 0xcd, 0xc2, + 0x11, 0x3b, 0xdb, 0x0b, 0xc2, 0xd8, 0xd9, 0x4e, 0xc8, 0xd3, 0x5a, 0x67, 0x4b, 0x76, 0x86, 0x35, + 0xfb, 0x12, 0xcc, 0x68, 0x6f, 0x36, 0x5e, 0x0b, 0xc2, 0xae, 0xab, 0x0e, 0xcc, 0x78, 0x81, 0x87, + 0xce, 0xb0, 0xdb, 0x08, 0xb6, 0xf3, 0x98, 0xec, 0x6b, 0x45, 0x09, 0x21, 0x86, 0xd3, 0x08, 0x85, + 0x82, 0x84, 0x10, 0x83, 0x22, 0x14, 0xf2, 0xa9, 0x21, 0x76, 0x0e, 0x8b, 0x60, 0xaa, 0xca, 0xaf, + 0x3f, 0x56, 0x84, 0x52, 0x32, 0x72, 0x03, 0x22, 0x95, 0x16, 0xa0, 0xb2, 0xd8, 0xb8, 0x86, 0xa7, + 0x2e, 0xe5, 0x05, 0xec, 0xef, 0xba, 0x7e, 0x0b, 0x0f, 0xb2, 0x14, 0xfe, 0xa7, 0x6f, 0x94, 0x8b, + 0x8d, 0x6b, 0xcc, 0x85, 0x13, 0x0d, 0x1e, 0x76, 0xbd, 0xf8, 0x2b, 0x6f, 0xbe, 0xa9, 0x0d, 0x55, + 0x15, 0x9b, 0x76, 0x99, 0x9a, 0x36, 0xdf, 0x43, 0x14, 0xe7, 0xe1, 0x9b, 0x6f, 0x16, 0x0e, 0x48, + 0xd2, 0xb0, 0x22, 0x5e, 0x62, 0xc3, 0x5a, 0x77, 0x1f, 0xa6, 0xd1, 0xd6, 0x11, 0xa5, 0xd6, 0x79, + 0x4e, 0x4d, 0xad, 0x34, 0x52, 0xdb, 0xd8, 0xb0, 0x4c, 0x22, 0xf6, 0x2e, 0xda, 0xea, 0x55, 0x2a, + 0x25, 0x4a, 0x4a, 0x30, 0xa7, 0x6c, 0xf1, 0x2a, 0xff, 0x92, 0x7e, 0x8c, 0xd1, 0xd0, 0xd9, 0xdd, + 0xc4, 0x9a, 0x22, 0x8f, 0x1a, 0x74, 0x3f, 0x7e, 0x59, 0xb7, 0xa6, 0xc8, 0x23, 0x8a, 0xf1, 0x59, + 0xd3, 0x89, 0x09, 0x4e, 0x86, 0x9f, 0xdb, 0x26, 0x97, 0xbc, 0x91, 0x66, 0xe2, 0xf1, 0x8d, 0x34, + 0x1c, 0x86, 0xd6, 0x82, 0xd6, 0x7d, 0x7a, 0x97, 0xec, 0x03, 0x21, 0x85, 0x3b, 0x41, 0xeb, 0xfe, + 0x93, 0x8b, 0xcc, 0x42, 0xf6, 0xec, 0xb6, 0xcc, 0x48, 0x18, 0xb6, 0xa9, 0x4f, 0x28, 0xda, 0xe7, + 0x64, 0x72, 0x84, 0xd3, 0xca, 0xd2, 0x3c, 0x85, 0x61, 0x5b, 0x75, 0xad, 0x6d, 0x92, 0x33, 0x0e, + 0xb5, 0x65, 0x1e, 0xdd, 0x8f, 0x83, 0xde, 0x52, 0xc7, 0xeb, 0x61, 0x92, 0x4f, 0x7a, 0x3f, 0xfc, + 0xd8, 0x32, 0xb9, 0x2d, 0xe9, 0x9d, 0x96, 0x62, 0x60, 0xe7, 0x58, 0xb2, 0xaf, 0xc1, 0x94, 0x98, + 0xdc, 0x2b, 0x0f, 0x63, 0xee, 0xcb, 0x91, 0x9f, 0x41, 0x8d, 0x4e, 0xb5, 0xdb, 0x28, 0x94, 0x73, + 0x0a, 0x17, 0x3b, 0x4f, 0x08, 0x8c, 0x37, 0xdd, 0x0c, 0x56, 0xac, 0x0d, 0xb3, 0xeb, 0xee, 0xc3, + 0xf4, 0xa0, 0xa9, 0x4f, 0x52, 0x86, 0x13, 0xec, 0xc2, 0xa3, 0x83, 0xf9, 0x97, 0xc4, 0x04, 0x4b, + 0x9f, 0xb4, 0x1f, 0x30, 0x5f, 0x07, 0x72, 0x62, 0xdf, 0x86, 0x33, 0xf4, 0x59, 0xcb, 0x5e, 0x88, + 0xe1, 0x90, 0xfb, 0xcd, 0x5d, 0x17, 0x33, 0x2d, 0x9c, 0x18, 0xd0, 0x61, 0x97, 0x8b, 0x45, 0xa2, + 0xea, 0xb0, 0xb6, 0xe2, 0xe3, 0x44, 0x92, 0x91, 0x3d, 0xa8, 0x06, 0xf6, 0x21, 0x4c, 0xc9, 0xbb, + 0xf6, 0x1b, 0x81, 0xf4, 0xb6, 0xa2, 0x5b, 0xa2, 0x63, 0x87, 0x2f, 0xca, 0x0b, 0x7c, 0x19, 0xd0, + 0x9c, 0x31, 0x61, 0x64, 0x38, 0xb3, 0x77, 0xd0, 0xf1, 0x5e, 0xbe, 0xba, 0xb8, 0xda, 0xc0, 0x3b, + 0x23, 0xda, 0x81, 0x7a, 0x9e, 0xef, 0x28, 0x5b, 0x43, 0x2f, 0x11, 0x17, 0x3a, 0x36, 0xdb, 0x84, + 0xf1, 0x66, 0xf3, 0xc6, 0x35, 0x4f, 0xe8, 0x25, 0x3d, 0x75, 0x05, 0x94, 0x6f, 0xe5, 0x8b, 0x85, + 0xad, 0x9c, 0x8c, 0xa2, 0x5d, 0x67, 0xdb, 0xeb, 0x70, 0xa7, 0x15, 0xf4, 0xf6, 0x6d, 0x9d, 0x53, + 0x41, 0x48, 0xdf, 0x99, 0x27, 0x1c, 0xd2, 0xb7, 0x0a, 0xd3, 0x5a, 0xf4, 0x07, 0x3a, 0x13, 0xce, + 0xa6, 0xef, 0xaf, 0xe8, 0x21, 0x7c, 0xd9, 0x24, 0x32, 0x59, 0x3a, 0x15, 0xcb, 0x77, 0xf6, 0x71, + 0x63, 0xf9, 0x3c, 0x98, 0x91, 0x83, 0x41, 0xf3, 0x00, 0x47, 0x7a, 0x6e, 0x40, 0x1f, 0x5e, 0x2c, + 0xec, 0xc3, 0x13, 0x34, 0xd2, 0x6a, 0x92, 0xa1, 0x6f, 0x49, 0x9e, 0x2b, 0xdb, 0x06, 0x46, 0x40, + 0x37, 0x76, 0xb7, 0xdc, 0x88, 0x63, 0x5d, 0xcf, 0x0c, 0xa8, 0xeb, 0xa5, 0xc2, 0xba, 0xa6, 0x54, + 0x5d, 0x5b, 0xb2, 0x9a, 0x02, 0x8e, 0xcc, 0x57, 0xf5, 0xa8, 0xf9, 0x85, 0x1d, 0xfb, 0xac, 0x71, + 0x47, 0x91, 0x47, 0x90, 0x59, 0x04, 0xb2, 0x93, 0x36, 0xdb, 0xef, 0x05, 0x9c, 0xd9, 0x43, 0x38, + 0x9d, 0x6f, 0x05, 0xd6, 0xf9, 0x1c, 0xd6, 0xf9, 0x9c, 0x51, 0x67, 0x16, 0x49, 0xce, 0x1b, 0xf3, + 0xb3, 0xb2, 0xb5, 0x0e, 0xe0, 0xcf, 0xbe, 0x53, 0x82, 0x33, 0xeb, 0xd7, 0xea, 0xf7, 0x78, 0x28, + 0xd5, 0x12, 0x4c, 0xd0, 0x4d, 0xc9, 0x77, 0x9e, 0xa7, 0x7b, 0xac, 0xec, 0x55, 0xa8, 0xd2, 0x38, + 0x50, 0x54, 0x08, 0xd5, 0xfd, 0xc5, 0xee, 0xb6, 0xeb, 0xec, 0x69, 0x2c, 0x0a, 0x32, 0xf4, 0xfc, + 0xf2, 0x1f, 0xcd, 0x97, 0xec, 0x41, 0x55, 0xb1, 0x0e, 0xcc, 0x99, 0xdd, 0xa2, 0xa2, 0x2d, 0x77, + 0x79, 0xa7, 0x33, 0x3b, 0x8f, 0x33, 0xfa, 0xb5, 0x47, 0x07, 0xf3, 0x17, 0x72, 0xbd, 0x9b, 0x44, + 0x70, 0x0a, 0x4c, 0xfd, 0x62, 0x6f, 0x30, 0x3f, 0xd6, 0x2d, 0x50, 0xba, 0x67, 0xcf, 0x19, 0x59, + 0x3a, 0x73, 0xe5, 0x49, 0xea, 0xd9, 0xe7, 0xc4, 0x7a, 0x1f, 0xa8, 0x20, 0xda, 0x05, 0xea, 0xfc, + 0x5f, 0x2c, 0xc1, 0xec, 0x26, 0xdf, 0xda, 0x10, 0x0a, 0x8a, 0xef, 0x76, 0x92, 0x0d, 0x06, 0x07, + 0xf8, 0x05, 0x1c, 0xe0, 0xf9, 0x34, 0x44, 0xb9, 0x10, 0x4d, 0x3e, 0x1a, 0xf3, 0x80, 0x6f, 0xe1, + 0xf5, 0xa9, 0x28, 0x4e, 0x77, 0xae, 0xec, 0x50, 0x0f, 0xac, 0xeb, 0xe6, 0x50, 0x75, 0xb2, 0x36, + 0x55, 0x10, 0x82, 0x68, 0xfd, 0x6e, 0x39, 0xb3, 0x43, 0xb3, 0x55, 0x18, 0xa5, 0x85, 0x37, 0xf0, + 0xb4, 0xf3, 0x5c, 0xe1, 0xf2, 0x1a, 0xa5, 0x35, 0x6c, 0x2b, 0x7a, 0xf6, 0x40, 0xb0, 0xc2, 0xde, + 0xa7, 0xa3, 0xf7, 0x37, 0xe4, 0x06, 0x8c, 0x20, 0x43, 0xd5, 0x58, 0x7e, 0xfc, 0xdc, 0x01, 0x66, + 0x86, 0x09, 0xd4, 0x39, 0x54, 0x6d, 0xec, 0x3e, 0x54, 0x9a, 0xcd, 0x1b, 0x74, 0x7a, 0xff, 0x2a, + 0x89, 0xea, 0x4f, 0xa1, 0x42, 0x51, 0x8b, 0xf5, 0x3b, 0x25, 0x98, 0x34, 0xb6, 0x78, 0x76, 0x55, + 0xcb, 0xae, 0x90, 0x5e, 0xbf, 0x1b, 0x38, 0x28, 0xf5, 0xb3, 0x79, 0x17, 0xae, 0x6a, 0xd9, 0xb5, + 0x07, 0xd0, 0xe1, 0x94, 0xc8, 0xa6, 0x12, 0x51, 0x3e, 0xeb, 0x95, 0x41, 0x3e, 0xeb, 0x32, 0x53, + 0xe2, 0x50, 0xf1, 0xeb, 0x08, 0xd6, 0xaf, 0xbc, 0x0c, 0x53, 0xe6, 0xd1, 0x9c, 0xbd, 0x06, 0x23, + 0x78, 0xc9, 0xa3, 0xec, 0x3c, 0x68, 0x9f, 0xc2, 0x7b, 0x20, 0x23, 0xac, 0x53, 0xe2, 0xb0, 0x97, + 0x01, 0x92, 0xf8, 0x27, 0x75, 0x99, 0x30, 0xfc, 0xe8, 0x60, 0xbe, 0xf4, 0xba, 0xad, 0x15, 0xb0, + 0x6f, 0x02, 0xdc, 0x0e, 0xda, 0x1c, 0x7d, 0xb9, 0xa2, 0xc4, 0x34, 0x50, 0xe8, 0xe3, 0xf5, 0x0a, + 0xf9, 0x78, 0x8d, 0x48, 0xe4, 0x47, 0x07, 0xf3, 0xa7, 0xfc, 0xa0, 0xcd, 0x1d, 0x4c, 0x9b, 0x63, + 0x38, 0x51, 0xa5, 0x1c, 0xd9, 0xdb, 0x30, 0x6c, 0xf7, 0x3b, 0x5c, 0xdd, 0x13, 0x8c, 0xab, 0xad, + 0xb6, 0xdf, 0xe1, 0xa9, 0xc1, 0x22, 0xec, 0x67, 0xdd, 0xa7, 0x05, 0x80, 0xbd, 0x07, 0x20, 0xf4, + 0x28, 0x7a, 0xaa, 0x6f, 0x38, 0xbd, 0xf4, 0xd5, 0x54, 0xb0, 0xdc, 0x63, 0x7d, 0x1a, 0x09, 0xbb, + 0x03, 0xa3, 0xfa, 0x6d, 0xa5, 0x96, 0x26, 0x4b, 0xbf, 0xd1, 0xd6, 0xac, 0x1f, 0xf4, 0x0a, 0x51, + 0xf6, 0x22, 0x53, 0x71, 0x61, 0xef, 0xc2, 0x98, 0x60, 0x2f, 0x44, 0x58, 0x44, 0xc7, 0x2b, 0xbc, + 0x40, 0xd2, 0x1a, 0x24, 0xc4, 0xa0, 0xf1, 0x18, 0x51, 0x42, 0xc0, 0xbe, 0x06, 0x63, 0xf5, 0x5e, + 0x8f, 0xba, 0xfa, 0x50, 0xdf, 0xbf, 0xf3, 0xb9, 0xae, 0x3e, 0xe9, 0xf6, 0x7a, 0xf9, 0x9e, 0x4e, + 0xf9, 0xb1, 0x9d, 0x24, 0x4b, 0x33, 0x55, 0x70, 0xa8, 0xbf, 0xde, 0xab, 0xb9, 0x0a, 0x66, 0x55, + 0xe2, 0xe1, 0x5c, 0x25, 0x26, 0x5f, 0xd6, 0x83, 0x5a, 0xaa, 0xdd, 0x52, 0x5d, 0x70, 0x58, 0x5d, + 0xaf, 0xe7, 0xea, 0xd2, 0x07, 0x30, 0x57, 0x5d, 0x8e, 0x3b, 0x6b, 0xc3, 0x94, 0xda, 0x29, 0xa9, + 0xbe, 0xf1, 0xc3, 0xea, 0x7b, 0x39, 0x57, 0xdf, 0x89, 0xf6, 0x56, 0xbe, 0x9e, 0x0c, 0x4f, 0xf6, + 0x2e, 0x4c, 0x2a, 0x08, 0xae, 0x0f, 0xf4, 0xb9, 0x23, 0xcb, 0x64, 0x7b, 0x4b, 0xbe, 0x90, 0x69, + 0xbc, 0xad, 0xa3, 0x23, 0xeb, 0xd4, 0x72, 0x76, 0x4c, 0x1a, 0xd4, 0xd9, 0x59, 0x61, 0x22, 0xb3, + 0xaf, 0xc2, 0xf8, 0x6a, 0x57, 0x7c, 0x48, 0xe0, 0xbb, 0x31, 0xa7, 0x04, 0x0e, 0xca, 0x8f, 0x51, + 0x2b, 0xd1, 0xa6, 0x2a, 0x7a, 0x70, 0x79, 0x69, 0x91, 0x7e, 0xde, 0xd5, 0x28, 0x44, 0xe7, 0xc9, + 0xeb, 0x69, 0x9a, 0xc3, 0x2a, 0xb9, 0xc3, 0x73, 0x05, 0xbe, 0x84, 0x1a, 0x7b, 0x7a, 0x68, 0x4d, + 0x40, 0xd5, 0xf5, 0x70, 0xe6, 0x49, 0x4c, 0x9d, 0x27, 0xfb, 0x12, 0x8c, 0xd7, 0x37, 0x9b, 0x42, + 0x60, 0xd5, 0xed, 0xdb, 0xd1, 0x6c, 0x2d, 0x0d, 0x77, 0x71, 0x1f, 0xc8, 0xeb, 0x4a, 0xc7, 0x0d, + 0x33, 0x4e, 0xfb, 0x29, 0x3e, 0xfb, 0x0a, 0x9c, 0xdc, 0xf4, 0xfc, 0x76, 0xf0, 0x20, 0xa2, 0x6d, + 0x8a, 0x04, 0xdd, 0x4c, 0x7a, 0x07, 0xfa, 0x40, 0x96, 0x27, 0x4a, 0x69, 0x4e, 0xf0, 0x15, 0x72, + 0x60, 0x3f, 0x9a, 0xe3, 0x2c, 0x67, 0x10, 0x3b, 0x6c, 0x06, 0x2d, 0xe4, 0x66, 0x50, 0xbe, 0xfa, + 0xec, 0x74, 0x2a, 0xac, 0x86, 0x05, 0xc0, 0xcc, 0xfd, 0xfd, 0x66, 0xe0, 0xf9, 0xb3, 0x27, 0x50, + 0x16, 0x3e, 0x93, 0x4d, 0xf3, 0x84, 0x78, 0x8d, 0xa0, 0xe3, 0xb5, 0xf6, 0xe5, 0xf3, 0xaf, 0xd9, + 0xc3, 0xc7, 0x87, 0x81, 0x71, 0x6f, 0x53, 0xc0, 0x9a, 0x7d, 0x15, 0x26, 0xc4, 0xff, 0x89, 0x75, + 0xe4, 0xa4, 0xe1, 0x7d, 0xae, 0x61, 0x52, 0x3d, 0x38, 0x46, 0x82, 0x6f, 0x91, 0xe1, 0xc4, 0x60, + 0xc5, 0xbe, 0x08, 0x20, 0xf4, 0x37, 0x12, 0xc7, 0xa7, 0xd2, 0x37, 0x45, 0x51, 0xfd, 0xcb, 0x0b, + 0xe2, 0x14, 0x99, 0xbd, 0x0b, 0xe3, 0xe2, 0x57, 0xb3, 0xdf, 0x0e, 0xc4, 0xda, 0x38, 0x8d, 0xb4, + 0x32, 0x97, 0x86, 0xa0, 0x8d, 0x24, 0xdc, 0xc8, 0xa5, 0x91, 0xa2, 0xb3, 0x1b, 0x30, 0x8d, 0x2f, + 0xc5, 0xd2, 0xab, 0x83, 0x1e, 0x8f, 0x66, 0xcf, 0x68, 0x97, 0xf7, 0xa2, 0xc8, 0xf1, 0x92, 0x32, + 0xfd, 0x50, 0x95, 0x21, 0x63, 0x11, 0x9c, 0xc8, 0x5f, 0x26, 0x47, 0xb3, 0xb3, 0xd8, 0x49, 0xea, + 0x28, 0x91, 0xc7, 0x90, 0xf2, 0x58, 0x8c, 0x88, 0x26, 0xb8, 0xd4, 0xed, 0x96, 0x5e, 0x61, 0x11, + 0x77, 0x66, 0x03, 0xbb, 0xbe, 0xd4, 0xc8, 0x3e, 0x8e, 0x7a, 0x16, 0xbf, 0x00, 0x87, 0x79, 0xa7, + 0xd5, 0x73, 0x0e, 0x79, 0x20, 0xb5, 0x80, 0x9a, 0xfd, 0x30, 0x9c, 0x52, 0x12, 0x84, 0x8a, 0x68, + 0x5e, 0xcf, 0x3d, 0xa6, 0x24, 0x6e, 0x6f, 0x25, 0x55, 0xe7, 0xa6, 0x74, 0x71, 0x15, 0xcc, 0x85, + 0x71, 0x1c, 0x56, 0xaa, 0xf1, 0x99, 0xc3, 0x6a, 0xbc, 0x90, 0xab, 0xf1, 0x34, 0x4e, 0x94, 0x7c, + 0x65, 0x3a, 0x4f, 0xf9, 0xda, 0x19, 0xae, 0x23, 0x9a, 0x6d, 0xcf, 0x62, 0x6f, 0xd1, 0x6b, 0x67, + 0x72, 0x05, 0xe6, 0x26, 0x9c, 0x49, 0xa2, 0x4b, 0x64, 0x79, 0xab, 0xf5, 0x9c, 0x21, 0x91, 0x73, + 0x2e, 0x14, 0x06, 0xb2, 0x90, 0x48, 0xa9, 0x16, 0xa3, 0x3d, 0x22, 0xf4, 0x3c, 0xea, 0x6b, 0x28, + 0x91, 0x34, 0xe5, 0xc7, 0x29, 0x7c, 0x50, 0xa8, 0x90, 0x03, 0xbb, 0x0b, 0x27, 0x92, 0x5d, 0x5b, + 0x63, 0x3c, 0x9f, 0x3e, 0xbf, 0x99, 0x6e, 0xf5, 0xc5, 0x7c, 0x8b, 0xe8, 0x99, 0x0b, 0x67, 0x8c, + 0x7d, 0x5a, 0x63, 0x7d, 0x2e, 0x7d, 0xf7, 0xd9, 0xdc, 0xe4, 0x8b, 0xd9, 0x0f, 0xe2, 0xc3, 0x3e, + 0x84, 0xb9, 0xec, 0xde, 0xac, 0xd5, 0xf2, 0x02, 0xd6, 0xf2, 0xea, 0xa3, 0x83, 0xf9, 0xf3, 0xb9, + 0xed, 0xbd, 0xb8, 0xa2, 0x43, 0xb8, 0xb1, 0x6f, 0xc2, 0xac, 0xb9, 0x3f, 0x6b, 0x35, 0x59, 0xe9, + 0x03, 0xd9, 0xc9, 0xc6, 0x5e, 0x5c, 0xc3, 0x40, 0x1e, 0x2c, 0x86, 0xf9, 0xc2, 0xd9, 0xad, 0x55, + 0xf3, 0x62, 0xfa, 0x41, 0xb9, 0x55, 0x52, 0x5c, 0xdd, 0x51, 0x2c, 0xd9, 0x03, 0x78, 0xbe, 0x68, + 0x9b, 0xd0, 0x2a, 0x7d, 0x29, 0xb1, 0x46, 0x7f, 0xb6, 0x78, 0xcb, 0x29, 0xae, 0xf9, 0x08, 0xb6, + 0xec, 0x6b, 0x70, 0x4a, 0x5b, 0x5f, 0x5a, 0x7d, 0x2f, 0x63, 0x7d, 0x98, 0x54, 0x45, 0x5f, 0x98, + 0xc5, 0xb5, 0x14, 0xf3, 0x60, 0x5d, 0x38, 0xa1, 0x3e, 0x1c, 0xcd, 0xfe, 0xb4, 0xf5, 0x9c, 0x37, + 0xa4, 0x6a, 0x1e, 0x63, 0xf1, 0x1c, 0x49, 0xd5, 0xd9, 0xf6, 0x96, 0xd3, 0x4b, 0x09, 0xf5, 0x99, + 0x5e, 0xc0, 0x97, 0xdd, 0x80, 0x91, 0x66, 0x63, 0xf5, 0xda, 0xb5, 0x95, 0xd9, 0x57, 0xb0, 0x06, + 0x15, 0x36, 0x2d, 0x81, 0xc6, 0xa1, 0x89, 0xfc, 0x5e, 0x7b, 0xde, 0xf6, 0xb6, 0x71, 0x73, 0x26, + 0x51, 0xd9, 0x8f, 0xa2, 0xc7, 0xa9, 0x90, 0xa8, 0xf5, 0x28, 0xf2, 0x76, 0x7c, 0xf9, 0xb0, 0xe9, + 0xab, 0x86, 0xe3, 0x81, 0x7a, 0xea, 0x76, 0x89, 0xfb, 0x31, 0x0f, 0x73, 0xe8, 0xa9, 0x83, 0x93, + 0x7a, 0x4a, 0xdd, 0x4d, 0x59, 0xe9, 0x42, 0x3c, 0x5f, 0x91, 0xe8, 0xb7, 0x1d, 0x2f, 0x76, 0x76, + 0xfb, 0xc6, 0xe7, 0xcf, 0x7e, 0xd6, 0xf0, 0xa8, 0xba, 0xee, 0xc5, 0x37, 0xfa, 0x5b, 0x5a, 0xaf, + 0xbd, 0x44, 0x15, 0x3e, 0x2b, 0x4f, 0xcb, 0x03, 0x7a, 0x6e, 0x66, 0x27, 0x43, 0x17, 0xb1, 0xbf, + 0x50, 0x82, 0xd3, 0x9b, 0x41, 0x78, 0xbf, 0x13, 0xb8, 0x6d, 0xf5, 0x55, 0x24, 0xc3, 0x5f, 0x3b, + 0x4c, 0x86, 0x7f, 0x2e, 0x27, 0xc3, 0xad, 0x07, 0xc4, 0xc6, 0x49, 0x5e, 0x0a, 0xce, 0xc9, 0xf3, + 0x01, 0x55, 0xb1, 0x1f, 0x85, 0x73, 0xc5, 0x25, 0xda, 0xa4, 0x7c, 0x1d, 0x27, 0xe5, 0x9b, 0x8f, + 0x0e, 0xe6, 0x5f, 0x1f, 0x54, 0x53, 0xf1, 0x04, 0x3d, 0x92, 0x35, 0x7b, 0x1b, 0x2a, 0xeb, 0x4b, + 0x8d, 0xd9, 0x4b, 0xc6, 0x1d, 0xf8, 0xfa, 0x52, 0x43, 0xeb, 0x28, 0x69, 0x5a, 0xed, 0xb6, 0x0c, + 0xd3, 0xea, 0xfa, 0x52, 0x43, 0x6c, 0xe4, 0x6b, 0x9e, 0xdf, 0x7f, 0x68, 0xea, 0xa8, 0x97, 0xd3, + 0x8d, 0xbc, 0x23, 0x4a, 0x07, 0x6b, 0xa8, 0x05, 0xd4, 0xec, 0x41, 0x86, 0xa7, 0x1c, 0x8f, 0x37, + 0x0e, 0x1b, 0x8f, 0x37, 0x72, 0xe3, 0x91, 0xad, 0x38, 0x3b, 0x16, 0x05, 0x55, 0xb0, 0x8f, 0xe0, + 0xd9, 0x3c, 0x54, 0x1b, 0x83, 0x37, 0x71, 0x0c, 0x5e, 0x7f, 0x74, 0x30, 0x7f, 0xb1, 0x88, 0x7b, + 0x71, 0xff, 0x1f, 0xca, 0x92, 0x7d, 0x13, 0x60, 0x91, 0xbb, 0x5d, 0xfa, 0xc6, 0x85, 0xc7, 0xb4, + 0x35, 0x6c, 0x71, 0xb7, 0x5b, 0x60, 0x6b, 0x48, 0x39, 0x8a, 0x3d, 0x3b, 0xfd, 0xa5, 0x7d, 0xca, + 0x95, 0x74, 0xcf, 0xd6, 0x98, 0x0c, 0xd8, 0xb3, 0x8b, 0x38, 0xb0, 0x4d, 0x98, 0xa8, 0xf7, 0x7a, + 0xa9, 0xc2, 0xf8, 0x39, 0x5c, 0xa2, 0xca, 0x9d, 0x43, 0x2b, 0x4a, 0x03, 0xed, 0xc5, 0x26, 0x5e, + 0xa4, 0x22, 0x1a, 0x8c, 0xc4, 0xae, 0xad, 0xff, 0x4e, 0xab, 0x8c, 0x66, 0xdf, 0xc2, 0x79, 0x85, + 0xbb, 0xb6, 0xc1, 0x4b, 0x6b, 0xb7, 0xf1, 0xcc, 0xca, 0x00, 0x3e, 0x37, 0x87, 0xaa, 0x17, 0x6a, + 0x17, 0x6f, 0x0e, 0x55, 0x2f, 0xd6, 0x5e, 0xb5, 0x9f, 0x6d, 0xd6, 0xd7, 0xd7, 0x56, 0xdb, 0x4a, + 0x9d, 0x54, 0xcf, 0x77, 0xcb, 0xef, 0xb5, 0xcf, 0x1f, 0x56, 0xaa, 0x3d, 0x8e, 0xf8, 0x93, 0x43, + 0x30, 0xae, 0xd5, 0xc6, 0x2e, 0xc2, 0x70, 0xc3, 0x8d, 0x77, 0x95, 0x5d, 0x0a, 0xfd, 0x93, 0x7b, + 0x02, 0xa0, 0x9b, 0x74, 0x10, 0x83, 0x5d, 0x86, 0xd1, 0x75, 0x1e, 0xef, 0x06, 0x6d, 0x65, 0x92, + 0x42, 0x8b, 0x4b, 0x57, 0x82, 0x74, 0x8b, 0x0b, 0x61, 0x09, 0xde, 0x9b, 0xbb, 0x3c, 0x54, 0x76, + 0x34, 0xe4, 0xfd, 0x40, 0x00, 0x74, 0xde, 0x88, 0xc1, 0xde, 0x83, 0x09, 0x74, 0x74, 0x59, 0xf1, + 0x5b, 0x41, 0x1b, 0x63, 0x53, 0xd3, 0x43, 0x28, 0xa6, 0x8d, 0xe3, 0xb2, 0xc0, 0x18, 0x0c, 0x8d, + 0x00, 0x9d, 0x45, 0xf1, 0x01, 0xd4, 0x4e, 0x87, 0xfc, 0xe3, 0xa5, 0xb3, 0xa8, 0x7c, 0x1f, 0xd5, + 0xb0, 0x67, 0x27, 0x88, 0xec, 0x2a, 0x8c, 0xc9, 0x74, 0x73, 0x41, 0x5b, 0x3d, 0x37, 0x2f, 0x93, + 0xad, 0xcb, 0x4c, 0x75, 0xa6, 0x31, 0x38, 0x45, 0x45, 0x6f, 0x7a, 0xf1, 0xc3, 0x08, 0x2a, 0x95, + 0xde, 0xf4, 0x48, 0x99, 0xf3, 0xc6, 0xd7, 0xd1, 0xd9, 0xfb, 0x30, 0xb1, 0xcc, 0xfd, 0x7d, 0xc1, + 0x09, 0x23, 0xe3, 0xaa, 0xfa, 0x6b, 0xc0, 0xfe, 0x3e, 0xd6, 0x9b, 0x8d, 0x8e, 0x33, 0x28, 0xd8, + 0x35, 0x98, 0x12, 0xbf, 0x25, 0x3f, 0xe4, 0x31, 0x96, 0x3e, 0x92, 0x8c, 0x3c, 0x28, 0x51, 0x45, + 0x86, 0x4b, 0x86, 0xca, 0xfa, 0x6b, 0x25, 0x98, 0x3f, 0x62, 0x8f, 0x14, 0xea, 0x7c, 0x2a, 0x5f, + 0x9b, 0xc9, 0x83, 0x7e, 0x32, 0xe5, 0x41, 0x52, 0xe0, 0x98, 0xee, 0x7b, 0x26, 0x09, 0xa6, 0x1c, + 0x91, 0x8c, 0xf5, 0xcc, 0x33, 0xb4, 0xe9, 0x1a, 0x29, 0x47, 0x24, 0xc8, 0x5a, 0x83, 0x5a, 0x76, + 0xef, 0x64, 0x5f, 0x80, 0x49, 0xfd, 0xf5, 0x6d, 0x35, 0x63, 0x65, 0x62, 0xc8, 0x70, 0xc7, 0x38, + 0x0f, 0x18, 0x88, 0xd6, 0x79, 0x98, 0x32, 0x77, 0x09, 0x76, 0x12, 0x86, 0xe3, 0x20, 0xe8, 0x10, + 0x0f, 0x5b, 0xfe, 0xb0, 0x7e, 0xad, 0x04, 0x27, 0x0a, 0x14, 0x11, 0x76, 0x1e, 0x86, 0xc4, 0x0a, + 0xd0, 0xbd, 0x3c, 0xc5, 0x12, 0xd1, 0x3d, 0xac, 0x44, 0x39, 0x7b, 0x03, 0x46, 0x97, 0x6f, 0x37, + 0x9b, 0xf5, 0xdb, 0x6a, 0x81, 0xc8, 0xf3, 0x8a, 0x1f, 0x39, 0x91, 0x6b, 0x3a, 0x87, 0x11, 0x1a, + 0x7b, 0x1d, 0x46, 0x56, 0x1b, 0x48, 0xa0, 0xbd, 0xa4, 0xee, 0xf5, 0xb2, 0xf8, 0x84, 0x64, 0xfd, + 0x74, 0x09, 0x58, 0x5e, 0xab, 0x62, 0x6f, 0xc2, 0xb8, 0xae, 0xbb, 0xc9, 0x7e, 0x41, 0x8f, 0x19, + 0x4d, 0xbf, 0xb0, 0x75, 0x1c, 0xb6, 0x0c, 0xc3, 0xf8, 0x14, 0x71, 0xe2, 0x95, 0x56, 0x28, 0xc9, + 0xcf, 0xe4, 0x24, 0xf9, 0x30, 0x3e, 0x6e, 0x6c, 0x4b, 0x62, 0xeb, 0x5f, 0x95, 0x80, 0x15, 0x3b, + 0x78, 0x1f, 0xcb, 0x2b, 0xf6, 0x2d, 0x2d, 0xc3, 0x99, 0xee, 0xe1, 0xed, 0x2b, 0xa0, 0xbe, 0xfa, + 0xd2, 0x5c, 0x68, 0xe7, 0x0d, 0xeb, 0xfc, 0xe0, 0xb4, 0x38, 0x17, 0x61, 0xf8, 0x1e, 0x0f, 0xb7, + 0x54, 0x18, 0x15, 0x8a, 0x9f, 0x3d, 0x01, 0xd0, 0xc5, 0x0f, 0x62, 0x18, 0xae, 0xe6, 0xc3, 0xc7, + 0x75, 0x35, 0xff, 0xe3, 0x12, 0x9c, 0x2c, 0xb2, 0xff, 0x1c, 0x91, 0xf2, 0xc6, 0xca, 0x64, 0xeb, + 0x41, 0x37, 0x5a, 0x19, 0xcc, 0x91, 0xe4, 0xe8, 0x99, 0x87, 0x61, 0xd1, 0x43, 0x6a, 0x5a, 0xe0, + 0x15, 0x83, 0xe8, 0xc2, 0xc8, 0x96, 0x70, 0x81, 0x90, 0xbe, 0x48, 0x39, 0x2c, 0x11, 0xe4, 0x43, + 0x94, 0x12, 0x2e, 0x10, 0xd6, 0x83, 0x36, 0x57, 0xa6, 0x77, 0x44, 0xe8, 0x0a, 0x80, 0x2d, 0xe1, + 0xec, 0x3c, 0x8c, 0xde, 0xf1, 0xd7, 0xb8, 0xbb, 0xa7, 0x24, 0x1f, 0xba, 0xfd, 0x06, 0xbe, 0xd3, + 0x11, 0x30, 0x5b, 0x15, 0x5a, 0xbf, 0x50, 0x82, 0x99, 0x9c, 0xe9, 0xe9, 0xe8, 0xac, 0x3e, 0x87, + 0x67, 0x31, 0x38, 0xce, 0xf7, 0xc9, 0xe6, 0x0f, 0x15, 0x37, 0xdf, 0xfa, 0xaf, 0x46, 0xe0, 0xcc, + 0x80, 0x9b, 0x80, 0x34, 0xcb, 0x4b, 0xe9, 0xc8, 0x2c, 0x2f, 0x5f, 0x87, 0xc9, 0xa5, 0x8e, 0xeb, + 0x75, 0xa3, 0x8d, 0x20, 0x6d, 0x71, 0x1a, 0xac, 0x8d, 0x65, 0x14, 0xac, 0x98, 0x44, 0xf5, 0x9e, + 0x6d, 0x21, 0x85, 0x13, 0x07, 0x79, 0x43, 0x84, 0xc1, 0x2c, 0x97, 0x67, 0xa5, 0xf2, 0x67, 0x24, + 0xcf, 0x8a, 0x19, 0x79, 0x3f, 0xf4, 0x44, 0x23, 0xef, 0x8b, 0x03, 0xb3, 0x86, 0x3f, 0x49, 0x98, + 0xde, 0x52, 0x36, 0xbc, 0x66, 0x24, 0xe7, 0x2b, 0x7d, 0x74, 0x5c, 0xcd, 0x0d, 0x33, 0x4a, 0x7c, + 0x14, 0x1d, 0x83, 0xce, 0x0f, 0x8e, 0x02, 0x37, 0xd3, 0x49, 0xea, 0xd1, 0xe0, 0xdf, 0x86, 0x93, + 0x45, 0xa6, 0xc4, 0xd9, 0xaa, 0x11, 0x52, 0x31, 0x30, 0xfe, 0xe5, 0xf8, 0x06, 0xc9, 0xfb, 0x85, + 0x06, 0x49, 0x95, 0x3d, 0x68, 0x6c, 0x70, 0xe8, 0x73, 0xba, 0x16, 0x24, 0xee, 0xe1, 0x39, 0x86, + 0xac, 0x9f, 0x2c, 0x65, 0x72, 0x6c, 0x65, 0xe9, 0xd9, 0x3b, 0x46, 0x2a, 0xd4, 0x57, 0xf2, 0xa9, + 0x50, 0x8b, 0xd3, 0x6a, 0xc9, 0xdb, 0xd4, 0xd7, 0x60, 0x84, 0xfc, 0xea, 0xb4, 0xf4, 0x64, 0x39, + 0x7f, 0x3a, 0xc2, 0xb1, 0xbe, 0x57, 0x36, 0x53, 0xcc, 0xfc, 0x59, 0x5c, 0xd7, 0x8f, 0xa1, 0xd8, + 0x5e, 0x83, 0xa9, 0x86, 0x9c, 0xe7, 0x6a, 0xf2, 0x0e, 0xa5, 0xe6, 0xef, 0x1e, 0x5d, 0xd2, 0x14, + 0xcc, 0xde, 0x0c, 0x95, 0xe0, 0x23, 0x33, 0xfd, 0x5c, 0x0b, 0x42, 0x79, 0x49, 0x35, 0x9c, 0xf2, + 0x91, 0xf9, 0x7c, 0x9c, 0xed, 0x20, 0xcc, 0x5d, 0x56, 0x65, 0xa8, 0xac, 0xeb, 0x99, 0xa1, 0xa6, + 0xf4, 0xb1, 0x32, 0x6a, 0x5a, 0x2a, 0x3b, 0x53, 0x69, 0x12, 0x81, 0x54, 0xc6, 0xdb, 0x19, 0xa8, + 0xb5, 0x0d, 0xcf, 0x1f, 0xca, 0x48, 0xe8, 0x18, 0xd0, 0x4b, 0x7e, 0x65, 0xa2, 0x7a, 0x0e, 0x25, + 0xb5, 0x35, 0x3a, 0x6b, 0x2d, 0xcd, 0x2b, 0xb0, 0xba, 0x8c, 0x89, 0x0d, 0xde, 0x86, 0x09, 0x3d, + 0xe6, 0x8d, 0x38, 0x17, 0x84, 0xc8, 0x0d, 0x89, 0x81, 0xb5, 0xc7, 0x15, 0xf2, 0x6a, 0x3b, 0xb2, + 0xfe, 0x97, 0x0a, 0xcc, 0x0e, 0x8a, 0xac, 0x67, 0x3f, 0x9b, 0x64, 0xe2, 0xc3, 0xb0, 0xf1, 0xc0, + 0x8c, 0x37, 0x1c, 0x5f, 0x78, 0xfb, 0x88, 0xd0, 0xfc, 0x4b, 0x85, 0xc4, 0x32, 0x20, 0x25, 0x09, + 0xd7, 0x43, 0x7d, 0x02, 0xb3, 0x38, 0x39, 0x5a, 0x0e, 0x07, 0xbb, 0xb8, 0x62, 0xf6, 0x01, 0x9c, + 0xb2, 0x79, 0x2b, 0xe8, 0x76, 0xb9, 0xdf, 0xd6, 0x63, 0xda, 0x69, 0x29, 0x51, 0x52, 0xba, 0x04, + 0xc1, 0x64, 0x59, 0x48, 0xc9, 0x6e, 0xc3, 0x4c, 0x9a, 0xf5, 0x56, 0xbd, 0xf1, 0xa6, 0x3d, 0x85, + 0x9a, 0x66, 0xe9, 0x55, 0x2f, 0xbc, 0xe9, 0xa6, 0xa9, 0x1c, 0x29, 0xbb, 0x0c, 0xb0, 0xe4, 0xfa, + 0x8d, 0x30, 0x68, 0x71, 0x4a, 0x2a, 0x54, 0x25, 0x77, 0x6d, 0x17, 0x33, 0xe5, 0x09, 0xb0, 0xad, + 0xa1, 0xcc, 0x39, 0x30, 0x37, 0xb8, 0xa3, 0x0a, 0x82, 0x6a, 0x3e, 0x6b, 0xc6, 0x6a, 0x9d, 0xca, + 0x0d, 0xb4, 0xe0, 0xa3, 0xc7, 0xda, 0x7c, 0x1b, 0x26, 0xf4, 0x05, 0x8e, 0xca, 0x92, 0xf8, 0x4d, + 0xe2, 0x4b, 0x2a, 0x4b, 0x02, 0x60, 0x4b, 0x78, 0xea, 0xd1, 0x51, 0x2e, 0xf6, 0xe8, 0x48, 0x25, + 0x4f, 0xe5, 0x28, 0xc9, 0x23, 0x2a, 0xc7, 0xbd, 0x58, 0xab, 0x1c, 0x7f, 0xeb, 0x95, 0x63, 0x1e, + 0x60, 0x5b, 0xc2, 0x9f, 0x68, 0xe5, 0xff, 0xb0, 0x04, 0x43, 0x76, 0xbf, 0x43, 0x71, 0xfc, 0x66, + 0xb4, 0x2c, 0xc5, 0xf1, 0xe7, 0xf7, 0x99, 0x14, 0x33, 0x55, 0x99, 0xcb, 0x47, 0xaa, 0xcc, 0x8f, + 0x21, 0x03, 0xf1, 0xf8, 0x27, 0xa5, 0xc0, 0x50, 0x7a, 0xcc, 0x71, 0x73, 0xce, 0xb7, 0x0a, 0xcb, + 0xfa, 0xe5, 0x12, 0x9c, 0x2a, 0xbc, 0x39, 0x17, 0xb5, 0x4a, 0xe9, 0xa7, 0x6d, 0x01, 0x59, 0x91, + 0x27, 0x31, 0x1e, 0x27, 0xd7, 0xdf, 0xf1, 0xbf, 0xc5, 0x7a, 0x01, 0xc6, 0x12, 0xbf, 0x2d, 0x71, + 0x8c, 0x94, 0x43, 0x27, 0x13, 0xc6, 0x93, 0xfb, 0xcf, 0xdf, 0x2c, 0x03, 0x88, 0x26, 0x7c, 0x8a, + 0xa1, 0x5e, 0xb2, 0x0f, 0x06, 0x84, 0x7a, 0x65, 0xfb, 0x23, 0x4b, 0xc7, 0xfe, 0x62, 0x09, 0x46, + 0xa4, 0xbe, 0x78, 0xb8, 0x46, 0xba, 0xf9, 0x64, 0x34, 0xd2, 0x1a, 0x4e, 0x77, 0xe3, 0x3c, 0x2b, + 0xcb, 0xac, 0x7f, 0x58, 0x86, 0x11, 0xd1, 0xa4, 0xa7, 0xf6, 0x61, 0xa1, 0xe2, 0x98, 0x33, 0xf1, + 0x49, 0x87, 0x3c, 0xa3, 0xb6, 0x92, 0x79, 0x46, 0xed, 0x84, 0x4e, 0x86, 0x05, 0xf7, 0x16, 0xd2, + 0xcc, 0x77, 0x83, 0x9e, 0x4d, 0xd3, 0x42, 0xd7, 0xfe, 0x49, 0x49, 0xe6, 0x1b, 0x55, 0xc4, 0xec, + 0xab, 0x30, 0xa5, 0x1e, 0x4b, 0x91, 0xd9, 0x28, 0xc9, 0x73, 0x4e, 0xb9, 0xdb, 0xab, 0xc7, 0x52, + 0xf4, 0xec, 0x95, 0x06, 0xbe, 0xae, 0xfa, 0xf4, 0x74, 0x64, 0xd6, 0x06, 0xd6, 0xdd, 0x76, 0x9d, + 0x07, 0xdc, 0xbd, 0xcf, 0xa3, 0xd8, 0x91, 0x6e, 0xd1, 0xe4, 0x60, 0xa7, 0xd8, 0xaf, 0x5f, 0xab, + 0x4b, 0x8f, 0x68, 0xcc, 0x89, 0x23, 0x5f, 0xbd, 0xc9, 0xd1, 0xe8, 0x5e, 0x43, 0xdd, 0x6d, 0x77, + 0x53, 0x16, 0x4a, 0x3a, 0xeb, 0x5f, 0x8e, 0xc8, 0x25, 0x44, 0xaf, 0x2b, 0x6d, 0xc1, 0xd4, 0x9d, + 0xd5, 0xe5, 0x25, 0xcd, 0x87, 0xc0, 0x7c, 0xe0, 0x6a, 0xe5, 0x61, 0xcc, 0x43, 0xdf, 0xed, 0x28, + 0x5b, 0x56, 0xaa, 0xd2, 0x05, 0x5e, 0xbb, 0x55, 0xec, 0x5f, 0x90, 0xe1, 0x28, 0xea, 0x90, 0x26, + 0xd4, 0xa4, 0x8e, 0xf2, 0x31, 0xeb, 0x88, 0xdc, 0x6e, 0x67, 0x40, 0x1d, 0x26, 0x47, 0xb6, 0x8b, + 0x66, 0xad, 0xdd, 0xfe, 0x96, 0x56, 0x4b, 0xe5, 0xf0, 0x5a, 0x5e, 0xa4, 0x5a, 0x9e, 0xa1, 0x1b, + 0xa3, 0xc2, 0x7a, 0x72, 0x5c, 0x53, 0xe1, 0x37, 0x74, 0xa4, 0xf0, 0xd3, 0xa4, 0xc1, 0xf0, 0xbf, + 0x49, 0x69, 0xc0, 0x96, 0x33, 0xeb, 0x82, 0x25, 0xea, 0xd6, 0x8e, 0xe7, 0x6b, 0xaf, 0x09, 0x1e, + 0xba, 0x2c, 0xd8, 0x6a, 0x9a, 0x8b, 0x70, 0xf4, 0xc8, 0x04, 0x54, 0x2a, 0x7f, 0xe3, 0x28, 0xe5, + 0x22, 0x34, 0x33, 0x10, 0xae, 0xc1, 0x18, 0x65, 0x38, 0x5c, 0xdc, 0x27, 0x9f, 0xbf, 0x9a, 0xe1, + 0x3e, 0xde, 0x5e, 0xdc, 0x4f, 0x4f, 0xc6, 0x94, 0x23, 0xd1, 0x4c, 0xcf, 0x99, 0x32, 0x60, 0x77, + 0x60, 0x2c, 0x7d, 0x7d, 0xca, 0x7c, 0xf0, 0x35, 0x81, 0x53, 0x52, 0x6f, 0x95, 0x26, 0xad, 0xe0, + 0xb1, 0xa9, 0x94, 0x07, 0x5b, 0x83, 0x1a, 0x86, 0x1c, 0xf0, 0xb6, 0x5c, 0x35, 0xab, 0xcb, 0x32, + 0x8b, 0x1e, 0xe9, 0x71, 0xb1, 0x2c, 0xa3, 0xe5, 0x96, 0xc9, 0xd9, 0x90, 0xa3, 0xb4, 0x7e, 0xbe, + 0x0c, 0xb5, 0xec, 0xec, 0x63, 0xef, 0xc2, 0x78, 0xf2, 0xfa, 0x57, 0x92, 0x25, 0x09, 0x0d, 0xdd, + 0xe9, 0x73, 0x61, 0x46, 0xbe, 0x24, 0x1d, 0x9d, 0x2d, 0x40, 0x55, 0x2c, 0x62, 0x3f, 0x7d, 0x13, + 0x01, 0xc5, 0x76, 0x9f, 0x60, 0xba, 0x51, 0x4d, 0xe1, 0xb1, 0x26, 0x9c, 0x10, 0x8b, 0xa6, 0xe9, + 0xf9, 0x3b, 0x1d, 0xbe, 0x16, 0xec, 0x04, 0xfd, 0xf8, 0xae, 0xbd, 0x46, 0x32, 0x5c, 0xda, 0x0f, + 0xdc, 0x6e, 0xc7, 0x28, 0x0e, 0x75, 0xe3, 0x7e, 0x11, 0x35, 0x7b, 0x5d, 0x6e, 0x33, 0xab, 0xcb, + 0xe4, 0xb2, 0x8b, 0xfa, 0x07, 0xfa, 0xbc, 0x1b, 0x8d, 0x27, 0x24, 0x4d, 0xb2, 0xfe, 0x51, 0x19, + 0xc6, 0xb5, 0xe9, 0xc7, 0x2e, 0x42, 0x75, 0x35, 0x5a, 0x0b, 0x5a, 0xf7, 0x93, 0x47, 0x22, 0x26, + 0x1f, 0x1d, 0xcc, 0x8f, 0x79, 0x91, 0xd3, 0x41, 0xa0, 0x9d, 0x14, 0xb3, 0x45, 0x98, 0x94, 0x7f, + 0x29, 0x15, 0xbb, 0x9c, 0xda, 0xcd, 0x25, 0x72, 0x81, 0x7a, 0x6d, 0x92, 0xb0, 0x6f, 0x00, 0x48, + 0x00, 0x66, 0x6f, 0xab, 0x1c, 0x3f, 0xef, 0x1c, 0x55, 0x50, 0x90, 0xb7, 0x4d, 0x63, 0xc8, 0xbe, + 0x25, 0x1f, 0x17, 0x53, 0xcb, 0x65, 0xe8, 0xf8, 0x89, 0xf3, 0x04, 0x7f, 0xa7, 0x38, 0x7f, 0xa7, + 0xce, 0x92, 0x5e, 0x3e, 0x9f, 0x13, 0x07, 0x91, 0x3d, 0x1e, 0xee, 0xd7, 0x63, 0x44, 0xd4, 0x30, + 0xac, 0xff, 0xa9, 0xa4, 0x2d, 0x32, 0x76, 0x1b, 0xc6, 0x92, 0x09, 0x44, 0xde, 0xf2, 0xc9, 0x91, + 0x5d, 0xc1, 0x6d, 0xbe, 0xbd, 0xf8, 0x0c, 0x45, 0x10, 0x9c, 0x48, 0xa6, 0xa1, 0xb1, 0xe6, 0x14, + 0x90, 0xbd, 0x0f, 0x43, 0xd8, 0x75, 0xe5, 0x23, 0x3f, 0x4d, 0xed, 0xf2, 0x43, 0xa2, 0xcf, 0xf0, + 0x43, 0x90, 0x92, 0xbd, 0x41, 0x29, 0x60, 0x64, 0xe7, 0x4f, 0x69, 0x5b, 0xb5, 0x68, 0x47, 0xb2, + 0xbd, 0xa7, 0x29, 0x5c, 0xb5, 0xd9, 0xf3, 0xd7, 0xca, 0x50, 0xcb, 0x2e, 0x6d, 0xf6, 0x1e, 0x4c, + 0xa8, 0xed, 0xf7, 0x86, 0x4b, 0x6f, 0x00, 0x4f, 0xd0, 0x1b, 0xbc, 0x6a, 0x0f, 0xde, 0x75, 0x75, + 0xef, 0x7a, 0xdb, 0x20, 0x10, 0xba, 0xd0, 0x06, 0xe5, 0xfa, 0xd7, 0x16, 0x55, 0x1c, 0xc4, 0xbd, + 0xcc, 0x9b, 0x56, 0x0a, 0x8d, 0xbd, 0x05, 0x95, 0xf5, 0x6b, 0x75, 0x4a, 0xa2, 0x50, 0xcb, 0x6e, + 0xd2, 0x74, 0x65, 0x6e, 0xc4, 0x46, 0x09, 0x7c, 0xb6, 0xa6, 0x3d, 0xff, 0x36, 0x62, 0x84, 0x74, + 0x28, 0x70, 0xf2, 0x71, 0x47, 0xbf, 0x03, 0x77, 0x73, 0xa8, 0x5a, 0xa9, 0x0d, 0xd1, 0x4b, 0x3b, + 0x8b, 0x30, 0x93, 0x63, 0xa1, 0x2d, 0x52, 0xd9, 0x25, 0x87, 0x2f, 0x52, 0xcb, 0x87, 0x09, 0x7d, + 0x4a, 0x1c, 0xf1, 0x02, 0xe3, 0x69, 0xcc, 0xf2, 0x26, 0xfb, 0x6b, 0xe4, 0xd1, 0xc1, 0x7c, 0xd9, + 0x6b, 0x63, 0x6e, 0xb7, 0x0b, 0x50, 0x55, 0xc2, 0x8e, 0x64, 0x0c, 0x9a, 0xc1, 0x95, 0xc3, 0x82, + 0x9d, 0x94, 0x5a, 0xaf, 0xc0, 0x28, 0x8d, 0xfa, 0xe1, 0xc6, 0x6f, 0xeb, 0xbb, 0x65, 0x98, 0xb6, + 0xb9, 0xd0, 0x34, 0xb9, 0x7c, 0xf5, 0xf8, 0xa9, 0xd5, 0x6e, 0x8b, 0x5f, 0x38, 0x30, 0xbe, 0xed, + 0x90, 0x17, 0xfc, 0x7f, 0xa3, 0x04, 0x27, 0x0a, 0x70, 0xd9, 0x02, 0x8c, 0xab, 0x87, 0xfd, 0xd3, + 0x4e, 0xc4, 0xcc, 0x7b, 0xca, 0xc1, 0x0d, 0x3b, 0x53, 0x47, 0x62, 0x57, 0x61, 0x6c, 0xd9, 0x73, + 0x3b, 0xf5, 0x76, 0x3b, 0x49, 0xf9, 0x86, 0x5b, 0x24, 0xbe, 0xca, 0xed, 0x0a, 0xa8, 0xbe, 0xfe, + 0x13, 0x54, 0xf6, 0x2a, 0x4d, 0x8a, 0x4a, 0xd2, 0xad, 0x38, 0x29, 0xbe, 0x7f, 0x30, 0x0f, 0xb2, + 0x4d, 0x1b, 0xc9, 0x14, 0xc1, 0x57, 0x47, 0x24, 0x30, 0x0d, 0xfb, 0x7c, 0x6a, 0x87, 0xae, 0xf8, + 0xd5, 0x91, 0xec, 0xe7, 0x1d, 0xeb, 0xcd, 0xd3, 0x7f, 0xbb, 0x0c, 0xa7, 0x8b, 0x09, 0x3f, 0xd6, + 0x50, 0xbe, 0x06, 0x63, 0xf8, 0x96, 0xae, 0xf6, 0x52, 0xd2, 0xd4, 0xa3, 0x83, 0x79, 0x90, 0x0f, + 0xef, 0x22, 0x7e, 0x8a, 0xc0, 0xb6, 0x61, 0x72, 0xcd, 0x8d, 0xe2, 0x1b, 0xdc, 0x0d, 0xe3, 0x2d, + 0xee, 0xc6, 0xc7, 0xd8, 0x04, 0x95, 0x8f, 0xd5, 0x2c, 0x3e, 0xcb, 0xb6, 0xab, 0x28, 0x33, 0xdb, + 0x94, 0xc9, 0x36, 0x99, 0x28, 0x43, 0xc7, 0x98, 0x28, 0x1f, 0xc1, 0x74, 0x93, 0x77, 0xdd, 0xde, + 0x6e, 0x10, 0xaa, 0x74, 0x2e, 0x97, 0x60, 0x32, 0x01, 0x15, 0xce, 0x16, 0xb3, 0xd8, 0xc0, 0xd7, + 0x3a, 0x22, 0x15, 0x25, 0x66, 0xb1, 0xf5, 0x2b, 0x65, 0x38, 0x53, 0x6f, 0x91, 0xc3, 0x38, 0x15, + 0xa8, 0xb8, 0x96, 0x4f, 0xb9, 0x6e, 0x76, 0x19, 0xc6, 0xd6, 0xdd, 0x87, 0x6b, 0xdc, 0x8d, 0x78, + 0x44, 0xcf, 0x70, 0xc9, 0x1d, 0xc3, 0x7d, 0x98, 0x5e, 0x5b, 0xd8, 0x29, 0x8e, 0xae, 0x81, 0x0f, + 0x7d, 0x42, 0x0d, 0xdc, 0x82, 0x91, 0x1b, 0x41, 0xa7, 0xcd, 0x43, 0xba, 0x95, 0xc5, 0xbb, 0xd2, + 0x5d, 0x84, 0xd8, 0x54, 0x62, 0xfd, 0x71, 0x09, 0xa6, 0x92, 0x16, 0x63, 0x13, 0x3e, 0xf5, 0x2e, + 0x39, 0x0f, 0xa3, 0x58, 0xd1, 0xea, 0xb2, 0xbe, 0x69, 0x74, 0x04, 0xc8, 0xf1, 0xda, 0xb6, 0x2a, + 0xd4, 0x7b, 0x62, 0xf8, 0x93, 0xf5, 0x84, 0xf5, 0x77, 0xf0, 0x1a, 0x56, 0xff, 0x4a, 0xb1, 0x13, + 0x69, 0x0d, 0x29, 0x1d, 0xb3, 0x21, 0xe5, 0x27, 0x36, 0x24, 0x95, 0x81, 0x43, 0xf2, 0x97, 0xca, + 0x30, 0x9e, 0x34, 0xf6, 0x07, 0xec, 0xb9, 0xae, 0xe4, 0xbb, 0x8e, 0x95, 0x82, 0xad, 0xa9, 0xc9, + 0x0a, 0xca, 0x74, 0xf6, 0x3e, 0x8c, 0xd0, 0x62, 0x2a, 0x65, 0xe2, 0x3b, 0x32, 0xa3, 0xbb, 0x38, + 0x45, 0xac, 0x47, 0x70, 0x40, 0x23, 0x9b, 0xe8, 0x30, 0xc7, 0xdd, 0x26, 0xdf, 0xa2, 0x5b, 0xf9, + 0xa7, 0x76, 0x8f, 0x2a, 0xce, 0x71, 0x97, 0x7e, 0xd8, 0xb1, 0x76, 0xa7, 0xff, 0x61, 0x0c, 0x6a, + 0x59, 0x92, 0xa3, 0x1f, 0x44, 0x6b, 0xf4, 0xb7, 0xb0, 0x2f, 0x26, 0xe4, 0x83, 0x68, 0xbd, 0xfe, + 0x96, 0x2d, 0x60, 0xe8, 0xe9, 0x13, 0x7a, 0x7b, 0xf8, 0xd5, 0x13, 0xe4, 0xe9, 0x13, 0x7a, 0x7b, + 0x86, 0xa7, 0x4f, 0xe8, 0xed, 0xa1, 0x0e, 0xbe, 0xd6, 0xc4, 0xfc, 0x2f, 0x43, 0x88, 0x2a, 0x75, + 0xf0, 0x4e, 0x94, 0x7d, 0x8e, 0x5a, 0xa1, 0x89, 0xad, 0x72, 0x91, 0xbb, 0x21, 0x3d, 0xde, 0x45, + 0xe2, 0x0c, 0xb7, 0xca, 0x2d, 0x04, 0x3b, 0xb1, 0x80, 0xdb, 0x3a, 0x12, 0xeb, 0x00, 0xd3, 0x7e, + 0xaa, 0x05, 0x3c, 0x72, 0xe4, 0x02, 0x56, 0xbe, 0xd9, 0x27, 0x75, 0xd6, 0x8e, 0xbe, 0x9a, 0x0b, + 0xf8, 0x3e, 0x49, 0xc3, 0x49, 0x83, 0x12, 0xbf, 0xe3, 0xd9, 0xab, 0x7a, 0x24, 0x33, 0x95, 0xb7, + 0x0a, 0x64, 0x62, 0xf8, 0xe4, 0x04, 0x96, 0x32, 0x61, 0x5f, 0x86, 0x71, 0x3d, 0xab, 0x8f, 0xcc, + 0x3d, 0xf3, 0xac, 0x4c, 0xd7, 0x9c, 0xa6, 0xf3, 0x31, 0xef, 0xec, 0xf5, 0xf4, 0x3d, 0x5b, 0x70, + 0x66, 0x29, 0xf0, 0xa3, 0x7e, 0x57, 0x5d, 0x85, 0xa5, 0x6f, 0x95, 0x00, 0x0e, 0x05, 0xa6, 0x08, + 0x69, 0x11, 0x8a, 0xba, 0x46, 0x53, 0xc9, 0x5f, 0x8c, 0x03, 0xc8, 0x20, 0x46, 0x6c, 0x03, 0xc6, + 0xd1, 0xf8, 0x40, 0x9e, 0xab, 0xe3, 0xa6, 0xd8, 0x48, 0x4b, 0x96, 0xc5, 0xc2, 0x90, 0x6f, 0xba, + 0xb8, 0xdd, 0x8e, 0x8a, 0xdd, 0x32, 0xde, 0x74, 0x49, 0x91, 0xd9, 0x37, 0x60, 0x4a, 0x9e, 0xd3, + 0x36, 0xf9, 0x96, 0x9c, 0x3b, 0x13, 0xc6, 0x35, 0x98, 0x59, 0xa8, 0x5c, 0x00, 0xd1, 0xe4, 0x83, + 0x41, 0xee, 0x02, 0x6a, 0xba, 0x00, 0xea, 0xf8, 0xec, 0x2e, 0x9c, 0xb8, 0xe1, 0x46, 0x12, 0xa8, + 0xa5, 0x67, 0x99, 0x44, 0xe3, 0x06, 0x46, 0xb4, 0xec, 0xba, 0x91, 0xb2, 0x21, 0x15, 0xa6, 0x63, + 0x29, 0xa2, 0x67, 0xdf, 0x2d, 0xc1, 0xac, 0x61, 0x62, 0x22, 0x0f, 0x29, 0x4c, 0xda, 0x3e, 0x65, + 0x04, 0xea, 0x0f, 0x42, 0x93, 0x43, 0x92, 0xb1, 0x62, 0x85, 0x69, 0xb9, 0x1e, 0x2a, 0x32, 0x88, + 0x07, 0x2d, 0x54, 0x5c, 0xd3, 0xd3, 0xe6, 0x42, 0xcd, 0xac, 0x6b, 0x85, 0xc6, 0x96, 0x60, 0x72, + 0x79, 0xb1, 0xb9, 0xd4, 0xe8, 0x6f, 0x75, 0xbc, 0x96, 0x38, 0x64, 0xd7, 0x90, 0x0e, 0x6f, 0x6b, + 0xda, 0x5b, 0x51, 0xcb, 0xe9, 0x61, 0x49, 0xe6, 0xac, 0x6d, 0xd2, 0xb0, 0xba, 0x7a, 0x58, 0x71, + 0x06, 0x3f, 0xf5, 0x74, 0x4e, 0xb6, 0x1d, 0xf9, 0xb2, 0xa2, 0x75, 0x35, 0x3b, 0xee, 0x6c, 0x0a, + 0xca, 0x1e, 0xc9, 0x78, 0xbb, 0xec, 0xb5, 0xa5, 0x13, 0xa3, 0x98, 0x10, 0x94, 0x6c, 0x10, 0x7f, + 0x58, 0x6f, 0xe8, 0xf2, 0x90, 0xd4, 0xd3, 0x43, 0xe5, 0xa1, 0xf5, 0xdf, 0x8f, 0xc0, 0x74, 0x66, + 0x7a, 0xd2, 0x79, 0xb9, 0x94, 0x3b, 0x2f, 0x37, 0x01, 0xa4, 0xb5, 0xe6, 0x98, 0x66, 0x15, 0x15, + 0x26, 0x3e, 0x4e, 0xd9, 0x26, 0x92, 0xb5, 0xad, 0xb1, 0x11, 0x4c, 0xa5, 0xe4, 0x38, 0xa6, 0x99, + 0x2b, 0x61, 0x2a, 0x85, 0x8f, 0xc6, 0x34, 0x65, 0xc3, 0xe6, 0x61, 0x18, 0xd3, 0xc4, 0xeb, 0x51, + 0xfa, 0x9e, 0x00, 0xd8, 0x12, 0xce, 0x5e, 0x84, 0x11, 0xa1, 0xcc, 0xad, 0x2e, 0x93, 0x30, 0xc6, + 0x3d, 0x4e, 0x68, 0x7b, 0x42, 0x73, 0xa2, 0x22, 0x76, 0x15, 0x26, 0xe4, 0x5f, 0x94, 0x8d, 0x6c, + 0xc4, 0xf4, 0x39, 0x74, 0xbc, 0xb6, 0x4a, 0x48, 0x66, 0xe0, 0x89, 0x53, 0x4e, 0xb3, 0xbf, 0xf5, + 0x21, 0x6f, 0x09, 0x09, 0x33, 0x9a, 0x9e, 0x72, 0x22, 0x09, 0x14, 0x55, 0xa4, 0x08, 0x42, 0xa7, + 0xa2, 0x58, 0xb9, 0x2a, 0x9e, 0x6d, 0x51, 0xa7, 0x92, 0x31, 0x72, 0x36, 0x95, 0xb0, 0x8b, 0xd2, + 0x98, 0x8a, 0xea, 0xa9, 0xf4, 0xf6, 0x45, 0xd3, 0x23, 0x1a, 0x48, 0x50, 0x47, 0x4d, 0x8a, 0x45, + 0xe5, 0xe2, 0xef, 0x95, 0xae, 0xeb, 0x75, 0x48, 0xbc, 0x61, 0xe5, 0x88, 0xcb, 0x05, 0xd4, 0x4e, + 0x11, 0xd8, 0xbb, 0x30, 0x25, 0x5f, 0xc1, 0xee, 0x76, 0x03, 0x1f, 0xd9, 0x8f, 0xa7, 0x6e, 0x3a, + 0xf4, 0x84, 0xb8, 0x28, 0x92, 0xb5, 0x64, 0x70, 0xc5, 0xbe, 0x86, 0x17, 0x35, 0x7d, 0x69, 0xe6, + 0x9d, 0x48, 0xf7, 0x35, 0x24, 0x8d, 0x24, 0xdc, 0xd6, 0x91, 0xd8, 0x17, 0x61, 0x52, 0xfc, 0xbc, + 0xee, 0xed, 0x71, 0x59, 0xe1, 0x64, 0x7a, 0xed, 0x8a, 0x54, 0x3b, 0xa2, 0x44, 0xd6, 0x67, 0x62, + 0xb2, 0x0f, 0xe0, 0x14, 0x72, 0x6a, 0x05, 0x3d, 0xde, 0xae, 0x6f, 0x6f, 0x7b, 0x1d, 0x4f, 0xfa, + 0x73, 0x4d, 0xa5, 0xfe, 0x10, 0xb2, 0x62, 0xc4, 0x70, 0xdc, 0x14, 0xc5, 0x2e, 0xa6, 0x64, 0x9b, + 0x50, 0x5b, 0xea, 0x47, 0x71, 0xd0, 0xad, 0xc7, 0x71, 0xe8, 0x6d, 0xf5, 0x63, 0x1e, 0xcd, 0x4e, + 0x1b, 0xd9, 0xa9, 0xc4, 0xe2, 0x48, 0x0a, 0xa5, 0x5d, 0xaa, 0x85, 0x14, 0x8e, 0x9b, 0x90, 0xd8, + 0x39, 0x26, 0xd6, 0x7f, 0x57, 0x82, 0x49, 0x83, 0x94, 0xbd, 0x05, 0x13, 0xd7, 0x42, 0x8f, 0xfb, + 0xed, 0xce, 0xbe, 0x76, 0x60, 0xc6, 0xd3, 0xd4, 0x36, 0xc1, 0xe5, 0x57, 0x1b, 0x68, 0x89, 0xbd, + 0xa9, 0x5c, 0xe8, 0x6c, 0x79, 0x59, 0x26, 0x8b, 0xa0, 0x09, 0x5a, 0x49, 0xd3, 0xe5, 0xe1, 0x04, + 0xa5, 0xd9, 0xa9, 0xa1, 0xb0, 0x2f, 0xc1, 0x88, 0xbc, 0xd2, 0x21, 0xcf, 0xbf, 0xb3, 0x45, 0x9f, + 0x29, 0x13, 0x93, 0xe0, 0x44, 0x44, 0x67, 0x84, 0xc8, 0x26, 0x22, 0xeb, 0x17, 0x4b, 0xc0, 0xf2, + 0xa8, 0x47, 0xd8, 0xdf, 0x8e, 0x74, 0x72, 0x78, 0x3f, 0x59, 0x8d, 0x15, 0xe3, 0x2e, 0x55, 0xd4, + 0x24, 0x0b, 0x64, 0xc7, 0xd3, 0xaa, 0xd3, 0x0d, 0x82, 0xb2, 0xd8, 0xfa, 0x4e, 0x19, 0x20, 0xc5, + 0x66, 0x5f, 0x90, 0x6f, 0x08, 0x7f, 0xd0, 0x77, 0x3b, 0xde, 0xb6, 0x67, 0x26, 0xf1, 0x46, 0x26, + 0x1f, 0xa9, 0x12, 0xdb, 0x44, 0x64, 0xef, 0xc1, 0x74, 0xb3, 0x61, 0xd2, 0x6a, 0x5e, 0xeb, 0x51, + 0xcf, 0xc9, 0x90, 0x67, 0xb1, 0xd1, 0xc3, 0x57, 0x1f, 0x0d, 0xe9, 0xe1, 0x2b, 0x07, 0x82, 0x4a, + 0x84, 0x60, 0x69, 0x36, 0x28, 0x4a, 0xa3, 0x9d, 0x5c, 0x4c, 0x60, 0xeb, 0xa2, 0x9e, 0xd3, 0xa3, + 0xf0, 0x0d, 0x21, 0x27, 0x0c, 0xbc, 0xb4, 0x23, 0x87, 0x07, 0x24, 0x1f, 0xf9, 0x25, 0x34, 0x3f, + 0x76, 0x83, 0x98, 0x93, 0xd5, 0xe5, 0xa9, 0x3d, 0x7f, 0xa5, 0xf7, 0x81, 0xc3, 0x46, 0x4e, 0x05, + 0xe3, 0xeb, 0xe8, 0xce, 0xfb, 0x4a, 0x7a, 0x58, 0x92, 0x37, 0x83, 0x05, 0xd7, 0xe4, 0x7f, 0xa3, + 0x04, 0xa7, 0x0a, 0x69, 0xd9, 0x25, 0x80, 0xd4, 0xb6, 0x45, 0xbd, 0x84, 0x12, 0x33, 0x4d, 0x12, + 0x67, 0x6b, 0x18, 0xec, 0xeb, 0x59, 0xab, 0xd4, 0xd1, 0x1b, 0xe1, 0x9c, 0xca, 0xcd, 0x6a, 0x5a, + 0xa5, 0x0a, 0x6c, 0x51, 0xd6, 0x6f, 0x54, 0x60, 0x46, 0xcb, 0x41, 0x27, 0xdb, 0x7a, 0x84, 0xc7, + 0xf5, 0x7d, 0x98, 0x10, 0x5f, 0xe3, 0xb5, 0x28, 0x40, 0x4b, 0xde, 0x5d, 0xbf, 0x9a, 0x8b, 0x8a, + 0x27, 0x6e, 0x97, 0x74, 0x64, 0xe9, 0xa0, 0x86, 0xa2, 0x13, 0x5f, 0xc6, 0x6f, 0xe5, 0x23, 0xb5, + 0x0c, 0xe6, 0x2c, 0x82, 0xc9, 0xe5, 0x7d, 0xdf, 0xed, 0x26, 0xb5, 0xc9, 0x3b, 0xec, 0xcf, 0x0e, + 0xac, 0xcd, 0xc0, 0x96, 0xd5, 0xa5, 0xf1, 0xa3, 0xb2, 0xac, 0x20, 0x75, 0x89, 0x41, 0x35, 0xf7, + 0x1e, 0xcc, 0xe4, 0x1a, 0xfd, 0x58, 0xc9, 0x9b, 0x37, 0x81, 0xe5, 0xdb, 0x71, 0x7c, 0x77, 0x33, + 0xb1, 0xe7, 0xb9, 0x7e, 0x5b, 0xde, 0x88, 0x2f, 0xe8, 0xee, 0x66, 0xff, 0x6b, 0x59, 0xcf, 0x4c, + 0xf0, 0xb4, 0xaf, 0xba, 0xf7, 0x8d, 0x53, 0xf9, 0xf3, 0x83, 0xc6, 0x74, 0xb0, 0xf5, 0x83, 0x5d, + 0x05, 0x5a, 0x83, 0x99, 0xc4, 0x3d, 0x79, 0x1e, 0xb4, 0x52, 0x25, 0xb6, 0x18, 0x3a, 0xdc, 0xb5, + 0xa5, 0xbe, 0x64, 0xcb, 0x1f, 0xda, 0xfa, 0xfd, 0xc3, 0x0a, 0x9c, 0x19, 0xd0, 0x0e, 0xb6, 0x9f, + 0x9d, 0x92, 0xd2, 0xb6, 0xf2, 0xe6, 0xe1, 0xcd, 0x7f, 0x12, 0x13, 0x93, 0x7d, 0x41, 0x66, 0x3a, + 0x6a, 0x05, 0xfe, 0xb6, 0xb7, 0x43, 0x56, 0x05, 0xbc, 0x9c, 0xb8, 0x9f, 0x40, 0xb3, 0x29, 0x8e, + 0x24, 0x94, 0xbd, 0x07, 0xc3, 0x98, 0xe4, 0x22, 0x93, 0x53, 0x57, 0x60, 0x20, 0x5c, 0xcb, 0x0a, + 0x2d, 0x7e, 0x1a, 0x59, 0xa1, 0x05, 0x80, 0x7d, 0x1e, 0x2a, 0xf5, 0xcd, 0x26, 0x8d, 0xf2, 0x94, + 0x4e, 0xbe, 0xd9, 0x4c, 0x9f, 0xab, 0x73, 0x8d, 0x77, 0xe5, 0x04, 0x85, 0x20, 0xbc, 0xbe, 0xd4, + 0xa0, 0x31, 0xd6, 0x09, 0xaf, 0x2f, 0x35, 0x52, 0xc2, 0x1d, 0x33, 0x8c, 0xf6, 0xfa, 0x52, 0xe3, + 0xd3, 0x5b, 0x44, 0x7f, 0xa5, 0x2c, 0xd3, 0x33, 0xc9, 0x0f, 0x7b, 0x0f, 0x26, 0x94, 0x53, 0xa2, + 0x26, 0xf4, 0xc8, 0xdb, 0x95, 0x5c, 0x7f, 0x33, 0x2e, 0x04, 0x06, 0x81, 0x7a, 0xf8, 0x31, 0xf1, + 0xce, 0xd5, 0x6f, 0xdf, 0x13, 0x0e, 0xd9, 0xd0, 0x1e, 0x93, 0x84, 0x5d, 0x81, 0xea, 0x06, 0xf7, + 0x5d, 0x3f, 0x4e, 0xcc, 0xbc, 0xe8, 0x42, 0x19, 0x23, 0xcc, 0xd4, 0x41, 0x12, 0x44, 0xf4, 0xf6, + 0xeb, 0x6f, 0x45, 0xad, 0xd0, 0xc3, 0x34, 0x6e, 0xc9, 0xce, 0x2e, 0xbd, 0xfd, 0xb4, 0x12, 0x93, + 0x41, 0x86, 0xc8, 0xfa, 0xa5, 0x12, 0x8c, 0xd2, 0x40, 0xca, 0x07, 0x83, 0x77, 0xd2, 0x9d, 0x89, + 0x9c, 0xf9, 0x77, 0xbc, 0xac, 0x33, 0xff, 0x8e, 0xcc, 0x95, 0x36, 0x46, 0x51, 0x74, 0xc9, 0x85, + 0xa7, 0x8c, 0x69, 0xa4, 0x10, 0x77, 0xf3, 0x3d, 0xd6, 0x04, 0xf5, 0xb8, 0x51, 0x55, 0xd6, 0xaf, + 0x52, 0xcb, 0xae, 0x2f, 0x35, 0xd8, 0x02, 0x54, 0xd7, 0x02, 0x99, 0x7f, 0x90, 0xda, 0x86, 0x42, + 0xac, 0x43, 0x30, 0xbd, 0x83, 0x14, 0x9e, 0x68, 0x5f, 0x23, 0x0c, 0xe8, 0x64, 0xa4, 0xb5, 0xaf, + 0x27, 0x81, 0x99, 0xf6, 0x25, 0xa8, 0xc7, 0x6e, 0xdf, 0xb7, 0x8a, 0x84, 0x84, 0x14, 0x30, 0x2b, + 0x30, 0x86, 0xe9, 0x99, 0xf7, 0x38, 0x3d, 0x4e, 0x3d, 0xbe, 0xf0, 0xca, 0x20, 0x01, 0xb1, 0xac, + 0x10, 0x49, 0x48, 0xa5, 0x94, 0xd6, 0x26, 0x9c, 0x3b, 0x0a, 0x9d, 0x5d, 0x81, 0x8a, 0xfb, 0x40, + 0xa5, 0x8e, 0x7f, 0x61, 0x50, 0x25, 0xf5, 0xcd, 0x26, 0xb1, 0x17, 0xd8, 0xd6, 0x6f, 0x97, 0xf4, + 0x24, 0x1d, 0x59, 0x1c, 0x76, 0x19, 0x4e, 0x46, 0x3c, 0xee, 0xf7, 0x94, 0x75, 0x6a, 0x3b, 0x08, + 0x1d, 0x37, 0xa4, 0x9e, 0xb7, 0x67, 0xb0, 0x4c, 0xda, 0xa4, 0xae, 0x05, 0x61, 0x3d, 0xf4, 0xd9, + 0x39, 0x18, 0xf7, 0xfc, 0x98, 0xef, 0x50, 0xc8, 0x8b, 0xdc, 0x11, 0x75, 0x10, 0xbb, 0x0e, 0xa7, + 0x93, 0xef, 0x72, 0xe4, 0x93, 0x7f, 0x6d, 0x0c, 0x45, 0xc8, 0x68, 0xe1, 0xf2, 0xb1, 0x3f, 0x3b, + 0xe8, 0x70, 0xfb, 0x64, 0x42, 0x20, 0x81, 0x6d, 0x01, 0xb5, 0x78, 0x41, 0xaf, 0xdf, 0xbb, 0x82, + 0xfe, 0xe6, 0x37, 0xf5, 0x18, 0x41, 0x2a, 0x52, 0xf2, 0x79, 0x6e, 0x50, 0xcf, 0xdc, 0xbb, 0x62, + 0x17, 0x50, 0x59, 0x7f, 0x52, 0xd6, 0x99, 0x35, 0x79, 0xb8, 0xf7, 0x14, 0xef, 0xb4, 0xc5, 0x77, + 0xb4, 0xd9, 0xcf, 0x3b, 0x64, 0xa3, 0xfd, 0x7c, 0x66, 0xa3, 0x9d, 0x1f, 0xc4, 0x82, 0xf4, 0xdc, + 0x63, 0xef, 0xb4, 0x3f, 0x5d, 0x81, 0xd3, 0xc5, 0xed, 0xd0, 0xbb, 0xa6, 0x74, 0x48, 0xd7, 0x5c, + 0x80, 0xea, 0x8d, 0x20, 0x8a, 0x35, 0xd7, 0x2f, 0xbc, 0x99, 0xda, 0x25, 0x98, 0x9d, 0x94, 0xb2, + 0x17, 0x61, 0x44, 0xfc, 0x9d, 0xc8, 0x58, 0xe4, 0x87, 0x99, 0xa5, 0xbc, 0xb6, 0x4d, 0x45, 0xec, + 0x3a, 0x54, 0x6d, 0x8a, 0x5e, 0xcb, 0xf4, 0xb4, 0x02, 0x27, 0x0a, 0x36, 0x0b, 0x09, 0x62, 0x3c, + 0xaa, 0x42, 0x30, 0x56, 0x87, 0x51, 0x9a, 0x4c, 0x19, 0xaf, 0x86, 0x82, 0x19, 0x28, 0x9b, 0x42, + 0x37, 0xd9, 0xb6, 0xa2, 0x13, 0xdb, 0x02, 0xde, 0x4f, 0xaf, 0x2e, 0xab, 0x40, 0x34, 0xdc, 0x16, + 0xe4, 0xfd, 0xb5, 0xe9, 0x65, 0x97, 0x20, 0xb2, 0x79, 0x18, 0x0f, 0x79, 0xc7, 0xdd, 0x97, 0x7b, + 0x0e, 0xf5, 0x3b, 0x20, 0x48, 0x6e, 0x36, 0xcf, 0xc0, 0x98, 0x44, 0xf0, 0xda, 0x64, 0x05, 0xb2, + 0xab, 0x08, 0x58, 0x6d, 0x47, 0xd6, 0x06, 0xcc, 0x0e, 0x1a, 0x53, 0x71, 0xce, 0x8d, 0xdd, 0x70, + 0x87, 0xe3, 0xa1, 0xa2, 0x43, 0x51, 0xca, 0xa9, 0xdb, 0xd4, 0x06, 0x96, 0xdd, 0xc0, 0x22, 0x7b, + 0x22, 0xd6, 0x7e, 0x59, 0xdf, 0x2d, 0x03, 0x28, 0xe3, 0xe2, 0x53, 0xbb, 0x88, 0x3e, 0x6f, 0x2c, + 0xa2, 0x53, 0x5a, 0xf2, 0x57, 0xf1, 0x59, 0xc7, 0xba, 0xa3, 0xbb, 0x03, 0x53, 0x26, 0xfe, 0xd1, + 0x6f, 0x74, 0x6f, 0xa4, 0x76, 0x57, 0x8a, 0xe8, 0xc0, 0xdb, 0x1b, 0x09, 0xb7, 0xb6, 0xe0, 0xe4, + 0x75, 0x1e, 0xa7, 0x56, 0x58, 0x75, 0x53, 0x7f, 0x38, 0xdb, 0xd7, 0x60, 0x8c, 0xf0, 0x93, 0x8d, + 0x51, 0x9a, 0x0c, 0x29, 0x81, 0x1c, 0x9a, 0x0c, 0x15, 0x82, 0x10, 0xb8, 0xcb, 0xbc, 0xc3, 0x63, + 0xfe, 0xe9, 0x56, 0xd3, 0x04, 0x26, 0x3f, 0x05, 0xbf, 0xec, 0x78, 0x35, 0x1c, 0xd9, 0x3f, 0xf7, + 0xe0, 0x54, 0xd2, 0xf6, 0x27, 0xc9, 0xf7, 0x32, 0x4c, 0x2b, 0x0d, 0x4e, 0xe3, 0x78, 0x88, 0xab, + 0xd6, 0xf7, 0x4a, 0x30, 0xa7, 0x28, 0x36, 0xbd, 0xc4, 0x47, 0xef, 0x58, 0xc4, 0xec, 0x5d, 0x18, + 0xd7, 0x68, 0x28, 0x18, 0x03, 0xaf, 0x75, 0x1e, 0x78, 0xf1, 0xae, 0x13, 0x49, 0xb8, 0x7e, 0xad, + 0xa3, 0xa1, 0xb3, 0x2d, 0x98, 0x6b, 0xd6, 0xd7, 0xd7, 0xd2, 0x70, 0xaa, 0xdb, 0xc1, 0xb5, 0xa0, + 0xd3, 0x09, 0x1e, 0xdc, 0xb5, 0xd7, 0xd4, 0x3b, 0x86, 0x98, 0x97, 0x06, 0xef, 0x88, 0xb4, 0x98, + 0x2c, 0x3f, 0x70, 0xb6, 0x11, 0xd1, 0xe9, 0x87, 0x9d, 0xc8, 0x3e, 0x84, 0x8b, 0xf5, 0x9f, 0x95, + 0xe0, 0x99, 0x24, 0xb6, 0xa7, 0xe0, 0xfb, 0x32, 0x5f, 0x50, 0x7a, 0x92, 0x5f, 0x50, 0x7e, 0x22, + 0x5f, 0x70, 0x3b, 0x1d, 0x9f, 0x55, 0x3f, 0xc9, 0x00, 0xa0, 0xda, 0xcf, 0xf4, 0xf1, 0xa1, 0x51, + 0x79, 0x36, 0x97, 0x53, 0x40, 0x4b, 0x1d, 0x60, 0xbd, 0xa3, 0x75, 0x48, 0x01, 0x43, 0x83, 0xb8, + 0x94, 0x25, 0xfe, 0x6e, 0x19, 0xa6, 0xef, 0xac, 0x2e, 0x2f, 0x25, 0x6e, 0x87, 0x4f, 0xad, 0xd0, + 0x2c, 0x76, 0xec, 0x33, 0xbe, 0x6d, 0xb0, 0xe4, 0xb4, 0xee, 0xc2, 0x89, 0x4c, 0x37, 0xa0, 0x9e, + 0xf7, 0x65, 0x19, 0xa4, 0x91, 0x80, 0x95, 0x8e, 0x77, 0xba, 0x88, 0xfd, 0xbd, 0x2b, 0x76, 0x06, + 0xdb, 0xfa, 0x67, 0x13, 0x19, 0xbe, 0x24, 0x8c, 0x5f, 0x83, 0xb1, 0xd5, 0x28, 0xea, 0xf3, 0xf0, + 0xae, 0xbd, 0xa6, 0xdb, 0xe6, 0x3c, 0x04, 0x8a, 0x39, 0x64, 0xa7, 0x08, 0xec, 0x22, 0x54, 0xe9, + 0xf1, 0x16, 0x25, 0xdd, 0xf0, 0x9a, 0x24, 0x79, 0xfb, 0xc5, 0x4e, 0x8a, 0xd9, 0x5b, 0x30, 0x21, + 0xff, 0x96, 0x33, 0x9a, 0x3a, 0x1c, 0xad, 0xf1, 0x84, 0x2e, 0x57, 0x80, 0x6d, 0xa0, 0xb1, 0x57, + 0xa1, 0x52, 0x5f, 0xb2, 0xc9, 0xfe, 0x4a, 0x47, 0xab, 0xd0, 0x91, 0x46, 0x72, 0xe3, 0x9c, 0xbd, + 0x64, 0x8b, 0x03, 0x92, 0xca, 0xc4, 0x43, 0x57, 0x47, 0x38, 0x03, 0x94, 0x79, 0x37, 0xa3, 0x2a, + 0x20, 0x8c, 0x5d, 0x86, 0xd1, 0x65, 0x2f, 0xea, 0x75, 0xdc, 0x7d, 0xba, 0x38, 0x92, 0xcf, 0x9f, + 0x4b, 0x90, 0x91, 0x44, 0x44, 0x82, 0xd8, 0x45, 0xf5, 0xf2, 0x6f, 0x35, 0x8d, 0xf5, 0x18, 0xf0, + 0xbc, 0x6f, 0x1a, 0x8a, 0x3d, 0x76, 0x74, 0x28, 0x76, 0x3e, 0x88, 0x1a, 0x9e, 0x64, 0x10, 0xf5, + 0x16, 0x9c, 0xb9, 0x8e, 0xe6, 0x52, 0x33, 0x3f, 0xe6, 0x5d, 0x7b, 0x95, 0x2e, 0xa0, 0xf0, 0xfe, + 0x57, 0x5a, 0x54, 0xb3, 0x29, 0x36, 0x9d, 0x7e, 0xe8, 0xe9, 0x57, 0xf2, 0x03, 0x18, 0xb1, 0xaf, + 0xc0, 0xc9, 0xa2, 0x22, 0xba, 0xa6, 0xc2, 0xac, 0x52, 0xc5, 0x15, 0xe8, 0x59, 0xa5, 0x8a, 0x38, + 0xb0, 0x35, 0xa8, 0x49, 0x78, 0xbd, 0xdd, 0xf5, 0x7c, 0x79, 0xd5, 0x36, 0x99, 0x06, 0xd1, 0x12, + 0x57, 0x57, 0x14, 0xca, 0x2b, 0x37, 0x23, 0x5c, 0x27, 0x43, 0xc9, 0x7e, 0xb6, 0x04, 0x13, 0x36, + 0x97, 0x0f, 0x82, 0xa0, 0xf8, 0x9c, 0x22, 0xe7, 0x81, 0x24, 0x7e, 0xa6, 0x19, 0x87, 0x9e, 0xbf, + 0x43, 0xa1, 0x38, 0x1b, 0x14, 0x8a, 0xf3, 0xee, 0xc7, 0x0a, 0xc5, 0x91, 0xac, 0xa2, 0x47, 0x07, + 0xf3, 0x13, 0x21, 0xd5, 0x89, 0xab, 0xc8, 0x68, 0x81, 0xe8, 0x3a, 0x8c, 0xcb, 0xbe, 0xeb, 0xcb, + 0xe7, 0x08, 0x78, 0x5b, 0x7e, 0xe4, 0x34, 0x0a, 0x76, 0xec, 0x3a, 0x99, 0xec, 0xa8, 0x9f, 0x20, + 0xe4, 0x3e, 0xb4, 0x90, 0x03, 0x5b, 0x94, 0xd7, 0x7f, 0x62, 0x2b, 0x95, 0x61, 0xb9, 0xb5, 0xd4, + 0x36, 0xa3, 0x62, 0x43, 0x1c, 0x9c, 0x46, 0xfa, 0xe4, 0x31, 0x48, 0xd8, 0x65, 0x18, 0x59, 0x77, + 0x1f, 0xd6, 0xe9, 0x86, 0x7d, 0x7c, 0x61, 0x52, 0x89, 0x3f, 0x04, 0x2e, 0x56, 0xff, 0xa9, 0x8c, + 0x0f, 0xf8, 0x8c, 0x4d, 0x68, 0xec, 0xcf, 0x97, 0xe0, 0xb4, 0x5c, 0xc6, 0xea, 0x2b, 0x9b, 0x3c, + 0x8e, 0x45, 0x3f, 0x50, 0x3a, 0xe1, 0x73, 0xc9, 0x73, 0x05, 0x77, 0x8a, 0xf1, 0x30, 0x59, 0x84, + 0x45, 0x92, 0x21, 0xe9, 0xb8, 0x88, 0x4a, 0x8d, 0x07, 0x22, 0x0a, 0xe9, 0xd9, 0x06, 0x8c, 0xaf, + 0x5f, 0xab, 0x27, 0xd5, 0x9e, 0x30, 0xce, 0x6c, 0x86, 0xe4, 0xd3, 0xd0, 0x8a, 0xbc, 0xf3, 0x75, + 0x36, 0x78, 0x1c, 0xb9, 0xb5, 0xb4, 0x82, 0xe9, 0x19, 0x4e, 0xa6, 0x56, 0xaa, 0xde, 0xfd, 0x16, + 0xcf, 0x3e, 0x67, 0x90, 0x20, 0xb2, 0xf7, 0x64, 0xc0, 0x20, 0x66, 0xf1, 0x11, 0xa7, 0xf1, 0x53, + 0x69, 0xba, 0x2d, 0xf9, 0xe4, 0x05, 0x15, 0xe8, 0xf6, 0x35, 0x9d, 0x80, 0xdd, 0x01, 0x95, 0xee, + 0xe1, 0x0e, 0x5e, 0x62, 0x63, 0xf5, 0xa7, 0xd3, 0x20, 0x1d, 0xe5, 0x39, 0x13, 0xc8, 0xeb, 0xee, + 0x4c, 0x43, 0xf2, 0xb4, 0xec, 0x2e, 0xcc, 0x72, 0x3f, 0x0e, 0x5d, 0xc7, 0x6b, 0x53, 0x76, 0xd8, + 0xe4, 0x12, 0x8c, 0x1e, 0xf9, 0x57, 0xd7, 0x3f, 0x2b, 0x02, 0x6d, 0x75, 0x59, 0x5e, 0x8b, 0x2b, + 0xa9, 0x69, 0x9f, 0x42, 0xea, 0xd5, 0xb6, 0x09, 0xa6, 0x78, 0x83, 0x7d, 0x38, 0x55, 0x48, 0xc5, + 0xe6, 0xa0, 0xda, 0xf6, 0xa2, 0xf4, 0x29, 0xba, 0xaa, 0x9d, 0xfc, 0x66, 0xcf, 0x01, 0xc8, 0xac, + 0x9a, 0xa2, 0x5a, 0xa5, 0x4a, 0x20, 0x04, 0x6f, 0x34, 0x5f, 0x86, 0xa9, 0x9d, 0xd0, 0xed, 0xed, + 0x3a, 0xdc, 0x6f, 0xf7, 0x02, 0xcf, 0xa7, 0xfd, 0xc3, 0x9e, 0x44, 0xe8, 0x0a, 0x01, 0xad, 0xcf, + 0xab, 0x89, 0xca, 0x5e, 0xd7, 0x83, 0x85, 0x2b, 0x38, 0x4a, 0xa3, 0x5d, 0xf7, 0xa1, 0xe3, 0xee, + 0x70, 0xc3, 0x6f, 0x89, 0xee, 0xf1, 0x7e, 0xa1, 0x04, 0x67, 0x07, 0xce, 0x45, 0x76, 0x15, 0x64, + 0x5e, 0x34, 0xde, 0x76, 0x76, 0xe3, 0xb8, 0x17, 0x39, 0xea, 0x64, 0xad, 0x52, 0x5a, 0x9d, 0xa2, + 0xe2, 0x1b, 0xa2, 0x54, 0x1d, 0xb6, 0x23, 0xf6, 0x1e, 0x3c, 0xeb, 0xf9, 0x11, 0x6f, 0xf5, 0x43, + 0xee, 0x28, 0x06, 0x2d, 0xaf, 0x1d, 0x3a, 0xa1, 0xeb, 0xef, 0xa8, 0x58, 0x69, 0xfb, 0xac, 0xc2, + 0xa1, 0xcc, 0x0c, 0x4b, 0x5e, 0x3b, 0xb4, 0x11, 0xc1, 0xfa, 0x1b, 0x65, 0x98, 0x1d, 0x34, 0x57, + 0xd9, 0x2c, 0x8c, 0x72, 0x5f, 0xef, 0x4d, 0xf5, 0x53, 0x1c, 0x6f, 0x93, 0x2d, 0x98, 0xfa, 0xb2, + 0xda, 0xa2, 0x47, 0xd6, 0xd8, 0x8b, 0x60, 0x6e, 0xb8, 0xd4, 0x93, 0x13, 0x2d, 0x7d, 0xdb, 0x7d, + 0x0e, 0x20, 0xdd, 0x67, 0xa5, 0x51, 0xd5, 0x1e, 0x73, 0x5b, 0xa1, 0x14, 0x89, 0xec, 0x34, 0x8c, + 0xc8, 0x7d, 0x4c, 0x6e, 0xcc, 0x36, 0xfd, 0x12, 0x0a, 0x15, 0x75, 0x32, 0x6e, 0xc0, 0x95, 0xc5, + 0x09, 0xa3, 0xb3, 0x47, 0xba, 0x72, 0x70, 0x0a, 0xe7, 0xf3, 0xe8, 0xc7, 0x9f, 0xcf, 0xd6, 0x4f, + 0x4e, 0x4a, 0x65, 0xb1, 0xde, 0x8f, 0x77, 0x95, 0x7a, 0xb9, 0x50, 0x14, 0x4d, 0x27, 0xdd, 0xe5, + 0xb5, 0x68, 0x3a, 0x33, 0x86, 0x4e, 0x5d, 0xab, 0x97, 0x0b, 0xaf, 0xd5, 0x5f, 0x83, 0xb1, 0xa5, + 0x5d, 0xde, 0xba, 0x9f, 0x84, 0x28, 0x55, 0xe9, 0xde, 0x52, 0x00, 0xe5, 0xa3, 0x3c, 0x29, 0x02, + 0xbb, 0x0c, 0x80, 0x41, 0xbc, 0xf2, 0x14, 0xa5, 0x3d, 0xac, 0x87, 0x31, 0xbf, 0xe4, 0x81, 0xa8, + 0xa1, 0x20, 0xfb, 0xa6, 0x7d, 0x4d, 0x77, 0x59, 0x94, 0xec, 0xa3, 0x70, 0x9b, 0xd0, 0x53, 0x04, + 0xf1, 0x79, 0xda, 0x0e, 0x42, 0xfa, 0x4e, 0x2d, 0xb7, 0xcd, 0xe8, 0x48, 0xec, 0xf3, 0x30, 0xba, + 0xc4, 0xc3, 0x78, 0x63, 0x63, 0x0d, 0xfd, 0x04, 0xe5, 0x7b, 0x72, 0x55, 0x7c, 0xfb, 0x2b, 0x8e, + 0x3b, 0xdf, 0x3f, 0x98, 0x9f, 0x8c, 0xbd, 0x2e, 0x4f, 0xde, 0xc9, 0xb1, 0x15, 0x36, 0x5b, 0x84, + 0x9a, 0xf4, 0x20, 0x4a, 0xcf, 0xbf, 0xa8, 0xd3, 0x54, 0xa5, 0x86, 0x45, 0xee, 0x46, 0x0f, 0xf8, + 0x56, 0xf2, 0xf2, 0x59, 0x0e, 0x9f, 0xad, 0xa8, 0x07, 0x03, 0xf5, 0x66, 0x43, 0x2a, 0x43, 0xb3, + 0xb2, 0x5e, 0xb4, 0x3e, 0x4f, 0xc1, 0xea, 0x30, 0xb9, 0x14, 0x74, 0x7b, 0x6e, 0xec, 0xe1, 0xfb, + 0xe1, 0xfb, 0xa4, 0xbe, 0xa0, 0x34, 0x6d, 0xe9, 0x05, 0x86, 0x2e, 0xa4, 0x17, 0xb0, 0x6b, 0x30, + 0x65, 0x07, 0x7d, 0xd1, 0xed, 0xca, 0x3a, 0x35, 0x91, 0x26, 0xf4, 0x0b, 0x45, 0x89, 0x50, 0xa8, + 0xc8, 0x14, 0x65, 0xa4, 0xf2, 0x37, 0xa8, 0xd8, 0xed, 0x82, 0x9b, 0x63, 0x5d, 0x2d, 0xd1, 0xdf, + 0x3f, 0xcb, 0x31, 0x2b, 0xb8, 0x74, 0xbe, 0x02, 0xe3, 0xcd, 0xe6, 0x9d, 0x0d, 0x1e, 0xc5, 0xd7, + 0x3a, 0xc1, 0x03, 0xd4, 0x4a, 0xaa, 0xf4, 0x5c, 0x6f, 0x14, 0x38, 0xb1, 0x58, 0x11, 0xdb, 0x9d, + 0xe0, 0x81, 0xad, 0x63, 0xb1, 0x6f, 0x8a, 0xfe, 0xd0, 0x74, 0x78, 0x7a, 0xb4, 0xe0, 0xb0, 0x63, + 0x06, 0xee, 0xfd, 0xe9, 0x22, 0x10, 0x87, 0x0d, 0xb3, 0xb3, 0x34, 0x74, 0x8c, 0xa0, 0x0b, 0x83, + 0x87, 0xfb, 0xf5, 0x76, 0x3b, 0xe4, 0x51, 0x44, 0xea, 0x83, 0x8c, 0xa0, 0x43, 0x23, 0x9c, 0x2b, + 0x0b, 0x8c, 0x08, 0x3a, 0x8d, 0x80, 0x2d, 0x09, 0xbd, 0x56, 0x8c, 0x22, 0xfa, 0x97, 0xae, 0x36, + 0x50, 0x03, 0xa0, 0x2b, 0x1a, 0x1a, 0x73, 0xe9, 0x89, 0xea, 0xf5, 0x4c, 0xf5, 0x55, 0xa3, 0x61, + 0xab, 0x30, 0x2d, 0x01, 0x62, 0x69, 0xc9, 0xc7, 0x3a, 0x4f, 0xa4, 0xcf, 0x85, 0x11, 0x1b, 0xdc, + 0x4c, 0xf1, 0xc1, 0x4e, 0x3d, 0x1f, 0x43, 0x86, 0x8e, 0xbd, 0x07, 0x53, 0xf8, 0x12, 0x52, 0xe2, + 0x58, 0x88, 0x1b, 0xf9, 0x84, 0x4c, 0xd0, 0x4f, 0x25, 0x19, 0x7f, 0xc3, 0x89, 0x28, 0xda, 0x4d, + 0xdd, 0x0d, 0xdf, 0x83, 0x29, 0x74, 0x69, 0x4c, 0x19, 0x9c, 0x4a, 0x19, 0x50, 0x49, 0x96, 0x41, + 0xdc, 0x89, 0x52, 0x06, 0x3f, 0x5f, 0x82, 0xb3, 0xa2, 0x22, 0x37, 0x16, 0x63, 0x2a, 0x8f, 0xe9, + 0x28, 0x14, 0xd0, 0x5f, 0x53, 0xbe, 0xe2, 0xf6, 0xfa, 0x25, 0xa5, 0x54, 0x5e, 0xd2, 0xd0, 0x2e, + 0xed, 0xbd, 0x79, 0xa9, 0x9e, 0xfe, 0x6c, 0x2a, 0x22, 0x99, 0xfc, 0x74, 0x20, 0x4f, 0x5d, 0x79, + 0x8f, 0xa2, 0xdd, 0x22, 0x0e, 0xd8, 0x28, 0xd1, 0xf8, 0xe2, 0x46, 0x9d, 0xf9, 0xd8, 0x8d, 0x1a, + 0xc8, 0x53, 0x6f, 0x54, 0xdc, 0x89, 0x0a, 0x1b, 0x75, 0x15, 0x26, 0x51, 0xb5, 0x22, 0x95, 0x36, + 0xa4, 0x37, 0xe2, 0x70, 0x4d, 0x18, 0x05, 0xf6, 0x84, 0xf8, 0x79, 0x8f, 0x7e, 0xb1, 0xcf, 0x03, + 0x79, 0x37, 0xef, 0x0a, 0x55, 0xe1, 0x6c, 0x7a, 0x78, 0x4c, 0xa1, 0xfa, 0xbd, 0x17, 0x42, 0x31, + 0xd7, 0x67, 0x62, 0xab, 0x9f, 0xd3, 0x6c, 0xf5, 0x37, 0x87, 0xaa, 0xa3, 0xb5, 0xea, 0xcd, 0xa1, + 0xea, 0x4c, 0x8d, 0xd9, 0x63, 0xc9, 0x38, 0xda, 0xa7, 0x0a, 0xbf, 0x0b, 0x2d, 0x17, 0xcd, 0xfa, + 0xfa, 0x5a, 0x7a, 0xfc, 0xfe, 0xc1, 0x0a, 0x49, 0x34, 0xbe, 0xed, 0x90, 0x90, 0xc4, 0xbb, 0x32, + 0xb8, 0x5c, 0xeb, 0x06, 0x65, 0xb9, 0x30, 0xc0, 0x59, 0xcb, 0x45, 0x86, 0xc6, 0xce, 0x60, 0x5b, + 0x7f, 0x73, 0x32, 0xc3, 0x97, 0xc2, 0x10, 0x2c, 0x18, 0x91, 0x86, 0x09, 0xea, 0x64, 0xf4, 0x03, + 0x93, 0x66, 0x0b, 0x9b, 0x4a, 0xd8, 0x59, 0xa8, 0x34, 0x9b, 0x77, 0xa8, 0x93, 0x31, 0x18, 0x21, + 0x8a, 0x02, 0x5b, 0xc0, 0xc4, 0x08, 0x61, 0x84, 0x81, 0xf6, 0x0a, 0x95, 0xd8, 0x18, 0x6d, 0x84, + 0x8a, 0xfe, 0x56, 0x66, 0x82, 0xa1, 0xb4, 0xbf, 0xc9, 0x4c, 0x90, 0x1a, 0x07, 0x96, 0x60, 0xb6, + 0x1e, 0x45, 0x3c, 0x14, 0x33, 0x82, 0x1c, 0xd7, 0x43, 0x3a, 0xca, 0xd2, 0x8e, 0x8e, 0x95, 0xba, + 0xad, 0xc8, 0x1e, 0x88, 0xc8, 0x2e, 0x40, 0xb5, 0xde, 0x6f, 0x7b, 0xdc, 0x6f, 0x19, 0xc9, 0x22, + 0x5d, 0x82, 0xd9, 0x49, 0x29, 0xfb, 0x00, 0x4e, 0x65, 0x32, 0x10, 0x53, 0x0f, 0x8c, 0xa6, 0x42, + 0x5a, 0x1d, 0xb5, 0x53, 0x27, 0x37, 0xd9, 0x25, 0xc5, 0x94, 0xac, 0x0e, 0xb5, 0x15, 0x0c, 0xc1, + 0x5d, 0xe6, 0xf2, 0x86, 0x3c, 0x08, 0x29, 0x65, 0xee, 0x29, 0xf9, 0x24, 0x34, 0x66, 0x16, 0x6f, + 0x27, 0x85, 0x76, 0x0e, 0x9d, 0xdd, 0x82, 0x13, 0x59, 0x98, 0xd8, 0xea, 0xa5, 0x0d, 0x04, 0x85, + 0x64, 0x8e, 0x0b, 0x6e, 0xf6, 0x45, 0x54, 0x6c, 0x0b, 0x66, 0x52, 0x27, 0x4f, 0xd3, 0x32, 0xa2, + 0x62, 0x58, 0x92, 0x72, 0x65, 0x1d, 0x79, 0x86, 0x26, 0xe3, 0x89, 0xd4, 0x61, 0x34, 0xb1, 0x90, + 0xd8, 0x79, 0x76, 0xac, 0x0d, 0x53, 0x4d, 0x6f, 0xc7, 0xf7, 0xfc, 0x9d, 0x5b, 0x7c, 0xbf, 0xe1, + 0x7a, 0x21, 0x45, 0x13, 0xcc, 0x26, 0xf7, 0xb1, 0xfb, 0xdd, 0x2e, 0x8f, 0x43, 0x5c, 0xf5, 0xa2, + 0x1c, 0x43, 0xf3, 0x4b, 0x98, 0x0b, 0x4e, 0xd2, 0x09, 0x71, 0xef, 0xf4, 0x5c, 0xcf, 0xd0, 0x16, + 0x4c, 0x9e, 0x86, 0x75, 0x6a, 0xe2, 0x98, 0xd6, 0xa9, 0x0e, 0xcc, 0xac, 0xf8, 0xad, 0x70, 0x1f, + 0x1d, 0x15, 0x54, 0xe3, 0x26, 0x8f, 0x68, 0xdc, 0x4b, 0xd4, 0xb8, 0x67, 0x5d, 0x35, 0xc3, 0x8a, + 0x9a, 0x97, 0x67, 0xcc, 0x9a, 0x30, 0x83, 0x47, 0x90, 0xd5, 0xe5, 0xc6, 0xaa, 0xef, 0xc5, 0x9e, + 0x1b, 0xf3, 0x36, 0x69, 0x21, 0xc9, 0x23, 0x82, 0xd2, 0x0a, 0xe1, 0xb5, 0x7b, 0x8e, 0xa7, 0x50, + 0x74, 0xa6, 0x39, 0xfa, 0xc3, 0x4c, 0x01, 0xd3, 0xff, 0x9a, 0x4c, 0x01, 0xab, 0x30, 0x9d, 0xcd, + 0x70, 0x51, 0x4b, 0x95, 0x87, 0x08, 0x8b, 0x84, 0x0e, 0x12, 0xf4, 0x51, 0xeb, 0x34, 0x92, 0x39, + 0x65, 0x72, 0x5b, 0x64, 0xac, 0x0a, 0x33, 0x86, 0x55, 0xc1, 0x90, 0x4a, 0x8f, 0x63, 0x55, 0x68, + 0x00, 0x5c, 0x0b, 0xc2, 0x16, 0xaf, 0x63, 0xf4, 0x3f, 0x33, 0x9e, 0x5a, 0x15, 0x4c, 0xd3, 0x42, + 0xb9, 0x7e, 0xb6, 0xc5, 0x6f, 0x27, 0x1b, 0xfd, 0xaf, 0xf1, 0x60, 0x2e, 0x9c, 0x69, 0x84, 0x7c, + 0x9b, 0x87, 0x21, 0x6f, 0xd3, 0x81, 0x68, 0xd1, 0xf3, 0xdb, 0xea, 0xfd, 0x5c, 0xca, 0x96, 0xde, + 0x53, 0x28, 0x49, 0xec, 0xcd, 0x96, 0x44, 0xd2, 0xf7, 0xe6, 0x01, 0x7c, 0x72, 0x56, 0x8d, 0x93, + 0x8f, 0x6b, 0xd5, 0x58, 0x81, 0xa9, 0x55, 0xbf, 0xd5, 0xe9, 0xb7, 0x39, 0xf9, 0xe6, 0xd3, 0xeb, + 0xb7, 0xa8, 0x19, 0x7a, 0xb2, 0xc4, 0x21, 0x17, 0x7e, 0x7d, 0x5d, 0x99, 0x44, 0x87, 0xda, 0x32, + 0x4e, 0x7f, 0x6c, 0x5b, 0x06, 0xab, 0xc3, 0x78, 0x2b, 0xe4, 0x98, 0x8f, 0xc0, 0xed, 0x44, 0xa4, + 0x00, 0x15, 0x8e, 0xf4, 0x52, 0x8a, 0x66, 0xeb, 0x34, 0xd6, 0x1d, 0x98, 0x1d, 0x84, 0xc8, 0xae, + 0xc0, 0x70, 0x20, 0x06, 0x30, 0xf3, 0x54, 0xfe, 0x1d, 0x31, 0x7c, 0x72, 0x1a, 0xeb, 0x6c, 0x25, + 0xae, 0xf5, 0x43, 0x70, 0xba, 0x18, 0xc1, 0x3c, 0xf1, 0x97, 0x8e, 0x3a, 0xf1, 0x97, 0xf3, 0x27, + 0x7e, 0xeb, 0xa7, 0xca, 0x99, 0xd6, 0x1e, 0xcf, 0xd4, 0xf0, 0x59, 0xc8, 0xef, 0x09, 0xc4, 0xbf, + 0xc6, 0xb3, 0x3b, 0xc3, 0x02, 0x14, 0x8b, 0x7e, 0x32, 0x41, 0x9c, 0xc8, 0x12, 0xdc, 0x0d, 0x3b, + 0xec, 0x2a, 0x8c, 0x6b, 0xd3, 0x1d, 0x77, 0xdf, 0x41, 0x8b, 0xc3, 0x86, 0xed, 0x74, 0x05, 0x9c, + 0x06, 0xda, 0xfc, 0x95, 0x89, 0x42, 0xfe, 0x62, 0x35, 0x10, 0x7b, 0xbe, 0xdc, 0x58, 0x6d, 0xf1, + 0x27, 0x63, 0x80, 0x3b, 0x3d, 0xb9, 0x09, 0xe0, 0xdf, 0xd6, 0x5f, 0x9e, 0x94, 0x3a, 0x9c, 0x6e, + 0x50, 0x18, 0x14, 0xa5, 0x93, 0x31, 0x34, 0x94, 0x1f, 0xc7, 0xd0, 0x50, 0x39, 0xda, 0xd0, 0x30, + 0x74, 0x94, 0xa1, 0x21, 0x63, 0x09, 0x18, 0x7e, 0x4c, 0x4b, 0xc0, 0xe8, 0x63, 0x59, 0x02, 0x0c, + 0x23, 0x45, 0xf5, 0x28, 0x23, 0xc5, 0xff, 0x6f, 0x37, 0x78, 0x5a, 0xed, 0x06, 0x45, 0x4a, 0xfe, + 0x63, 0xd9, 0x0d, 0x72, 0xc7, 0xfe, 0x99, 0x27, 0x73, 0xec, 0x67, 0x4f, 0xec, 0xd8, 0x7f, 0xe2, + 0x93, 0x1e, 0xfb, 0x4f, 0x3e, 0xc9, 0x63, 0xff, 0xa9, 0x3f, 0x8b, 0xc7, 0xfe, 0xd3, 0xff, 0x66, + 0x8e, 0xfd, 0x57, 0xa0, 0xda, 0x08, 0xa2, 0xf8, 0x5a, 0x10, 0x76, 0x71, 0xe3, 0x9d, 0xa0, 0x5b, + 0x96, 0x20, 0xc2, 0xb4, 0xcb, 0x5d, 0x43, 0x57, 0x26, 0x44, 0xb6, 0xa8, 0x26, 0x9c, 0x3a, 0x18, + 0xcf, 0xa6, 0x17, 0x5d, 0x34, 0x53, 0xe8, 0x7c, 0x9c, 0x9f, 0x6f, 0xea, 0xbc, 0x7c, 0x1b, 0x66, + 0x54, 0x9c, 0x20, 0x6e, 0xad, 0x68, 0x73, 0x38, 0x9b, 0xae, 0xcc, 0x34, 0xa0, 0x50, 0x95, 0x1a, + 0x2f, 0x68, 0x67, 0x49, 0x07, 0x9a, 0x13, 0x46, 0x6a, 0xa3, 0x37, 0x87, 0xaa, 0xb5, 0xda, 0xcc, + 0x31, 0xcc, 0x09, 0x3f, 0x02, 0xb5, 0xec, 0x09, 0xe7, 0xe8, 0x07, 0x01, 0x9e, 0x58, 0x8e, 0x5b, + 0x71, 0xfe, 0xca, 0x9e, 0x30, 0xd8, 0x65, 0x80, 0x46, 0xe8, 0xed, 0xb9, 0x31, 0xbf, 0xa5, 0x1c, + 0xd1, 0xe9, 0x05, 0x0c, 0x09, 0x15, 0xcb, 0xc0, 0xd6, 0x50, 0x92, 0xc3, 0x75, 0xb9, 0xe8, 0x70, + 0x6d, 0xfd, 0x44, 0x19, 0x66, 0x64, 0x4e, 0xc5, 0xa7, 0xdf, 0xd9, 0xe3, 0xcb, 0x86, 0xc9, 0xe4, + 0xd9, 0xf4, 0x69, 0x32, 0xfd, 0xeb, 0x0e, 0x71, 0xf7, 0xf8, 0x06, 0x9c, 0xca, 0x75, 0x05, 0x9a, + 0x4d, 0x96, 0x55, 0x36, 0xcb, 0x9c, 0xe1, 0x64, 0xb6, 0xb8, 0x92, 0x7b, 0x57, 0xec, 0x1c, 0x85, + 0xf5, 0x9b, 0xc3, 0x39, 0xfe, 0xe4, 0xf8, 0xa1, 0xbb, 0x72, 0x94, 0x1e, 0xcf, 0x95, 0xa3, 0x7c, + 0x3c, 0x57, 0x8e, 0x8c, 0x9e, 0x52, 0x39, 0x8e, 0x9e, 0xf2, 0x01, 0x4c, 0x6e, 0x70, 0xb7, 0x1b, + 0x6d, 0x04, 0xf4, 0x86, 0xd8, 0x90, 0xf1, 0x9e, 0x94, 0x28, 0x53, 0xa7, 0xfe, 0x24, 0x7c, 0x23, + 0x16, 0x04, 0x62, 0x27, 0x96, 0xcf, 0x8a, 0xd9, 0x26, 0x07, 0xdd, 0x94, 0x33, 0x7c, 0x88, 0x29, + 0xa7, 0x09, 0x13, 0x44, 0x97, 0xbe, 0x82, 0x90, 0xda, 0x1c, 0x44, 0x11, 0xc2, 0x55, 0xed, 0x2a, + 0xab, 0xc2, 0x54, 0x52, 0xbb, 0x34, 0x37, 0x18, 0x4c, 0x44, 0x17, 0xa8, 0xbb, 0x4a, 0xd1, 0x05, + 0xa3, 0x69, 0x17, 0xa8, 0x7b, 0x4d, 0xd9, 0x05, 0x1a, 0x12, 0x7b, 0x17, 0xa6, 0xea, 0x8d, 0x55, + 0x9d, 0xac, 0x9a, 0x7a, 0x93, 0xb8, 0xff, 0x1f, 0x7b, 0x5f, 0x1b, 0x23, 0x47, 0x72, 0x1d, 0xc6, + 0x9e, 0xd9, 0x8f, 0xd9, 0xb7, 0x5f, 0xb3, 0xc5, 0x25, 0xb9, 0xfc, 0x5c, 0xb2, 0xef, 0x8e, 0xba, + 0xdb, 0xbb, 0xe3, 0x1d, 0x79, 0xba, 0x0f, 0x4a, 0xf7, 0xa1, 0xd9, 0x99, 0x59, 0xee, 0x90, 0xb3, + 0x33, 0xa3, 0xee, 0x59, 0x32, 0xd4, 0x49, 0x6e, 0x37, 0x67, 0x7a, 0x77, 0xfb, 0x38, 0x3b, 0x3d, + 0xea, 0xee, 0x21, 0xb9, 0x82, 0x03, 0x5b, 0x76, 0x22, 0x23, 0x0e, 0x64, 0xcb, 0x1f, 0x41, 0x0e, + 0x82, 0x93, 0x38, 0x8e, 0x63, 0xf8, 0x47, 0x90, 0x18, 0xf1, 0x8f, 0x18, 0x16, 0x90, 0xc4, 0xff, + 0x8c, 0x18, 0x8e, 0x9d, 0xc4, 0x70, 0x10, 0xc5, 0x38, 0xd8, 0xf2, 0x9f, 0x44, 0x70, 0x7e, 0x19, + 0x09, 0x10, 0x03, 0x0e, 0x82, 0x7a, 0x55, 0xd5, 0x5d, 0xd5, 0xdd, 0x33, 0xbb, 0x14, 0x79, 0x72, + 0xa4, 0xe8, 0x0f, 0xb9, 0xf3, 0xea, 0xbd, 0xd7, 0xf5, 0xf9, 0xea, 0xd5, 0xab, 0x57, 0xef, 0x0d, + 0x5c, 0x4b, 0x21, 0x4d, 0xe0, 0x8e, 0x3b, 0x7e, 0xcf, 0x7c, 0x97, 0x8e, 0xdf, 0xc9, 0x83, 0x22, + 0x3c, 0xe6, 0x41, 0x51, 0xff, 0xc3, 0x39, 0x21, 0x1f, 0x3e, 0xde, 0xfb, 0x3d, 0xf5, 0xc6, 0x2e, + 0xff, 0x98, 0x37, 0x76, 0x13, 0x87, 0x29, 0xc3, 0x92, 0xce, 0x3d, 0xf5, 0xc4, 0xb7, 0x6f, 0xd3, + 0x8f, 0xa9, 0x45, 0x27, 0x16, 0x5f, 0xe1, 0x28, 0x8b, 0x2f, 0x53, 0xf3, 0x9e, 0x79, 0x72, 0xcd, + 0x1b, 0x1e, 0x5b, 0xf3, 0x36, 0xe3, 0x68, 0x29, 0xb3, 0x87, 0x3e, 0xfe, 0x3c, 0xcf, 0x6d, 0x55, + 0x4b, 0xd9, 0x21, 0x33, 0xa3, 0xb8, 0x29, 0xdf, 0x57, 0xea, 0xfc, 0x0f, 0x67, 0xab, 0xf3, 0xe3, + 0x37, 0xa0, 0x1f, 0x28, 0xf4, 0x3f, 0x50, 0xe8, 0xbf, 0x2b, 0x0a, 0x7d, 0x13, 0x88, 0x3d, 0x0c, + 0xf7, 0xa8, 0x5e, 0xdc, 0xc1, 0xb0, 0xcd, 0x74, 0x88, 0x51, 0xb5, 0xe7, 0x6b, 0x24, 0x5d, 0x2a, + 0xaf, 0x11, 0xa5, 0x14, 0x6d, 0x1e, 0x91, 0x62, 0xbd, 0xa2, 0x2a, 0xd6, 0x93, 0xc5, 0xa9, 0x23, + 0x2b, 0xd6, 0x3e, 0xae, 0xb3, 0x3b, 0xb6, 0xdf, 0xc7, 0x6d, 0x0a, 0x13, 0x2e, 0xb2, 0xe0, 0xc1, + 0x5a, 0x7c, 0xe1, 0x91, 0x8e, 0x1a, 0x2c, 0xb0, 0xc8, 0x35, 0x28, 0x08, 0x62, 0x39, 0x03, 0xdd, + 0x43, 0x0e, 0x53, 0xe2, 0xb2, 0x72, 0x98, 0xfe, 0xab, 0x13, 0x42, 0x96, 0xd3, 0x66, 0xb4, 0x6c, + 0xdf, 0xde, 0xc7, 0x1c, 0xde, 0xd1, 0x52, 0x93, 0xd4, 0xfa, 0xc4, 0xea, 0x4c, 0xbc, 0xbf, 0x53, + 0x49, 0xbe, 0xa3, 0xe8, 0xcf, 0x2f, 0xc1, 0x14, 0xd7, 0xaa, 0xd8, 0x39, 0x00, 0x35, 0x8a, 0x54, + 0x2e, 0x56, 0x8e, 0x43, 0xae, 0xb3, 0x97, 0xb7, 0x3c, 0x74, 0xca, 0x44, 0x9c, 0xd4, 0x9e, 0xca, + 0xb7, 0x8c, 0xa4, 0xf6, 0x31, 0x32, 0x79, 0x83, 0xbd, 0x36, 0x95, 0x33, 0xe9, 0x44, 0x6f, 0x76, + 0x53, 0x09, 0x14, 0x62, 0xd4, 0xf8, 0x9c, 0x32, 0xf5, 0x38, 0x71, 0xd5, 0xa7, 0xff, 0x5a, 0xe3, + 0xaa, 0x57, 0x01, 0xf8, 0x9e, 0x1b, 0xbb, 0xd9, 0x3c, 0x87, 0x22, 0x89, 0x3f, 0xf9, 0x08, 0xc3, + 0xde, 0x88, 0xbc, 0x57, 0x12, 0xa1, 0xfe, 0xef, 0x08, 0x2c, 0x99, 0x66, 0xb3, 0xe2, 0xda, 0xbb, + 0x7d, 0x2f, 0x08, 0xdd, 0x4e, 0xad, 0xbf, 0xe3, 0x51, 0x25, 0x3d, 0xda, 0x17, 0xa4, 0x88, 0xd8, + 0xf1, 0x9e, 0x10, 0x15, 0xd3, 0x43, 0x60, 0xd5, 0xf7, 0x85, 0x31, 0x96, 0x1d, 0x02, 0x1d, 0x0a, + 0x30, 0x18, 0x9c, 0xea, 0xc1, 0xe6, 0x10, 0x1f, 0x30, 0x72, 0x5f, 0x26, 0xd4, 0x83, 0x03, 0x06, + 0x32, 0x44, 0x19, 0x71, 0xd2, 0x13, 0x96, 0x9f, 0x8b, 0x4e, 0x29, 0xd1, 0xd9, 0xe3, 0x62, 0xb6, + 0xa2, 0xb9, 0x56, 0x82, 0xb2, 0x7c, 0x80, 0x70, 0xd9, 0x27, 0x37, 0xb5, 0x06, 0x0e, 0xe0, 0x84, + 0x12, 0xe6, 0xe4, 0xa8, 0x77, 0x7d, 0x6b, 0x5c, 0xef, 0xd6, 0xf1, 0xdd, 0x43, 0xc6, 0x85, 0x9f, + 0x9c, 0x94, 0x3b, 0xf3, 0x0b, 0xe4, 0xef, 0x68, 0x70, 0x3e, 0xb3, 0x24, 0x5a, 0xdd, 0xb3, 0x4a, + 0x84, 0x7c, 0x49, 0x68, 0xb0, 0xf4, 0xe3, 0xa3, 0x3e, 0x6d, 0x65, 0x88, 0x82, 0xf1, 0x5f, 0x22, + 0xdf, 0xd0, 0xe0, 0x94, 0x82, 0x11, 0xc9, 0xd0, 0x20, 0x8a, 0x44, 0x96, 0x39, 0xaf, 0x3f, 0x78, + 0x3a, 0xf3, 0xfa, 0x19, 0xb5, 0x2d, 0xb1, 0xb4, 0x54, 0x12, 0xe8, 0x8e, 0xa8, 0x21, 0x79, 0x00, + 0x4b, 0x58, 0x24, 0xee, 0x1d, 0xe9, 0x9c, 0xe5, 0xd7, 0x95, 0xcb, 0x71, 0xb5, 0x59, 0xe8, 0x1e, + 0xaa, 0x71, 0xaf, 0x5f, 0xfb, 0xe6, 0x47, 0xab, 0xf3, 0x0a, 0xba, 0x88, 0x39, 0x6f, 0xc5, 0x97, + 0x97, 0x6e, 0x7f, 0xc7, 0x53, 0xcc, 0x2c, 0xc9, 0x4f, 0x90, 0x7f, 0xa3, 0xb1, 0xbb, 0x0b, 0xd6, + 0x8c, 0x0d, 0xdf, 0xdb, 0x8f, 0xca, 0x85, 0x73, 0xf7, 0x88, 0x6e, 0xeb, 0x3d, 0x9d, 0x6e, 0x7b, + 0x0e, 0xab, 0xcc, 0x64, 0x82, 0xb5, 0xe3, 0x7b, 0xfb, 0x71, 0xf5, 0xe5, 0x8e, 0x1b, 0x59, 0x49, + 0xf2, 0x65, 0x0d, 0x4e, 0x2b, 0x26, 0x57, 0x39, 0xab, 0x11, 0x0f, 0x90, 0x14, 0x3d, 0x32, 0x94, + 0x8a, 0xd6, 0xaf, 0xf0, 0xf9, 0x7f, 0x19, 0x6b, 0x10, 0xef, 0x16, 0x58, 0x17, 0x6b, 0x9f, 0x61, + 0x49, 0x55, 0x18, 0xfd, 0x15, 0xe2, 0xc2, 0x12, 0x7a, 0x8b, 0x29, 0x8f, 0x10, 0x96, 0x47, 0x3f, + 0x42, 0x88, 0x32, 0xeb, 0x61, 0xda, 0x8f, 0xd1, 0x2f, 0x11, 0xd2, 0x5c, 0xc9, 0xdf, 0x84, 0xd3, + 0x29, 0x60, 0xb4, 0xda, 0x4e, 0x8c, 0x5c, 0x6d, 0x2f, 0x7e, 0xfb, 0xa3, 0xd5, 0x4f, 0x64, 0x7d, + 0x2d, 0x6b, 0xa5, 0x8d, 0xfe, 0x02, 0xb1, 0x01, 0xe2, 0x42, 0xae, 0x13, 0x65, 0x4f, 0xd0, 0x17, + 0xf9, 0xfc, 0x90, 0xf0, 0xa9, 0x2c, 0x97, 0xea, 0x20, 0x6f, 0x79, 0x31, 0x12, 0x71, 0x60, 0x4e, + 0x4a, 0x79, 0x72, 0xc0, 0xef, 0x0f, 0x47, 0x7c, 0xe4, 0x9b, 0x1f, 0xad, 0x2a, 0xd8, 0xf4, 0x64, + 0x24, 0xe7, 0x52, 0x51, 0x54, 0x50, 0x19, 0x91, 0xfc, 0x86, 0x06, 0xcb, 0x14, 0x10, 0x4f, 0x2a, + 0xde, 0xa8, 0x95, 0x71, 0xb3, 0x7e, 0xef, 0xe9, 0xcc, 0xfa, 0x4b, 0x58, 0x47, 0x79, 0xd6, 0xa7, + 0xba, 0x24, 0xb3, 0x72, 0x38, 0xdb, 0x15, 0xc7, 0x44, 0x65, 0xb6, 0x9f, 0x3e, 0xc2, 0x6c, 0x67, + 0x03, 0x70, 0xf8, 0x6c, 0x1f, 0xf9, 0x15, 0xd2, 0x86, 0x39, 0x7e, 0x28, 0x62, 0x1d, 0x76, 0x41, + 0x79, 0xc8, 0x2b, 0x17, 0xb1, 0x93, 0x2a, 0xcf, 0x08, 0x93, 0x6a, 0xa1, 0xc2, 0x85, 0xf4, 0xe1, + 0x38, 0xfb, 0xad, 0x5a, 0xad, 0x56, 0x47, 0x5a, 0xad, 0x9e, 0xe7, 0x2d, 0xba, 0xc8, 0xf9, 0x27, + 0x8c, 0x57, 0x72, 0xe4, 0xc7, 0x0c, 0xc6, 0x64, 0x00, 0x44, 0x01, 0xb3, 0x45, 0x7b, 0x71, 0xbc, + 0xad, 0xea, 0x13, 0xfc, 0x9b, 0xab, 0xc9, 0x6f, 0x26, 0x57, 0x6e, 0x06, 0x6f, 0x62, 0xc3, 0x22, + 0x87, 0x7a, 0xf7, 0x1d, 0x26, 0xe1, 0x2f, 0x29, 0xb1, 0x37, 0x13, 0xa5, 0xec, 0x64, 0x27, 0xbe, + 0x84, 0xb1, 0x51, 0x13, 0x02, 0x3d, 0xc9, 0x8f, 0x34, 0x61, 0xa9, 0x34, 0x18, 0xf4, 0x5c, 0xa7, + 0x8b, 0xad, 0x34, 0x86, 0xb4, 0x4d, 0x7a, 0x9c, 0xd3, 0xd4, 0x66, 0x85, 0xfc, 0xb8, 0xe9, 0x0f, + 0x13, 0xe2, 0x26, 0x45, 0xab, 0xff, 0xa4, 0x96, 0xaa, 0x34, 0x79, 0x09, 0x66, 0xf0, 0x87, 0x14, + 0x46, 0x0d, 0x6d, 0x37, 0xac, 0x8a, 0x68, 0x15, 0x8a, 0x11, 0xa8, 0xb2, 0x24, 0x87, 0x74, 0xce, + 0x33, 0x65, 0x89, 0x1b, 0x18, 0x62, 0x93, 0xc2, 0xaa, 0x78, 0x1c, 0x96, 0x8f, 0x95, 0x2e, 0x3c, + 0x9a, 0xf0, 0x27, 0x61, 0xfa, 0xaf, 0xe7, 0xd4, 0x69, 0x47, 0x9e, 0x97, 0xf4, 0x76, 0x29, 0xa8, + 0xb4, 0xd0, 0xdb, 0x25, 0x6d, 0xfd, 0x9f, 0x6a, 0x70, 0xbc, 0x29, 0x65, 0xda, 0x6e, 0x7b, 0x38, + 0x2e, 0xe3, 0x73, 0x4a, 0x3f, 0xad, 0x34, 0xb7, 0x72, 0x8a, 0x6f, 0x3a, 0x53, 0x70, 0xca, 0x18, + 0x59, 0xf5, 0xc1, 0x87, 0xc3, 0x58, 0x31, 0x29, 0xdb, 0x30, 0x43, 0x67, 0xf0, 0xc7, 0xcc, 0x0f, + 0xa3, 0xff, 0x74, 0x0e, 0x66, 0xa5, 0x15, 0x43, 0x3e, 0x09, 0x73, 0xf2, 0x67, 0x65, 0xc3, 0x9f, + 0x5c, 0x4b, 0x43, 0xc1, 0x42, 0xcb, 0x9f, 0x63, 0xef, 0x2b, 0x96, 0x3f, 0xba, 0x2e, 0x10, 0xfa, + 0x98, 0x27, 0xa1, 0xf7, 0x32, 0x4e, 0x42, 0x38, 0xcb, 0x25, 0x4b, 0xcf, 0xd8, 0xf3, 0xd0, 0xdb, + 0xe9, 0xf3, 0x10, 0x1a, 0x9d, 0x24, 0xfa, 0xd1, 0xa7, 0x22, 0xfd, 0x67, 0x35, 0x28, 0x26, 0xd7, + 0xf4, 0xc7, 0xd2, 0x2b, 0x8f, 0x71, 0x4d, 0xf4, 0xd5, 0x5c, 0x94, 0x1e, 0x49, 0x44, 0x74, 0xf8, + 0x5e, 0x75, 0x7a, 0x7d, 0x47, 0xb9, 0xc1, 0x39, 0xab, 0xc6, 0xcd, 0x95, 0x03, 0x5a, 0x65, 0x07, + 0xcb, 0x9e, 0xf8, 0xf0, 0x97, 0x56, 0x8f, 0xe9, 0x77, 0x61, 0x39, 0xd9, 0x1d, 0x78, 0x8b, 0x53, + 0x82, 0x45, 0x15, 0x9e, 0x4c, 0xae, 0x96, 0xa4, 0x32, 0x92, 0xf8, 0xfa, 0x1f, 0xe4, 0x92, 0xbc, + 0xb9, 0x03, 0x2c, 0x95, 0x51, 0xb2, 0x93, 0x0e, 0x97, 0x51, 0x0c, 0x64, 0x88, 0xb2, 0xc7, 0xc9, + 0xd4, 0x18, 0xc5, 0x00, 0xc8, 0x67, 0xc7, 0x00, 0x20, 0x6f, 0x24, 0x1e, 0x07, 0x48, 0x71, 0x15, + 0x1f, 0x3a, 0xf7, 0xac, 0xf8, 0x81, 0x40, 0xea, 0x4d, 0xc0, 0xb2, 0x92, 0xac, 0x44, 0xd0, 0x4f, + 0xc6, 0x36, 0xf7, 0x10, 0x0b, 0x18, 0x71, 0x26, 0x32, 0xd9, 0x84, 0x69, 0x5a, 0xcd, 0x2d, 0x7b, + 0xc0, 0x2f, 0x67, 0x48, 0x14, 0xa5, 0xa4, 0x17, 0x9d, 0x0f, 0xa5, 0x40, 0x25, 0x3d, 0x87, 0x6a, + 0x08, 0xf2, 0xc4, 0xe2, 0x88, 0xfa, 0x5f, 0x68, 0x74, 0xfd, 0x77, 0xee, 0x7f, 0x9f, 0x65, 0x46, + 0xa4, 0x4d, 0x1a, 0xe3, 0x9f, 0xfd, 0xa7, 0x39, 0x96, 0xf0, 0x8a, 0x4f, 0x9f, 0xeb, 0x30, 0xc5, + 0xc2, 0x97, 0x70, 0x97, 0x34, 0x99, 0x0b, 0x2b, 0x88, 0xa3, 0x3e, 0xb2, 0xc8, 0x26, 0x06, 0x27, + 0x90, 0x4d, 0x67, 0xb9, 0x23, 0x99, 0xce, 0x24, 0x7b, 0x7e, 0xfe, 0xa9, 0xd9, 0xf3, 0x3f, 0x17, + 0xe5, 0xb6, 0x2a, 0x85, 0x47, 0xc8, 0x85, 0x71, 0x31, 0x99, 0x4a, 0x2e, 0x95, 0xb5, 0x24, 0x66, + 0x47, 0xde, 0x90, 0x93, 0xd3, 0x49, 0x8f, 0xd1, 0x0f, 0x49, 0x43, 0xa7, 0x7f, 0x75, 0x92, 0xf5, + 0x31, 0xef, 0xa8, 0xcb, 0x4a, 0xc8, 0x0d, 0x5c, 0x27, 0x09, 0x5b, 0x27, 0x0b, 0xbe, 0x71, 0x19, + 0x26, 0xe8, 0xdc, 0xe4, 0xbd, 0x89, 0x78, 0x74, 0xfe, 0xca, 0x78, 0xb4, 0x9c, 0xae, 0x65, 0xdc, + 0x93, 0xe4, 0x54, 0xaa, 0xb8, 0x6d, 0xc9, 0x6b, 0x19, 0x31, 0x68, 0x0b, 0xa2, 0x24, 0x54, 0x72, + 0x0b, 0xf6, 0x77, 0xec, 0x74, 0x8e, 0xc8, 0x18, 0x95, 0x54, 0x61, 0xe1, 0x8e, 0xdb, 0xef, 0x7a, + 0x0f, 0x83, 0x8a, 0x13, 0xdc, 0x0f, 0xbd, 0x01, 0xf7, 0x49, 0x47, 0xbb, 0xff, 0x43, 0x56, 0x62, + 0x75, 0x59, 0x91, 0x7c, 0x49, 0xa2, 0x12, 0x91, 0x75, 0x98, 0x57, 0x22, 0xb8, 0xf3, 0xbb, 0x4f, + 0xb4, 0x71, 0xaa, 0xf1, 0xdf, 0x65, 0x1b, 0xa7, 0x42, 0x42, 0x77, 0x69, 0x5e, 0x7f, 0xe9, 0x06, + 0x34, 0x55, 0x77, 0x8e, 0x43, 0x5e, 0x83, 0x02, 0x0b, 0xde, 0x53, 0xab, 0xc8, 0x97, 0x56, 0x01, + 0xc2, 0x12, 0x01, 0xe5, 0x04, 0x22, 0x29, 0xc3, 0xfc, 0xba, 0x17, 0xd6, 0xfa, 0x41, 0x68, 0xf7, + 0x3b, 0x4e, 0x14, 0xaf, 0x1e, 0x1b, 0x7b, 0xcf, 0x0b, 0x2d, 0x97, 0x97, 0xa8, 0xf4, 0x2a, 0x0d, + 0xed, 0xea, 0x9b, 0x9e, 0xdb, 0x67, 0xa2, 0x73, 0x36, 0xee, 0xea, 0x0f, 0x3c, 0xb7, 0x9f, 0x0a, + 0x11, 0x1f, 0xa3, 0x92, 0xf7, 0x60, 0xae, 0xee, 0xf6, 0x87, 0x8f, 0x44, 0x47, 0xcf, 0xc5, 0xf7, + 0x64, 0x3d, 0x0a, 0xcf, 0xe8, 0x66, 0x85, 0xe0, 0x53, 0xcb, 0x74, 0xef, 0xf9, 0x83, 0x7f, 0xbc, + 0x7a, 0xec, 0x6b, 0xbf, 0xbc, 0x7a, 0xec, 0xc3, 0x5f, 0x66, 0x11, 0x77, 0xd8, 0xab, 0x5c, 0x63, + 0xa2, 0xe1, 0x75, 0x1d, 0x9d, 0xe7, 0xb8, 0xe3, 0x91, 0xca, 0x5f, 0x84, 0x69, 0xb6, 0x92, 0x93, + 0x69, 0xbd, 0xe3, 0x29, 0x6b, 0x08, 0x0c, 0xa2, 0xc3, 0xbc, 0xdb, 0xb7, 0x98, 0x2f, 0xa6, 0xd7, + 0xef, 0xb1, 0x7c, 0x68, 0x05, 0x63, 0xd6, 0xed, 0xa3, 0x07, 0x66, 0xb3, 0xdf, 0x3b, 0xd0, 0x5f, + 0x85, 0x22, 0x97, 0xce, 0x51, 0x16, 0x61, 0xf4, 0xfd, 0xa8, 0x55, 0x0c, 0x59, 0xa2, 0x76, 0xdc, + 0xae, 0x6f, 0x20, 0x14, 0x1f, 0xde, 0x36, 0x9c, 0xf0, 0xa1, 0xe7, 0xdf, 0x37, 0x9c, 0x20, 0xf4, + 0x5d, 0x96, 0x94, 0x18, 0x65, 0xd2, 0x27, 0xc9, 0xdb, 0x30, 0x89, 0x1e, 0xe5, 0x89, 0x4d, 0x32, + 0xf9, 0x8d, 0xf5, 0x79, 0xbe, 0x96, 0x27, 0xd1, 0x3d, 0xdd, 0x60, 0x44, 0xe4, 0x3a, 0x4c, 0x54, + 0x9c, 0xfe, 0x41, 0x22, 0xb5, 0x68, 0x8a, 0x38, 0x92, 0x8d, 0x5d, 0xa7, 0x7f, 0x60, 0x20, 0x89, + 0xfe, 0xb3, 0x39, 0x38, 0x91, 0x51, 0xad, 0xdb, 0x9f, 0xfc, 0x1e, 0xdd, 0x20, 0xd6, 0x95, 0x0d, + 0x42, 0xdc, 0xf9, 0x8f, 0xec, 0xf8, 0xcc, 0xfd, 0xe2, 0x17, 0x35, 0x38, 0xa5, 0xae, 0x6a, 0xfe, + 0x84, 0xe4, 0xf6, 0x6b, 0xe4, 0xd3, 0x30, 0xb5, 0xe9, 0xd8, 0x5d, 0x47, 0xe4, 0x11, 0x4c, 0xa6, + 0x0d, 0x67, 0x85, 0x8c, 0x6d, 0x1c, 0x2d, 0x80, 0x41, 0x49, 0x85, 0x57, 0x8e, 0x9d, 0x64, 0x74, + 0x11, 0x55, 0x2a, 0xeb, 0x53, 0x63, 0x3c, 0x67, 0x3e, 0xcc, 0xc1, 0xd9, 0x31, 0x34, 0x74, 0xe0, + 0xe8, 0xd0, 0xcb, 0x03, 0x87, 0xca, 0x05, 0x42, 0xc9, 0xbb, 0xb0, 0xd8, 0xe6, 0x27, 0x21, 0x31, + 0x1c, 0xb9, 0x58, 0xc8, 0x88, 0x43, 0x92, 0xf0, 0x20, 0x33, 0x92, 0xc8, 0x4a, 0x08, 0xb6, 0xfc, + 0xd8, 0x10, 0x6c, 0x72, 0x44, 0xb3, 0x89, 0xef, 0x30, 0xa2, 0xd9, 0xe4, 0xf8, 0x88, 0x66, 0x53, + 0x89, 0x88, 0x66, 0x5f, 0xd7, 0x60, 0x59, 0xed, 0x1a, 0x2e, 0x00, 0xe2, 0x70, 0x70, 0xda, 0xe8, + 0x70, 0x70, 0xe3, 0x03, 0xa2, 0xbf, 0x07, 0x53, 0x26, 0x0e, 0x31, 0x36, 0x7b, 0x21, 0x52, 0x98, + 0x13, 0x43, 0x81, 0x28, 0xec, 0x4d, 0x16, 0x8f, 0x19, 0xc5, 0xc9, 0xf4, 0xdf, 0xd7, 0xa0, 0xa8, + 0x22, 0x3f, 0xe9, 0x7c, 0x7a, 0x47, 0x99, 0x4f, 0x23, 0x2a, 0x34, 0x3a, 0xd0, 0xdf, 0x7b, 0x51, + 0xa0, 0xbf, 0xfc, 0x38, 0x06, 0x2c, 0x45, 0x2e, 0xc8, 0x41, 0xb0, 0xd9, 0xff, 0xfa, 0xad, 0x64, + 0x6f, 0x47, 0x61, 0x2a, 0x79, 0xdb, 0x79, 0x82, 0xe8, 0x71, 0x5d, 0x65, 0x70, 0x54, 0xfd, 0x7f, + 0xa4, 0xc6, 0xee, 0x48, 0xf3, 0x59, 0x87, 0xa9, 0x8a, 0xb7, 0x6f, 0xbb, 0x62, 0x1a, 0x63, 0x3d, + 0xbb, 0x08, 0x31, 0x78, 0xc9, 0xd1, 0x82, 0x01, 0x5e, 0x84, 0xc9, 0x86, 0xd7, 0x2f, 0x55, 0xb8, + 0x1f, 0x3a, 0xf2, 0xe9, 0x7b, 0x7d, 0xcb, 0xee, 0x1a, 0xac, 0x80, 0xd4, 0x01, 0xcc, 0x8e, 0xef, + 0x38, 0x7d, 0xd3, 0xfd, 0x92, 0x93, 0x50, 0x41, 0xe9, 0x78, 0xf5, 0x86, 0x28, 0x66, 0xd9, 0xbd, + 0x3c, 0x22, 0x5a, 0x81, 0xfb, 0x25, 0x79, 0xcb, 0x96, 0xe8, 0x51, 0xca, 0xf0, 0xa0, 0xb7, 0x89, + 0x59, 0x71, 0xf5, 0xe3, 0x90, 0x32, 0x99, 0x9f, 0xc2, 0x1e, 0xbe, 0x9a, 0x29, 0x65, 0x7e, 0x5f, + 0x83, 0xb3, 0x63, 0x68, 0x9e, 0xc2, 0xa8, 0x7c, 0xb7, 0x3b, 0xdc, 0x01, 0x88, 0x89, 0x30, 0x39, + 0xbf, 0xdb, 0xe5, 0xef, 0x52, 0xe6, 0x79, 0x72, 0x7e, 0x0a, 0x50, 0x92, 0xf3, 0x53, 0x00, 0x55, + 0xc7, 0x36, 0x1d, 0x77, 0x77, 0x8f, 0x39, 0x00, 0xce, 0x33, 0x49, 0xb9, 0x87, 0x10, 0x59, 0x1d, + 0x63, 0x38, 0xfa, 0x7f, 0x9c, 0x82, 0xd3, 0x86, 0xb3, 0xeb, 0xd2, 0x03, 0xeb, 0x76, 0xe0, 0xf6, + 0x77, 0x95, 0x58, 0x74, 0x7a, 0x42, 0x10, 0xf1, 0x3c, 0x67, 0x14, 0x12, 0xcd, 0xc4, 0x17, 0xa0, + 0x40, 0x75, 0x18, 0x49, 0x16, 0xe1, 0xe5, 0x67, 0xdf, 0xeb, 0x3a, 0x3c, 0x27, 0x87, 0x28, 0x26, + 0x6b, 0x5c, 0x7f, 0x96, 0x32, 0x51, 0x52, 0xfd, 0xf9, 0x2f, 0x3f, 0x5a, 0x05, 0x96, 0x9a, 0x1f, + 0x83, 0xab, 0x32, 0x1d, 0x3a, 0x3a, 0xe4, 0x4e, 0x8c, 0x38, 0xe4, 0x6e, 0xc1, 0x72, 0xa9, 0xcb, + 0x74, 0x05, 0xbb, 0xd7, 0xf2, 0xdd, 0x7e, 0xc7, 0x1d, 0xd8, 0x3d, 0x61, 0xb8, 0xc1, 0x5e, 0xb6, + 0xa3, 0x72, 0x6b, 0x10, 0x21, 0x18, 0x99, 0x64, 0xb4, 0x19, 0x95, 0x86, 0x89, 0x81, 0xce, 0xf8, + 0xbd, 0x36, 0x36, 0xa3, 0xdb, 0x0f, 0xb0, 0x15, 0x81, 0x11, 0x15, 0xe3, 0xf1, 0x1a, 0xbd, 0x18, + 0xda, 0x75, 0xf3, 0x96, 0xc3, 0x22, 0x54, 0x89, 0x44, 0x59, 0xcc, 0x6b, 0x25, 0xec, 0x05, 0xe8, + 0x6c, 0xab, 0xe0, 0xc5, 0x74, 0xa6, 0xb9, 0x49, 0xe9, 0x0a, 0x29, 0xba, 0x20, 0xd8, 0x93, 0xe9, + 0x18, 0x1e, 0x79, 0x85, 0x4e, 0x85, 0x7d, 0x2f, 0x74, 0x70, 0x0a, 0xcf, 0xc4, 0x87, 0x71, 0x1f, + 0xa1, 0xec, 0x30, 0x2e, 0xa1, 0x90, 0xb7, 0xe1, 0x78, 0xb5, 0x7c, 0x4d, 0xdc, 0x46, 0x54, 0xbc, + 0xce, 0x10, 0xbd, 0x4c, 0x00, 0xbf, 0x87, 0x63, 0xe8, 0x74, 0xae, 0x51, 0x69, 0x92, 0x85, 0x46, + 0x2e, 0xc3, 0x74, 0xad, 0x22, 0x6b, 0xc9, 0x3c, 0x1b, 0x2c, 0x77, 0x9d, 0x13, 0x85, 0xa4, 0x19, + 0x9f, 0x16, 0xe7, 0x0e, 0x3d, 0xd6, 0x9d, 0x3e, 0xc2, 0x49, 0xf1, 0x7a, 0x52, 0xcb, 0x97, 0x52, + 0xa2, 0x24, 0xb4, 0xfc, 0xa4, 0x6e, 0xff, 0x16, 0x92, 0xde, 0x70, 0xfa, 0x8e, 0x1f, 0xa7, 0x42, + 0x99, 0x64, 0x7d, 0x4b, 0x49, 0x77, 0xa3, 0x12, 0x43, 0x45, 0x24, 0x06, 0x9c, 0x68, 0xf9, 0xce, + 0x03, 0xd7, 0x1b, 0x06, 0xea, 0xc7, 0x17, 0xe3, 0x93, 0xd0, 0x80, 0x23, 0x58, 0xc9, 0x5a, 0x64, + 0x93, 0xf2, 0xec, 0xb7, 0x2c, 0x40, 0x70, 0xd9, 0xeb, 0x3a, 0x01, 0x93, 0x40, 0xdf, 0x47, 0xd9, + 0x6f, 0xa5, 0xb6, 0x8d, 0x91, 0xca, 0x5f, 0xc7, 0xec, 0xb7, 0x29, 0x5c, 0xf2, 0x16, 0x4c, 0xe2, + 0x4f, 0x7e, 0x7e, 0x38, 0x9e, 0xc1, 0x36, 0x3e, 0x3b, 0x74, 0x28, 0xa6, 0xc1, 0x08, 0x48, 0x0d, + 0xa6, 0xf9, 0x29, 0xfe, 0x71, 0x72, 0x38, 0x72, 0x73, 0x00, 0x9b, 0x6d, 0x9c, 0x5e, 0xef, 0xc2, + 0x9c, 0xfc, 0x41, 0xba, 0xca, 0x36, 0xed, 0x60, 0xcf, 0xe9, 0xd2, 0x5f, 0x3c, 0xfd, 0x32, 0xae, + 0xb2, 0x3d, 0x84, 0x5a, 0xb4, 0x1e, 0x86, 0x84, 0x42, 0xf7, 0xe9, 0x5a, 0xb0, 0x1d, 0xf0, 0xaa, + 0x70, 0xbb, 0x9e, 0x8b, 0x36, 0xe2, 0xae, 0xc1, 0x8b, 0xf4, 0xcf, 0xc1, 0x72, 0x63, 0xd8, 0xeb, + 0xd9, 0xf7, 0x7a, 0x8e, 0x48, 0xcf, 0x17, 0xda, 0xa1, 0x43, 0xd6, 0x61, 0x12, 0xff, 0xe0, 0x3a, + 0xc7, 0xf1, 0x28, 0xff, 0x61, 0x8c, 0x83, 0xae, 0xd3, 0x1a, 0x06, 0xb2, 0xa3, 0x3f, 0x95, 0x40, + 0x76, 0x14, 0xa0, 0xff, 0x8e, 0x06, 0xcb, 0xc2, 0x37, 0xc5, 0xb7, 0x3b, 0xf7, 0x1d, 0x9f, 0xeb, + 0x8f, 0x97, 0x95, 0xb9, 0x86, 0x8b, 0x20, 0x31, 0x8d, 0xd8, 0xac, 0xbb, 0x29, 0x2a, 0xa1, 0xaa, + 0x64, 0x59, 0x15, 0x3e, 0xac, 0x32, 0xe4, 0x6d, 0x98, 0xe5, 0x5b, 0xae, 0x14, 0xa1, 0x1c, 0xe3, + 0x68, 0xf2, 0xe3, 0x71, 0xd2, 0x53, 0x4a, 0x46, 0xd7, 0x7f, 0x5a, 0x83, 0xa2, 0xda, 0x94, 0x27, + 0xd5, 0x2b, 0xb2, 0xb5, 0x4d, 0xf5, 0x1b, 0x63, 0xa6, 0xee, 0xbf, 0x9d, 0x4d, 0xf6, 0x2d, 0x9f, + 0xbb, 0x6f, 0xc8, 0xa1, 0x63, 0xb5, 0xd8, 0x8a, 0x10, 0x87, 0x8e, 0x95, 0xad, 0x08, 0x11, 0x6a, + 0x34, 0x26, 0xb9, 0x43, 0xc6, 0xe4, 0x5d, 0x31, 0x26, 0xf9, 0xd1, 0x13, 0xe3, 0xf8, 0x98, 0x71, + 0x30, 0xe3, 0x15, 0x32, 0x71, 0x24, 0x1b, 0xde, 0x31, 0x4c, 0xe5, 0xc4, 0x48, 0x92, 0x92, 0x99, + 0x73, 0x92, 0x0d, 0x83, 0x93, 0x47, 0x67, 0x7a, 0x88, 0xb8, 0xff, 0x14, 0xcc, 0x95, 0xc2, 0xd0, + 0xee, 0xec, 0x39, 0xdd, 0x0a, 0x15, 0x4f, 0x52, 0x6c, 0x48, 0x9b, 0xc3, 0x65, 0x93, 0x8a, 0x8c, + 0xcb, 0xd2, 0x01, 0xd8, 0x01, 0xf7, 0xb8, 0x8e, 0xd2, 0x01, 0x50, 0x88, 0x9a, 0x0e, 0x80, 0x42, + 0xc8, 0x2b, 0x30, 0x5d, 0xeb, 0x3f, 0x70, 0x69, 0x9f, 0xb0, 0xf0, 0x90, 0x68, 0x08, 0x75, 0x19, + 0x48, 0x16, 0xae, 0x1c, 0x8b, 0x5c, 0x97, 0x8e, 0x8d, 0x33, 0xb1, 0xa9, 0x49, 0xc4, 0x98, 0xe6, + 0x45, 0xf2, 0x91, 0x30, 0x3a, 0x47, 0xbe, 0x01, 0xd3, 0xc2, 0x6c, 0x0e, 0xf1, 0x0e, 0xc2, 0x29, + 0xd3, 0x41, 0x79, 0x04, 0x32, 0x5d, 0x3f, 0x72, 0x1a, 0xe9, 0xd9, 0x78, 0xfd, 0xc8, 0x69, 0xa4, + 0xe5, 0xf5, 0x23, 0x27, 0x94, 0x8e, 0x2c, 0x8e, 0x73, 0x87, 0x5a, 0x1c, 0x6f, 0xc3, 0x5c, 0xcb, + 0xf6, 0x43, 0x97, 0xea, 0x3d, 0xfd, 0x30, 0x58, 0x99, 0x57, 0x8c, 0xf4, 0x52, 0xd1, 0xfa, 0x05, + 0x3e, 0x90, 0x27, 0x07, 0x12, 0xbe, 0x9a, 0x92, 0x3f, 0x86, 0x67, 0xfb, 0x5b, 0x2f, 0x3c, 0x89, + 0xbf, 0x35, 0x76, 0x2a, 0x1a, 0x66, 0x17, 0x63, 0x43, 0x21, 0x1e, 0x84, 0x12, 0xd6, 0xd9, 0x08, + 0x91, 0x7c, 0x1e, 0xe6, 0xe8, 0xdf, 0x2d, 0xaf, 0xe7, 0x76, 0x5c, 0x27, 0x58, 0x29, 0x62, 0xe3, + 0x2e, 0x64, 0xae, 0x7e, 0x44, 0x3a, 0x30, 0x9d, 0x90, 0x2d, 0x60, 0x64, 0x9c, 0xbc, 0x71, 0x51, + 0xb8, 0x91, 0xf7, 0x60, 0x8e, 0xce, 0xbe, 0x7b, 0x76, 0xc0, 0xd4, 0xdd, 0xa5, 0xd8, 0x12, 0xd8, + 0xe5, 0xf0, 0x54, 0x46, 0x0e, 0x99, 0x80, 0x6e, 0xf3, 0xa5, 0x01, 0x13, 0x90, 0x44, 0x9a, 0xed, + 0x83, 0x94, 0x70, 0x14, 0x68, 0xe4, 0x33, 0x30, 0x57, 0x1a, 0x0c, 0x62, 0x89, 0x73, 0x5c, 0xb2, + 0xcf, 0x0e, 0x06, 0x56, 0xa6, 0xd4, 0x51, 0x28, 0x92, 0x82, 0x79, 0xf9, 0xb1, 0x04, 0x33, 0x79, + 0x39, 0x3a, 0x01, 0x9c, 0x88, 0xaf, 0x10, 0xf8, 0x61, 0x54, 0x39, 0x4e, 0xb0, 0xc3, 0x40, 0x19, + 0xe6, 0x99, 0x81, 0x52, 0x68, 0x33, 0x27, 0x53, 0xab, 0x27, 0x43, 0xa9, 0x51, 0x69, 0x58, 0x04, + 0x02, 0x37, 0x74, 0xed, 0x1e, 0x4f, 0x95, 0xb2, 0x72, 0x0a, 0x57, 0x2d, 0x8f, 0x40, 0x80, 0x25, + 0x98, 0x8f, 0xcf, 0x56, 0xb8, 0x24, 0x88, 0xf4, 0x3f, 0xd3, 0xe0, 0xd4, 0x88, 0x11, 0x8f, 0x12, + 0x69, 0x68, 0xe3, 0x13, 0x69, 0x50, 0xc9, 0xa1, 0xda, 0x9d, 0xb0, 0xfd, 0xe9, 0x07, 0x8b, 0x91, + 0xbe, 0xe5, 0x01, 0xe1, 0xa9, 0x37, 0xf9, 0xa7, 0x6f, 0x7a, 0x78, 0x0f, 0x90, 0x4f, 0x6f, 0x42, + 0x1c, 0x8f, 0x55, 0x8a, 0x45, 0x89, 0xe6, 0x99, 0x3d, 0xa3, 0x61, 0xfd, 0xc0, 0x53, 0x56, 0x70, + 0x06, 0x6b, 0xfd, 0x67, 0x72, 0x30, 0x2b, 0xad, 0x43, 0x72, 0x51, 0x7a, 0x0e, 0x5f, 0x64, 0xb9, + 0x61, 0x25, 0x0e, 0x39, 0xb6, 0x13, 0xe1, 0xa2, 0xca, 0x1d, 0x7e, 0xdb, 0x81, 0x11, 0x00, 0xa5, + 0x64, 0x23, 0x89, 0x90, 0x7f, 0x58, 0x4e, 0xbe, 0x00, 0x50, 0xb7, 0x83, 0xb0, 0xd4, 0x09, 0xdd, + 0x07, 0xce, 0x11, 0x36, 0x1d, 0x11, 0x25, 0xf7, 0x04, 0x66, 0x01, 0xb3, 0x91, 0x2c, 0xb1, 0x47, + 0x48, 0x0c, 0xe9, 0x10, 0xc8, 0xd9, 0x0a, 0xf8, 0x10, 0xa4, 0x45, 0x88, 0xc0, 0xd2, 0x7f, 0x5c, + 0x03, 0xd8, 0xae, 0x95, 0x31, 0xbd, 0xd0, 0x93, 0x6a, 0x11, 0xd9, 0x91, 0xf5, 0x05, 0xf7, 0x31, + 0xfa, 0xc3, 0x1f, 0x69, 0xb0, 0xa0, 0xa2, 0x91, 0x77, 0x61, 0xd1, 0xec, 0xf8, 0x5e, 0xaf, 0x77, + 0xcf, 0xee, 0xdc, 0xaf, 0xbb, 0x7d, 0x87, 0x45, 0x1b, 0x9f, 0x64, 0x9b, 0x57, 0x10, 0x15, 0x59, + 0x3d, 0x5a, 0x66, 0x24, 0x91, 0xc9, 0x4f, 0x68, 0x30, 0x6f, 0xee, 0x79, 0x0f, 0xa3, 0xe0, 0xdd, + 0x7c, 0x04, 0xbf, 0x40, 0x85, 0x41, 0xb0, 0xe7, 0x3d, 0x8c, 0x33, 0xd1, 0x2b, 0x9e, 0xcc, 0xef, + 0x1c, 0xcd, 0xc9, 0xa4, 0xe3, 0xe1, 0x01, 0x26, 0x0c, 0xae, 0x28, 0x1f, 0x31, 0xd4, 0x6f, 0xea, + 0x7f, 0xa5, 0xc1, 0x2c, 0x1e, 0x75, 0x7a, 0x3d, 0x54, 0xd2, 0xbe, 0x9f, 0xd2, 0x9a, 0x47, 0xed, + 0x1a, 0x33, 0xb0, 0xaf, 0xc3, 0x62, 0x02, 0x8d, 0xe8, 0x30, 0x65, 0x62, 0x68, 0x0c, 0xd9, 0x4a, + 0xc2, 0x82, 0x65, 0x18, 0xbc, 0x44, 0xaf, 0x4a, 0x64, 0xb7, 0xaf, 0xa2, 0xd3, 0xc1, 0x35, 0x00, + 0x57, 0x80, 0xc4, 0x51, 0x88, 0x24, 0x6b, 0x72, 0xfb, 0xaa, 0x21, 0x61, 0xe9, 0x0d, 0x98, 0x32, + 0x3d, 0x3f, 0x5c, 0x3f, 0x60, 0xa7, 0x8f, 0x8a, 0x13, 0x74, 0x64, 0xaf, 0x02, 0x17, 0xef, 0xfc, + 0x3a, 0x06, 0x2f, 0x22, 0xab, 0x30, 0xb9, 0xe1, 0x3a, 0xbd, 0xae, 0xec, 0x6d, 0xbe, 0x43, 0x01, + 0x06, 0x83, 0xd3, 0x13, 0xda, 0xc9, 0x38, 0xa7, 0x5f, 0xec, 0xd6, 0xfe, 0xa4, 0xeb, 0xa6, 0xac, + 0xf4, 0xef, 0xa5, 0x28, 0xf3, 0x55, 0xfa, 0x4b, 0x63, 0xba, 0xfa, 0xd7, 0x35, 0x38, 0x33, 0x9a, + 0x44, 0xf6, 0x94, 0xd7, 0xc6, 0x78, 0xca, 0x3f, 0x97, 0xbc, 0x05, 0x47, 0x34, 0x7e, 0x0b, 0x1e, + 0xdf, 0x7d, 0x57, 0xf0, 0xa1, 0x42, 0xc7, 0x11, 0x89, 0xfc, 0x2e, 0x8e, 0xa9, 0x33, 0x22, 0xb2, + 0x61, 0x0e, 0x91, 0xc6, 0xe0, 0xb4, 0xfa, 0xbf, 0x9e, 0x80, 0xd3, 0x23, 0x29, 0xc8, 0xa6, 0x94, + 0x1e, 0x74, 0x21, 0x4a, 0x4c, 0x38, 0x12, 0xff, 0x0a, 0xfe, 0x8b, 0xbe, 0xa8, 0xc9, 0x37, 0x91, + 0xcd, 0x28, 0x2d, 0x64, 0x0e, 0x79, 0xbd, 0x78, 0x28, 0x2f, 0x86, 0x8e, 0xcc, 0x20, 0x9d, 0x21, + 0x12, 0x9f, 0xdf, 0x3a, 0xa1, 0xed, 0xf6, 0x02, 0x79, 0xd9, 0x75, 0x19, 0xc8, 0x10, 0x65, 0xf1, + 0xf3, 0x85, 0x89, 0xec, 0xe7, 0x0b, 0xfa, 0xff, 0xd1, 0x60, 0x26, 0xaa, 0x36, 0x39, 0x03, 0x27, + 0xdb, 0x46, 0xa9, 0x5c, 0xb5, 0xda, 0x77, 0x5b, 0x55, 0x6b, 0xbb, 0x61, 0xb6, 0xaa, 0xe5, 0xda, + 0x46, 0xad, 0x5a, 0x29, 0x1e, 0x23, 0x4b, 0x30, 0xbf, 0xdd, 0xb8, 0xd5, 0x68, 0xde, 0x69, 0x58, + 0x55, 0xc3, 0x68, 0x1a, 0x45, 0x8d, 0xcc, 0xc3, 0x8c, 0xb1, 0x5e, 0x2a, 0x5b, 0x8d, 0x66, 0xa5, + 0x5a, 0xcc, 0x91, 0x22, 0xcc, 0x95, 0x9b, 0x8d, 0x46, 0xb5, 0xdc, 0xae, 0xdd, 0xae, 0xb5, 0xef, + 0x16, 0xf3, 0x84, 0xc0, 0x02, 0x22, 0xb4, 0x8c, 0x5a, 0xa3, 0x5c, 0x6b, 0x95, 0xea, 0xc5, 0x09, + 0x0a, 0xa3, 0xf8, 0x12, 0x6c, 0x32, 0x62, 0x74, 0x6b, 0x7b, 0xbd, 0x5a, 0x9c, 0xa2, 0x28, 0xf4, + 0x2f, 0x09, 0x65, 0x9a, 0x7e, 0x1e, 0x51, 0x2a, 0xa5, 0x76, 0x69, 0xbd, 0x64, 0x56, 0x8b, 0x05, + 0x72, 0x0a, 0x8e, 0x2b, 0x20, 0xab, 0xde, 0xbc, 0x51, 0x6b, 0x14, 0x67, 0xc8, 0x32, 0x14, 0x23, + 0x58, 0x65, 0xdd, 0xda, 0x36, 0xab, 0x46, 0x11, 0x92, 0xd0, 0x46, 0x69, 0xab, 0x5a, 0x9c, 0xd5, + 0xdf, 0x61, 0xaf, 0x55, 0x59, 0x57, 0x93, 0x93, 0x40, 0xcc, 0x76, 0xa9, 0xbd, 0x6d, 0x26, 0x1a, + 0x3f, 0x0b, 0xd3, 0xe6, 0x76, 0xb9, 0x5c, 0x35, 0xcd, 0xa2, 0x46, 0x00, 0xa6, 0x36, 0x4a, 0xb5, + 0x7a, 0xb5, 0x52, 0xcc, 0xe9, 0x5f, 0xd3, 0x60, 0x49, 0xa8, 0x8c, 0xe2, 0x1e, 0xef, 0x09, 0xd7, + 0xe2, 0xbb, 0xca, 0x49, 0x58, 0x3c, 0x3d, 0x4c, 0x7c, 0x64, 0xcc, 0x32, 0xfc, 0x45, 0x0d, 0x4e, + 0x64, 0x62, 0x93, 0xbb, 0x50, 0x14, 0x35, 0x88, 0x9e, 0x11, 0x6b, 0x8a, 0xc6, 0x2d, 0xe8, 0x12, + 0x68, 0xcc, 0xb6, 0x1a, 0x6d, 0x57, 0x46, 0x8a, 0xcd, 0xd1, 0x73, 0x27, 0xe9, 0x1f, 0x6a, 0x70, + 0x6a, 0xc4, 0x67, 0x48, 0x19, 0xa6, 0xa2, 0x54, 0x88, 0x63, 0xdc, 0x31, 0x97, 0xbf, 0xf9, 0xd1, + 0x2a, 0x47, 0xa4, 0x4b, 0x83, 0x25, 0x3b, 0x34, 0xa6, 0xa2, 0xdc, 0x86, 0x98, 0x60, 0x90, 0x75, + 0xdf, 0xe9, 0x44, 0xcf, 0xf3, 0x2f, 0x95, 0xee, 0x98, 0xeb, 0xb3, 0xbc, 0xef, 0xf2, 0xf6, 0xc3, + 0x00, 0x33, 0x0c, 0xea, 0xbf, 0xa0, 0x51, 0x6d, 0x30, 0x89, 0x48, 0x95, 0xe4, 0x38, 0x97, 0x58, + 0xc9, 0x68, 0xf0, 0x6d, 0x03, 0xd5, 0x5b, 0x96, 0x7f, 0x0c, 0xcf, 0x21, 0x96, 0xed, 0x2b, 0x31, + 0x31, 0x14, 0x1a, 0x72, 0x1d, 0xa0, 0xfa, 0x28, 0x74, 0xfc, 0xbe, 0xdd, 0x8b, 0xa2, 0x1b, 0xb1, + 0x28, 0x7e, 0x1c, 0xaa, 0x2a, 0xe8, 0x12, 0xb2, 0xfe, 0x15, 0x0d, 0xe6, 0x44, 0x62, 0xb5, 0x9e, + 0xe3, 0x87, 0x4f, 0x36, 0xbd, 0xae, 0x2b, 0xd3, 0x2b, 0x7a, 0x7d, 0x24, 0xf1, 0xa7, 0xc5, 0x99, + 0x33, 0xeb, 0xdf, 0x6b, 0x50, 0x4c, 0x22, 0x92, 0x77, 0xa1, 0x60, 0x3a, 0x0f, 0x1c, 0xdf, 0x0d, + 0x0f, 0xb8, 0xa0, 0x14, 0x29, 0xa8, 0x19, 0x0e, 0x2f, 0x63, 0xf3, 0x21, 0xe0, 0xbf, 0x8c, 0x88, + 0xe6, 0xa8, 0xf2, 0x5e, 0xb2, 0x93, 0xe4, 0x9f, 0x96, 0x9d, 0x44, 0xff, 0x93, 0x1c, 0x9c, 0xba, + 0xe1, 0x84, 0x72, 0x9b, 0x22, 0x37, 0x99, 0x57, 0x8f, 0xd6, 0x2e, 0xa9, 0x25, 0x2b, 0x30, 0x8d, + 0x45, 0x62, 0x7c, 0x0d, 0xf1, 0x93, 0xac, 0x47, 0xf3, 0x3a, 0xaf, 0xe4, 0xb8, 0x1d, 0xf1, 0xed, + 0x2b, 0x52, 0x9e, 0xca, 0x68, 0x5a, 0x5f, 0x86, 0x05, 0xcc, 0x64, 0x33, 0xa4, 0xcb, 0xc1, 0xe9, + 0x72, 0x7b, 0x51, 0xc1, 0x48, 0x40, 0xc9, 0x1a, 0x14, 0x29, 0xa4, 0xd4, 0xb9, 0xdf, 0xf7, 0x1e, + 0xf6, 0x9c, 0xee, 0xae, 0xd3, 0xc5, 0x6d, 0xbd, 0x60, 0xa4, 0xe0, 0x82, 0xe7, 0x76, 0x9f, 0x9d, + 0xf5, 0x9c, 0x2e, 0x1a, 0x75, 0x38, 0xcf, 0x18, 0x7a, 0xe6, 0x3a, 0xcc, 0x7e, 0x87, 0x19, 0x6c, + 0xf5, 0xff, 0xaa, 0xc1, 0x32, 0x36, 0x4e, 0xfa, 0x30, 0x5e, 0x5b, 0xbc, 0x1a, 0xf7, 0x96, 0x94, + 0x86, 0xd1, 0xa6, 0x20, 0x75, 0x29, 0x44, 0xbd, 0x18, 0x1b, 0x91, 0x72, 0x47, 0x30, 0x22, 0x49, + 0x36, 0xb0, 0x89, 0xa7, 0x65, 0x03, 0xbb, 0x39, 0x51, 0xc8, 0x17, 0x27, 0xe2, 0x21, 0xd7, 0x7f, + 0x22, 0x07, 0xd3, 0x86, 0xd3, 0x73, 0xec, 0xc0, 0x21, 0x97, 0x61, 0xba, 0xe1, 0x85, 0x4e, 0xb0, + 0x55, 0x91, 0x5d, 0xd0, 0xfb, 0x14, 0x64, 0xed, 0x77, 0x0d, 0x51, 0x48, 0x27, 0x7c, 0xcb, 0xf7, + 0xba, 0xc3, 0x4e, 0x28, 0x4f, 0xf8, 0x01, 0x03, 0x19, 0xa2, 0x8c, 0xbc, 0x04, 0x33, 0x9c, 0x73, + 0x74, 0xb3, 0x8c, 0x9e, 0xf5, 0x3e, 0x03, 0x62, 0xce, 0xab, 0x08, 0x01, 0x75, 0x5a, 0xa6, 0x60, + 0x4c, 0x48, 0x3a, 0x6d, 0x4a, 0x67, 0x10, 0xaa, 0xfa, 0xe4, 0x18, 0x55, 0xfd, 0x55, 0x98, 0x2a, + 0x05, 0x81, 0x13, 0x8a, 0x60, 0x1d, 0x73, 0x71, 0x3e, 0x45, 0x27, 0x64, 0x8c, 0x6d, 0x2c, 0x37, + 0x38, 0x9e, 0xfe, 0x17, 0x39, 0x98, 0xc4, 0x3f, 0xf1, 0xde, 0xd6, 0xef, 0xec, 0x29, 0xf7, 0xb6, + 0x7e, 0x67, 0xcf, 0x40, 0x28, 0xb9, 0x8a, 0xa6, 0x0d, 0x91, 0x31, 0x94, 0xb7, 0x1e, 0x6d, 0xf6, + 0xdd, 0x18, 0x6c, 0xc8, 0x38, 0x91, 0xd7, 0x44, 0x3e, 0xd3, 0x6b, 0xe2, 0x24, 0xe4, 0x9a, 0x26, + 0x6f, 0x31, 0xc6, 0x92, 0xf3, 0x02, 0x23, 0xd7, 0x34, 0xb1, 0x37, 0x36, 0x4b, 0xd7, 0x5e, 0x7f, + 0x83, 0x37, 0x94, 0xf5, 0xc6, 0x9e, 0x7d, 0xed, 0xf5, 0x37, 0x0c, 0x5e, 0x42, 0xfb, 0x17, 0xeb, + 0x8c, 0xb7, 0xbf, 0x2c, 0x92, 0x04, 0xf6, 0x2f, 0xb6, 0x0d, 0x6f, 0x7a, 0x8d, 0x18, 0x81, 0x5c, + 0x83, 0x59, 0x1e, 0xd2, 0x04, 0xf1, 0xa5, 0x90, 0x23, 0x3c, 0xe4, 0x09, 0xa3, 0x90, 0x91, 0xd8, + 0x3d, 0x20, 0x1f, 0x20, 0x9e, 0x1f, 0x4f, 0xdc, 0x03, 0x8a, 0x21, 0x0c, 0x0c, 0x09, 0x85, 0x56, + 0x89, 0x5d, 0x24, 0xc6, 0x11, 0x22, 0xb0, 0x4a, 0xfc, 0xb6, 0x11, 0xb3, 0x06, 0x45, 0x08, 0xfa, + 0xaf, 0xe5, 0xa0, 0xd0, 0xea, 0x0d, 0x77, 0xdd, 0xfe, 0xed, 0xab, 0x84, 0x00, 0x1e, 0xe3, 0x44, + 0x5a, 0x29, 0xfa, 0x37, 0x39, 0x0d, 0x05, 0x71, 0x72, 0x13, 0x02, 0x29, 0xe0, 0xa7, 0xb6, 0x15, + 0x10, 0xe3, 0xce, 0x83, 0x06, 0x8a, 0x9f, 0xe4, 0x2a, 0x44, 0xe7, 0xaf, 0x51, 0x07, 0xb5, 0x09, + 0xba, 0x58, 0x8c, 0x08, 0x8d, 0xbc, 0x0c, 0xb8, 0x49, 0xf0, 0xc3, 0x83, 0xb0, 0x80, 0xb3, 0xaa, + 0x71, 0x3d, 0x85, 0x91, 0x20, 0x1a, 0xfa, 0x6f, 0xc8, 0x19, 0x20, 0x4f, 0xa8, 0x04, 0x2c, 0x47, + 0xa0, 0x20, 0x11, 0xd9, 0x1f, 0xdf, 0x56, 0xe3, 0x48, 0x4e, 0x2b, 0xf7, 0x5b, 0x8c, 0x52, 0x0a, + 0xe4, 0x78, 0xfb, 0xaa, 0x1a, 0x42, 0xf2, 0x77, 0x67, 0x60, 0x4e, 0xae, 0x0f, 0x31, 0xe0, 0x78, + 0xd0, 0xa3, 0x67, 0x77, 0xee, 0x34, 0x39, 0xc0, 0x42, 0xbe, 0x9d, 0x5e, 0x54, 0x2b, 0x44, 0xf1, + 0x98, 0x07, 0xa5, 0x88, 0xc5, 0xb2, 0x79, 0xcc, 0x58, 0x0a, 0x62, 0x30, 0xc3, 0x23, 0x25, 0x28, + 0x78, 0x83, 0x60, 0xd7, 0xe9, 0xbb, 0xe2, 0x82, 0xe6, 0x19, 0x85, 0x51, 0x93, 0x17, 0xa6, 0x78, + 0x45, 0x64, 0xe4, 0x75, 0x98, 0xf2, 0x06, 0x4e, 0xdf, 0x76, 0x13, 0x3e, 0x33, 0x82, 0x81, 0xd3, + 0x2f, 0xd5, 0x24, 0x42, 0x8e, 0x4c, 0x5e, 0x81, 0x09, 0xef, 0x7e, 0x34, 0x5e, 0xa7, 0x55, 0xa2, + 0xfb, 0xa1, 0x2d, 0x91, 0x20, 0x22, 0x25, 0xf8, 0xc0, 0xde, 0xdf, 0xe1, 0x23, 0xa6, 0x12, 0xdc, + 0xb4, 0xf7, 0x77, 0x64, 0x02, 0x8a, 0x48, 0xde, 0x03, 0x18, 0xd8, 0xbb, 0x8e, 0x6f, 0x75, 0x87, + 0xe1, 0x41, 0x22, 0x45, 0x36, 0x23, 0x6b, 0xd1, 0xe2, 0xca, 0x30, 0x3c, 0x90, 0x68, 0x67, 0x06, + 0x02, 0x48, 0x4a, 0x00, 0xfb, 0x76, 0x18, 0x3a, 0xfe, 0xbe, 0xc7, 0xbd, 0x56, 0xe3, 0x30, 0xa0, + 0x8c, 0xc1, 0x56, 0x54, 0x2c, 0x71, 0x90, 0x88, 0xb0, 0xd2, 0xae, 0x6f, 0xe3, 0x35, 0x7c, 0xaa, + 0xd2, 0xae, 0xaf, 0xb4, 0x92, 0x22, 0x92, 0xb7, 0x60, 0x1a, 0xf3, 0xbe, 0xfa, 0x5d, 0x1e, 0xa4, + 0xe7, 0x9c, 0x42, 0x53, 0x61, 0x65, 0x12, 0x99, 0x40, 0xa7, 0xb5, 0xe5, 0x01, 0x9f, 0x1b, 0xde, + 0x43, 0xbc, 0x17, 0x48, 0xd6, 0xd6, 0x8c, 0x8a, 0xe5, 0xda, 0xc6, 0x44, 0x74, 0x28, 0x77, 0xdd, + 0xb0, 0x67, 0xdf, 0xe3, 0x97, 0xed, 0xea, 0x50, 0xde, 0xc0, 0x22, 0x79, 0x28, 0x19, 0x32, 0xb9, + 0x0e, 0x05, 0x11, 0x86, 0x95, 0x3f, 0xf9, 0x55, 0x2b, 0xcd, 0x83, 0xaf, 0xca, 0x95, 0xe6, 0x81, + 0x57, 0x69, 0xff, 0x04, 0x1d, 0x77, 0x9f, 0xbf, 0xd4, 0x55, 0xfb, 0xc7, 0x2c, 0xd7, 0xb6, 0xe4, + 0xfe, 0xa1, 0x88, 0xe4, 0x5d, 0x98, 0xa6, 0xeb, 0xb7, 0xeb, 0xed, 0xf2, 0x30, 0x27, 0xba, 0xda, + 0x3f, 0xac, 0x2c, 0x35, 0x5d, 0x05, 0x11, 0x5d, 0xc8, 0xf6, 0xc3, 0xc0, 0x72, 0x3b, 0x18, 0x07, + 0x38, 0xb9, 0x1c, 0x4b, 0x77, 0xcc, 0x5a, 0x59, 0x22, 0x9b, 0xb4, 0x1f, 0x06, 0xb5, 0x0e, 0xb9, + 0x06, 0x93, 0x98, 0x71, 0x89, 0x07, 0xfd, 0x55, 0x69, 0x30, 0xd7, 0x92, 0x4c, 0x83, 0xa8, 0x74, + 0x20, 0xf7, 0x03, 0x7c, 0xfc, 0xc4, 0xf3, 0x1e, 0xa9, 0x7d, 0xb2, 0x65, 0xe2, 0x8b, 0x28, 0xb9, + 0x8a, 0x1c, 0x9d, 0x56, 0xb1, 0xef, 0x84, 0x96, 0xfb, 0x45, 0x9e, 0xb9, 0x48, 0xfd, 0x5c, 0xc3, + 0x09, 0x6b, 0x9f, 0x95, 0x3f, 0xd7, 0x77, 0xc2, 0xda, 0x17, 0xf9, 0xd0, 0xed, 0x0d, 0xef, 0xa1, + 0xf1, 0x3d, 0x63, 0xe8, 0xf6, 0x86, 0xc9, 0xa1, 0xdb, 0x1b, 0xde, 0xa3, 0x64, 0x6e, 0x3f, 0x1c, + 0xf6, 0x1d, 0xfe, 0xf4, 0x56, 0x25, 0xab, 0x61, 0x91, 0x4c, 0xc6, 0x90, 0xc9, 0x05, 0x80, 0xd8, + 0xdb, 0x81, 0xdd, 0x23, 0x19, 0x12, 0xe4, 0x53, 0x13, 0xff, 0xed, 0x97, 0x56, 0xb5, 0x75, 0x80, + 0x82, 0x88, 0x15, 0x45, 0xd5, 0xf0, 0xe5, 0xac, 0xba, 0x90, 0x4b, 0x30, 0x27, 0x47, 0xb2, 0xe2, + 0x9b, 0xc1, 0xac, 0x3d, 0x70, 0x45, 0x2c, 0xab, 0xf1, 0x39, 0x6d, 0x5e, 0x84, 0x25, 0xe5, 0x85, + 0x5a, 0xec, 0x86, 0x69, 0x14, 0xe5, 0x02, 0xdc, 0x7b, 0xcb, 0x00, 0x41, 0x68, 0xfb, 0xa1, 0xd5, + 0xb5, 0xc3, 0xa3, 0x98, 0x91, 0x0b, 0x54, 0x9e, 0xb3, 0x07, 0x01, 0x48, 0x57, 0xb1, 0x43, 0x87, + 0x35, 0x4e, 0xaf, 0xc3, 0xe9, 0x91, 0xb2, 0x96, 0xbc, 0x00, 0xc5, 0x1d, 0x9b, 0x5b, 0x5a, 0x3b, + 0x7b, 0x76, 0xbf, 0xef, 0xf4, 0x78, 0xc3, 0x16, 0x05, 0xbc, 0xcc, 0xc0, 0x9c, 0xdb, 0x7b, 0x52, + 0xef, 0x48, 0x8b, 0xec, 0x08, 0xbd, 0xc3, 0x19, 0x7c, 0x43, 0x83, 0x73, 0xe3, 0x44, 0x36, 0x39, + 0x03, 0x85, 0x81, 0xef, 0x7a, 0x78, 0x34, 0xe0, 0x7d, 0x28, 0x7e, 0x63, 0xca, 0x1f, 0xd4, 0x61, + 0x43, 0x7b, 0x97, 0x3f, 0xf9, 0x32, 0x66, 0x10, 0xd2, 0xb6, 0x77, 0x03, 0xda, 0xc5, 0x5d, 0x67, + 0xc7, 0x1e, 0xf6, 0x42, 0x2b, 0xe8, 0xec, 0x39, 0x5d, 0x7c, 0x94, 0x89, 0xfe, 0xab, 0x46, 0x91, + 0x17, 0x98, 0x02, 0x9e, 0xaa, 0xf1, 0xe4, 0x88, 0x1a, 0xdf, 0x9c, 0x28, 0x68, 0xc5, 0x9c, 0x81, + 0x2e, 0x72, 0xfa, 0x8f, 0xe5, 0x60, 0x65, 0x94, 0x8c, 0x22, 0xef, 0x64, 0xf5, 0x01, 0xbb, 0x5d, + 0x92, 0xe1, 0xf2, 0xed, 0x92, 0x3c, 0x7b, 0xae, 0x41, 0xf4, 0xa4, 0xf2, 0xb0, 0xf0, 0x28, 0x02, + 0x46, 0x69, 0x06, 0x76, 0x10, 0x3c, 0xa4, 0x62, 0x38, 0x2f, 0x05, 0x6c, 0xe7, 0x30, 0x99, 0x46, + 0xc0, 0xc8, 0x9b, 0x00, 0x9d, 0x9e, 0x17, 0x38, 0xe8, 0xc4, 0xc1, 0xf5, 0x3b, 0xf6, 0x50, 0x24, + 0x82, 0xca, 0xb7, 0xf6, 0x08, 0x2d, 0x7b, 0x5d, 0x31, 0x9f, 0x6c, 0x38, 0x35, 0x62, 0x53, 0xa2, + 0xc3, 0x83, 0x6f, 0x24, 0x99, 0x0c, 0xe2, 0xe9, 0x32, 0x29, 0x84, 0xa5, 0x79, 0x4b, 0xf6, 0x78, + 0x6e, 0xd4, 0x1c, 0x39, 0x00, 0x92, 0xde, 0x79, 0x28, 0x77, 0xfe, 0x30, 0x62, 0xe8, 0x47, 0xdc, + 0x19, 0x64, 0xdb, 0xef, 0x91, 0x55, 0x98, 0x15, 0x19, 0xe3, 0xe9, 0xf9, 0x89, 0x31, 0x07, 0x0e, + 0xba, 0xe5, 0xe0, 0xe4, 0xc1, 0xf8, 0xca, 0x2c, 0x7d, 0x17, 0x5b, 0x79, 0x33, 0x08, 0x69, 0x1f, + 0x0c, 0x44, 0xeb, 0xce, 0x89, 0xf9, 0xad, 0xea, 0x03, 0xbc, 0xf4, 0xef, 0x69, 0x62, 0xf8, 0xd3, + 0x1b, 0xea, 0x61, 0xf5, 0x23, 0x80, 0xef, 0x16, 0x79, 0xc5, 0xf0, 0x6f, 0xaa, 0x29, 0x8a, 0x55, + 0xc7, 0x35, 0x45, 0xfe, 0x93, 0x5c, 0x86, 0x45, 0x9f, 0xb9, 0x73, 0x87, 0x1e, 0xef, 0x4f, 0x96, + 0xe1, 0x6a, 0x9e, 0x81, 0xdb, 0x1e, 0xf6, 0x29, 0xaf, 0xd7, 0xcd, 0xa8, 0xc3, 0x24, 0xfd, 0x82, + 0x5c, 0x81, 0x19, 0xaa, 0x5f, 0x60, 0xcc, 0xac, 0xc4, 0x83, 0x29, 0xc4, 0x43, 0x6d, 0xcd, 0x28, + 0x7c, 0xc0, 0xff, 0xe6, 0xbc, 0x7e, 0x3c, 0x12, 0x80, 0xaa, 0x54, 0x25, 0x27, 0x61, 0x2a, 0x74, + 0xfa, 0x76, 0xb4, 0xb8, 0xf9, 0x2f, 0xf2, 0x1c, 0x2c, 0xb0, 0x57, 0xce, 0x89, 0x81, 0x9d, 0x47, + 0x68, 0x34, 0xbd, 0x8f, 0x96, 0x1e, 0x8d, 0x57, 0xe2, 0x37, 0x73, 0xa2, 0x45, 0xb2, 0x8a, 0x45, + 0x4e, 0xc1, 0xb4, 0xe7, 0xef, 0x4a, 0xfd, 0x3b, 0xe5, 0xf9, 0xbb, 0xb4, 0x73, 0x9f, 0x87, 0x22, + 0x7b, 0x44, 0xc8, 0x82, 0xb9, 0x04, 0x07, 0xfd, 0x0e, 0x7f, 0xfb, 0xb1, 0xc0, 0xe0, 0xdb, 0x81, + 0xe3, 0x9b, 0x07, 0xfd, 0x0e, 0xc5, 0x0c, 0x02, 0xcf, 0x92, 0xe3, 0xef, 0xf1, 0x8a, 0x2c, 0x04, + 0x81, 0x17, 0x07, 0xe2, 0xeb, 0x92, 0x75, 0x98, 0xa7, 0x7c, 0xa2, 0x30, 0x82, 0x5c, 0x0c, 0x9f, + 0x4f, 0x6b, 0x80, 0x07, 0xfd, 0x8e, 0xa8, 0xa2, 0x31, 0x17, 0x48, 0xbf, 0xc8, 0x2d, 0x28, 0x4a, + 0xaa, 0x32, 0xbe, 0x2a, 0x4f, 0xbc, 0x6f, 0x88, 0xd9, 0x48, 0x2a, 0x76, 0xad, 0xbf, 0xe3, 0x19, + 0x8b, 0x1d, 0x15, 0x10, 0x89, 0xa3, 0xa9, 0xe2, 0xb4, 0xb1, 0xc2, 0x9b, 0x1b, 0xa0, 0xab, 0xa8, + 0xd5, 0xf3, 0x76, 0x2d, 0xe7, 0x11, 0x9d, 0x18, 0xfa, 0x3f, 0xd2, 0x84, 0xc0, 0xcf, 0x60, 0x4a, + 0x74, 0x98, 0xdf, 0xb3, 0x03, 0x2b, 0x08, 0xf6, 0x99, 0x07, 0x23, 0x8f, 0x95, 0x3e, 0xbb, 0x67, + 0x07, 0x66, 0xb0, 0x2f, 0xd2, 0x76, 0x9d, 0xa0, 0x38, 0x18, 0xcf, 0xdd, 0x92, 0x0f, 0x06, 0xac, + 0x47, 0x8f, 0xef, 0xd9, 0x41, 0x93, 0x96, 0xc9, 0xc1, 0xdd, 0x9f, 0x85, 0x05, 0xe4, 0xdb, 0x71, + 0x05, 0x63, 0x0c, 0xd8, 0x63, 0xcc, 0x51, 0xc6, 0x1d, 0x97, 0x71, 0xe6, 0x83, 0xfb, 0x9f, 0xa7, + 0xe0, 0x64, 0x76, 0xef, 0xe1, 0x1a, 0xa2, 0x7d, 0x8e, 0x4f, 0x8b, 0x79, 0xdd, 0x66, 0x28, 0x84, + 0x05, 0x5b, 0xca, 0x1a, 0xbc, 0x5c, 0xe6, 0xe0, 0xad, 0xc1, 0x12, 0x32, 0xe2, 0x47, 0x90, 0x9e, + 0x1b, 0x84, 0x3c, 0x86, 0x90, 0xb1, 0x48, 0x0b, 0xd8, 0xa6, 0x53, 0xa7, 0x60, 0x3a, 0x33, 0xc5, + 0xb6, 0xe1, 0x3d, 0xec, 0xd3, 0x0f, 0xb3, 0x3d, 0x63, 0x9e, 0x43, 0x9b, 0x08, 0x24, 0x27, 0x60, + 0xca, 0x1e, 0x0c, 0xe8, 0x27, 0xd9, 0x56, 0x31, 0x69, 0x0f, 0x06, 0x2c, 0x72, 0x3d, 0xcb, 0x0a, + 0xb8, 0x83, 0xfe, 0x66, 0xe2, 0x65, 0xc3, 0x1c, 0x02, 0x99, 0x0f, 0x1a, 0xbe, 0x8d, 0xa0, 0xb4, + 0x02, 0x65, 0x1a, 0x51, 0xc0, 0x1e, 0x44, 0x08, 0xa7, 0xa1, 0x20, 0x3c, 0x1f, 0xd8, 0xcb, 0x31, + 0x63, 0xda, 0xe6, 0x5e, 0x0f, 0xaf, 0xc3, 0x29, 0x9e, 0x82, 0xd0, 0x62, 0x4d, 0x1a, 0x0c, 0xf8, + 0xd3, 0x6d, 0x16, 0xa7, 0xdc, 0x58, 0xe6, 0xc5, 0xb4, 0x27, 0x4b, 0x83, 0x41, 0xf4, 0x80, 0xfb, + 0x8c, 0x20, 0xbb, 0xe7, 0xb2, 0x48, 0x87, 0xcc, 0xfb, 0x17, 0x17, 0x07, 0x20, 0xe5, 0x0a, 0xc7, + 0x58, 0x97, 0x11, 0xc4, 0x32, 0x89, 0x56, 0x92, 0xc5, 0x5f, 0x04, 0x30, 0xf5, 0x09, 0xef, 0xc7, + 0x71, 0xd0, 0x10, 0x4a, 0xde, 0x84, 0x91, 0x73, 0x11, 0xb5, 0xf3, 0x82, 0x71, 0x82, 0x95, 0x33, + 0xaf, 0xe6, 0xba, 0xb7, 0x5b, 0xc5, 0x42, 0xf2, 0x1e, 0x9c, 0x13, 0x15, 0xb4, 0x83, 0xc0, 0xdd, + 0xed, 0x5b, 0x62, 0x14, 0xd0, 0xf1, 0x04, 0x35, 0xf4, 0x82, 0x71, 0x9a, 0xe3, 0x94, 0x10, 0xa5, + 0xc2, 0x30, 0xd8, 0xd3, 0xdf, 0x57, 0x61, 0x39, 0x74, 0xf7, 0x1d, 0xeb, 0x9e, 0x13, 0x3e, 0x74, + 0x9c, 0xbe, 0xe5, 0xee, 0x53, 0xbe, 0x2c, 0x9a, 0xce, 0x8c, 0x41, 0x68, 0xd9, 0x3a, 0x2b, 0xaa, + 0xb1, 0x12, 0xd2, 0x82, 0xe7, 0x14, 0x0a, 0xf6, 0xdd, 0x7d, 0xaa, 0xb1, 0x0d, 0x7c, 0x8f, 0xcd, + 0x14, 0xcf, 0x1b, 0xb0, 0xb4, 0x1f, 0x33, 0xc6, 0x25, 0x89, 0x45, 0x29, 0x42, 0x6d, 0x31, 0xcc, + 0x3a, 0x45, 0x24, 0x65, 0xb8, 0xc0, 0x5d, 0x39, 0x38, 0x03, 0xb7, 0xbf, 0x6b, 0x51, 0xb5, 0xc9, + 0xdb, 0xd9, 0xb1, 0x82, 0xd0, 0x19, 0xb0, 0x34, 0x1e, 0xc6, 0x59, 0x86, 0xd5, 0x8a, 0x90, 0xd6, + 0x19, 0x8e, 0x19, 0x3a, 0x03, 0x52, 0x82, 0xf3, 0xa3, 0x99, 0xec, 0xdb, 0x8f, 0x98, 0x6f, 0x8d, + 0x71, 0x66, 0x04, 0x8f, 0x2d, 0xfb, 0x11, 0x5f, 0x59, 0x6f, 0xc1, 0x22, 0x3f, 0x78, 0x71, 0xad, + 0x0d, 0x67, 0x1e, 0xdf, 0x53, 0xf0, 0xd9, 0x0d, 0xcb, 0x05, 0x09, 0x1c, 0x54, 0xeb, 0x8a, 0x9d, + 0xed, 0x8f, 0x35, 0x38, 0x91, 0x79, 0x72, 0x23, 0x3f, 0x0c, 0xec, 0x5d, 0x72, 0xe8, 0x59, 0xbe, + 0xd3, 0x71, 0x07, 0x2e, 0x06, 0x7a, 0x62, 0x37, 0x1b, 0xd7, 0xc6, 0x9d, 0xf9, 0xf0, 0x8d, 0x73, + 0xdb, 0x33, 0x22, 0x22, 0x66, 0x72, 0x2d, 0xfa, 0x09, 0xf0, 0x99, 0xf7, 0xe1, 0x44, 0x26, 0x6a, + 0x86, 0x29, 0xf4, 0x25, 0xd9, 0x14, 0x1a, 0xdf, 0x55, 0x27, 0x1a, 0x2d, 0x99, 0x48, 0x79, 0xf3, + 0x7e, 0x3b, 0x6a, 0x5e, 0xe2, 0x8c, 0x47, 0xaa, 0x49, 0x29, 0x9f, 0x65, 0xa6, 0x10, 0x44, 0xa3, + 0x05, 0xfd, 0xfb, 0x70, 0x82, 0x8b, 0x1a, 0xb6, 0xc9, 0x45, 0xec, 0x58, 0x45, 0x3f, 0x91, 0xc5, + 0x8e, 0xc9, 0xa0, 0x1b, 0x14, 0x3f, 0xe2, 0x7a, 0xdc, 0x4e, 0x03, 0x79, 0x1b, 0xbe, 0x9d, 0x17, + 0x82, 0x3d, 0xa3, 0x3a, 0x19, 0x42, 0x4c, 0xcb, 0x12, 0x62, 0x47, 0x97, 0xa0, 0x0d, 0x20, 0xf2, + 0xd6, 0x15, 0xc8, 0x0f, 0xa8, 0x56, 0xd5, 0x6c, 0x25, 0xd2, 0x46, 0xc0, 0x5f, 0x06, 0x2d, 0x75, + 0x92, 0x20, 0x7a, 0x38, 0x62, 0x0a, 0x03, 0xfd, 0x24, 0xd3, 0x65, 0x0a, 0x0c, 0x50, 0xeb, 0x92, + 0x8b, 0x30, 0xc7, 0x4e, 0xe6, 0x8a, 0x84, 0x05, 0x84, 0x95, 0x50, 0xcc, 0xbe, 0x9d, 0x25, 0x66, + 0xe3, 0x2b, 0x16, 0xae, 0x94, 0x1f, 0xf4, 0x3b, 0x4c, 0xa2, 0x26, 0xe4, 0xaf, 0x05, 0xa7, 0xa5, + 0x9d, 0x80, 0xf7, 0x90, 0x68, 0xd3, 0x34, 0xb6, 0xe9, 0x59, 0xb5, 0x4d, 0xf1, 0x06, 0xc1, 0x7a, + 0x8e, 0x37, 0xec, 0xa4, 0x9d, 0x09, 0x27, 0x9b, 0xb0, 0x80, 0xd3, 0xc8, 0xed, 0x87, 0x8e, 0xff, + 0x80, 0x6e, 0x96, 0x05, 0xe5, 0xb6, 0x3f, 0x35, 0x70, 0x35, 0x81, 0x68, 0xe0, 0xfc, 0x8b, 0x7e, + 0x46, 0xa7, 0xb6, 0x33, 0xa3, 0x49, 0xc8, 0x32, 0x4c, 0x76, 0x9d, 0x5e, 0x68, 0xf3, 0xa5, 0xc1, + 0x7e, 0x50, 0x0d, 0x73, 0x67, 0xd8, 0xeb, 0x09, 0x0d, 0x93, 0xfe, 0xcd, 0xb9, 0xfd, 0x54, 0x0e, + 0x8a, 0xc9, 0x1e, 0x22, 0x3a, 0xe4, 0x44, 0x22, 0x96, 0xb4, 0x73, 0xd4, 0xe6, 0x31, 0x23, 0xe7, + 0x76, 0xc9, 0x75, 0x00, 0x4c, 0xbe, 0xec, 0x3b, 0xbb, 0xce, 0x23, 0x7e, 0x2a, 0xc1, 0xb3, 0x42, + 0x0c, 0x55, 0x68, 0x66, 0xd0, 0x7e, 0x4c, 0xc1, 0xe4, 0x2a, 0x80, 0xf3, 0x88, 0x25, 0xc8, 0x11, + 0x2a, 0x56, 0xc6, 0x67, 0x34, 0x63, 0x86, 0x63, 0xd5, 0xba, 0x64, 0x13, 0x88, 0x20, 0x91, 0xbe, + 0x3a, 0x71, 0xc8, 0x57, 0x35, 0xa3, 0xc8, 0xa9, 0x1a, 0xe2, 0xe3, 0xfc, 0x5c, 0x3f, 0x03, 0xd3, + 0x3c, 0x47, 0x0f, 0xfd, 0x93, 0x23, 0xe9, 0x5f, 0xd6, 0xe0, 0xe2, 0x61, 0xeb, 0x90, 0xdc, 0x81, + 0x93, 0xe8, 0x52, 0x18, 0x78, 0xd1, 0x52, 0xb6, 0x3a, 0x76, 0x67, 0xcf, 0xe1, 0x92, 0x4f, 0xcf, + 0x5c, 0xd0, 0x83, 0x81, 0x69, 0x36, 0xa5, 0xb5, 0x3c, 0x18, 0x98, 0x81, 0x27, 0x7e, 0x97, 0x29, + 0x39, 0x1f, 0x90, 0x2e, 0x9c, 0x1d, 0x43, 0x29, 0xa9, 0x1a, 0x9a, 0xac, 0x6a, 0x3c, 0x0f, 0xc5, + 0x1d, 0xa7, 0xeb, 0xf8, 0xf8, 0x9c, 0x9f, 0x56, 0xed, 0xc1, 0x35, 0x1c, 0x93, 0x39, 0x63, 0x21, + 0x82, 0x9b, 0x81, 0x77, 0xfb, 0x1a, 0xff, 0xca, 0x97, 0xf3, 0x42, 0x8b, 0x96, 0x4d, 0x54, 0xe4, + 0x1a, 0x1c, 0x4f, 0x44, 0x62, 0x93, 0x42, 0xfb, 0xe4, 0x56, 0x34, 0x63, 0x89, 0x16, 0xab, 0xb1, + 0x3b, 0x9f, 0x83, 0x39, 0x79, 0x77, 0xe6, 0x53, 0x81, 0x22, 0xcf, 0x76, 0xe3, 0x3d, 0x99, 0xdc, + 0x83, 0x05, 0x49, 0xb4, 0x50, 0x6d, 0x37, 0x9f, 0x21, 0xff, 0xe4, 0xda, 0x5c, 0x89, 0x65, 0x4e, + 0x7f, 0xc7, 0x63, 0x9e, 0x75, 0x2a, 0x0b, 0x63, 0xbe, 0x23, 0xa3, 0x10, 0x13, 0x80, 0xb6, 0xd4, + 0xea, 0xb9, 0xfb, 0x6e, 0x1c, 0x44, 0x21, 0xc9, 0xdf, 0xb0, 0x43, 0xa7, 0x4e, 0x31, 0xd8, 0xcc, + 0x89, 0x29, 0xe4, 0xb3, 0xad, 0x2f, 0x90, 0xce, 0x7c, 0x56, 0x8a, 0x6f, 0x8a, 0x5f, 0x39, 0x07, + 0x13, 0xfd, 0xcc, 0xf4, 0x05, 0x78, 0xee, 0x3e, 0x07, 0x13, 0x61, 0x66, 0x90, 0xee, 0x30, 0x3a, + 0x4a, 0xf2, 0x31, 0xf8, 0xdf, 0x1a, 0x1c, 0xcf, 0xa8, 0x15, 0x79, 0x0e, 0xa6, 0xed, 0x07, 0x8e, + 0x2f, 0x42, 0xb9, 0xf2, 0x68, 0x4d, 0x1c, 0x64, 0x88, 0x3f, 0xc8, 0x2a, 0x4c, 0xde, 0x1b, 0xfa, + 0x41, 0xc8, 0x43, 0x3a, 0xa1, 0x8f, 0x09, 0x02, 0x0c, 0xf6, 0x1f, 0xb9, 0x0e, 0x0b, 0x03, 0xc7, + 0x77, 0xbd, 0xae, 0x15, 0x38, 0x1d, 0xaf, 0xdf, 0x65, 0x5a, 0x6e, 0x9e, 0xbf, 0xa4, 0x52, 0x4a, + 0x8c, 0x79, 0xf6, 0xdb, 0x64, 0x3f, 0xc9, 0x5d, 0x38, 0xbd, 0x6f, 0x3f, 0xa2, 0xd3, 0xa0, 0x33, + 0xf4, 0x7d, 0xaa, 0x04, 0x79, 0x03, 0x6e, 0x18, 0x63, 0xae, 0xed, 0x2c, 0xfc, 0xf7, 0x68, 0x24, + 0xe3, 0xd4, 0xbe, 0xfd, 0xa8, 0x1c, 0x95, 0x34, 0xa3, 0x02, 0xde, 0xf6, 0xa1, 0x98, 0xe5, 0x99, + 0xd6, 0xce, 0xa3, 0x58, 0xd4, 0x5e, 0x06, 0x12, 0xd9, 0xa7, 0x22, 0xed, 0x83, 0x0b, 0xb8, 0x25, + 0x51, 0x12, 0xa9, 0x0d, 0xfc, 0xb3, 0xbf, 0x31, 0x25, 0xba, 0x5c, 0x31, 0x93, 0x92, 0x97, 0xa1, + 0x48, 0xc5, 0xf3, 0xae, 0x2f, 0x19, 0xe0, 0xe2, 0x49, 0xbf, 0x28, 0x95, 0xf1, 0xfb, 0xaf, 0x29, + 0xdf, 0xd9, 0x8d, 0xee, 0xd2, 0x0c, 0xfe, 0x8b, 0x6a, 0x25, 0xb6, 0x2f, 0xae, 0x76, 0xe8, 0x9f, + 0xa4, 0x06, 0x4b, 0x98, 0xce, 0x2b, 0x70, 0x3d, 0xcc, 0xb4, 0x87, 0x07, 0xee, 0x09, 0xc5, 0x98, + 0x8a, 0x35, 0x69, 0x49, 0x48, 0xf4, 0xc4, 0x6d, 0x14, 0x07, 0x09, 0x08, 0xf9, 0x34, 0x9c, 0x91, + 0x37, 0xaa, 0xc4, 0x96, 0x8e, 0xcf, 0xf9, 0x8c, 0x53, 0xf1, 0x1e, 0x54, 0x51, 0x36, 0xf7, 0x75, + 0xb8, 0x80, 0x0b, 0xdb, 0xed, 0x0e, 0xac, 0x54, 0x4e, 0x45, 0x6c, 0x2e, 0x4b, 0x29, 0x75, 0x86, + 0x62, 0xd5, 0xba, 0x83, 0x44, 0x7a, 0x45, 0x6c, 0x75, 0x2b, 0x73, 0xdb, 0x67, 0x5b, 0xe4, 0x79, + 0xb9, 0x31, 0xa9, 0x4d, 0x9f, 0x89, 0x8e, 0xf4, 0xc6, 0xbf, 0x0b, 0x4b, 0xf1, 0x51, 0x42, 0xec, + 0xde, 0x05, 0x94, 0xa2, 0x67, 0x64, 0x86, 0xe2, 0x88, 0xce, 0xb6, 0x27, 0x16, 0xad, 0x2a, 0x45, + 0x28, 0xc7, 0x64, 0x1b, 0x2a, 0x04, 0x01, 0xa9, 0xc3, 0xb2, 0xfd, 0x30, 0xa0, 0x47, 0x3e, 0x6f, + 0xd8, 0x0f, 0x83, 0xe8, 0x5b, 0x33, 0xe9, 0x6f, 0x09, 0x67, 0x0e, 0xae, 0x2c, 0x10, 0xfb, 0x61, + 0x50, 0xe2, 0x64, 0x82, 0xdb, 0x07, 0x40, 0x98, 0xc2, 0xa1, 0xd4, 0x1b, 0x0e, 0xe3, 0xc5, 0xfc, + 0xfb, 0xd3, 0x94, 0x72, 0x64, 0x59, 0x2c, 0x95, 0x6b, 0xde, 0x56, 0x2f, 0xe0, 0x66, 0x15, 0xef, + 0x91, 0x64, 0x6f, 0x33, 0xcf, 0x16, 0x09, 0x5f, 0x36, 0x28, 0x4a, 0x60, 0xb4, 0x21, 0x61, 0xe4, + 0x49, 0xac, 0xc7, 0xbe, 0xd7, 0x75, 0xd8, 0x8b, 0x08, 0x63, 0x1e, 0xc1, 0xb4, 0x02, 0x5b, 0xb1, + 0x5d, 0xef, 0x43, 0x0d, 0x8a, 0xc9, 0x4f, 0x91, 0xb7, 0x61, 0x8a, 0x9d, 0xea, 0xb8, 0x5a, 0xac, + 0x67, 0xd7, 0x89, 0x8d, 0x34, 0x3b, 0xe0, 0x6d, 0x1e, 0x33, 0x38, 0x0d, 0x79, 0x03, 0x26, 0x3c, + 0xb7, 0x2b, 0xbc, 0x61, 0x2e, 0x8e, 0xa3, 0x6d, 0xd6, 0x2a, 0x65, 0xbc, 0x41, 0x73, 0xbb, 0xdc, + 0x10, 0xb5, 0x5e, 0x10, 0x4f, 0xd1, 0xf5, 0x0f, 0xe0, 0xec, 0x98, 0x0f, 0x92, 0x2a, 0x2c, 0x26, + 0x3c, 0x85, 0x8e, 0xe8, 0x44, 0x64, 0xc7, 0x4e, 0x44, 0xbe, 0x30, 0x4e, 0xf4, 0xe0, 0xf4, 0xc8, + 0x0a, 0x92, 0xda, 0x48, 0x09, 0x82, 0xb1, 0xd1, 0x92, 0x65, 0xf2, 0x64, 0x4d, 0x48, 0x17, 0xfe, + 0xb5, 0xaf, 0xe6, 0xe0, 0x78, 0xc6, 0x24, 0xfa, 0xff, 0x56, 0x37, 0xfb, 0x67, 0x1a, 0xef, 0x0f, + 0x55, 0x18, 0x90, 0x36, 0x70, 0x47, 0x34, 0x2e, 0x38, 0x2e, 0x8f, 0x16, 0x1c, 0xb2, 0x7f, 0x0f, + 0x0f, 0x92, 0x87, 0x00, 0xd9, 0x8b, 0x85, 0x41, 0x9e, 0xc0, 0xf3, 0x86, 0x0f, 0xdf, 0xfb, 0x70, + 0x22, 0x53, 0xb0, 0x93, 0xd3, 0x50, 0xc0, 0x07, 0x30, 0xb1, 0xa5, 0x72, 0x9a, 0xfe, 0xde, 0xf6, + 0xd1, 0x0a, 0x7e, 0xcf, 0xb1, 0x7d, 0xc7, 0xe7, 0x76, 0x32, 0x6e, 0x05, 0x67, 0x30, 0xd9, 0x4c, + 0xd6, 0x55, 0x77, 0x31, 0x7e, 0x75, 0x4f, 0xb6, 0xe0, 0x38, 0x93, 0x2e, 0xcc, 0xdc, 0x61, 0xf1, + 0xeb, 0x7e, 0x35, 0x47, 0x23, 0x92, 0xa0, 0x21, 0x88, 0x99, 0x3e, 0x18, 0xb5, 0xb1, 0xb4, 0x9b, + 0x04, 0x51, 0x6d, 0xf8, 0x64, 0x36, 0x36, 0x59, 0x87, 0x59, 0xc6, 0x9c, 0xdd, 0x14, 0x30, 0x3f, + 0xad, 0x4b, 0x63, 0xbf, 0x50, 0xc6, 0x77, 0xa1, 0x41, 0xf4, 0x37, 0x79, 0x06, 0xe6, 0xd1, 0x25, + 0xd6, 0xda, 0x97, 0xdd, 0xd0, 0x8c, 0x39, 0x04, 0x72, 0xf7, 0x33, 0xfd, 0x3f, 0x45, 0x3a, 0x92, + 0x72, 0x47, 0x49, 0x77, 0xe0, 0xc0, 0xe9, 0x0b, 0x57, 0xbc, 0x19, 0x83, 0xff, 0x7a, 0x4c, 0xad, + 0x80, 0xbc, 0x09, 0x73, 0x94, 0xed, 0xee, 0xb0, 0xcf, 0x76, 0xe6, 0xbc, 0x12, 0xbc, 0x77, 0x8b, + 0x15, 0xd1, 0x61, 0xdb, 0x3c, 0x66, 0xcc, 0xee, 0xc7, 0x3f, 0xc9, 0x15, 0x98, 0x09, 0xf6, 0xc3, + 0x81, 0xbc, 0x9f, 0x0b, 0x7f, 0x0d, 0x73, 0xab, 0xdd, 0xe2, 0x24, 0x05, 0x8a, 0x13, 0x1b, 0xd0, + 0xd7, 0xa7, 0x98, 0xc7, 0x86, 0xfe, 0x22, 0xcc, 0x4a, 0xbc, 0x69, 0x63, 0x58, 0x14, 0x05, 0xd1, + 0x18, 0xf6, 0x8b, 0x0f, 0xf6, 0x3d, 0x28, 0x08, 0x96, 0xf4, 0x1c, 0xb7, 0xe7, 0x05, 0x42, 0x1f, + 0xc2, 0xbf, 0x29, 0x0c, 0x2d, 0x6a, 0xb4, 0x91, 0x93, 0x06, 0xfe, 0x8d, 0x67, 0x79, 0xbc, 0x24, + 0xc4, 0x44, 0x10, 0xf8, 0x72, 0x26, 0x32, 0x65, 0x53, 0x78, 0xbb, 0x17, 0xb0, 0xf7, 0x34, 0xc2, + 0xa8, 0x1e, 0x19, 0x41, 0x12, 0x97, 0xba, 0xa3, 0xce, 0x1b, 0xca, 0x91, 0x3d, 0x97, 0x3e, 0xb2, + 0xb3, 0xa0, 0xac, 0x9c, 0x92, 0x7d, 0x19, 0x10, 0xc6, 0x8e, 0xec, 0xb1, 0x02, 0x35, 0xa1, 0x28, + 0x50, 0xd2, 0x35, 0x5d, 0x3c, 0x7a, 0xec, 0xc4, 0x2f, 0xae, 0xe9, 0x92, 0x2a, 0xdd, 0xaf, 0x44, + 0x33, 0x44, 0xb9, 0x56, 0x26, 0xd7, 0xe0, 0x04, 0x33, 0x53, 0xb3, 0xcc, 0x99, 0x49, 0x5d, 0xf2, + 0x38, 0x16, 0xb2, 0xb4, 0xd1, 0x91, 0x4e, 0x79, 0xf8, 0x35, 0x14, 0x79, 0x15, 0x96, 0xdd, 0x7e, + 0xe0, 0x74, 0x86, 0xbe, 0x63, 0x05, 0xf7, 0xdd, 0x01, 0xcb, 0xe1, 0x7e, 0xc0, 0x0d, 0xc8, 0x44, + 0x94, 0x99, 0xf7, 0xdd, 0x01, 0xe6, 0x73, 0x17, 0x3d, 0xfc, 0xab, 0x39, 0x71, 0xb9, 0xb9, 0xee, + 0x79, 0x61, 0x10, 0xfa, 0xf6, 0x40, 0x71, 0x9c, 0x21, 0xfb, 0x70, 0x1a, 0xab, 0x74, 0x0d, 0x53, + 0x85, 0x7a, 0xbe, 0xb8, 0x0c, 0x8e, 0x16, 0xd8, 0xec, 0xb5, 0x57, 0xd4, 0x8b, 0x81, 0x12, 0xc5, + 0x2e, 0xc9, 0xc8, 0x74, 0x5d, 0x49, 0x5c, 0x37, 0x8f, 0x19, 0xa7, 0x18, 0xcf, 0x14, 0x16, 0xd9, + 0xcc, 0x90, 0x35, 0x49, 0xcf, 0x99, 0xf5, 0x58, 0xf0, 0xa8, 0x5c, 0x65, 0x91, 0x44, 0xde, 0x85, + 0x19, 0xb7, 0x2b, 0x72, 0xb3, 0xe6, 0x33, 0x7c, 0x36, 0x6a, 0x5d, 0x96, 0x42, 0x2b, 0xe6, 0x41, + 0x97, 0x86, 0xcb, 0xa1, 0xeb, 0xf3, 0x8a, 0x86, 0xa3, 0x6f, 0x8a, 0x7b, 0xb4, 0x34, 0x19, 0x59, + 0x88, 0xf7, 0x3e, 0xdc, 0xe7, 0x50, 0x0a, 0x48, 0x39, 0x61, 0xf9, 0x2f, 0xde, 0xe5, 0x3f, 0x02, + 0xcf, 0x1f, 0xb5, 0xa7, 0xa8, 0xdc, 0x18, 0xd1, 0xed, 0x33, 0x2c, 0x81, 0x87, 0xda, 0x7b, 0x97, + 0x40, 0x4e, 0x3c, 0xe4, 0x8a, 0x89, 0x22, 0x60, 0xdb, 0xbe, 0xab, 0xff, 0x79, 0x1e, 0x16, 0x54, + 0xd7, 0x2a, 0xf2, 0x22, 0x4c, 0x48, 0xe2, 0xf2, 0x54, 0x86, 0xff, 0x15, 0x0a, 0x49, 0x44, 0x3a, + 0x92, 0x78, 0x24, 0x37, 0x61, 0x01, 0x5f, 0x87, 0xa1, 0x1a, 0x17, 0xba, 0xdc, 0x6d, 0xe0, 0xa8, + 0x0e, 0x01, 0x73, 0x94, 0x96, 0x6e, 0x8f, 0xb4, 0x50, 0xf2, 0x9c, 0x99, 0x18, 0xed, 0x39, 0xc3, + 0x9b, 0x32, 0xc2, 0x73, 0x66, 0x72, 0x8c, 0xe7, 0x4c, 0x4c, 0x29, 0x7b, 0xce, 0xa0, 0xff, 0xd4, + 0xf4, 0x28, 0xff, 0xa9, 0x98, 0x86, 0xf9, 0x4f, 0xc5, 0x9e, 0x2f, 0x85, 0x91, 0x9e, 0x2f, 0x31, + 0x0d, 0xf7, 0x7c, 0x89, 0x7d, 0x51, 0x66, 0x46, 0xfa, 0xa2, 0x48, 0x44, 0xcc, 0x17, 0xe5, 0x59, + 0xde, 0xb1, 0xbe, 0xfd, 0xd0, 0xc2, 0x1e, 0xe7, 0xe7, 0x23, 0xec, 0x32, 0xc3, 0x7e, 0x88, 0xaf, + 0x38, 0xa8, 0x7a, 0xc2, 0x9f, 0x7e, 0xe8, 0xdf, 0x48, 0x88, 0x21, 0x31, 0xe6, 0xcf, 0xc1, 0x02, + 0xdb, 0x8d, 0x79, 0x62, 0x18, 0xb6, 0x1d, 0xcf, 0x1b, 0xf3, 0x02, 0xca, 0xae, 0xaf, 0x3e, 0x01, + 0x8b, 0x11, 0x1a, 0xbf, 0xc1, 0xc1, 0xb0, 0x34, 0x46, 0x44, 0xcd, 0xef, 0x6e, 0x64, 0x7e, 0x3e, + 0x0f, 0x6f, 0xab, 0xf0, 0x63, 0x17, 0x20, 0x2f, 0x03, 0x89, 0xd1, 0xa2, 0x87, 0x70, 0x13, 0x88, + 0xba, 0x14, 0xa1, 0x46, 0xaf, 0xd5, 0x7e, 0x5e, 0x4b, 0x38, 0x6f, 0x7c, 0x5c, 0xd5, 0x7f, 0x11, + 0xa2, 0xaf, 0x5b, 0xfc, 0x02, 0x5e, 0xb4, 0xa0, 0x28, 0x0a, 0x5a, 0x1c, 0xae, 0xff, 0xad, 0x94, + 0x69, 0xfe, 0xe3, 0xaa, 0xd6, 0x59, 0x98, 0x89, 0x4f, 0x42, 0x6c, 0xbb, 0x2a, 0x04, 0xfc, 0x10, + 0xa4, 0xff, 0xea, 0x84, 0x72, 0xe3, 0x2c, 0xea, 0x40, 0x75, 0xa0, 0xc0, 0xb3, 0xf8, 0x04, 0xe0, + 0x22, 0xfa, 0xd2, 0x88, 0x49, 0xcc, 0x1f, 0x06, 0x99, 0x66, 0xd3, 0x80, 0x20, 0xf0, 0xc4, 0x3b, + 0x21, 0x8b, 0xd9, 0x12, 0xa5, 0x33, 0xa1, 0x60, 0xc7, 0xe4, 0xf1, 0xda, 0x78, 0x76, 0xe2, 0x4a, + 0x8f, 0xae, 0x61, 0xb4, 0x29, 0x46, 0xbf, 0xc4, 0x07, 0xb6, 0x01, 0xbd, 0x44, 0x02, 0x95, 0x79, + 0x96, 0xe5, 0x2d, 0xc5, 0x1c, 0xbb, 0x10, 0x39, 0xe3, 0x7d, 0x5f, 0x20, 0xb3, 0xad, 0xc2, 0x1c, + 0xde, 0xe7, 0x0a, 0x86, 0x13, 0x19, 0x7e, 0x6c, 0xe9, 0xc6, 0x97, 0x6b, 0x5b, 0xc6, 0x2c, 0xa5, + 0x13, 0x6c, 0xf6, 0x14, 0xdb, 0x7b, 0xa2, 0x92, 0x93, 0x22, 0xd9, 0xd3, 0xd8, 0x1e, 0x88, 0x2f, + 0x6b, 0xb1, 0xaa, 0x92, 0x11, 0x5e, 0xa9, 0x70, 0x07, 0x4e, 0xa7, 0xee, 0x20, 0xa3, 0x2f, 0x31, + 0xbf, 0xca, 0xe7, 0x47, 0x7c, 0x29, 0x71, 0x39, 0xe9, 0xf8, 0xc6, 0xc9, 0x40, 0x05, 0xf1, 0x8f, + 0xe0, 0x43, 0xbc, 0xd1, 0x03, 0x3f, 0x26, 0x0d, 0x79, 0xac, 0x64, 0xe5, 0x64, 0x25, 0x4b, 0xbe, + 0xf9, 0xcd, 0xab, 0x37, 0xbf, 0x1b, 0x70, 0x91, 0x4a, 0x44, 0x3e, 0x73, 0x9c, 0x07, 0x8e, 0x7f, + 0xe0, 0xf5, 0x31, 0x42, 0xf0, 0x20, 0x12, 0x0c, 0xec, 0xaa, 0xfa, 0x1c, 0xc5, 0xc3, 0x79, 0x51, + 0xe5, 0x58, 0x5b, 0x88, 0xc4, 0x22, 0x5f, 0xff, 0x93, 0x3c, 0x3c, 0x73, 0x84, 0xc9, 0x35, 0xa6, + 0xee, 0x9f, 0x51, 0x8f, 0x02, 0x39, 0xe5, 0x16, 0x88, 0x32, 0x8d, 0x6f, 0x17, 0x46, 0x1c, 0x04, + 0x7e, 0x18, 0x16, 0xd9, 0x26, 0xc6, 0x5e, 0x22, 0xee, 0x0c, 0x7b, 0x47, 0xd8, 0xc5, 0xce, 0x8a, + 0x38, 0x2b, 0x09, 0x52, 0xdc, 0xd8, 0x50, 0x76, 0x9b, 0x11, 0x8c, 0xb4, 0x61, 0x16, 0xd1, 0x76, + 0x6c, 0xb7, 0x77, 0xa4, 0x80, 0x1f, 0x22, 0x8a, 0x8b, 0x4c, 0xc6, 0x5e, 0x5c, 0x53, 0xc0, 0x06, + 0xfe, 0x26, 0x97, 0x61, 0xb1, 0x3f, 0xdc, 0xa7, 0x4a, 0x2e, 0x9b, 0xb9, 0xfc, 0xc1, 0xc7, 0xa4, + 0x31, 0xdf, 0x1f, 0xee, 0x97, 0x06, 0x03, 0x9c, 0x80, 0xf8, 0x32, 0x64, 0x89, 0xe2, 0xf1, 0xec, + 0xfe, 0x1c, 0x73, 0x0a, 0x31, 0x29, 0x03, 0x26, 0x82, 0x38, 0xee, 0x32, 0xb0, 0x77, 0x82, 0x3c, + 0x9d, 0x3b, 0xfb, 0xa1, 0xff, 0xaf, 0x9c, 0xb8, 0x97, 0x18, 0xbd, 0x4a, 0x7f, 0x30, 0x44, 0x19, + 0x43, 0xf4, 0x3c, 0x14, 0x69, 0xd7, 0xc7, 0x22, 0x30, 0x1a, 0xa3, 0x85, 0xfe, 0x70, 0x3f, 0xea, + 0x3b, 0xb9, 0xe3, 0xa7, 0xe4, 0x8e, 0x7f, 0x53, 0x98, 0xa9, 0x33, 0x85, 0xd9, 0xe8, 0x2e, 0xa7, + 0xda, 0xdf, 0xe5, 0xa3, 0x89, 0xac, 0x1f, 0x8c, 0x5b, 0xc6, 0xb8, 0x25, 0x9c, 0x62, 0x26, 0x53, + 0x4e, 0x31, 0x19, 0x6b, 0x6f, 0x2a, 0x6b, 0xed, 0xa5, 0x5c, 0x70, 0xa6, 0x33, 0x5c, 0x70, 0x32, + 0x17, 0x68, 0xe1, 0x90, 0x05, 0x3a, 0x23, 0xcf, 0x93, 0xdf, 0xcb, 0xc1, 0xa5, 0x43, 0xf7, 0x8d, + 0x1f, 0x8c, 0x74, 0xc6, 0x48, 0x67, 0xf7, 0xe7, 0x9f, 0xe7, 0x84, 0x36, 0xad, 0x1e, 0x92, 0xdf, + 0x87, 0xe3, 0xe2, 0x90, 0xcc, 0xf4, 0x86, 0xd8, 0x53, 0x6d, 0xf6, 0xda, 0x0b, 0x59, 0xc7, 0x63, + 0x44, 0xcb, 0x38, 0xc2, 0x2e, 0xf1, 0x83, 0x71, 0x5c, 0xfe, 0xff, 0xce, 0x91, 0x98, 0xdc, 0x85, + 0x93, 0x98, 0xf0, 0xb1, 0x23, 0xfb, 0xd8, 0x59, 0xbe, 0xb3, 0xc3, 0x7b, 0xfd, 0x52, 0xea, 0xe8, + 0xe8, 0x76, 0xa4, 0xea, 0x18, 0xce, 0xce, 0xe6, 0x31, 0x63, 0x39, 0xc8, 0x80, 0x73, 0x43, 0x54, + 0xe2, 0xcc, 0xfd, 0x2f, 0x35, 0xd0, 0x0f, 0xef, 0x35, 0x34, 0x8f, 0x24, 0xbb, 0x7d, 0xc6, 0x98, + 0xb5, 0xa5, 0x3e, 0x7c, 0x06, 0xe6, 0x7d, 0x67, 0xc7, 0x77, 0x82, 0x3d, 0xc5, 0x86, 0x39, 0xc7, + 0x81, 0xa2, 0x7b, 0x44, 0xee, 0x99, 0xc7, 0x3a, 0xae, 0x0a, 0xa2, 0xc8, 0x0f, 0xe2, 0xdc, 0xb8, + 0x31, 0xa1, 0x33, 0x4b, 0xae, 0x26, 0xfb, 0x11, 0x79, 0x4a, 0xe6, 0x8a, 0x79, 0x83, 0x67, 0xcb, + 0xd9, 0x71, 0x7b, 0x8e, 0xfe, 0x5b, 0x91, 0xee, 0x96, 0xd5, 0x9d, 0xe4, 0x7d, 0xe9, 0x85, 0x74, + 0x3e, 0xa5, 0x96, 0x66, 0x91, 0x1c, 0xc5, 0xd8, 0x5c, 0x7f, 0x4a, 0xc6, 0xe6, 0xeb, 0xe2, 0x99, + 0x15, 0xdd, 0x55, 0x6e, 0x5f, 0x25, 0x2f, 0xc0, 0x34, 0x7b, 0x59, 0x25, 0xaa, 0xbb, 0xa8, 0x54, + 0xf7, 0xf6, 0x55, 0x43, 0x94, 0xeb, 0x1f, 0x46, 0x3e, 0xa1, 0xa9, 0x46, 0xdc, 0xbe, 0x4a, 0xde, + 0x3c, 0xda, 0x8b, 0xe7, 0x82, 0x78, 0xf1, 0x1c, 0xbd, 0x76, 0x7e, 0x4b, 0x79, 0xed, 0xfc, 0xec, + 0xf8, 0xde, 0xe2, 0xee, 0xc6, 0x2c, 0x03, 0x49, 0xe1, 0xc3, 0x5f, 0x62, 0x91, 0xdf, 0xf5, 0x9f, + 0xc8, 0xc3, 0xf9, 0xb1, 0x14, 0xe4, 0x1c, 0x14, 0x4a, 0xad, 0x5a, 0x3b, 0x1e, 0x65, 0xba, 0x8a, + 0x04, 0x84, 0xdc, 0x80, 0x99, 0x75, 0x3b, 0x70, 0x3b, 0x74, 0x4a, 0x67, 0x3a, 0x5b, 0xa5, 0xd8, + 0x46, 0xe8, 0x9b, 0xc7, 0x8c, 0x98, 0x96, 0x58, 0xb0, 0x84, 0xeb, 0x42, 0x49, 0x1b, 0x9f, 0xcf, + 0x30, 0xc9, 0xa5, 0x18, 0xa6, 0xc8, 0xa8, 0xe4, 0x49, 0x01, 0xc9, 0x3d, 0x20, 0xa6, 0xb9, 0x59, + 0x76, 0xfc, 0x90, 0x1b, 0xa9, 0x42, 0x37, 0x7a, 0x3e, 0xfb, 0xea, 0x21, 0x7d, 0x97, 0xa2, 0xdb, + 0x3c, 0x66, 0x64, 0x70, 0x23, 0x97, 0x60, 0x76, 0xe0, 0xbb, 0x0f, 0xec, 0xd0, 0x41, 0x27, 0x78, + 0xaa, 0x05, 0xcd, 0x6d, 0x1e, 0x33, 0x80, 0x03, 0x6f, 0x39, 0x07, 0xd9, 0xb2, 0xe1, 0x47, 0x84, + 0xea, 0x39, 0xba, 0xb7, 0x1e, 0x23, 0x0d, 0xd4, 0xf3, 0x50, 0x68, 0x89, 0x57, 0x09, 0x52, 0xbc, + 0x02, 0xf1, 0x02, 0xc1, 0x88, 0x4a, 0xf9, 0xcc, 0xfe, 0x39, 0x4d, 0x18, 0xf1, 0x0e, 0xef, 0x5b, + 0xf2, 0x02, 0x88, 0x2c, 0xff, 0xe2, 0x82, 0x0c, 0xe3, 0x27, 0x44, 0x0f, 0x6f, 0x8c, 0xa8, 0x98, + 0xbc, 0x0e, 0x4a, 0x76, 0x7f, 0x5e, 0x17, 0x4c, 0x53, 0xcd, 0xd1, 0x99, 0xf4, 0x36, 0x14, 0x34, + 0x5e, 0x29, 0x0f, 0x5e, 0x38, 0xf2, 0x68, 0x90, 0xb7, 0xa1, 0x88, 0x49, 0xd2, 0x6d, 0x69, 0x64, + 0xd5, 0x74, 0x05, 0x2c, 0x7c, 0x6d, 0xcb, 0x76, 0x7d, 0x63, 0xb1, 0xa3, 0x52, 0xf3, 0x0f, 0xfe, + 0x1a, 0xcf, 0x10, 0x59, 0xeb, 0xb6, 0x12, 0x17, 0xf3, 0x4f, 0x1a, 0xf4, 0xa2, 0xaa, 0xac, 0x53, + 0xb1, 0xd7, 0x65, 0x7f, 0x6b, 0x74, 0xec, 0x0b, 0x69, 0xd1, 0xfe, 0xfd, 0x3c, 0x9c, 0x1b, 0x47, + 0x4e, 0x4a, 0x50, 0xac, 0xb2, 0x40, 0xba, 0xfc, 0xb9, 0xb1, 0xe7, 0xcb, 0x19, 0x8b, 0x59, 0x90, + 0x5d, 0xab, 0x1b, 0x15, 0x1a, 0x29, 0x74, 0x3a, 0xce, 0x0c, 0x16, 0x45, 0x74, 0xc0, 0x71, 0xe6, + 0xa4, 0x74, 0x9c, 0x45, 0x31, 0x79, 0x06, 0xa6, 0x4a, 0x65, 0x73, 0xdb, 0xa8, 0xcb, 0xe1, 0x5a, + 0xec, 0x4e, 0x80, 0x8f, 0x7a, 0x79, 0x11, 0xf9, 0x21, 0x28, 0x26, 0x33, 0xb3, 0xe2, 0xe1, 0x59, + 0x0a, 0x45, 0x26, 0xa7, 0x0c, 0x15, 0xc9, 0x79, 0xb0, 0xbe, 0x71, 0xb2, 0x51, 0x9e, 0xbf, 0xcf, + 0x48, 0xf1, 0x22, 0x3a, 0x4c, 0xb5, 0x7c, 0x27, 0x70, 0x42, 0xf9, 0x59, 0xf4, 0x00, 0x21, 0x06, + 0x2f, 0xe1, 0x8f, 0x96, 0xed, 0x03, 0x16, 0xd4, 0x72, 0x4a, 0x0e, 0x5e, 0x8c, 0xaf, 0x9c, 0x29, + 0xd8, 0x90, 0x50, 0x28, 0x41, 0xdd, 0x1e, 0xf6, 0x3b, 0x7b, 0xdb, 0x46, 0x9d, 0xab, 0xb5, 0x8c, + 0xa0, 0x87, 0x50, 0xda, 0xc0, 0xc0, 0x90, 0x50, 0xf4, 0x9f, 0xd4, 0x60, 0x39, 0xab, 0x1d, 0x87, + 0xf8, 0x33, 0xbd, 0x0a, 0xb3, 0x78, 0x7f, 0xbb, 0xe3, 0xf9, 0xfb, 0x76, 0x28, 0x3f, 0x1e, 0x97, + 0xc0, 0x06, 0xde, 0x37, 0x6f, 0xe0, 0xdf, 0x64, 0x55, 0xec, 0x56, 0x52, 0xde, 0x25, 0x04, 0xf0, + 0x8d, 0x4b, 0x2f, 0x01, 0xd4, 0xba, 0xad, 0xe6, 0x80, 0x25, 0x0f, 0x7d, 0x0d, 0x26, 0x68, 0xb5, + 0x12, 0xb3, 0x97, 0xce, 0x9f, 0xd2, 0x56, 0x9d, 0x23, 0xb1, 0x5a, 0x05, 0xf6, 0x7e, 0xcf, 0x40, + 0x64, 0xfd, 0x0e, 0x2c, 0xa8, 0x18, 0xa4, 0xaa, 0xe6, 0x8f, 0x9a, 0xbd, 0x56, 0xe4, 0x9c, 0xd6, + 0x3d, 0x8f, 0x05, 0x30, 0x59, 0x3f, 0xfd, 0xcd, 0x8f, 0x56, 0x81, 0xfe, 0x64, 0x34, 0x59, 0xf9, + 0xa5, 0xf4, 0x9f, 0xc9, 0xc1, 0x72, 0x1c, 0x64, 0x51, 0xac, 0xa1, 0xef, 0xd9, 0x00, 0x5e, 0x25, + 0x25, 0xc0, 0x94, 0x50, 0x42, 0xd3, 0x0d, 0x1c, 0x13, 0xd7, 0xe6, 0x06, 0xac, 0x8c, 0xc2, 0x27, + 0x2f, 0xc2, 0x0c, 0xc6, 0xfa, 0x1e, 0xd8, 0x1d, 0x47, 0x16, 0xb9, 0x7d, 0x01, 0x34, 0xe2, 0x72, + 0xfd, 0xf7, 0x34, 0x38, 0xc3, 0xc3, 0x6e, 0x6c, 0xd9, 0x6e, 0x1f, 0xaf, 0x0b, 0x3b, 0xce, 0xd3, + 0x09, 0x40, 0x77, 0x43, 0x91, 0x63, 0xcf, 0xa9, 0xd1, 0x55, 0x52, 0x5f, 0x1b, 0xdd, 0x5a, 0xf2, + 0x02, 0xc6, 0xaf, 0xe7, 0xee, 0xcc, 0x13, 0x2c, 0x42, 0x68, 0x9f, 0x02, 0xe4, 0x08, 0xa1, 0x88, + 0xa1, 0xff, 0x28, 0x5c, 0x18, 0xff, 0x01, 0xf2, 0x05, 0x98, 0xc7, 0x6c, 0xff, 0xdb, 0x83, 0x5d, + 0xdf, 0xee, 0x3a, 0xc2, 0x48, 0x2c, 0x6e, 0x3a, 0xe4, 0x32, 0x16, 0x8e, 0x9f, 0x47, 0xac, 0xa4, + 0x70, 0x6b, 0xc8, 0x89, 0x94, 0xd8, 0x36, 0x32, 0x37, 0xfd, 0xc7, 0x34, 0x20, 0x69, 0x1e, 0xe4, + 0x0d, 0x98, 0xdb, 0x6e, 0x97, 0xcd, 0xd0, 0xf6, 0xc3, 0x4d, 0x6f, 0xe8, 0xf3, 0x58, 0xf8, 0x2c, + 0x80, 0x61, 0xd8, 0xb1, 0xd8, 0xc5, 0xf0, 0x9e, 0x37, 0xf4, 0x0d, 0x05, 0x0f, 0x33, 0xc2, 0x3b, + 0xce, 0xfd, 0xae, 0x7d, 0xa0, 0x66, 0x84, 0xe7, 0x30, 0x25, 0x23, 0x3c, 0x87, 0xe9, 0xbf, 0xa2, + 0xc1, 0x59, 0xf1, 0x70, 0xb2, 0x9b, 0x51, 0x97, 0x32, 0x86, 0xe9, 0xf5, 0x45, 0x56, 0xae, 0x71, + 0x8a, 0xfe, 0x92, 0x88, 0x64, 0x8d, 0x15, 0x44, 0x8d, 0x9f, 0xd1, 0x92, 0xcf, 0xc0, 0x84, 0x19, + 0x7a, 0x83, 0x23, 0x84, 0xb2, 0x2e, 0x46, 0x23, 0x1a, 0x7a, 0x03, 0x64, 0x81, 0x94, 0xba, 0x03, + 0xcb, 0x72, 0xe5, 0x44, 0x8d, 0xc9, 0x16, 0x4c, 0xf3, 0x3c, 0x08, 0x09, 0xe7, 0xdd, 0x31, 0x6d, + 0x5a, 0x5f, 0x14, 0xf1, 0xb2, 0x79, 0xfe, 0x28, 0x43, 0xf0, 0xd0, 0x7f, 0x5a, 0x83, 0x59, 0xaa, + 0xea, 0xa0, 0xc5, 0xe0, 0x49, 0xa7, 0xb4, 0xaa, 0x42, 0x8b, 0xf7, 0x0c, 0x11, 0xfb, 0x23, 0xed, + 0xc6, 0xaf, 0xc3, 0x62, 0x82, 0x80, 0xe8, 0x18, 0x29, 0xb5, 0xe7, 0x76, 0xb8, 0x37, 0x27, 0x7b, + 0x0b, 0xa0, 0xc0, 0xf4, 0x9f, 0xd2, 0x60, 0xb9, 0x79, 0x3f, 0xb4, 0x99, 0xff, 0x86, 0x31, 0xec, + 0x89, 0xf5, 0x4e, 0xd5, 0x37, 0xf1, 0x02, 0x97, 0x05, 0x65, 0x64, 0xea, 0x1b, 0x87, 0x19, 0x51, + 0x29, 0xd9, 0x84, 0x02, 0xdf, 0x5f, 0x02, 0x9e, 0xc1, 0xe8, 0x82, 0x64, 0xce, 0x88, 0x19, 0x73, + 0x24, 0xda, 0x12, 0x14, 0x61, 0x9c, 0xc6, 0x88, 0xa8, 0xf5, 0xbf, 0xd0, 0xe0, 0xd4, 0x08, 0x1a, + 0xf2, 0x0e, 0x4c, 0x62, 0xc0, 0x28, 0x3e, 0x7a, 0xe7, 0x46, 0x7c, 0x22, 0xec, 0xec, 0xdd, 0xbe, + 0xca, 0x36, 0xa2, 0x7d, 0xfa, 0xc3, 0x60, 0x54, 0xe4, 0x7d, 0x98, 0x29, 0x75, 0xbb, 0xfc, 0x60, + 0x97, 0x53, 0x0e, 0x76, 0x23, 0xbe, 0x78, 0x25, 0xc2, 0x67, 0x07, 0x3b, 0x16, 0xba, 0xa4, 0xdb, + 0xb5, 0x78, 0x30, 0xac, 0x98, 0xdf, 0x99, 0xb7, 0x61, 0x41, 0x45, 0x7e, 0xac, 0xf8, 0x3d, 0x1f, + 0x6a, 0x50, 0x54, 0xeb, 0xf0, 0xf1, 0x44, 0xfa, 0xce, 0x1a, 0xe6, 0x43, 0x26, 0xd5, 0xcf, 0xe6, + 0xe0, 0x44, 0x66, 0x0f, 0x93, 0x97, 0x61, 0xaa, 0x34, 0x18, 0xd4, 0x2a, 0x7c, 0x56, 0x71, 0x0d, + 0x09, 0x6f, 0x36, 0x94, 0x73, 0x2f, 0x43, 0x22, 0xaf, 0x41, 0x81, 0xb9, 0x09, 0x55, 0x84, 0xc0, + 0xc1, 0xd0, 0xc5, 0xdc, 0x87, 0x49, 0xcd, 0x25, 0x24, 0x10, 0xc9, 0x06, 0x2c, 0xf0, 0xa0, 0xbf, + 0xe8, 0x33, 0x16, 0xe5, 0xf7, 0x44, 0x37, 0x3b, 0x71, 0x5d, 0xc2, 0xbc, 0xcd, 0x14, 0xd9, 0x99, + 0xa0, 0x22, 0x75, 0x28, 0x22, 0x4f, 0x99, 0x13, 0x4b, 0x68, 0x24, 0xb9, 0x69, 0x8e, 0xe0, 0x95, + 0xa2, 0x8c, 0x86, 0x2b, 0x7e, 0x0b, 0xf6, 0xf1, 0x0d, 0x57, 0xfc, 0x8d, 0x23, 0x0d, 0xd7, 0x2f, + 0x4c, 0xb0, 0xc5, 0x9c, 0x24, 0xa3, 0x1a, 0x8d, 0x94, 0xce, 0x0f, 0x35, 0x1a, 0x7a, 0x62, 0xe3, + 0x61, 0x6d, 0x2b, 0x30, 0xdd, 0xe6, 0xd9, 0xd5, 0xd8, 0xca, 0x38, 0x9f, 0x59, 0x05, 0x86, 0x73, + 0xfb, 0x2a, 0x53, 0x5f, 0x78, 0xb6, 0x35, 0x43, 0x90, 0x92, 0xdb, 0x30, 0x5b, 0xee, 0x39, 0x76, + 0x7f, 0x38, 0x68, 0x1f, 0xcd, 0x3b, 0x61, 0x85, 0xb7, 0x65, 0xae, 0xc3, 0xc8, 0xd0, 0xab, 0x01, + 0x25, 0xb9, 0xcc, 0x88, 0xb4, 0xa3, 0x60, 0x36, 0x13, 0x68, 0x2b, 0x7d, 0x75, 0x4c, 0xff, 0x24, + 0x81, 0x23, 0x53, 0x1f, 0x11, 0x0b, 0x16, 0xea, 0x76, 0x10, 0xb6, 0x7d, 0xbb, 0x1f, 0x60, 0xea, + 0x93, 0x23, 0x84, 0x71, 0x3f, 0xcb, 0x2b, 0xcc, 0x6c, 0xa8, 0x61, 0x44, 0xca, 0x6c, 0xa8, 0x2a, + 0x3b, 0xaa, 0x2f, 0x6d, 0xb8, 0x7d, 0xbb, 0xe7, 0x7e, 0x49, 0xc4, 0xfc, 0x62, 0xfa, 0xd2, 0x8e, + 0x00, 0x1a, 0x71, 0xb9, 0xfe, 0xf9, 0xd4, 0xb8, 0xb1, 0x5a, 0xce, 0xc2, 0x34, 0x8f, 0x08, 0xc9, + 0x22, 0x24, 0xb6, 0xaa, 0x8d, 0x4a, 0xad, 0x71, 0xa3, 0xa8, 0x91, 0x05, 0x80, 0x96, 0xd1, 0x2c, + 0x57, 0x4d, 0x93, 0xfe, 0xce, 0xd1, 0xdf, 0x3c, 0x7c, 0xe2, 0xc6, 0x76, 0xbd, 0x98, 0x97, 0x22, + 0x28, 0x4e, 0xe8, 0x5f, 0xc9, 0xc1, 0xc9, 0xec, 0xa1, 0x24, 0x6d, 0xfe, 0x84, 0x81, 0xf9, 0xa9, + 0xbc, 0x31, 0x76, 0xdc, 0x33, 0xc1, 0xc9, 0x58, 0x9c, 0x21, 0x8b, 0xf1, 0x98, 0x13, 0x17, 0x9c, + 0x2c, 0x68, 0x94, 0xdb, 0xe5, 0xce, 0xaa, 0x6a, 0xc2, 0xaa, 0x4b, 0x63, 0xbe, 0x67, 0xaa, 0xa9, + 0xaa, 0xca, 0xb0, 0x32, 0xea, 0xf3, 0x6a, 0x2f, 0x2d, 0xc2, 0x6c, 0xa9, 0xd5, 0xaa, 0xd7, 0xca, + 0xa5, 0x76, 0xad, 0xd9, 0x28, 0x6a, 0x64, 0x06, 0x26, 0x6f, 0x18, 0xcd, 0xed, 0x56, 0x31, 0xa7, + 0xff, 0xa1, 0x06, 0x67, 0x46, 0x7f, 0x8b, 0xac, 0xc0, 0xb4, 0x37, 0x0c, 0x3b, 0x9e, 0x38, 0x20, + 0x19, 0xe2, 0x27, 0x59, 0x80, 0x1c, 0x57, 0x4a, 0x66, 0x8c, 0x9c, 0x37, 0x20, 0xbb, 0xdc, 0x67, + 0x84, 0xbf, 0xd4, 0x3c, 0x52, 0xc4, 0xbc, 0x67, 0xf9, 0xec, 0x59, 0x51, 0x29, 0x13, 0x71, 0xd0, + 0xe6, 0x69, 0x69, 0x4b, 0x14, 0x92, 0x67, 0x60, 0x7e, 0xc7, 0x76, 0x7b, 0x43, 0xdf, 0xb1, 0xd0, + 0x31, 0x1d, 0xd7, 0xc0, 0xa4, 0x31, 0xc7, 0x81, 0x65, 0x0a, 0xd3, 0xff, 0x48, 0x83, 0xf9, 0x5a, + 0xec, 0x59, 0xfc, 0xa4, 0xe2, 0xe8, 0x53, 0x8a, 0x38, 0x5a, 0x89, 0xe2, 0xef, 0x46, 0x1f, 0x18, + 0xa3, 0x53, 0xaf, 0x47, 0x41, 0xd2, 0xf2, 0x8a, 0x07, 0x8e, 0x4c, 0x2d, 0xc2, 0x4f, 0x45, 0x49, + 0x5a, 0xd5, 0x20, 0x6a, 0x92, 0x3c, 0xfb, 0x17, 0x79, 0x58, 0x4a, 0x7d, 0x97, 0x98, 0x30, 0x5d, + 0xba, 0x63, 0x36, 0x6b, 0x95, 0x32, 0x6f, 0xdd, 0x6a, 0xec, 0x8d, 0x4a, 0xa1, 0xe9, 0x9a, 0xb2, + 0xb8, 0x77, 0x0f, 0x03, 0xcb, 0x73, 0xbb, 0x1d, 0xc5, 0x1d, 0x5a, 0x70, 0x42, 0xfd, 0xe0, 0x4b, + 0x43, 0x1f, 0x3d, 0xbc, 0x79, 0xcb, 0x23, 0x27, 0x57, 0x01, 0x4f, 0x33, 0x46, 0x9f, 0x67, 0x9b, + 0x96, 0xa7, 0x59, 0xc7, 0xfc, 0x48, 0x03, 0xa6, 0x6e, 0xb8, 0xe1, 0xe6, 0xf0, 0x1e, 0xef, 0x95, + 0x0b, 0x71, 0x06, 0xfa, 0xcd, 0xe1, 0xbd, 0x34, 0x5b, 0xb4, 0x21, 0xb3, 0x98, 0x37, 0x0a, 0x4b, + 0xce, 0x85, 0xdc, 0x82, 0xc9, 0xd2, 0x1d, 0xd3, 0x28, 0x71, 0x99, 0x25, 0xf9, 0xfb, 0x1a, 0xa5, + 0x11, 0xdc, 0x68, 0xeb, 0x7d, 0x5b, 0xe1, 0xc6, 0x78, 0x24, 0xe3, 0x7e, 0x4d, 0x3c, 0x56, 0xdc, + 0xaf, 0xf5, 0x79, 0x98, 0xe5, 0xc7, 0x5c, 0x3c, 0x41, 0x3e, 0x82, 0xe3, 0x19, 0x43, 0x4d, 0x6c, + 0x7c, 0x55, 0x80, 0x7e, 0x08, 0xa5, 0xfe, 0xc1, 0xc3, 0x3d, 0xc7, 0x77, 0xd2, 0x63, 0xa7, 0xd6, + 0x5d, 0xcc, 0x92, 0xcc, 0xda, 0x1b, 0x29, 0x76, 0xfa, 0x6f, 0x6b, 0xb0, 0x32, 0x6a, 0x02, 0xd0, + 0x33, 0xbb, 0x1a, 0x59, 0xf4, 0x64, 0x94, 0x69, 0x59, 0x7d, 0x0d, 0x20, 0xd0, 0xc8, 0x7b, 0x30, + 0xcb, 0x3c, 0x46, 0xcd, 0xd7, 0xb6, 0x8d, 0x1a, 0x97, 0x65, 0xe7, 0xbf, 0xfd, 0xd1, 0xea, 0x29, + 0xee, 0x64, 0x1a, 0xbc, 0x66, 0x0d, 0x7d, 0x37, 0x26, 0x5d, 0xd1, 0x0c, 0x99, 0x82, 0x1e, 0xb1, + 0xec, 0x61, 0xd7, 0x75, 0xc4, 0x01, 0x53, 0x44, 0x5f, 0xe4, 0x30, 0x59, 0xe1, 0x11, 0x30, 0xfd, + 0x2b, 0x1a, 0x9c, 0x19, 0x3d, 0xdb, 0xa8, 0x12, 0xd5, 0x66, 0x8e, 0xb7, 0x22, 0xfe, 0x21, 0x2a, + 0x51, 0x91, 0x77, 0xae, 0xcc, 0x53, 0x20, 0x52, 0x22, 0x6e, 0x0a, 0x15, 0x16, 0x34, 0x24, 0x8a, + 0x2c, 0xa5, 0x32, 0x91, 0x40, 0xd4, 0xef, 0xc2, 0xa9, 0x11, 0x73, 0x93, 0xbc, 0x9b, 0x99, 0xbf, + 0x1d, 0xe3, 0xdb, 0xc8, 0x01, 0x8c, 0xe4, 0xe4, 0x0b, 0x32, 0xbe, 0xfe, 0x1f, 0x98, 0xab, 0x79, + 0xc6, 0x44, 0xa5, 0xfa, 0x1e, 0xe6, 0x0b, 0x2f, 0xf5, 0x3b, 0x7b, 0x9e, 0x1f, 0x0f, 0x16, 0xea, + 0x7b, 0x21, 0x2d, 0xb1, 0x6c, 0x2c, 0x4a, 0x0c, 0x5a, 0x82, 0x8a, 0x78, 0xb0, 0xd4, 0xf2, 0xbd, + 0x1d, 0x97, 0x05, 0x43, 0x60, 0xc7, 0x74, 0xbe, 0xa6, 0x9f, 0x97, 0xa6, 0x9b, 0x3c, 0x7d, 0x52, + 0xf8, 0x51, 0xae, 0x4b, 0x0a, 0x66, 0xde, 0x4a, 0x1d, 0x2c, 0x30, 0xd2, 0xbc, 0xf5, 0x6f, 0xe6, + 0xe0, 0xd2, 0xa1, 0x1c, 0x8f, 0x9a, 0xf6, 0xfc, 0x15, 0x00, 0x4e, 0x4b, 0x7b, 0x40, 0x32, 0xc2, + 0x89, 0xca, 0xd8, 0x7e, 0xdf, 0x90, 0x50, 0xc8, 0x7d, 0x38, 0x2f, 0x7e, 0x75, 0x3a, 0xce, 0x20, + 0x0c, 0x68, 0x3d, 0x78, 0x42, 0x84, 0x28, 0xb2, 0x63, 0x61, 0xfd, 0xb9, 0x6f, 0x7f, 0xb4, 0x7a, + 0x29, 0xe2, 0xc1, 0x30, 0xd9, 0x83, 0x18, 0x91, 0x5b, 0x01, 0x4d, 0x81, 0xe3, 0x79, 0x91, 0xcb, + 0xf1, 0x4a, 0x9a, 0x88, 0x8d, 0xfa, 0x62, 0x25, 0xc5, 0xeb, 0x67, 0x13, 0x08, 0x67, 0x44, 0xc9, + 0x36, 0xe4, 0xdb, 0x7d, 0x26, 0x35, 0x45, 0x4d, 0x98, 0x69, 0x91, 0x95, 0x1b, 0x19, 0x34, 0xfa, + 0xcf, 0xb3, 0x85, 0x9d, 0x29, 0x1d, 0xc8, 0x43, 0x58, 0xac, 0xb3, 0x0d, 0x53, 0x74, 0x36, 0x97, + 0x2b, 0xd7, 0x0e, 0x1f, 0xe8, 0x5a, 0xc8, 0x5f, 0x2a, 0x9a, 0xc3, 0xfd, 0x7d, 0xdb, 0x3f, 0x58, + 0x3f, 0x2d, 0xf2, 0x77, 0x8b, 0x1d, 0x3a, 0x1a, 0x7b, 0x23, 0xf9, 0x15, 0xfd, 0xcf, 0x72, 0xf0, + 0xe2, 0x63, 0xf0, 0x26, 0x2d, 0x98, 0x41, 0x0b, 0x07, 0xea, 0xc6, 0x87, 0x5b, 0x48, 0x4e, 0xf2, + 0xbd, 0x91, 0x07, 0x00, 0x8b, 0x34, 0xe3, 0x98, 0x09, 0xb9, 0x49, 0xa7, 0x53, 0x17, 0xf9, 0x1d, + 0x6e, 0x2d, 0x59, 0x16, 0x86, 0x43, 0xa7, 0xdf, 0x8d, 0xb9, 0x09, 0x06, 0x52, 0x6c, 0xd3, 0xfc, + 0xc8, 0xd8, 0xa6, 0xaf, 0xc3, 0x5c, 0x55, 0x72, 0x48, 0xe6, 0xc3, 0x8f, 0xf7, 0x28, 0x8a, 0xf7, + 0xb2, 0xa1, 0xa0, 0x91, 0x4f, 0xc3, 0x02, 0xf3, 0xa7, 0xe0, 0xbd, 0xc3, 0xbc, 0xfd, 0x26, 0x79, + 0xc6, 0x26, 0x2c, 0x11, 0x5d, 0x1d, 0x18, 0x09, 0x54, 0xba, 0xb0, 0x4e, 0x52, 0xad, 0xa4, 0xe7, + 0x04, 0x41, 0x69, 0x18, 0xee, 0xd1, 0x5d, 0x87, 0x59, 0x2e, 0xc8, 0x9b, 0x30, 0xb5, 0xf7, 0x78, + 0xf7, 0x95, 0x0c, 0x9d, 0x10, 0xc0, 0xb3, 0x8f, 0x78, 0x20, 0x4f, 0xff, 0x26, 0x6f, 0xc1, 0x24, + 0x9a, 0xdd, 0xf9, 0x11, 0x43, 0x98, 0x86, 0xb2, 0x3f, 0x8d, 0x46, 0x79, 0x83, 0x11, 0xd0, 0xd5, + 0x1a, 0x27, 0x26, 0xe7, 0xfb, 0xb1, 0x30, 0x47, 0x47, 0xb9, 0xc9, 0x8d, 0x99, 0xfd, 0x1d, 0x9b, + 0x67, 0xfb, 0x5e, 0x83, 0x25, 0x21, 0x7b, 0x07, 0x22, 0xfb, 0x11, 0xf7, 0x44, 0x5a, 0xe4, 0x61, + 0xe2, 0x06, 0x22, 0x03, 0xd2, 0xb3, 0xb0, 0x10, 0x04, 0x7b, 0x16, 0x0f, 0x4e, 0x7a, 0x5f, 0x24, + 0x56, 0x34, 0xe6, 0x82, 0x60, 0x8f, 0x45, 0x29, 0xbd, 0xe5, 0x1c, 0x50, 0x2c, 0x7c, 0xfb, 0x11, + 0x63, 0x15, 0x18, 0x56, 0xd8, 0x0b, 0x22, 0x2c, 0x1e, 0x57, 0x17, 0x62, 0x2c, 0xfd, 0xb3, 0x30, + 0x6b, 0x76, 0xbc, 0x81, 0x78, 0x6d, 0xb6, 0x0c, 0x93, 0x01, 0xfd, 0x29, 0x2e, 0xd1, 0xf1, 0x07, + 0x79, 0x09, 0x26, 0xf6, 0x63, 0x3f, 0x95, 0x95, 0x2b, 0x22, 0x41, 0xc6, 0x15, 0x2c, 0x0e, 0xae, + 0x3c, 0xb8, 0x7a, 0x65, 0x0b, 0x3d, 0xd9, 0x29, 0x96, 0xfe, 0xaf, 0xf2, 0x30, 0x73, 0xc7, 0x0e, + 0x3b, 0x7b, 0x68, 0xf7, 0x1e, 0x6f, 0x47, 0xbf, 0x06, 0xb3, 0x75, 0xcf, 0xe6, 0x7e, 0x10, 0x3c, + 0xac, 0x10, 0x7b, 0x7f, 0xd6, 0xf3, 0x6c, 0xe1, 0x52, 0x11, 0x18, 0x32, 0xd2, 0x21, 0xb1, 0x6a, + 0x6f, 0xc2, 0x14, 0x6b, 0x0b, 0xbf, 0xd2, 0x11, 0x36, 0xa2, 0xa8, 0x46, 0x57, 0x58, 0xb1, 0x74, + 0x51, 0xcf, 0x04, 0x8f, 0x6c, 0xb0, 0xe0, 0xbd, 0x21, 0x59, 0xf9, 0x27, 0x8f, 0x66, 0xe5, 0x97, + 0x12, 0xe3, 0x4c, 0x1d, 0x29, 0x31, 0x4e, 0x4b, 0xe9, 0x7f, 0xee, 0x1a, 0x4f, 0x22, 0xab, 0x64, + 0x54, 0xc2, 0x36, 0x57, 0xec, 0x6f, 0x2b, 0x55, 0x5f, 0x99, 0xc5, 0x99, 0xeb, 0x30, 0x2b, 0xb5, + 0xf0, 0xb1, 0x8c, 0x50, 0x5f, 0xce, 0xc1, 0x3c, 0xf6, 0x53, 0x24, 0x5a, 0xbf, 0x37, 0x6f, 0x41, + 0x3e, 0xa5, 0xdc, 0x82, 0xac, 0xc8, 0x33, 0x80, 0xfb, 0x6a, 0x8d, 0xbe, 0xfe, 0xb8, 0x09, 0x4b, + 0x29, 0x44, 0xf2, 0x3a, 0x4c, 0xd2, 0xea, 0x0b, 0xab, 0x71, 0x31, 0x39, 0xa7, 0xe2, 0xb4, 0x8c, + 0xb4, 0xe1, 0x81, 0xc1, 0xb0, 0xf5, 0xff, 0xa9, 0xc1, 0x1c, 0x4f, 0xd6, 0xdf, 0xdf, 0xf1, 0x0e, + 0xed, 0xce, 0xcb, 0xc9, 0xee, 0x64, 0xb1, 0xdc, 0x79, 0x77, 0x7e, 0xb7, 0x3b, 0xf1, 0xba, 0xd2, + 0x89, 0xa7, 0xa2, 0x24, 0x4d, 0xa2, 0x39, 0x63, 0xfa, 0xf0, 0x1b, 0x98, 0xb6, 0x50, 0x45, 0x24, + 0x3f, 0x04, 0x33, 0x0d, 0xe7, 0xa1, 0x62, 0x7c, 0xbd, 0x3c, 0x82, 0xe9, 0x95, 0x08, 0x91, 0xad, + 0x52, 0xf6, 0xaa, 0xd4, 0x79, 0x68, 0xa5, 0x5c, 0x6a, 0x62, 0x96, 0x67, 0xde, 0x86, 0x05, 0x95, + 0xec, 0x71, 0xa6, 0x3e, 0x0f, 0xed, 0x88, 0xe9, 0x09, 0xbe, 0x9e, 0x07, 0x88, 0xa3, 0xe2, 0xd1, + 0x25, 0xad, 0x78, 0xf1, 0x89, 0x7b, 0x6b, 0x04, 0xc9, 0x73, 0x5c, 0x38, 0xf7, 0x5d, 0xe6, 0xf7, + 0xab, 0xb9, 0xd1, 0x49, 0xb4, 0xf0, 0xa6, 0xf5, 0x36, 0x8f, 0x70, 0xd6, 0x75, 0x7a, 0xf6, 0x01, + 0x8f, 0xd5, 0xf0, 0x26, 0xe6, 0x4c, 0x8c, 0xa0, 0x4a, 0xc2, 0xa2, 0xb3, 0x95, 0xa1, 0x50, 0x7c, + 0x7c, 0xb7, 0xbf, 0xbb, 0xe1, 0xf9, 0x52, 0xec, 0x3e, 0x7c, 0xcf, 0x50, 0xa1, 0x34, 0xa9, 0xe0, + 0x93, 0x13, 0x8f, 0x17, 0x7c, 0xb2, 0x05, 0x33, 0x6e, 0xff, 0x81, 0xd3, 0x0f, 0x3d, 0xff, 0x00, + 0xef, 0x99, 0xe3, 0x0b, 0x2c, 0xfa, 0xbd, 0x9a, 0x28, 0x63, 0x43, 0x83, 0x1a, 0x72, 0x84, 0x2f, + 0x8f, 0x4c, 0x04, 0x8c, 0x7c, 0xb0, 0x26, 0x8b, 0x53, 0x2c, 0x66, 0xdd, 0xcd, 0x89, 0x42, 0xa1, + 0x38, 0x73, 0x73, 0xa2, 0x30, 0x53, 0x04, 0x43, 0x72, 0x1b, 0x89, 0xdc, 0x42, 0x24, 0x1f, 0x0e, + 0xd5, 0x3f, 0x43, 0xff, 0xe3, 0x3c, 0x90, 0x74, 0x35, 0xc8, 0xa7, 0x60, 0x96, 0x49, 0x45, 0xcb, + 0x0f, 0xbe, 0xc8, 0x1f, 0xda, 0xb2, 0xd7, 0xf1, 0x12, 0x58, 0xce, 0xfb, 0xc0, 0xc0, 0x46, 0xf0, + 0xc5, 0x1e, 0x19, 0xc0, 0x71, 0xec, 0x71, 0x1e, 0x22, 0x03, 0xb3, 0xfa, 0xd9, 0x3d, 0x1e, 0x66, + 0xe3, 0x33, 0xdf, 0xfe, 0x68, 0xf5, 0x7c, 0x46, 0xf1, 0xd1, 0x47, 0x06, 0xa3, 0xcf, 0xb5, 0x90, + 0xb8, 0xc5, 0x68, 0xc9, 0x2e, 0x14, 0x65, 0x96, 0x18, 0xaa, 0x87, 0x8d, 0xff, 0x3b, 0xf4, 0x94, + 0x93, 0x2c, 0x3b, 0xfa, 0xb7, 0x16, 0xe2, 0x6f, 0x6d, 0x0c, 0x7b, 0x3d, 0xf2, 0x26, 0x80, 0xd7, + 0xb7, 0xf6, 0x5d, 0x8c, 0x11, 0x26, 0x3f, 0xd1, 0x8e, 0xa1, 0xf2, 0x90, 0x79, 0xfd, 0x2d, 0x06, + 0x24, 0x9f, 0x04, 0x0c, 0x2a, 0x8d, 0xd1, 0xd6, 0xb9, 0xae, 0xc6, 0xce, 0x42, 0x02, 0xa8, 0xc6, + 0x13, 0xdd, 0x75, 0x30, 0x12, 0xfb, 0xa7, 0x60, 0x96, 0xe9, 0x40, 0x2c, 0xb0, 0xe6, 0x54, 0x3c, + 0x0a, 0x12, 0x58, 0x1e, 0x05, 0x06, 0x96, 0x82, 0x6e, 0x7e, 0x0e, 0x96, 0xb8, 0xce, 0x78, 0xc7, + 0x0d, 0xf7, 0xb8, 0x2d, 0xea, 0x49, 0x0c, 0x59, 0x92, 0x21, 0xe9, 0xb7, 0x26, 0x01, 0x4a, 0x77, + 0x4c, 0x91, 0x04, 0xe5, 0x05, 0x98, 0xa4, 0x1f, 0x16, 0x77, 0x17, 0xa8, 0x8a, 0x22, 0x5f, 0xf9, + 0xe6, 0x17, 0x31, 0xa8, 0x08, 0x30, 0xf0, 0xc1, 0xaa, 0xb8, 0xb7, 0x40, 0x11, 0xc0, 0xde, 0xb0, + 0x2a, 0x59, 0x2b, 0x39, 0x16, 0xa9, 0x03, 0xc4, 0x69, 0x49, 0xb8, 0xbd, 0x67, 0x29, 0x8e, 0xef, + 0xcf, 0x0b, 0x78, 0x36, 0xee, 0x38, 0x2a, 0x81, 0xdc, 0x35, 0x31, 0x1a, 0xb9, 0x05, 0x13, 0x6d, + 0x3b, 0x8a, 0x3a, 0x39, 0x22, 0x59, 0xcb, 0x45, 0xda, 0x7a, 0x25, 0x61, 0xcb, 0x42, 0x68, 0xef, + 0xca, 0xb5, 0x43, 0x26, 0xa4, 0x0a, 0x53, 0x2d, 0xdb, 0xb7, 0xf7, 0x83, 0x51, 0x49, 0xbe, 0x58, + 0xa9, 0xc8, 0x05, 0x8a, 0x40, 0x59, 0x35, 0x62, 0xc5, 0xe4, 0x1a, 0xe4, 0x4d, 0x73, 0x8b, 0x3f, + 0xa5, 0x99, 0x8f, 0xcf, 0x59, 0xa6, 0xb9, 0xc5, 0x8e, 0x03, 0x41, 0xb0, 0x2f, 0x91, 0x51, 0x64, + 0xf2, 0x69, 0x98, 0x95, 0x8e, 0x6f, 0x3c, 0xb8, 0x3f, 0xf6, 0x81, 0x14, 0x12, 0x41, 0x16, 0x4b, + 0x12, 0x36, 0xa9, 0x43, 0xf1, 0xd6, 0xf0, 0x9e, 0x53, 0x1a, 0x0c, 0x30, 0xc4, 0xdb, 0x03, 0xc7, + 0x67, 0x2a, 0x6e, 0x21, 0x4e, 0xa3, 0x89, 0xef, 0x8f, 0xbb, 0xa2, 0x54, 0x36, 0x14, 0x25, 0x29, + 0x49, 0x0b, 0x96, 0x4c, 0x27, 0x1c, 0x0e, 0x98, 0xe7, 0xeb, 0x06, 0x33, 0x34, 0xb0, 0x54, 0x00, + 0x98, 0x71, 0x30, 0xa0, 0x85, 0xc2, 0xe9, 0x78, 0x27, 0x65, 0x6c, 0x48, 0x13, 0x93, 0x2f, 0x24, + 0x4c, 0x22, 0x90, 0xb4, 0xca, 0xc9, 0xa5, 0x22, 0xdf, 0xcf, 0xd1, 0x2d, 0x26, 0xff, 0x85, 0x59, + 0x4c, 0x32, 0x98, 0x90, 0x2a, 0x2c, 0xc8, 0xe0, 0xc8, 0x2e, 0x84, 0x31, 0x2f, 0x94, 0x40, 0xd3, + 0x8a, 0xa1, 0x27, 0x41, 0x44, 0x1e, 0xc1, 0x71, 0x19, 0x62, 0xf7, 0xb6, 0xfb, 0x6e, 0x28, 0x1e, + 0xa6, 0xe9, 0xd9, 0xed, 0x40, 0x14, 0xd1, 0x18, 0xec, 0x38, 0x4f, 0x61, 0x61, 0x0d, 0x29, 0x82, + 0xf4, 0xd1, 0xac, 0x4f, 0xe8, 0x3f, 0x8a, 0x41, 0x3d, 0x46, 0xf1, 0x65, 0x69, 0x6a, 0x31, 0x28, + 0x84, 0x7c, 0xcd, 0xc8, 0x43, 0x46, 0xa8, 0x69, 0x6a, 0x11, 0x44, 0x09, 0xaa, 0x2c, 0x8a, 0x84, + 0xbc, 0x5c, 0x79, 0x60, 0x09, 0x99, 0x80, 0x63, 0xe9, 0x5f, 0xd3, 0xe4, 0xf5, 0x2a, 0x9b, 0x37, + 0xb4, 0x71, 0xe6, 0x8d, 0x57, 0x32, 0x72, 0x0d, 0xa1, 0x91, 0x46, 0xca, 0x35, 0x24, 0x67, 0x18, + 0x22, 0x2f, 0x40, 0x81, 0xd2, 0x4a, 0xa7, 0x17, 0x96, 0xf0, 0xc9, 0xe3, 0x26, 0x10, 0x23, 0x2a, + 0xd6, 0xff, 0x6a, 0x52, 0xca, 0x8c, 0xc7, 0xd7, 0xdc, 0x3b, 0x00, 0x37, 0x3d, 0xb7, 0xbf, 0xe5, + 0x84, 0x7b, 0x5e, 0x57, 0x1a, 0xe4, 0xd9, 0x0f, 0x3c, 0xb7, 0x6f, 0xed, 0x23, 0xf8, 0x2f, 0x3f, + 0x5a, 0x95, 0x90, 0x0c, 0xe9, 0x6f, 0xf2, 0x12, 0xcc, 0xd0, 0x5f, 0xed, 0xd8, 0x4f, 0x9b, 0x5d, + 0x67, 0x23, 0x35, 0x4b, 0x62, 0x1f, 0x23, 0x90, 0xeb, 0x00, 0x2c, 0x47, 0x9f, 0x54, 0x5b, 0x5c, + 0xab, 0xcc, 0x2d, 0x2f, 0x19, 0xd5, 0x44, 0x42, 0x26, 0x9b, 0x51, 0xd5, 0xdb, 0xfc, 0xa0, 0xc8, + 0x72, 0xe8, 0x88, 0xd0, 0x28, 0x58, 0x64, 0x89, 0x43, 0xa4, 0x1c, 0x1a, 0x25, 0x41, 0x86, 0x95, + 0x30, 0x37, 0x2b, 0xdc, 0x7a, 0x37, 0x29, 0x55, 0x22, 0xd8, 0xeb, 0x72, 0x5b, 0x9c, 0x52, 0x89, + 0x08, 0x99, 0xac, 0xc3, 0x22, 0x3b, 0xf7, 0xb6, 0x7c, 0xef, 0xd1, 0x01, 0x3d, 0x4c, 0xf3, 0xfd, + 0x48, 0x18, 0x9e, 0x1e, 0x1d, 0xe0, 0xc9, 0x5b, 0xfe, 0x7c, 0x82, 0x80, 0x6c, 0xc0, 0x24, 0x5a, + 0x62, 0xf9, 0xb1, 0xec, 0xac, 0x7c, 0x17, 0x90, 0x94, 0x97, 0xb8, 0x7f, 0xe0, 0x2d, 0x80, 0xbc, + 0x7f, 0x20, 0x2a, 0xf9, 0x1c, 0x40, 0xb5, 0xef, 0x7b, 0xbd, 0x1e, 0x26, 0x0e, 0x2d, 0x28, 0x11, + 0x94, 0x38, 0x1f, 0xe4, 0x12, 0x23, 0x61, 0xf6, 0xdc, 0x13, 0x0e, 0xfe, 0xb6, 0xd4, 0xf4, 0xa2, + 0x2b, 0x9a, 0x21, 0x71, 0x23, 0x2f, 0xc1, 0x94, 0x39, 0xdc, 0xd9, 0x71, 0x1f, 0x71, 0xf1, 0xc5, + 0xb2, 0x66, 0x22, 0x44, 0x16, 0xdb, 0x0c, 0x87, 0xbc, 0x0d, 0xb3, 0xdb, 0x83, 0xae, 0x1d, 0x3a, + 0x78, 0xa3, 0xcd, 0x53, 0x31, 0xa3, 0x14, 0x1a, 0x22, 0x98, 0x3d, 0x8a, 0x91, 0x65, 0xb0, 0x84, + 0x4e, 0x5c, 0x58, 0xda, 0x6c, 0xb7, 0x5b, 0xd8, 0x41, 0x22, 0xf0, 0x02, 0x0f, 0x51, 0x24, 0x0e, + 0x59, 0xa9, 0xf2, 0xf5, 0x4b, 0x54, 0x83, 0xda, 0x0b, 0xc3, 0x81, 0xc5, 0x3a, 0x5d, 0x04, 0xd1, + 0x93, 0xc5, 0x69, 0x8a, 0x4a, 0xff, 0x4d, 0x2d, 0xe3, 0x5b, 0xe4, 0x0d, 0x98, 0x89, 0x80, 0x72, + 0x96, 0xf1, 0x98, 0xbd, 0xac, 0xce, 0x44, 0xa8, 0x74, 0x1e, 0xd1, 0x1f, 0x26, 0x23, 0x94, 0xb2, + 0x82, 0x51, 0xc2, 0x20, 0x45, 0x29, 0x21, 0xd3, 0x33, 0x6e, 0xc3, 0x63, 0x74, 0x92, 0x05, 0xbf, + 0xef, 0xa5, 0x88, 0x04, 0x9a, 0x5e, 0x83, 0x29, 0xb6, 0x11, 0x62, 0x62, 0x64, 0xaf, 0x33, 0xdc, + 0x77, 0xfa, 0xa1, 0x94, 0x56, 0x97, 0x25, 0x46, 0xe6, 0xf0, 0x74, 0x62, 0x64, 0x89, 0x40, 0xbf, + 0x05, 0xcb, 0x59, 0x93, 0x4d, 0xb1, 0xe7, 0x6b, 0x47, 0xb5, 0xe7, 0xff, 0x71, 0x1e, 0xe6, 0x90, + 0x9b, 0x10, 0xac, 0x25, 0x98, 0x37, 0x87, 0xf7, 0xa2, 0x24, 0x40, 0x42, 0x13, 0xc2, 0xfa, 0x05, + 0x72, 0x81, 0xec, 0x92, 0xa6, 0x50, 0xd0, 0xbd, 0x47, 0x68, 0x61, 0x37, 0xc4, 0x83, 0xeb, 0x28, + 0x27, 0xb1, 0x78, 0x88, 0xce, 0x5f, 0x5a, 0xc9, 0x7b, 0x8f, 0x4a, 0x14, 0xeb, 0x62, 0xf9, 0xc7, + 0xd1, 0xc5, 0x26, 0x8e, 0xa4, 0x8b, 0xbd, 0x0f, 0x73, 0xe2, 0x6b, 0xa8, 0x45, 0x4d, 0x3e, 0x99, + 0x16, 0xa5, 0x30, 0x23, 0xf5, 0x48, 0x9b, 0x9a, 0x1a, 0xab, 0x4d, 0xa1, 0x9f, 0x9f, 0x90, 0x7c, + 0x03, 0x84, 0x65, 0x28, 0x55, 0x4f, 0xa2, 0x20, 0xe9, 0x7f, 0x9a, 0x07, 0xb8, 0x51, 0x6e, 0x7d, + 0x07, 0xea, 0xed, 0xeb, 0x30, 0x53, 0xf7, 0x84, 0x7f, 0x98, 0xe4, 0x98, 0xd3, 0x13, 0x40, 0x79, + 0x51, 0x45, 0x98, 0x91, 0x5a, 0x9a, 0x7f, 0x1a, 0x6a, 0xe9, 0x75, 0xbc, 0xf0, 0xf8, 0xc0, 0xe9, + 0x84, 0xb5, 0x8a, 0x18, 0x59, 0x6c, 0xb9, 0x88, 0xe4, 0xaf, 0xfa, 0x07, 0x49, 0xc8, 0x74, 0xbb, + 0xe1, 0xae, 0xe7, 0x22, 0x70, 0x1b, 0xbf, 0x62, 0xc0, 0xed, 0x46, 0x44, 0xbf, 0x13, 0xb1, 0xe0, + 0x64, 0x79, 0x9f, 0x20, 0x7b, 0xca, 0xa3, 0xf9, 0xd9, 0xe8, 0x0d, 0xd1, 0xf4, 0xb8, 0x1e, 0xd2, + 0x53, 0x3d, 0x34, 0xf2, 0xe5, 0x90, 0xfe, 0xbb, 0x9a, 0x9c, 0x4d, 0xfe, 0x3b, 0x18, 0xea, 0xb7, + 0x00, 0x22, 0x07, 0x5d, 0x31, 0xd6, 0x51, 0xcc, 0x2e, 0x06, 0x95, 0x7b, 0x39, 0xc6, 0x95, 0x5a, + 0x93, 0x7f, 0x5a, 0xad, 0x69, 0xc3, 0x6c, 0xf3, 0x7e, 0x68, 0xc7, 0x1e, 0xdd, 0x60, 0x46, 0xc7, + 0x57, 0x1e, 0x0a, 0xf3, 0x39, 0xdc, 0xec, 0xe3, 0xf3, 0xb0, 0x72, 0x14, 0x2e, 0x88, 0xa3, 0xb0, + 0x21, 0x11, 0xea, 0x7f, 0xa5, 0xc1, 0xa2, 0x1c, 0xc7, 0xf5, 0xa0, 0xdf, 0x21, 0xef, 0xb2, 0x5c, + 0x95, 0x9a, 0x62, 0xcd, 0x90, 0x90, 0xa8, 0xbc, 0x3e, 0xe8, 0x77, 0xd8, 0xc9, 0xc5, 0x7e, 0x28, + 0x57, 0x96, 0x12, 0x92, 0x7b, 0x30, 0xd7, 0xf2, 0x7a, 0x3d, 0x11, 0x76, 0x37, 0x4a, 0x7a, 0x99, + 0xbc, 0x6b, 0x11, 0x15, 0x5a, 0x7f, 0x86, 0x5b, 0xc5, 0x4e, 0x0d, 0xe8, 0x06, 0x2e, 0x42, 0x01, + 0xc7, 0x6c, 0x3f, 0xc4, 0x78, 0x2c, 0x32, 0xcf, 0x58, 0xd9, 0x50, 0xb3, 0xa2, 0xcb, 0xb5, 0xa4, + 0xc5, 0x58, 0xcf, 0x31, 0xca, 0x86, 0xfe, 0xb7, 0x35, 0xb8, 0x98, 0x6e, 0x5a, 0xb9, 0xe7, 0x0d, + 0xbb, 0x6d, 0xdf, 0x76, 0x7b, 0x75, 0x6f, 0x37, 0x60, 0x39, 0xfe, 0x76, 0xe3, 0xab, 0x5b, 0x9e, + 0xe3, 0x6f, 0xd7, 0x4d, 0xe6, 0xf8, 0xc3, 0x30, 0x4d, 0xaf, 0x41, 0xc1, 0xfc, 0xac, 0xf9, 0xd9, + 0xa1, 0x23, 0x2c, 0x67, 0x4c, 0x3e, 0x04, 0x5f, 0x0c, 0xac, 0x2f, 0x52, 0xa0, 0xbc, 0xdd, 0x08, + 0x44, 0x7d, 0x1f, 0x2e, 0xa4, 0xab, 0x51, 0xbd, 0x65, 0x96, 0x86, 0x5d, 0x37, 0xc4, 0x4a, 0x08, + 0x01, 0xa2, 0x3d, 0x05, 0x01, 0xa2, 0xff, 0xf3, 0x3c, 0x90, 0xf4, 0xf7, 0xe4, 0xed, 0x42, 0xfb, + 0x6b, 0x38, 0xba, 0x27, 0x24, 0xfa, 0xc4, 0x63, 0x1d, 0x79, 0xbf, 0x08, 0xc5, 0x0e, 0x1d, 0x36, + 0x2b, 0xa4, 0xe3, 0x66, 0xf5, 0xbc, 0x68, 0xf7, 0xfa, 0xc4, 0xc8, 0x29, 0xac, 0x8e, 0x33, 0x13, + 0x81, 0x49, 0x26, 0xf2, 0x46, 0xdc, 0x51, 0xe7, 0x85, 0x0b, 0x0b, 0xce, 0xfd, 0xc0, 0xb2, 0xe9, + 0x18, 0xb1, 0x0f, 0x4e, 0x29, 0x1e, 0xf6, 0xe3, 0x47, 0x94, 0x09, 0x46, 0x95, 0x81, 0xbc, 0x75, + 0x3a, 0xf7, 0x83, 0x08, 0x57, 0xff, 0xba, 0x06, 0xcb, 0x59, 0x93, 0x9b, 0xea, 0x14, 0xb2, 0x92, + 0xa1, 0x9e, 0x67, 0x65, 0xbd, 0x24, 0x71, 0x9e, 0x55, 0x89, 0x92, 0x5d, 0x9f, 0x7b, 0xac, 0xcd, + 0xf4, 0xcf, 0xf2, 0x30, 0xc7, 0xdc, 0xc2, 0x36, 0x1d, 0xbb, 0x17, 0xee, 0xd1, 0x79, 0xc4, 0x2f, + 0xe4, 0xe4, 0xd7, 0x4b, 0xfc, 0xea, 0x4e, 0xc9, 0xa2, 0xc9, 0xaf, 0xed, 0xae, 0x41, 0xa1, 0x45, + 0xc5, 0x42, 0xc7, 0xeb, 0xc9, 0x97, 0x23, 0x03, 0x0e, 0x93, 0x97, 0x8c, 0xc0, 0x43, 0x5d, 0x5e, + 0xbe, 0x81, 0x65, 0xba, 0x3c, 0x42, 0x14, 0x5d, 0x9e, 0xdd, 0xc5, 0x3e, 0x82, 0xe3, 0xb1, 0xab, + 0x61, 0x74, 0xcb, 0x7b, 0x84, 0x87, 0xda, 0x6b, 0xfc, 0x8a, 0xfb, 0x42, 0xec, 0xbd, 0x88, 0xd7, + 0xc1, 0x58, 0x9a, 0x70, 0x45, 0xcb, 0xfa, 0x04, 0xb9, 0x05, 0xc5, 0x18, 0xcc, 0x73, 0x85, 0xb2, + 0xc3, 0x19, 0x86, 0x6a, 0x95, 0xd8, 0xa6, 0xd2, 0x86, 0xa6, 0x08, 0xe9, 0xf6, 0x1d, 0xc3, 0xaa, + 0x71, 0x4c, 0x06, 0xe1, 0xf1, 0x11, 0xf1, 0xc2, 0x0b, 0x66, 0x79, 0xfb, 0x4e, 0x90, 0xd1, 0x31, + 0x12, 0xf7, 0xd2, 0xd3, 0xf1, 0x18, 0xf1, 0x1b, 0x69, 0x79, 0x8c, 0x38, 0xd6, 0xda, 0xd7, 0x34, + 0x58, 0xac, 0x95, 0xb6, 0x58, 0x68, 0x3c, 0xde, 0xab, 0x97, 0xe0, 0x7c, 0xad, 0xb4, 0x65, 0xb5, + 0x9a, 0xf5, 0x5a, 0xf9, 0xae, 0x95, 0x99, 0xa5, 0xfa, 0x3c, 0x9c, 0x4e, 0xa3, 0xc4, 0x5e, 0x99, + 0xe7, 0x60, 0x25, 0x5d, 0x2c, 0x32, 0x59, 0x67, 0x13, 0x8b, 0xa4, 0xd7, 0xf9, 0xb5, 0xf7, 0x60, + 0x51, 0x64, 0x6d, 0x6e, 0xd7, 0x4d, 0x3c, 0xe1, 0x2d, 0xc2, 0xec, 0xed, 0xaa, 0x51, 0xdb, 0xb8, + 0x6b, 0x6d, 0x6c, 0xd7, 0xeb, 0xc5, 0x63, 0x64, 0x1e, 0x66, 0x38, 0xa0, 0x5c, 0x2a, 0x6a, 0x64, + 0x0e, 0x0a, 0xb5, 0x86, 0x59, 0x2d, 0x6f, 0x1b, 0xd5, 0x62, 0x6e, 0xed, 0x1f, 0x68, 0x30, 0xcf, + 0xce, 0x6c, 0x3e, 0x6f, 0xd1, 0x05, 0x38, 0xb3, 0xdd, 0xaa, 0x94, 0xda, 0x55, 0x23, 0xbb, 0x39, + 0x27, 0x60, 0x29, 0x51, 0xde, 0xbc, 0x55, 0xd4, 0xc8, 0x59, 0x38, 0x95, 0x00, 0x57, 0x6a, 0x66, + 0x69, 0x9d, 0xb5, 0xe2, 0x34, 0x9c, 0x48, 0x14, 0xb6, 0x6a, 0x8d, 0x46, 0xb5, 0x52, 0xcc, 0xd3, + 0x06, 0xa6, 0x3e, 0x67, 0x54, 0x4b, 0x15, 0x4a, 0x5a, 0x9c, 0x58, 0x7b, 0x0f, 0x16, 0x5a, 0xd1, + 0x13, 0x54, 0xf4, 0xdc, 0x9c, 0x86, 0xbc, 0x51, 0xba, 0x53, 0x3c, 0x46, 0x00, 0xa6, 0x5a, 0xb7, + 0xca, 0xe6, 0xd5, 0xab, 0x45, 0x8d, 0xcc, 0xc2, 0xf4, 0x8d, 0x72, 0xcb, 0xba, 0xb5, 0x65, 0x16, + 0x73, 0xf4, 0x47, 0xe9, 0x8e, 0x89, 0x3f, 0xf2, 0x6b, 0xaf, 0xa2, 0x63, 0xcf, 0xa3, 0x83, 0xba, + 0x1b, 0x84, 0x4e, 0xdf, 0xf1, 0xb1, 0x8f, 0xe6, 0xa0, 0x60, 0x3a, 0x54, 0x13, 0x0b, 0x1d, 0xd6, + 0x41, 0x5b, 0xc3, 0x5e, 0xe8, 0x0e, 0x7a, 0xce, 0xa3, 0xa2, 0xb6, 0x76, 0x1d, 0x16, 0x0d, 0x6f, + 0x48, 0x4f, 0x90, 0x66, 0x48, 0x31, 0x76, 0x0f, 0xb0, 0xcd, 0x8d, 0xd2, 0xd6, 0x7a, 0xed, 0xc6, + 0x76, 0x73, 0xdb, 0xb4, 0xb6, 0x4a, 0xed, 0xf2, 0x26, 0xf3, 0x1b, 0xdd, 0x6a, 0x9a, 0x6d, 0xcb, + 0xa8, 0x96, 0xab, 0x8d, 0x76, 0x51, 0x5b, 0xfb, 0x19, 0xbc, 0xc9, 0xea, 0x78, 0xfd, 0xee, 0x86, + 0xdd, 0x09, 0x3d, 0x1f, 0x2b, 0xac, 0xc3, 0x05, 0xb3, 0x5a, 0x6e, 0x36, 0x2a, 0xd6, 0x46, 0xa9, + 0xdc, 0x6e, 0x1a, 0x59, 0x69, 0xdc, 0xcf, 0xc0, 0xc9, 0x0c, 0x9c, 0x66, 0xbb, 0x55, 0xd4, 0xc8, + 0x2a, 0x9c, 0xcd, 0x28, 0xbb, 0x53, 0x5d, 0x2f, 0x6d, 0xb7, 0x37, 0x1b, 0xc5, 0xdc, 0x08, 0x62, + 0xd3, 0x6c, 0x16, 0xf3, 0x6b, 0x7f, 0x57, 0x83, 0x85, 0xed, 0x80, 0xbf, 0x7f, 0xdf, 0x46, 0xef, + 0x8a, 0x8b, 0x70, 0x6e, 0xdb, 0xac, 0x1a, 0x56, 0xbb, 0x79, 0xab, 0xda, 0xb0, 0xb6, 0xcd, 0xd2, + 0x8d, 0x64, 0x6d, 0x56, 0xe1, 0xac, 0x84, 0x61, 0x54, 0xcb, 0xcd, 0xdb, 0x55, 0xc3, 0x6a, 0x95, + 0x4c, 0xf3, 0x4e, 0xd3, 0xa8, 0x14, 0x35, 0xfa, 0xc5, 0x0c, 0x84, 0xad, 0x8d, 0x12, 0xab, 0x8d, + 0x52, 0xd6, 0xa8, 0xde, 0x29, 0xd5, 0xad, 0xf5, 0x66, 0xbb, 0x98, 0x5f, 0xdb, 0xa2, 0x87, 0x2b, + 0x4c, 0xa6, 0xcc, 0x5e, 0x29, 0x16, 0x60, 0xa2, 0xd1, 0x6c, 0x54, 0x93, 0x8e, 0xca, 0x73, 0x50, + 0x28, 0xb5, 0x5a, 0x46, 0xf3, 0x36, 0x4e, 0x1e, 0x80, 0xa9, 0x4a, 0xb5, 0x51, 0xc3, 0xd9, 0x32, + 0x07, 0x85, 0x96, 0xd1, 0xdc, 0x6a, 0xb6, 0xab, 0x95, 0xe2, 0xc4, 0x5a, 0x09, 0x96, 0xd8, 0x96, + 0xc0, 0x99, 0xe2, 0x75, 0xe7, 0x3c, 0xcc, 0x6c, 0x37, 0x2a, 0xd5, 0x8d, 0x5a, 0x03, 0xdb, 0xb2, + 0x00, 0x60, 0x6e, 0x36, 0x8d, 0xb6, 0xd5, 0xae, 0x1a, 0x5b, 0x2c, 0x3b, 0x7e, 0xbd, 0xd9, 0xb8, + 0xc1, 0x7e, 0xe6, 0xd6, 0x0c, 0xa1, 0x06, 0x88, 0x7a, 0xa1, 0x07, 0xc3, 0x2c, 0x4c, 0x57, 0xaa, + 0x1b, 0xa5, 0xed, 0x7a, 0x9b, 0x8f, 0xf2, 0x5d, 0xcb, 0xa8, 0x7e, 0x76, 0xbb, 0x6a, 0xb6, 0xcd, + 0xa2, 0x46, 0x8a, 0x30, 0xd7, 0xa8, 0x56, 0x2b, 0xa6, 0x65, 0x54, 0x6f, 0xd7, 0xaa, 0x77, 0x8a, + 0x39, 0x5a, 0x2d, 0xf6, 0x37, 0xad, 0xe4, 0xda, 0xaf, 0x68, 0x40, 0x58, 0x2e, 0xeb, 0x4d, 0x2f, + 0x08, 0x69, 0xef, 0xe3, 0xa4, 0xbb, 0x00, 0x67, 0x36, 0xe9, 0x6c, 0xc1, 0xde, 0xd9, 0x6a, 0x56, + 0x92, 0xbd, 0x7e, 0x12, 0x48, 0xa2, 0xbc, 0xb9, 0xb1, 0x81, 0x2b, 0xeb, 0x78, 0x02, 0x5e, 0x31, + 0x9a, 0xad, 0x62, 0xee, 0x4c, 0xae, 0xa0, 0x91, 0x53, 0xa9, 0xc2, 0x5b, 0xd5, 0x6a, 0xab, 0x98, + 0xa7, 0xa3, 0x9c, 0x28, 0x10, 0xab, 0x9e, 0x91, 0x4f, 0xac, 0x7d, 0x45, 0x83, 0x93, 0xac, 0x9a, + 0x42, 0x84, 0x44, 0x55, 0x3d, 0x07, 0x2b, 0x3c, 0x43, 0x7f, 0x56, 0x45, 0x97, 0xa1, 0xa8, 0x94, + 0xb2, 0x6a, 0x9e, 0x80, 0x25, 0x05, 0x8a, 0xf5, 0xc8, 0x51, 0x01, 0xa9, 0x80, 0xd7, 0xab, 0x66, + 0xdb, 0xaa, 0x6e, 0x6c, 0xd0, 0x21, 0xc1, 0x8a, 0xe4, 0xd7, 0xfe, 0xa1, 0x06, 0x2b, 0x77, 0x9c, + 0x7b, 0x6d, 0xc7, 0xdf, 0x77, 0xfb, 0x76, 0xaf, 0xdc, 0x73, 0x07, 0xf7, 0x3c, 0xdb, 0xef, 0x62, + 0x55, 0x5e, 0x84, 0x4f, 0xdc, 0xa9, 0xae, 0xe3, 0x70, 0xd5, 0x1a, 0xa5, 0xba, 0x55, 0xae, 0xd7, + 0x5a, 0xeb, 0xcd, 0x92, 0x51, 0xc9, 0xaa, 0xd9, 0x4b, 0xf0, 0xfc, 0x78, 0x64, 0xa3, 0x6a, 0xb6, + 0x8d, 0x5a, 0x99, 0x4e, 0x1f, 0x8d, 0x7c, 0x02, 0x9e, 0x19, 0x87, 0xdd, 0x68, 0x5a, 0xe5, 0x66, + 0xeb, 0x6e, 0x31, 0xb7, 0xa6, 0xc3, 0x52, 0xd9, 0xf1, 0xc3, 0xea, 0xa3, 0xd0, 0xe9, 0x07, 0xae, + 0xd7, 0xc7, 0x8a, 0xcd, 0xc3, 0x4c, 0xf5, 0x6f, 0xb4, 0xab, 0x0d, 0xb3, 0xd6, 0x6c, 0x14, 0x8f, + 0xad, 0x9d, 0x4b, 0xe0, 0x08, 0x59, 0x65, 0x9a, 0x9b, 0xc5, 0x63, 0x6b, 0x36, 0xcc, 0x8b, 0xa7, + 0xeb, 0x6c, 0xe6, 0x5f, 0x80, 0x33, 0x62, 0x3d, 0xa1, 0xdc, 0x4b, 0xb6, 0x64, 0x05, 0x96, 0xd3, + 0xe5, 0xd5, 0x76, 0x51, 0xa3, 0xd3, 0x24, 0x51, 0x42, 0xe1, 0xb9, 0xb5, 0x5f, 0xd4, 0x60, 0x3e, + 0xf2, 0x1e, 0xc2, 0x95, 0xb0, 0x0a, 0x67, 0xb7, 0x36, 0x4a, 0x56, 0xa5, 0x7a, 0xbb, 0x56, 0xae, + 0x5a, 0xb7, 0x6a, 0x8d, 0x4a, 0xe2, 0x23, 0xa7, 0xe1, 0x44, 0x06, 0x02, 0x7e, 0x65, 0x05, 0x96, + 0x93, 0x45, 0x6d, 0x2a, 0x8e, 0x72, 0x74, 0x6e, 0x24, 0x4b, 0x22, 0x59, 0x94, 0xa7, 0xf3, 0x31, + 0x59, 0x4a, 0x05, 0xd1, 0xc4, 0xda, 0xe7, 0x61, 0xf1, 0x8e, 0x73, 0x8f, 0xbb, 0x05, 0x32, 0x41, + 0x74, 0x09, 0xce, 0xd3, 0xfe, 0x37, 0xab, 0x26, 0xed, 0xc3, 0x4c, 0x49, 0x74, 0x19, 0xf4, 0x34, + 0x4a, 0x09, 0x77, 0x3f, 0xeb, 0x86, 0x51, 0x6a, 0x6d, 0x5a, 0xa5, 0x56, 0xad, 0xa8, 0xad, 0xdd, + 0x86, 0x05, 0xb3, 0xb4, 0x55, 0xdf, 0xf0, 0xfc, 0x8e, 0x53, 0x1a, 0x86, 0x7b, 0x7d, 0xba, 0x1f, + 0x6d, 0x34, 0x8d, 0x72, 0xd5, 0xc2, 0x9a, 0x25, 0xd8, 0x1e, 0x87, 0x45, 0xb9, 0xf0, 0x6e, 0x95, + 0x2e, 0x6b, 0x02, 0x0b, 0x32, 0xb0, 0xd1, 0x2c, 0xe6, 0xd6, 0x2c, 0x58, 0xe6, 0xaf, 0xf7, 0x2a, + 0x4e, 0x70, 0x3f, 0xf4, 0x06, 0x3c, 0x34, 0x3d, 0xad, 0x57, 0xad, 0x51, 0x69, 0xde, 0x31, 0xad, + 0x4a, 0xd5, 0xbc, 0xd5, 0x6e, 0xb6, 0x2c, 0xb3, 0xb9, 0x4d, 0x89, 0x53, 0x92, 0x74, 0x04, 0x5e, + 0xbd, 0x52, 0x6a, 0x15, 0xb5, 0xb5, 0xcf, 0xc3, 0x1c, 0xef, 0x13, 0x36, 0x2f, 0x4e, 0xc1, 0x71, + 0xf9, 0x77, 0xcb, 0xe9, 0x77, 0xdd, 0xfe, 0x6e, 0xf1, 0x58, 0xb2, 0xc0, 0x18, 0xf6, 0xfb, 0xb4, + 0x00, 0x65, 0xb1, 0x5c, 0xc0, 0x57, 0x51, 0xe8, 0x74, 0x8b, 0xb9, 0xb5, 0x2b, 0x30, 0xaf, 0xa4, + 0xc5, 0xa7, 0x13, 0xb2, 0xde, 0xe4, 0x9b, 0xe7, 0x56, 0xb5, 0x52, 0xdb, 0xde, 0x2a, 0x4e, 0x52, + 0x29, 0xbc, 0x59, 0xbb, 0xb1, 0x59, 0x84, 0xb5, 0x9f, 0xd3, 0x60, 0x81, 0x0a, 0x42, 0xd7, 0x77, + 0xb6, 0x36, 0x4a, 0x62, 0x0a, 0xd3, 0xf5, 0x8d, 0x12, 0x9a, 0x0f, 0x03, 0x53, 0x5a, 0xc4, 0x98, + 0x94, 0x1a, 0x15, 0x6b, 0xb3, 0x64, 0x54, 0xee, 0x94, 0x0c, 0xba, 0xe8, 0xef, 0x16, 0x73, 0x28, + 0xc9, 0x24, 0x88, 0xd5, 0x6e, 0x6e, 0x97, 0x37, 0x8b, 0x79, 0x2a, 0x38, 0x14, 0x78, 0xab, 0xd6, + 0x28, 0x4e, 0xa0, 0x5c, 0x4c, 0x61, 0x23, 0x5b, 0x5a, 0x3e, 0xb9, 0xf6, 0x2d, 0x0d, 0x4e, 0x99, + 0xee, 0x6e, 0xdf, 0x0e, 0x87, 0xbe, 0x53, 0xea, 0xed, 0x7a, 0xbe, 0x1b, 0xee, 0xed, 0x9b, 0x43, + 0x37, 0x74, 0xc8, 0x0b, 0xf0, 0x9c, 0x59, 0xbb, 0xd1, 0x28, 0xb5, 0xa9, 0x5c, 0x2b, 0xd5, 0x6f, + 0x34, 0x8d, 0x5a, 0x7b, 0x73, 0xcb, 0x32, 0xb7, 0x6b, 0xa9, 0x15, 0xf5, 0x2c, 0x5c, 0x1c, 0x8d, + 0x5a, 0xaf, 0xde, 0x28, 0x95, 0xef, 0x16, 0xb5, 0xf1, 0x0c, 0xd7, 0x4b, 0xf5, 0x52, 0xa3, 0x5c, + 0xad, 0x58, 0xb7, 0xaf, 0x16, 0x73, 0xe4, 0x39, 0xb8, 0x34, 0x1a, 0x75, 0xa3, 0xd6, 0x32, 0x29, + 0x5a, 0x7e, 0xfc, 0x77, 0x37, 0xcd, 0x2d, 0x8a, 0x35, 0xb1, 0xf6, 0x75, 0x0d, 0x56, 0x46, 0x25, + 0x45, 0xa2, 0xb3, 0xad, 0xda, 0x68, 0x1b, 0xa5, 0x5a, 0xc5, 0x2a, 0x1b, 0xd5, 0x4a, 0xb5, 0xd1, + 0xae, 0x95, 0xea, 0x66, 0x3c, 0xe3, 0xc4, 0x3b, 0x96, 0x67, 0x60, 0x75, 0x0c, 0x5e, 0xb3, 0x56, + 0x29, 0x17, 0x35, 0x72, 0x15, 0x5e, 0x1e, 0x83, 0x64, 0xde, 0x35, 0xdb, 0xd5, 0x2d, 0xb9, 0xa4, + 0x98, 0x5b, 0xfb, 0xef, 0x1a, 0x9c, 0x1f, 0x9b, 0xdd, 0x48, 0x66, 0xca, 0x57, 0x67, 0xbd, 0x66, + 0xb6, 0xad, 0xe6, 0x9d, 0x46, 0xd5, 0x30, 0xb3, 0x97, 0xc6, 0x4b, 0xf0, 0xfc, 0xe1, 0x24, 0xad, + 0xfa, 0xf6, 0x8d, 0x1a, 0x9d, 0x70, 0x2f, 0xc3, 0x0b, 0x87, 0x63, 0x73, 0x8c, 0x62, 0x8e, 0xbc, + 0x05, 0x9f, 0x3c, 0x2a, 0x73, 0x9c, 0x69, 0x82, 0x32, 0x8f, 0xbb, 0x62, 0x76, 0x9a, 0x0a, 0x3a, + 0x92, 0x58, 0x32, 0x7e, 0x10, 0x2e, 0xc1, 0xf9, 0x91, 0x58, 0x7c, 0x08, 0x9e, 0x81, 0xd5, 0x91, + 0x28, 0x6c, 0x00, 0x8a, 0xb9, 0xb5, 0xf7, 0xe1, 0xcc, 0xe8, 0x50, 0xe9, 0x54, 0x29, 0x51, 0xbb, + 0xb3, 0x00, 0x13, 0x15, 0xaa, 0x4a, 0x69, 0xd2, 0x33, 0x2e, 0x5c, 0x89, 0x46, 0xb5, 0xb6, 0xd5, + 0xa2, 0xbb, 0x2d, 0xd7, 0x60, 0x50, 0x45, 0xf9, 0x31, 0x2d, 0xca, 0xeb, 0x14, 0xf3, 0x4c, 0xbe, + 0x1c, 0x33, 0xb6, 0x1b, 0x0d, 0xa6, 0x90, 0x2d, 0xc2, 0x6c, 0xb3, 0xbd, 0x59, 0x35, 0xac, 0xaa, + 0x61, 0x34, 0x8d, 0x62, 0x8e, 0xaa, 0x40, 0xdb, 0x0d, 0x2a, 0x27, 0x9b, 0x46, 0xed, 0x73, 0xa8, + 0x99, 0xad, 0xc0, 0xb2, 0x59, 0x2f, 0x95, 0x6f, 0x59, 0x8d, 0x66, 0xdb, 0xaa, 0x35, 0xac, 0xf2, + 0x66, 0xa9, 0xd1, 0xa8, 0xd6, 0x8b, 0x40, 0x15, 0x83, 0xe6, 0xad, 0x76, 0xc9, 0x2a, 0x37, 0x1b, + 0x1b, 0xb5, 0x1b, 0x9c, 0xc5, 0x32, 0xce, 0xf8, 0x51, 0xb1, 0xcb, 0xe8, 0xe4, 0x40, 0x1a, 0x3e, + 0x44, 0xe6, 0xdd, 0x46, 0x59, 0x1c, 0x0f, 0xca, 0xe9, 0x6d, 0xff, 0x79, 0x78, 0x76, 0x2c, 0xb6, + 0x38, 0x2f, 0x69, 0x74, 0x25, 0x8d, 0xc5, 0xe4, 0xed, 0x5b, 0xfb, 0x3d, 0x0d, 0xce, 0x8e, 0xf1, + 0xe4, 0xa5, 0xd3, 0x71, 0xb3, 0x5a, 0xaa, 0xd4, 0xe9, 0xcc, 0xa2, 0xdd, 0x40, 0x07, 0x91, 0xbd, + 0x1e, 0xcb, 0xdc, 0xcd, 0x5f, 0x80, 0xe7, 0xc6, 0xa3, 0xc7, 0xba, 0xef, 0xf3, 0xf0, 0xec, 0x78, + 0x54, 0xae, 0x0b, 0xe7, 0xc8, 0x1a, 0x5c, 0x1e, 0x8f, 0x19, 0xe9, 0xd0, 0xf9, 0xb5, 0xaf, 0x6a, + 0x70, 0x32, 0xfb, 0xea, 0x90, 0xd6, 0xad, 0xd6, 0x30, 0xdb, 0xa5, 0x7a, 0xdd, 0x6a, 0x95, 0x8c, + 0xd2, 0x96, 0x55, 0x6d, 0x18, 0xcd, 0x7a, 0x3d, 0x4b, 0xbd, 0x7a, 0x16, 0x2e, 0x8e, 0x46, 0x35, + 0xcb, 0x46, 0xad, 0x45, 0x55, 0x07, 0x1d, 0x2e, 0x8c, 0xc6, 0xaa, 0xd6, 0xca, 0xd5, 0x62, 0x6e, + 0xfd, 0x9d, 0xdf, 0xf9, 0xd3, 0x0b, 0xc7, 0x7e, 0xe7, 0x5b, 0x17, 0xb4, 0x3f, 0xf8, 0xd6, 0x05, + 0xed, 0x4f, 0xbe, 0x75, 0x41, 0xfb, 0xdc, 0x8b, 0xec, 0xed, 0xd2, 0x95, 0x8e, 0xb7, 0xff, 0xca, + 0xae, 0x6f, 0x3f, 0x70, 0x43, 0x7e, 0xe9, 0xff, 0x8a, 0xb8, 0xf9, 0x7d, 0xc5, 0x1e, 0xb8, 0xaf, + 0xa0, 0x5d, 0xe9, 0xde, 0x14, 0x9a, 0x29, 0x5e, 0xfb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x41, + 0x61, 0x66, 0x2e, 0xae, 0x23, 0x02, 0x00, } func (this *SSHKeyPair) Equal(that interface{}) bool { @@ -33809,6 +33875,18 @@ func (m *InstanceSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.AuditQueueStatus != nil { + { + size, err := m.AuditQueueStatus.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } if m.UpdaterInfo != nil { { size, err := m.UpdaterInfo.MarshalToSizedBuffer(dAtA[:i]) @@ -33861,12 +33939,12 @@ func (m *InstanceSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x32 } } - n47, err47 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSeen, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSeen):]) - if err47 != nil { - return 0, err47 + n48, err48 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSeen, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSeen):]) + if err48 != nil { + return 0, err48 } - i -= n47 - i = encodeVarintTypes(dAtA, i, uint64(n47)) + i -= n48 + i = encodeVarintTypes(dAtA, i, uint64(n48)) i-- dAtA[i] = 0x2a if len(m.AuthID) > 0 { @@ -33902,6 +33980,58 @@ func (m *InstanceSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AuditQueueStatus) 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 *AuditQueueStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AuditQueueStatus) 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.OldestDeadLetterAgeSeconds != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.OldestDeadLetterAgeSeconds)) + i-- + dAtA[i] = 0x28 + } + if m.OldestPendingAgeSeconds != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.OldestPendingAgeSeconds)) + i-- + dAtA[i] = 0x20 + } + if m.CorruptCount != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.CorruptCount)) + i-- + dAtA[i] = 0x18 + } + if m.DeadLetterCount != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.DeadLetterCount)) + i-- + dAtA[i] = 0x10 + } + if m.PendingCount != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.PendingCount)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *SystemClockMeasurement) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -33926,29 +34056,29 @@ func (m *SystemClockMeasurement) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n48, err48 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.RequestDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.RequestDuration):]) - if err48 != nil { - return 0, err48 - } - i -= n48 - i = encodeVarintTypes(dAtA, i, uint64(n48)) - i-- - dAtA[i] = 0x1a - n49, err49 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SystemClock):]) + n49, err49 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.RequestDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.RequestDuration):]) if err49 != nil { return 0, err49 } i -= n49 i = encodeVarintTypes(dAtA, i, uint64(n49)) i-- - dAtA[i] = 0x12 - n50, err50 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ControllerSystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.ControllerSystemClock):]) + dAtA[i] = 0x1a + n50, err50 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SystemClock):]) if err50 != nil { return 0, err50 } i -= n50 i = encodeVarintTypes(dAtA, i, uint64(n50)) i-- + dAtA[i] = 0x12 + n51, err51 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ControllerSystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.ControllerSystemClock):]) + if err51 != nil { + return 0, err51 + } + i -= n51 + i = encodeVarintTypes(dAtA, i, uint64(n51)) + i-- dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -34001,12 +34131,12 @@ func (m *InstanceControlLogEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x20 } - n51, err51 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err51 != nil { - return 0, err51 + n52, err52 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err52 != nil { + return 0, err52 } - i -= n51 - i = encodeVarintTypes(dAtA, i, uint64(n51)) + i -= n52 + i = encodeVarintTypes(dAtA, i, uint64(n52)) i-- dAtA[i] = 0x1a if m.ID != 0 { @@ -36308,12 +36438,12 @@ func (m *ProvisionTokenV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - n81, err81 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err81 != nil { - return 0, err81 + n82, err82 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err82 != nil { + return 0, err82 } - i -= n81 - i = encodeVarintTypes(dAtA, i, uint64(n81)) + i -= n82 + i = encodeVarintTypes(dAtA, i, uint64(n82)) i-- dAtA[i] = 0x12 if len(m.Roles) > 0 { @@ -38871,12 +39001,12 @@ func (m *ProvisionTokenSpecV2BoundKeypair) MarshalToSizedBuffer(dAtA []byte) (in copy(dAtA[i:], m.XXX_unrecognized) } if m.RotateAfter != nil { - n112, err112 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.RotateAfter, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.RotateAfter):]) - if err112 != nil { - return 0, err112 + n113, err113 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.RotateAfter, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.RotateAfter):]) + if err113 != nil { + return 0, err113 } - i -= n112 - i = encodeVarintTypes(dAtA, i, uint64(n112)) + i -= n113 + i = encodeVarintTypes(dAtA, i, uint64(n113)) i-- dAtA[i] = 0x1a } @@ -38932,12 +39062,12 @@ func (m *ProvisionTokenSpecV2BoundKeypair_OnboardingSpec) MarshalToSizedBuffer(d copy(dAtA[i:], m.XXX_unrecognized) } if m.MustRegisterBefore != nil { - n115, err115 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.MustRegisterBefore, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.MustRegisterBefore):]) - if err115 != nil { - return 0, err115 + n116, err116 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.MustRegisterBefore, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.MustRegisterBefore):]) + if err116 != nil { + return 0, err116 } - i -= n115 - i = encodeVarintTypes(dAtA, i, uint64(n115)) + i -= n116 + i = encodeVarintTypes(dAtA, i, uint64(n116)) i-- dAtA[i] = 0x1a } @@ -39068,22 +39198,22 @@ func (m *ProvisionTokenStatusV2BoundKeypair) MarshalToSizedBuffer(dAtA []byte) ( dAtA[i] = 0x3a } if m.LastRotatedAt != nil { - n117, err117 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastRotatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastRotatedAt):]) - if err117 != nil { - return 0, err117 + n118, err118 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastRotatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastRotatedAt):]) + if err118 != nil { + return 0, err118 } - i -= n117 - i = encodeVarintTypes(dAtA, i, uint64(n117)) + i -= n118 + i = encodeVarintTypes(dAtA, i, uint64(n118)) i-- dAtA[i] = 0x32 } if m.LastRecoveredAt != nil { - n118, err118 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastRecoveredAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastRecoveredAt):]) - if err118 != nil { - return 0, err118 + n119, err119 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastRecoveredAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastRecoveredAt):]) + if err119 != nil { + return 0, err119 } - i -= n118 - i = encodeVarintTypes(dAtA, i, uint64(n118)) + i -= n119 + i = encodeVarintTypes(dAtA, i, uint64(n119)) i-- dAtA[i] = 0x2a } @@ -40293,20 +40423,20 @@ func (m *AuthPreferenceSpecV2) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0xb2 } if len(m.SecondFactors) > 0 { - dAtA142 := make([]byte, len(m.SecondFactors)*10) - var j141 int + dAtA143 := make([]byte, len(m.SecondFactors)*10) + var j142 int for _, num := range m.SecondFactors { for num >= 1<<7 { - dAtA142[j141] = uint8(uint64(num)&0x7f | 0x80) + dAtA143[j142] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j141++ + j142++ } - dAtA142[j141] = uint8(num) - j141++ + dAtA143[j142] = uint8(num) + j142++ } - i -= j141 - copy(dAtA[i:], dAtA142[:j141]) - i = encodeVarintTypes(dAtA, i, uint64(j141)) + i -= j142 + copy(dAtA[i:], dAtA143[:j142]) + i = encodeVarintTypes(dAtA, i, uint64(j142)) i-- dAtA[i] = 0x1 i-- @@ -40979,12 +41109,12 @@ func (m *UserTokenSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n158, err158 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err158 != nil { - return 0, err158 + n159, err159 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err159 != nil { + return 0, err159 } - i -= n158 - i = encodeVarintTypes(dAtA, i, uint64(n158)) + i -= n159 + i = encodeVarintTypes(dAtA, i, uint64(n159)) i-- dAtA[i] = 0x22 if m.Usage != 0 { @@ -41101,12 +41231,12 @@ func (m *UserTokenSecretsSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n161, err161 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err161 != nil { - return 0, err161 + n162, err162 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err162 != nil { + return 0, err162 } - i -= n161 - i = encodeVarintTypes(dAtA, i, uint64(n161)) + i -= n162 + i = encodeVarintTypes(dAtA, i, uint64(n162)) i-- dAtA[i] = 0x1a if len(m.QRCode) > 0 { @@ -41436,12 +41566,12 @@ func (m *AccessReview) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x5a } if m.AssumeStartTime != nil { - n165, err165 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.AssumeStartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.AssumeStartTime):]) - if err165 != nil { - return 0, err165 + n166, err166 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.AssumeStartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.AssumeStartTime):]) + if err166 != nil { + return 0, err166 } - i -= n165 - i = encodeVarintTypes(dAtA, i, uint64(n165)) + i -= n166 + i = encodeVarintTypes(dAtA, i, uint64(n166)) i-- dAtA[i] = 0x52 } @@ -41458,20 +41588,20 @@ func (m *AccessReview) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x4a } if len(m.ThresholdIndexes) > 0 { - dAtA168 := make([]byte, len(m.ThresholdIndexes)*10) - var j167 int + dAtA169 := make([]byte, len(m.ThresholdIndexes)*10) + var j168 int for _, num := range m.ThresholdIndexes { for num >= 1<<7 { - dAtA168[j167] = uint8(uint64(num)&0x7f | 0x80) + dAtA169[j168] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j167++ + j168++ } - dAtA168[j167] = uint8(num) - j167++ + dAtA169[j168] = uint8(num) + j168++ } - i -= j167 - copy(dAtA[i:], dAtA168[:j167]) - i = encodeVarintTypes(dAtA, i, uint64(j167)) + i -= j168 + copy(dAtA[i:], dAtA169[:j168]) + i = encodeVarintTypes(dAtA, i, uint64(j168)) i-- dAtA[i] = 0x3a } @@ -41485,12 +41615,12 @@ func (m *AccessReview) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x32 - n170, err170 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err170 != nil { - return 0, err170 + n171, err171 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err171 != nil { + return 0, err171 } - i -= n170 - i = encodeVarintTypes(dAtA, i, uint64(n170)) + i -= n171 + i = encodeVarintTypes(dAtA, i, uint64(n171)) i-- dAtA[i] = 0x2a if len(m.Reason) > 0 { @@ -41593,20 +41723,20 @@ func (m *ThresholdIndexSet) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Indexes) > 0 { - dAtA173 := make([]byte, len(m.Indexes)*10) - var j172 int + dAtA174 := make([]byte, len(m.Indexes)*10) + var j173 int for _, num := range m.Indexes { for num >= 1<<7 { - dAtA173[j172] = uint8(uint64(num)&0x7f | 0x80) + dAtA174[j173] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j172++ + j173++ } - dAtA173[j172] = uint8(num) - j172++ + dAtA174[j173] = uint8(num) + j173++ } - i -= j172 - copy(dAtA[i:], dAtA173[:j172]) - i = encodeVarintTypes(dAtA, i, uint64(j172)) + i -= j173 + copy(dAtA[i:], dAtA174[:j173]) + i = encodeVarintTypes(dAtA, i, uint64(j173)) i-- dAtA[i] = 0xa } @@ -41730,24 +41860,24 @@ func (m *AccessRequestSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0xba } if m.ResourceExpiry != nil { - n176, err176 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.ResourceExpiry, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.ResourceExpiry):]) - if err176 != nil { - return 0, err176 + n177, err177 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.ResourceExpiry, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.ResourceExpiry):]) + if err177 != nil { + return 0, err177 } - i -= n176 - i = encodeVarintTypes(dAtA, i, uint64(n176)) + i -= n177 + i = encodeVarintTypes(dAtA, i, uint64(n177)) i-- dAtA[i] = 0x1 i-- dAtA[i] = 0xb2 } if m.AssumeStartTime != nil { - n177, err177 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.AssumeStartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.AssumeStartTime):]) - if err177 != nil { - return 0, err177 + n178, err178 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.AssumeStartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.AssumeStartTime):]) + if err178 != nil { + return 0, err178 } - i -= n177 - i = encodeVarintTypes(dAtA, i, uint64(n177)) + i -= n178 + i = encodeVarintTypes(dAtA, i, uint64(n178)) i-- dAtA[i] = 0x1 i-- @@ -41767,22 +41897,22 @@ func (m *AccessRequestSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0xa2 } - n179, err179 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SessionTTL, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SessionTTL):]) - if err179 != nil { - return 0, err179 + n180, err180 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SessionTTL, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SessionTTL):]) + if err180 != nil { + return 0, err180 } - i -= n179 - i = encodeVarintTypes(dAtA, i, uint64(n179)) + i -= n180 + i = encodeVarintTypes(dAtA, i, uint64(n180)) i-- dAtA[i] = 0x1 i-- dAtA[i] = 0x92 - n180, err180 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.MaxDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.MaxDuration):]) - if err180 != nil { - return 0, err180 + n181, err181 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.MaxDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.MaxDuration):]) + if err181 != nil { + return 0, err181 } - i -= n180 - i = encodeVarintTypes(dAtA, i, uint64(n180)) + i -= n181 + i = encodeVarintTypes(dAtA, i, uint64(n181)) i-- dAtA[i] = 0x1 i-- @@ -41915,21 +42045,21 @@ func (m *AccessRequestSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n184, err184 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err184 != nil { - return 0, err184 - } - i -= n184 - i = encodeVarintTypes(dAtA, i, uint64(n184)) - i-- - dAtA[i] = 0x2a - n185, err185 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + n185, err185 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) if err185 != nil { return 0, err185 } i -= n185 i = encodeVarintTypes(dAtA, i, uint64(n185)) i-- + dAtA[i] = 0x2a + n186, err186 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err186 != nil { + return 0, err186 + } + i -= n186 + i = encodeVarintTypes(dAtA, i, uint64(n186)) + i-- dAtA[i] = 0x22 if m.State != 0 { i = encodeVarintTypes(dAtA, i, uint64(m.State)) @@ -43005,12 +43135,12 @@ func (m *RoleOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0xfa } - n199, err199 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MFAVerificationInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MFAVerificationInterval):]) - if err199 != nil { - return 0, err199 + n200, err200 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MFAVerificationInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MFAVerificationInterval):]) + if err200 != nil { + return 0, err200 } - i -= n199 - i = encodeVarintTypes(dAtA, i, uint64(n199)) + i -= n200 + i = encodeVarintTypes(dAtA, i, uint64(n200)) i-- dAtA[i] = 0x1 i-- @@ -45412,12 +45542,12 @@ func (m *UserSpecV2) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x42 - n235, err235 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err235 != nil { - return 0, err235 + n236, err236 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err236 != nil { + return 0, err236 } - i -= n235 - i = encodeVarintTypes(dAtA, i, uint64(n235)) + i -= n236 + i = encodeVarintTypes(dAtA, i, uint64(n236)) i-- dAtA[i] = 0x3a { @@ -45573,21 +45703,21 @@ func (m *LoginStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n238, err238 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LockExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LockExpires):]) - if err238 != nil { - return 0, err238 - } - i -= n238 - i = encodeVarintTypes(dAtA, i, uint64(n238)) - i-- - dAtA[i] = 0x22 - n239, err239 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LockedTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LockedTime):]) + n239, err239 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LockExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LockExpires):]) if err239 != nil { return 0, err239 } i -= n239 i = encodeVarintTypes(dAtA, i, uint64(n239)) i-- + dAtA[i] = 0x22 + n240, err240 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LockedTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LockedTime):]) + if err240 != nil { + return 0, err240 + } + i -= n240 + i = encodeVarintTypes(dAtA, i, uint64(n240)) + i-- dAtA[i] = 0x1a if len(m.LockedMessage) > 0 { i -= len(m.LockedMessage) @@ -45643,12 +45773,12 @@ func (m *CreatedBy) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x1a - n241, err241 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err241 != nil { - return 0, err241 + n242, err242 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err242 != nil { + return 0, err242 } - i -= n241 - i = encodeVarintTypes(dAtA, i, uint64(n241)) + i -= n242 + i = encodeVarintTypes(dAtA, i, uint64(n242)) i-- dAtA[i] = 0x12 if m.Connector != nil { @@ -46066,12 +46196,12 @@ func (m *TunnelConnectionSpecV2) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x22 } - n248, err248 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastHeartbeat, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastHeartbeat):]) - if err248 != nil { - return 0, err248 + n249, err249 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastHeartbeat, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastHeartbeat):]) + if err249 != nil { + return 0, err249 } - i -= n248 - i = encodeVarintTypes(dAtA, i, uint64(n248)) + i -= n249 + i = encodeVarintTypes(dAtA, i, uint64(n249)) i-- dAtA[i] = 0x1a if len(m.ProxyName) > 0 { @@ -46163,12 +46293,12 @@ func (m *AcquireSemaphoreRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x2a } - n249, err249 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err249 != nil { - return 0, err249 + n250, err250 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err250 != nil { + return 0, err250 } - i -= n249 - i = encodeVarintTypes(dAtA, i, uint64(n249)) + i -= n250 + i = encodeVarintTypes(dAtA, i, uint64(n250)) i-- dAtA[i] = 0x22 if m.MaxLeases != 0 { @@ -46217,12 +46347,12 @@ func (m *SemaphoreLease) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n250, err250 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err250 != nil { - return 0, err250 + n251, err251 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err251 != nil { + return 0, err251 } - i -= n250 - i = encodeVarintTypes(dAtA, i, uint64(n250)) + i -= n251 + i = encodeVarintTypes(dAtA, i, uint64(n251)) i-- dAtA[i] = 0x2a if len(m.LeaseID) > 0 { @@ -46280,12 +46410,12 @@ func (m *SemaphoreLeaseRef) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - n251, err251 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err251 != nil { - return 0, err251 + n252, err252 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err252 != nil { + return 0, err252 } - i -= n251 - i = encodeVarintTypes(dAtA, i, uint64(n251)) + i -= n252 + i = encodeVarintTypes(dAtA, i, uint64(n252)) i-- dAtA[i] = 0x12 if len(m.LeaseID) > 0 { @@ -46573,29 +46703,29 @@ func (m *WebSessionSpecV2) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x48 } - n258, err258 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LoginTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LoginTime):]) - if err258 != nil { - return 0, err258 - } - i -= n258 - i = encodeVarintTypes(dAtA, i, uint64(n258)) - i-- - dAtA[i] = 0x42 - n259, err259 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + n259, err259 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LoginTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LoginTime):]) if err259 != nil { return 0, err259 } i -= n259 i = encodeVarintTypes(dAtA, i, uint64(n259)) i-- - dAtA[i] = 0x3a - n260, err260 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.BearerTokenExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.BearerTokenExpires):]) + dAtA[i] = 0x42 + n260, err260 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) if err260 != nil { return 0, err260 } i -= n260 i = encodeVarintTypes(dAtA, i, uint64(n260)) i-- + dAtA[i] = 0x3a + n261, err261 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.BearerTokenExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.BearerTokenExpires):]) + if err261 != nil { + return 0, err261 + } + i -= n261 + i = encodeVarintTypes(dAtA, i, uint64(n261)) + i-- dAtA[i] = 0x32 if len(m.BearerToken) > 0 { i -= len(m.BearerToken) @@ -46827,21 +46957,21 @@ func (m *SAMLSessionData) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n261, err261 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ExpireTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.ExpireTime):]) - if err261 != nil { - return 0, err261 - } - i -= n261 - i = encodeVarintTypes(dAtA, i, uint64(n261)) - i-- - dAtA[i] = 0x1a - n262, err262 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreateTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreateTime):]) + n262, err262 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ExpireTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.ExpireTime):]) if err262 != nil { return 0, err262 } i -= n262 i = encodeVarintTypes(dAtA, i, uint64(n262)) i-- + dAtA[i] = 0x1a + n263, err263 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreateTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreateTime):]) + if err263 != nil { + return 0, err263 + } + i -= n263 + i = encodeVarintTypes(dAtA, i, uint64(n263)) + i-- dAtA[i] = 0x12 if len(m.ID) > 0 { i -= len(m.ID) @@ -47122,12 +47252,12 @@ func (m *RemoteClusterStatusV3) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n266, err266 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastHeartbeat, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastHeartbeat):]) - if err266 != nil { - return 0, err266 + n267, err267 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastHeartbeat, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastHeartbeat):]) + if err267 != nil { + return 0, err267 } - i -= n266 - i = encodeVarintTypes(dAtA, i, uint64(n266)) + i -= n267 + i = encodeVarintTypes(dAtA, i, uint64(n267)) i-- dAtA[i] = 0x12 if len(m.Connection) > 0 { @@ -50415,12 +50545,12 @@ func (m *GithubAuthRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x62 } if m.Expires != nil { - n314, err314 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) - if err314 != nil { - return 0, err314 + n315, err315 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) + if err315 != nil { + return 0, err315 } - i -= n314 - i = encodeVarintTypes(dAtA, i, uint64(n314)) + i -= n315 + i = encodeVarintTypes(dAtA, i, uint64(n315)) i-- dAtA[i] = 0x5a } @@ -51432,21 +51562,21 @@ func (m *LockSpecV2) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - n332, err332 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):]) - if err332 != nil { - return 0, err332 + n333, err333 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):]) + if err333 != nil { + return 0, err333 } - i -= n332 - i = encodeVarintTypes(dAtA, i, uint64(n332)) + i -= n333 + i = encodeVarintTypes(dAtA, i, uint64(n333)) i-- dAtA[i] = 0x22 if m.Expires != nil { - n333, err333 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) - if err333 != nil { - return 0, err333 + n334, err334 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) + if err334 != nil { + return 0, err334 } - i -= n333 - i = encodeVarintTypes(dAtA, i, uint64(n333)) + i -= n334 + i = encodeVarintTypes(dAtA, i, uint64(n334)) i-- dAtA[i] = 0x1a } @@ -52296,12 +52426,12 @@ func (m *RegisterUsingTokenRequest) MarshalToSizedBuffer(dAtA []byte) (int, erro dAtA[i] = 0x6a } if m.Expires != nil { - n346, err346 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) - if err346 != nil { - return 0, err346 + n347, err347 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) + if err347 != nil { + return 0, err347 } - i -= n346 - i = encodeVarintTypes(dAtA, i, uint64(n346)) + i -= n347 + i = encodeVarintTypes(dAtA, i, uint64(n347)) i-- dAtA[i] = 0x62 } @@ -52481,12 +52611,12 @@ func (m *RecoveryCodesSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n349, err349 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err349 != nil { - return 0, err349 + n350, err350 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err350 != nil { + return 0, err350 } - i -= n349 - i = encodeVarintTypes(dAtA, i, uint64(n349)) + i -= n350 + i = encodeVarintTypes(dAtA, i, uint64(n350)) i-- dAtA[i] = 0x12 if len(m.Codes) > 0 { @@ -52866,21 +52996,21 @@ func (m *SessionTrackerSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n353, err353 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err353 != nil { - return 0, err353 - } - i -= n353 - i = encodeVarintTypes(dAtA, i, uint64(n353)) - i-- - dAtA[i] = 0x2a - n354, err354 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + n354, err354 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) if err354 != nil { return 0, err354 } i -= n354 i = encodeVarintTypes(dAtA, i, uint64(n354)) i-- + dAtA[i] = 0x2a + n355, err355 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err355 != nil { + return 0, err355 + } + i -= n355 + i = encodeVarintTypes(dAtA, i, uint64(n355)) + i-- dAtA[i] = 0x22 if m.State != 0 { i = encodeVarintTypes(dAtA, i, uint64(m.State)) @@ -52990,12 +53120,12 @@ func (m *Participant) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - n355, err355 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastActive, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastActive):]) - if err355 != nil { - return 0, err355 + n356, err356 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastActive, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastActive):]) + if err356 != nil { + return 0, err356 } - i -= n355 - i = encodeVarintTypes(dAtA, i, uint64(n355)) + i -= n356 + i = encodeVarintTypes(dAtA, i, uint64(n356)) i-- dAtA[i] = 0x22 if len(m.Mode) > 0 { @@ -53707,12 +53837,12 @@ func (m *ClusterAlertSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n368, err368 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err368 != nil { - return 0, err368 + n369, err369 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err369 != nil { + return 0, err369 } - i -= n368 - i = encodeVarintTypes(dAtA, i, uint64(n368)) + i -= n369 + i = encodeVarintTypes(dAtA, i, uint64(n369)) i-- dAtA[i] = 0x1a if len(m.Message) > 0 { @@ -53842,12 +53972,12 @@ func (m *AlertAcknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n369, err369 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err369 != nil { - return 0, err369 + n370, err370 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err370 != nil { + return 0, err370 } - i -= n369 - i = encodeVarintTypes(dAtA, i, uint64(n369)) + i -= n370 + i = encodeVarintTypes(dAtA, i, uint64(n370)) i-- dAtA[i] = 0x22 if len(m.Reason) > 0 { @@ -54622,12 +54752,12 @@ func (m *PluginGithubSettings) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n394, err394 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) - if err394 != nil { - return 0, err394 + n395, err395 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + if err395 != nil { + return 0, err395 } - i -= n394 - i = encodeVarintTypes(dAtA, i, uint64(n394)) + i -= n395 + i = encodeVarintTypes(dAtA, i, uint64(n395)) i-- dAtA[i] = 0x22 if len(m.OrganizationName) > 0 { @@ -57111,12 +57241,12 @@ func (m *PluginStatusV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n414, err414 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSyncTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSyncTime):]) - if err414 != nil { - return 0, err414 + n415, err415 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSyncTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSyncTime):]) + if err415 != nil { + return 0, err415 } - i -= n414 - i = encodeVarintTypes(dAtA, i, uint64(n414)) + i -= n415 + i = encodeVarintTypes(dAtA, i, uint64(n415)) i-- dAtA[i] = 0x1a if len(m.ErrorMessage) > 0 { @@ -57573,22 +57703,22 @@ func (m *PluginOktaStatusDetailsAppGroupSync) MarshalToSizedBuffer(dAtA []byte) dAtA[i] = 0x28 } if m.LastFailed != nil { - n426, err426 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err426 != nil { - return 0, err426 + n427, err427 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err427 != nil { + return 0, err427 } - i -= n426 - i = encodeVarintTypes(dAtA, i, uint64(n426)) + i -= n427 + i = encodeVarintTypes(dAtA, i, uint64(n427)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n427, err427 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err427 != nil { - return 0, err427 + n428, err428 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err428 != nil { + return 0, err428 } - i -= n427 - i = encodeVarintTypes(dAtA, i, uint64(n427)) + i -= n428 + i = encodeVarintTypes(dAtA, i, uint64(n428)) i-- dAtA[i] = 0x1a } @@ -57647,22 +57777,22 @@ func (m *PluginOktaStatusDetailsUsersSync) MarshalToSizedBuffer(dAtA []byte) (in dAtA[i] = 0x28 } if m.LastFailed != nil { - n428, err428 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err428 != nil { - return 0, err428 + n429, err429 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err429 != nil { + return 0, err429 } - i -= n428 - i = encodeVarintTypes(dAtA, i, uint64(n428)) + i -= n429 + i = encodeVarintTypes(dAtA, i, uint64(n429)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n429, err429 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err429 != nil { - return 0, err429 + n430, err430 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err430 != nil { + return 0, err430 } - i -= n429 - i = encodeVarintTypes(dAtA, i, uint64(n429)) + i -= n430 + i = encodeVarintTypes(dAtA, i, uint64(n430)) i-- dAtA[i] = 0x1a } @@ -57781,22 +57911,22 @@ func (m *PluginOktaStatusDetailsAccessListsSync) MarshalToSizedBuffer(dAtA []byt } } if m.LastFailed != nil { - n430, err430 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err430 != nil { - return 0, err430 + n431, err431 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err431 != nil { + return 0, err431 } - i -= n430 - i = encodeVarintTypes(dAtA, i, uint64(n430)) + i -= n431 + i = encodeVarintTypes(dAtA, i, uint64(n431)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n431, err431 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err431 != nil { - return 0, err431 + n432, err432 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err432 != nil { + return 0, err432 } - i -= n431 - i = encodeVarintTypes(dAtA, i, uint64(n431)) + i -= n432 + i = encodeVarintTypes(dAtA, i, uint64(n432)) i-- dAtA[i] = 0x1a } @@ -57850,22 +57980,22 @@ func (m *PluginOktaStatusSystemLogExporter) MarshalToSizedBuffer(dAtA []byte) (i dAtA[i] = 0x4a } if m.LastFailed != nil { - n432, err432 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err432 != nil { - return 0, err432 + n433, err433 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err433 != nil { + return 0, err433 } - i -= n432 - i = encodeVarintTypes(dAtA, i, uint64(n432)) + i -= n433 + i = encodeVarintTypes(dAtA, i, uint64(n433)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n433, err433 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err433 != nil { - return 0, err433 + n434, err434 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err434 != nil { + return 0, err434 } - i -= n433 - i = encodeVarintTypes(dAtA, i, uint64(n433)) + i -= n434 + i = encodeVarintTypes(dAtA, i, uint64(n434)) i-- dAtA[i] = 0x1a } @@ -58031,12 +58161,12 @@ func (m *PluginOAuth2AccessTokenCredentials) MarshalToSizedBuffer(dAtA []byte) ( i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n438, err438 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err438 != nil { - return 0, err438 + n439, err439 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err439 != nil { + return 0, err439 } - i -= n438 - i = encodeVarintTypes(dAtA, i, uint64(n438)) + i -= n439 + i = encodeVarintTypes(dAtA, i, uint64(n439)) i-- dAtA[i] = 0x1a if len(m.RefreshToken) > 0 { @@ -58994,21 +59124,21 @@ func (m *ScheduledAgentUpgradeWindow) MarshalToSizedBuffer(dAtA []byte) (int, er i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n453, err453 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Stop, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Stop):]) - if err453 != nil { - return 0, err453 - } - i -= n453 - i = encodeVarintTypes(dAtA, i, uint64(n453)) - i-- - dAtA[i] = 0x12 - n454, err454 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Start, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Start):]) + n454, err454 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Stop, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Stop):]) if err454 != nil { return 0, err454 } i -= n454 i = encodeVarintTypes(dAtA, i, uint64(n454)) i-- + dAtA[i] = 0x12 + n455, err455 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Start, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Start):]) + if err455 != nil { + return 0, err455 + } + i -= n455 + i = encodeVarintTypes(dAtA, i, uint64(n455)) + i-- dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -59434,12 +59564,12 @@ func (m *OktaAssignmentSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x30 } - n461, err461 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastTransition, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastTransition):]) - if err461 != nil { - return 0, err461 + n462, err462 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastTransition, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastTransition):]) + if err462 != nil { + return 0, err462 } - i -= n461 - i = encodeVarintTypes(dAtA, i, uint64(n461)) + i -= n462 + i = encodeVarintTypes(dAtA, i, uint64(n462)) i-- dAtA[i] = 0x2a if m.Status != 0 { @@ -59447,12 +59577,12 @@ func (m *OktaAssignmentSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x20 } - n462, err462 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CleanupTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CleanupTime):]) - if err462 != nil { - return 0, err462 + n463, err463 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CleanupTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CleanupTime):]) + if err463 != nil { + return 0, err463 } - i -= n462 - i = encodeVarintTypes(dAtA, i, uint64(n462)) + i -= n463 + i = encodeVarintTypes(dAtA, i, uint64(n463)) i-- dAtA[i] = 0x1a if len(m.Targets) > 0 { @@ -59559,12 +59689,12 @@ func (m *OktaAssignmentTargetStatus) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x20 } - n464, err464 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastProcessed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastProcessed):]) - if err464 != nil { - return 0, err464 + n465, err465 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastProcessed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastProcessed):]) + if err465 != nil { + return 0, err465 } - i -= n464 - i = encodeVarintTypes(dAtA, i, uint64(n464)) + i -= n465 + i = encodeVarintTypes(dAtA, i, uint64(n465)) i-- dAtA[i] = 0x1a if len(m.Op) > 0 { @@ -60133,21 +60263,21 @@ func (m *AWSRolesAnywhereProfileSyncIterationSummary) MarshalToSizedBuffer(dAtA i-- dAtA[i] = 0x1a } - n476, err476 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):]) - if err476 != nil { - return 0, err476 - } - i -= n476 - i = encodeVarintTypes(dAtA, i, uint64(n476)) - i-- - dAtA[i] = 0x12 - n477, err477 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):]) + n477, err477 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):]) if err477 != nil { return 0, err477 } i -= n477 i = encodeVarintTypes(dAtA, i, uint64(n477)) i-- + dAtA[i] = 0x12 + n478, err478 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):]) + if err478 != nil { + return 0, err478 + } + i -= n478 + i = encodeVarintTypes(dAtA, i, uint64(n478)) + i-- dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -61571,12 +61701,12 @@ func (m *AccessGraphSync) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x1a } } - n500, err500 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.PollInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.PollInterval):]) - if err500 != nil { - return 0, err500 + n501, err501 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.PollInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.PollInterval):]) + if err501 != nil { + return 0, err501 } - i -= n500 - i = encodeVarintTypes(dAtA, i, uint64(n500)) + i -= n501 + i = encodeVarintTypes(dAtA, i, uint64(n501)) i-- dAtA[i] = 0x12 if len(m.AWS) > 0 { @@ -61840,12 +61970,12 @@ func (m *TargetHealth) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x2a } if m.TransitionTimestamp != nil { - n505, err505 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.TransitionTimestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.TransitionTimestamp):]) - if err505 != nil { - return 0, err505 + n506, err506 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.TransitionTimestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.TransitionTimestamp):]) + if err506 != nil { + return 0, err506 } - i -= n505 - i = encodeVarintTypes(dAtA, i, uint64(n505)) + i -= n506 + i = encodeVarintTypes(dAtA, i, uint64(n506)) i-- dAtA[i] = 0x22 } @@ -62824,6 +62954,37 @@ func (m *InstanceSpecV1) Size() (n int) { l = m.UpdaterInfo.Size() n += 1 + l + sovTypes(uint64(l)) } + if m.AuditQueueStatus != nil { + l = m.AuditQueueStatus.Size() + n += 1 + l + sovTypes(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *AuditQueueStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PendingCount != 0 { + n += 1 + sovTypes(uint64(m.PendingCount)) + } + if m.DeadLetterCount != 0 { + n += 1 + sovTypes(uint64(m.DeadLetterCount)) + } + if m.CorruptCount != 0 { + n += 1 + sovTypes(uint64(m.CorruptCount)) + } + if m.OldestPendingAgeSeconds != 0 { + n += 1 + sovTypes(uint64(m.OldestPendingAgeSeconds)) + } + if m.OldestDeadLetterAgeSeconds != 0 { + n += 1 + sovTypes(uint64(m.OldestDeadLetterAgeSeconds)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -82231,6 +82392,188 @@ func (m *InstanceSpecV1) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuditQueueStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AuditQueueStatus == nil { + m.AuditQueueStatus = &AuditQueueStatus{} + } + if err := m.AuditQueueStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + 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 *AuditQueueStatus) 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 ErrIntOverflowTypes + } + 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: AuditQueueStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AuditQueueStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingCount", wireType) + } + m.PendingCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PendingCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeadLetterCount", wireType) + } + m.DeadLetterCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DeadLetterCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CorruptCount", wireType) + } + m.CorruptCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CorruptCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OldestPendingAgeSeconds", wireType) + } + m.OldestPendingAgeSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OldestPendingAgeSeconds |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OldestDeadLetterAgeSeconds", wireType) + } + m.OldestDeadLetterAgeSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OldestDeadLetterAgeSeconds |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/docs/pages/reference/cli/tctl.mdx b/docs/pages/reference/cli/tctl.mdx index 6645d4bd68a0..c3a917d6fbcf 100644 --- a/docs/pages/reference/cli/tctl.mdx +++ b/docs/pages/reference/cli/tctl.mdx @@ -1717,6 +1717,7 @@ Flags: |`--newer-than`|*no default*|Filter for newer teleport versions| |`--older-than`|*no default*|Filter for older teleport versions| |`--services`|*no default*|Filter output by service (node,kube,proxy,etc)| +|`--sort`|*no default* (one of: ``, `audit-queue`)|Sort output by column. Supported: audit-queue (largest audit queue backlog first, including dead-letter and corrupt events).| |`--update-group`|*no default*|Filter output by update group| |`--upgrader`|*no default*|Filter output by upgrader (kube,unit,none)| diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 3a9d4d2ba933..3f13823d674d 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -5737,6 +5737,7 @@ func (a *Server) RegisterInventoryControlStream(ics client.UpstreamInventoryCont RelayServerHeartbeatsCleanup: true, LinuxDesktopHeartbeats: true, LinuxDesktopCleanup: true, + InstanceStatus: true, }.Build(), }.Build() if err := ics.Send(a.CloseContext(), downstreamHello); err != nil { diff --git a/lib/events/auditqueue/auditqueue.go b/lib/events/auditqueue/auditqueue.go index c923b84b37f5..77cde7e3cfbd 100644 --- a/lib/events/auditqueue/auditqueue.go +++ b/lib/events/auditqueue/auditqueue.go @@ -26,9 +26,14 @@ import ( "github.com/gravitational/trace" + apidefaults "github.com/gravitational/teleport/api/defaults" apievents "github.com/gravitational/teleport/api/types/events" ) +// DefaultDeliveryTimeout is the default bound on a single invocation of the +// delivery handler over one batch of events. +const DefaultDeliveryTimeout = apidefaults.DefaultIOTimeout + var ( // ErrQueueFull is returned by Enqueue when the queue has no room for more // events. @@ -78,8 +83,11 @@ type Config struct { // warning messages. SoftLimit int64 // MaxAttempts is the number of delivery failures before an event is moved - // to the dead-letter queue. Defaults to 10 if unset. + // to the dead-letter queue. Defaults to 3 if unset. MaxAttempts int + // DeliveryTimeout bounds a single invocation of the delivery handler over + // one batch of events. Defaults to DefaultDeliveryTimeout if unset. + DeliveryTimeout time.Duration // DeadLetterSweepInterval is how often the dead-letter sweeper re-attempts // delivery of failed events. DeadLetterSweepInterval time.Duration @@ -87,7 +95,9 @@ type Config struct { // permanently deleted. DeadLetterTTL time.Duration // Synchronous controls the SQLite synchronous pragma. - Synchronous SynchronousMode + Synchronous SynchronousMode + StatsInterval time.Duration + OnStatsUpdated func() } // Item is an event yielded to a Handler. @@ -96,6 +106,23 @@ type Item struct { Event apievents.AuditEvent } +// Stats reports the current depth of a Queue. +type Stats struct { + // PendingCount is the number of events waiting in the main queue. + PendingCount int64 + // DeadLetterCount is the number of events in the dead-letter queue. + DeadLetterCount int64 + // CorruptCount is the number of events quarantined because their payloads + // failed to deserialize. + CorruptCount int64 + // OldestPendingTime is when the oldest event in the main queue was + // enqueued. Zero when the main queue is empty. + OldestPendingTime time.Time + // OldestDeadLetterTime is when the oldest event in the dead-letter queue + // failed. Zero when the dead-letter queue is empty. + OldestDeadLetterTime time.Time +} + // Handler is the function type that the caller of the auditqueue implements. // It will take a batch of items to forward to the inner EmitAuditEvent. // It will return the slice of items that were successfully delivered. @@ -118,6 +145,9 @@ type Queue interface { // dead-letter queue to empty or for ctx to be done, whichever comes first. // Callers must still call Close. Drain(ctx context.Context) error + // Stats reports the current depth of the queue (pending and dead-letter + // counts). + Stats(ctx context.Context) (Stats, error) // Close releases resources held by the queue. Close() error } diff --git a/lib/events/auditqueue/metrics.go b/lib/events/auditqueue/metrics.go index dab107d5ea7f..091fd8fa5749 100644 --- a/lib/events/auditqueue/metrics.go +++ b/lib/events/auditqueue/metrics.go @@ -121,6 +121,33 @@ var corruptExpired = prometheus.NewCounter( }, ) +var queuePending = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_pending", + Help: "Number of audit events currently pending in the audit queue.", + }, + []string{"queue"}, +) + +var queueDeadLetter = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_dead_letter_pending", + Help: "Number of audit events currently waiting in the dead-letter queue.", + }, + []string{"queue"}, +) + +var queueCorrupt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: teleport.MetricNamespace, + Name: "audit_queue_corrupt_pending", + Help: "Number of audit events currently quarantined as corrupt.", + }, + []string{"queue"}, +) + var prometheusCollectors = []prometheus.Collector{ batchSize, orphansAdopted, @@ -134,4 +161,7 @@ var prometheusCollectors = []prometheus.Collector{ corruptEvents, corruptRecovered, corruptExpired, + queuePending, + queueDeadLetter, + queueCorrupt, } diff --git a/lib/events/auditqueue/sqlite.go b/lib/events/auditqueue/sqlite.go index da219dba7058..b44ab80688fb 100644 --- a/lib/events/auditqueue/sqlite.go +++ b/lib/events/auditqueue/sqlite.go @@ -26,6 +26,7 @@ import ( "log/slog" "net/url" "os" + "path/filepath" "strings" "sync" "time" @@ -44,7 +45,7 @@ const ( auditQueueTable = "audit_queue" auditDeadLetterTable = "audit_dead_letter" corruptEventsTable = "corrupt_events" - defaultMaxAttempts = 10 + defaultMaxAttempts = 3 defaultDeadLetterSweepInterval = 10 * time.Minute defaultDeadLetterTTL = 30 * 24 * time.Hour // 30 days maxDrainKickBackoff = 30 * time.Second @@ -77,11 +78,14 @@ const ( // guard against potential future changes. const schemaSQL = ` CREATE TABLE IF NOT EXISTS audit_queue ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL, - attempts INTEGER NOT NULL DEFAULT 0 + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL, + attempts INTEGER NOT NULL DEFAULT 0, + enqueued_at INTEGER NOT NULL DEFAULT (unixepoch()) ) STRICT; +CREATE INDEX IF NOT EXISTS idx_audit_queue_enqueued_at ON audit_queue(enqueued_at); + CREATE TABLE IF NOT EXISTS audit_dead_letter ( id INTEGER PRIMARY KEY AUTOINCREMENT, payload BLOB NOT NULL, @@ -147,6 +151,8 @@ type sqliteQueue struct { deadLetterSweepInterval time.Duration deadLetterTTL time.Duration synchronous SynchronousMode + statsInterval time.Duration + onStatsUpdated func() // recoveryWatermark is the highest corrupt_events id that // recoverCorruptEvents has already examined in this process. It only ever @@ -196,6 +202,11 @@ func newBaseQueue(db *sql.DB, cfg Config) (*sqliteQueue, error) { deadLetterTTL = defaultDeadLetterTTL } + statsInterval := cfg.StatsInterval + if statsInterval <= 0 { + statsInterval = defaultStatsInterval + } + ctx, cancel := context.WithCancel(context.Background()) q := &sqliteQueue{ db: db, @@ -210,6 +221,8 @@ func newBaseQueue(db *sql.DB, cfg Config) (*sqliteQueue, error) { deadLetterTTL: deadLetterTTL, synchronous: cfg.Synchronous, deadLetterKick: make(chan struct{}, 1), + statsInterval: statsInterval, + onStatsUpdated: cfg.OnStatsUpdated, } q.wg.Go(q.writeLoop) @@ -577,6 +590,71 @@ func drainQueueState(ctx context.Context, db *sql.DB) (mainEmpty bool, deadLette return mainEmpty, deadLetterCount, nil } +const statsQuery = `SELECT + (SELECT COUNT(*) FROM audit_queue), + (SELECT COUNT(*) FROM audit_dead_letter), + (SELECT COUNT(*) FROM corrupt_events), + (SELECT MIN(enqueued_at) FROM audit_queue), + (SELECT MIN(failed_at) FROM audit_dead_letter)` + +// Stats reports the current depth of the queue: the number of events pending in +// the main queue, the number in the dead-letter queue, and the number +// quarantined as corrupt, along with the age of the oldest event in the main +// and dead-letter queues. +func (q *sqliteQueue) Stats(ctx context.Context) (Stats, error) { + var stats Stats + var oldestPending, oldestDeadLetter sql.NullInt64 + if err := q.db.QueryRowContext(ctx, statsQuery).Scan( + &stats.PendingCount, + &stats.DeadLetterCount, + &stats.CorruptCount, + &oldestPending, + &oldestDeadLetter, + ); err != nil { + return Stats{}, trace.Wrap(err) + } + if oldestPending.Valid { + stats.OldestPendingTime = time.Unix(oldestPending.Int64, 0).UTC() + } + if oldestDeadLetter.Valid { + stats.OldestDeadLetterTime = time.Unix(oldestDeadLetter.Int64, 0).UTC() + } + return stats, nil +} + +func (q *sqliteQueue) statsLoop() { + timer := time.NewTimer(q.statsInterval) + defer timer.Stop() + for { + stats, err := q.Stats(q.ctx) + if err != nil { + if q.ctx.Err() == nil { + slog.ErrorContext(q.ctx, + "Failed to read audit queue stats for metrics.", + "path", q.path, + "error", err, + ) + } + } else { + if q.path != "" { + label := filepath.Base(q.path) + queuePending.WithLabelValues(label).Set(float64(stats.PendingCount)) + queueDeadLetter.WithLabelValues(label).Set(float64(stats.DeadLetterCount)) + queueCorrupt.WithLabelValues(label).Set(float64(stats.CorruptCount)) + } + if q.onStatsUpdated != nil { + q.onStatsUpdated() + } + } + timer.Reset(q.statsInterval) + select { + case <-q.ctx.Done(): + return + case <-timer.C: + } + } +} + func (q *sqliteQueue) handleDeliveryFailures(ctx context.Context, items []Item, successfullyDelivered []Item) { failed := itemsNotIn(items, successfullyDelivered) if len(failed) == 0 { diff --git a/lib/events/auditqueue/sqlite_disk.go b/lib/events/auditqueue/sqlite_disk.go index 6b84f704003e..f36424a0319b 100644 --- a/lib/events/auditqueue/sqlite_disk.go +++ b/lib/events/auditqueue/sqlite_disk.go @@ -46,6 +46,7 @@ const ( tmpDirSuffix = ".tmp" defaultSoftLimit = 100 * 1024 * 1024 // 100 MiB softLimitCheckInterval = time.Minute + defaultStatsInterval = 15 * time.Second initQueueDirMaxAttempts = 10 initQueueDirRetryDelay = 50 * time.Millisecond @@ -146,6 +147,7 @@ func newSQLiteQueue(cfg Config) (*sqliteQueue, error) { q.wg.Go(q.softLimitLoop) q.wg.Go(q.vacuumLoop) + q.wg.Go(q.statsLoop) return q, nil } @@ -559,8 +561,8 @@ func (q *sqliteQueue) migrateOrphanDB(ctx context.Context, db *sql.DB, name stri func (q *sqliteQueue) migrateOrphanQueue(ctx context.Context, orphan *sql.DB, name string) error { return q.migrateOrphanTable(ctx, orphan, name, auditQueueTable, - "SELECT id, payload, attempts FROM audit_queue WHERE id > ? ORDER BY id ASC LIMIT ?", - "INSERT INTO audit_queue (payload, attempts) VALUES (?, ?)", + "SELECT id, payload, attempts, enqueued_at FROM audit_queue WHERE id > ? ORDER BY id ASC LIMIT ?", + "INSERT INTO audit_queue (payload, attempts, enqueued_at) VALUES (?, ?, ?)", ) } @@ -710,6 +712,10 @@ func (q *sqliteQueue) Close() error { q.cancel() q.wg.Wait() + label := filepath.Base(q.path) + queuePending.DeleteLabelValues(label) + queueDeadLetter.DeleteLabelValues(label) + // Flush the WAL file. if _, err := q.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil { slog.ErrorContext(q.ctx, diff --git a/lib/events/auditqueue/sqlite_memory.go b/lib/events/auditqueue/sqlite_memory.go index 56bb835fcfe1..0861c0b48cb5 100644 --- a/lib/events/auditqueue/sqlite_memory.go +++ b/lib/events/auditqueue/sqlite_memory.go @@ -105,6 +105,8 @@ func newSQLiteInMemoryQueue(cfg Config) (*sqliteInMemoryQueue, error) { return nil, trace.Wrap(err) } + inner.wg.Go(inner.statsLoop) + return &sqliteInMemoryQueue{ inner: inner, id: id, @@ -137,6 +139,11 @@ func (m *sqliteInMemoryQueue) Drain(ctx context.Context) error { return m.inner.Drain(ctx) } +// Stats reports the current depth of the in-memory queue. See Queue.Stats. +func (m *sqliteInMemoryQueue) Stats(ctx context.Context) (Stats, error) { + return m.inner.Stats(ctx) +} + // Close shuts down the in-memory queue. func (m *sqliteInMemoryQueue) Close() error { var err error diff --git a/lib/events/auditqueue/sqlite_test.go b/lib/events/auditqueue/sqlite_test.go index 16e0ffdff39d..a3e812fbcc38 100644 --- a/lib/events/auditqueue/sqlite_test.go +++ b/lib/events/auditqueue/sqlite_test.go @@ -93,6 +93,124 @@ func TestEnqueueDequeue_FIFO(t *testing.T) { require.NoError(t, q.ack(got)) } +func TestStats_CountsPendingDeadLetterAndCorrupt(t *testing.T) { + t.Parallel() + ctx := t.Context() + q, err := newSQLiteQueue(Config{ + Path: filepath.Join(t.TempDir(), queueDir), + MaxAttempts: 1, // promote on the first failure + }) + require.NoError(t, err) + t.Cleanup(func() { _ = q.Close() }) + + stats, err := q.Stats(ctx) + require.NoError(t, err) + require.Zero(t, stats.PendingCount) + require.Zero(t, stats.DeadLetterCount) + require.Zero(t, stats.CorruptCount) + + const n = 5 + for i := int64(0); i < n; i++ { + require.NoError(t, q.Enqueue(newTestEvent(i))) + } + stats, err = q.Stats(ctx) + require.NoError(t, err) + require.Equal(t, int64(n), stats.PendingCount) + require.Zero(t, stats.DeadLetterCount) + + items, err := q.fetch(2) + require.NoError(t, err) + require.Len(t, items, 2) + promoted, err := q.processFailedDeliveries(ctx, items) + require.NoError(t, err) + require.Equal(t, 2, promoted) + + stats, err = q.Stats(ctx) + require.NoError(t, err) + require.Equal(t, int64(n-2), stats.PendingCount, "promoted events should leave the main queue") + require.Equal(t, int64(2), stats.DeadLetterCount) + + _, err = q.db.Exec( + "INSERT INTO corrupt_events (payload, error, source, failed_at) VALUES (?, 'boom', 'audit_queue', ?)", + corruptPayload, time.Now().Unix()) + require.NoError(t, err) + + stats, err = q.Stats(ctx) + require.NoError(t, err) + require.Equal(t, int64(n-2), stats.PendingCount) + require.Equal(t, int64(2), stats.DeadLetterCount) + require.Equal(t, int64(1), stats.CorruptCount) +} + +func TestStats_OldestEventTimes(t *testing.T) { + t.Parallel() + ctx := t.Context() + q := newSqliteTestQueue(t) + + stats, err := q.Stats(ctx) + require.NoError(t, err) + require.Zero(t, stats.OldestPendingTime, "empty main queue must report a zero oldest time") + require.Zero(t, stats.OldestDeadLetterTime, "empty dead-letter queue must report a zero oldest time") + + _, err = q.db.Exec( + "INSERT INTO audit_queue (payload, enqueued_at) VALUES (?, 2000), (?, 1000), (?, 3000)", + corruptPayload, corruptPayload, corruptPayload) + require.NoError(t, err) + _, err = q.db.Exec( + "INSERT INTO audit_dead_letter (payload, failed_at) VALUES (?, 900), (?, 500)", + corruptPayload, corruptPayload) + require.NoError(t, err) + + stats, err = q.Stats(ctx) + require.NoError(t, err) + require.Equal(t, time.Unix(1000, 0).UTC(), stats.OldestPendingTime) + require.Equal(t, time.Unix(500, 0).UTC(), stats.OldestDeadLetterTime) +} + +func TestStats_OnStatsUpdatedSignals(t *testing.T) { + t.Parallel() + for _, kind := range []Kind{KindSQLiteDisk, KindSQLiteMemory} { + t.Run(string(kind), func(t *testing.T) { + t.Parallel() + signaled := make(chan struct{}, 1) + q, err := New(kind, Config{ + Path: filepath.Join(t.TempDir(), queueDir), + StatsInterval: 10 * time.Millisecond, + OnStatsUpdated: func() { + select { + case signaled <- struct{}{}: + default: + } + }, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, q.Close()) }) + + select { + case <-signaled: + case <-time.After(10 * time.Second): + t.Fatal("timed out waiting for stats signal") + } + }) + } +} + +func TestEnqueue_StampsEnqueuedAt(t *testing.T) { + t.Parallel() + q := newSqliteTestQueue(t) + + before := time.Now().Add(-time.Second) + require.NoError(t, q.Enqueue(newTestEvent(0))) + after := time.Now().Add(time.Second) + + stats, err := q.Stats(t.Context()) + require.NoError(t, err) + require.False(t, stats.OldestPendingTime.Before(before), + "enqueued_at default should stamp the enqueue time") + require.False(t, stats.OldestPendingTime.After(after), + "enqueued_at default should stamp the enqueue time") +} + func TestDequeue_RespectsLimit(t *testing.T) { t.Parallel() q := newSqliteTestQueue(t) @@ -653,6 +771,28 @@ func TestMigrateOrphanQueue_PreservesAttempts(t *testing.T) { require.Equal(t, 2, attempts, "attempt count should carry over during migration") } +func TestMigrateOrphanQueue_PreservesEnqueuedAt(t *testing.T) { + t.Parallel() + + a, err := newSQLiteQueue(Config{Path: filepath.Join(t.TempDir(), "a")}) + require.NoError(t, err) + t.Cleanup(func() { _ = a.Close() }) + require.NoError(t, a.Enqueue(newTestEvent(42))) + _, err = a.db.Exec("UPDATE audit_queue SET enqueued_at = 1234") + require.NoError(t, err) + + b, err := newSQLiteQueue(Config{Path: filepath.Join(t.TempDir(), "b")}) + require.NoError(t, err) + t.Cleanup(func() { _ = b.Close() }) + + require.NoError(t, b.migrateOrphanQueue(t.Context(), a.db, "a")) + + stats, err := b.Stats(t.Context()) + require.NoError(t, err) + require.Equal(t, time.Unix(1234, 0).UTC(), stats.OldestPendingTime, + "enqueued_at should carry over during migration") +} + func TestMigrateOrphanQueue_WatermarkPreventsDuplicates(t *testing.T) { t.Parallel() diff --git a/lib/events/emitter.go b/lib/events/emitter.go index 7e3f358cb1aa..3d1227a2d80a 100644 --- a/lib/events/emitter.go +++ b/lib/events/emitter.go @@ -98,13 +98,19 @@ func NewAsyncEmitter(cfg AsyncEmitterConfig) (*AsyncEmitter, error) { slog.InfoContext(context.TODO(), "Audit queue is enabled.") } + deliveryTimeout := cfg.AuditQueueCfg.DeliveryTimeout + if deliveryTimeout <= 0 { + deliveryTimeout = auditqueue.DefaultDeliveryTimeout + } + ctx, cancel := context.WithCancel(context.Background()) a := &AsyncEmitter{ - cancel: cancel, - ctx: ctx, - eventsCh: make(chan apievents.AuditEvent, cfg.BufferSize), - cfg: cfg, - queue: queue, + cancel: cancel, + ctx: ctx, + eventsCh: make(chan apievents.AuditEvent, cfg.BufferSize), + cfg: cfg, + queue: queue, + deliveryTimeout: deliveryTimeout, } if queue != nil { a.wg.Go(func() { @@ -151,12 +157,13 @@ func makeQueue(dataDir string, queueCfg auditqueue.Config, backends []auditqueue // AsyncEmitter accepts events to a buffered channel and emits // events in a separate goroutine without blocking the caller. type AsyncEmitter struct { - cfg AsyncEmitterConfig - eventsCh chan apievents.AuditEvent - cancel context.CancelFunc - ctx context.Context - queue auditqueue.Queue - wg sync.WaitGroup + cfg AsyncEmitterConfig + eventsCh chan apievents.AuditEvent + cancel context.CancelFunc + ctx context.Context + queue auditqueue.Queue + deliveryTimeout time.Duration + wg sync.WaitGroup } // Close closes emitter and cancels all in flight events. @@ -185,6 +192,15 @@ func (a *AsyncEmitter) Shutdown(ctx context.Context) error { return trace.Wrap(a.Close()) } +// Stats reports the current depth of the audit queue. It returns a zero Stats +// when the audit queue is disabled. +func (a *AsyncEmitter) Stats(ctx context.Context) (auditqueue.Stats, error) { + if a.queue == nil { + return auditqueue.Stats{}, nil + } + return a.queue.Stats(ctx) +} + func (a *AsyncEmitter) forward() { for { select { @@ -208,6 +224,9 @@ func (a *AsyncEmitter) forward() { func (a *AsyncEmitter) deliver(ctx context.Context, items []auditqueue.Item) []auditqueue.Item { var successfullyDelivered []auditqueue.Item + ctx, cancel := context.WithTimeout(ctx, a.deliveryTimeout) + defer cancel() + // TODO(kkloberdanz): We plan to update the Emitter interface such that // EmitAuditEvent will take a slice of events rather than a single event at // a time. This will allow us to add batching as a native feature of this @@ -367,6 +386,12 @@ func (c *CheckingAsyncEmitter) Shutdown(ctx context.Context) error { return c.asyncEmitter.Shutdown(ctx) } +// Stats reports the current depth of the underlying audit queue. +// See AsyncEmitter.Stats. +func (c *CheckingAsyncEmitter) Stats(ctx context.Context) (auditqueue.Stats, error) { + return c.asyncEmitter.Stats(ctx) +} + // checkAndSetEventFields updates passed event fields with additional information // common for all event types such as unique IDs, timestamps, codes, etc. // diff --git a/lib/events/fallback_emitter.go b/lib/events/fallback_emitter.go index 85d6162aec29..731f8a40c169 100644 --- a/lib/events/fallback_emitter.go +++ b/lib/events/fallback_emitter.go @@ -22,6 +22,7 @@ import ( "context" "log/slog" "sync" + "time" "github.com/gravitational/trace" "github.com/prometheus/client_golang/prometheus" @@ -87,11 +88,12 @@ func (c *FallbackEmitterConfig) CheckAndSetDefaults() error { // // See: https://github.com/gravitational/teleport.e/blob/rfd/0254-sqlite-audit-log-event-queue/rfd/0254-sqlite-audit-log-event-queue.md#auth-server--kubernetes-and-cloud-considerations type FallbackEmitter struct { - cfg FallbackEmitterConfig - queue auditqueue.Queue - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup + cfg FallbackEmitterConfig + queue auditqueue.Queue + ctx context.Context + cancel context.CancelFunc + deliveryTimeout time.Duration + wg sync.WaitGroup } // NewFallbackEmitter returns a FallbackEmitter wrapping cfg.Primary. @@ -112,12 +114,18 @@ func NewFallbackEmitter(cfg FallbackEmitterConfig) (*FallbackEmitter, error) { } } + deliveryTimeout := cfg.AuditQueueCfg.DeliveryTimeout + if deliveryTimeout <= 0 { + deliveryTimeout = auditqueue.DefaultDeliveryTimeout + } + ctx, cancel := context.WithCancel(context.Background()) f := &FallbackEmitter{ - cfg: cfg, - queue: queue, - ctx: ctx, - cancel: cancel, + cfg: cfg, + queue: queue, + ctx: ctx, + cancel: cancel, + deliveryTimeout: deliveryTimeout, } if queue != nil { slog.InfoContext(ctx, "Audit fallback queue is enabled.") @@ -165,6 +173,10 @@ func (f *FallbackEmitter) EmitAuditEvent(ctx context.Context, event apievents.Au // backend and returns the subset that were successfully delivered. func (f *FallbackEmitter) deliver(ctx context.Context, items []auditqueue.Item) []auditqueue.Item { var delivered []auditqueue.Item + + ctx, cancel := context.WithTimeout(ctx, f.deliveryTimeout) + defer cancel() + for _, item := range items { if ctx.Err() != nil { return delivered @@ -201,3 +213,12 @@ func (f *FallbackEmitter) Shutdown(ctx context.Context) error { } return trace.Wrap(f.Close()) } + +// Stats reports the current depth of the fallback queue. It returns a zero Stats +// when the fallback queue is disabled. +func (f *FallbackEmitter) Stats(ctx context.Context) (auditqueue.Stats, error) { + if f.queue == nil { + return auditqueue.Stats{}, nil + } + return f.queue.Stats(ctx) +} diff --git a/lib/events/fallback_emitter_test.go b/lib/events/fallback_emitter_test.go index a8dd8532df77..740010e4c6b1 100644 --- a/lib/events/fallback_emitter_test.go +++ b/lib/events/fallback_emitter_test.go @@ -34,9 +34,10 @@ import ( // fakePrimary mocks an audit backend and can be toggled to simulate failures. type fakePrimary struct { - mu sync.Mutex - fail bool - got []apievents.AuditEvent + mu sync.Mutex + fail bool + block bool + got []apievents.AuditEvent } func (f *fakePrimary) setFail(fail bool) { @@ -45,18 +46,31 @@ func (f *fakePrimary) setFail(fail bool) { f.fail = fail } +func (f *fakePrimary) setBlock(block bool) { + f.mu.Lock() + defer f.mu.Unlock() + f.block = block +} + func (f *fakePrimary) count() int { f.mu.Lock() defer f.mu.Unlock() return len(f.got) } -func (f *fakePrimary) EmitAuditEvent(_ context.Context, event apievents.AuditEvent) error { +func (f *fakePrimary) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error { f.mu.Lock() - defer f.mu.Unlock() - if f.fail { + fail, block := f.fail, f.block + f.mu.Unlock() + if block { + <-ctx.Done() + return trace.Wrap(ctx.Err()) + } + if fail { return trace.ConnectionProblem(nil, "backend unavailable") } + f.mu.Lock() + defer f.mu.Unlock() f.got = append(f.got, event) return nil } @@ -183,6 +197,84 @@ func TestFallbackEmitter_CloseDoesNotDrain(t *testing.T) { }, time.Second, 50*time.Millisecond, "Close must not drain queued events") } +func TestFallbackEmitter_SlowBackendBoundedByDeliveryTimeout(t *testing.T) { + t.Parallel() + primary := &fakePrimary{} + primary.setBlock(true) + + e, err := events.NewFallbackEmitter(events.FallbackEmitterConfig{ + Primary: primary, + DataDir: t.TempDir(), + EnableAuditQueue: true, + AuditQueueCfg: auditqueue.Config{ + MaxAttempts: 2, + DeliveryTimeout: 50 * time.Millisecond, + DeadLetterSweepInterval: 50 * time.Millisecond, + }, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, e.Close()) }) + + // The backend hangs on every emit, so the direct emit fails on the caller's + // deadline and the event falls back to the queue. + const n = 3 + for range n { + emitCtx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond) + require.NoError(t, e.EmitAuditEvent(emitCtx, testEvent())) + cancel() + } + + // Without the delivery timeout the consumer would hang inside the first + // delivery attempt forever and the events would never be promoted. + require.Eventually(t, func() bool { + stats, err := e.Stats(t.Context()) + return err == nil && stats.DeadLetterCount == n + }, 5*time.Second, 25*time.Millisecond, + "slow-failing delivery must be bounded by the delivery timeout and promoted to dead-letter") + + primary.setBlock(false) + require.Eventually(t, func() bool { + return primary.count() == n + }, 5*time.Second, 25*time.Millisecond, "dead-letter events should be delivered once the backend recovers") +} + +func TestAsyncEmitter_SlowBackendBoundedByDeliveryTimeout(t *testing.T) { + t.Parallel() + inner := &fakePrimary{} + inner.setBlock(true) + + a, err := events.NewAsyncEmitter(events.AsyncEmitterConfig{ + Inner: inner, + DataDir: t.TempDir(), + EnableAuditQueue: true, + AuditQueueCfg: auditqueue.Config{ + MaxAttempts: 2, + DeliveryTimeout: 50 * time.Millisecond, + DeadLetterSweepInterval: 50 * time.Millisecond, + }, + AuditQueueBackends: []auditqueue.Kind{auditqueue.KindSQLiteMemory}, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, a.Close()) }) + + const n = 3 + for range n { + require.NoError(t, a.EmitAuditEvent(t.Context(), testEvent())) + } + + require.Eventually(t, func() bool { + stats, err := a.Stats(t.Context()) + return err == nil && stats.DeadLetterCount == n + }, 5*time.Second, 25*time.Millisecond, + "slow-failing delivery must be bounded by the delivery timeout and promoted to dead-letter") + + inner.setBlock(false) + require.Eventually(t, func() bool { + return inner.count() == n + }, 5*time.Second, 25*time.Millisecond, "dead-letter events should be delivered once the backend recovers") +} + type authBoundary struct { fallback apievents.Emitter } diff --git a/lib/inventory/controller.go b/lib/inventory/controller.go index dc858980cf72..f7e654757770 100644 --- a/lib/inventory/controller.go +++ b/lib/inventory/controller.go @@ -547,6 +547,10 @@ func (c *Controller) handleControlStream(handle *upstreamHandle) { return case *proto.UpstreamInventoryAgentMetadata: c.handleAgentMetadata(handle, m) + case *proto.InstanceStatus: + if m.HasAuditQueue() { + handle.setAuditQueueStatus(m.GetAuditQueue()) + } case *proto.InventoryHeartbeat: // XXX: when adding new services to the heartbeat logic, make // sure to also update the 'icsServiceToMetricName' mapping in @@ -869,7 +873,7 @@ func (c *Controller) heartbeatInstanceState(handle *upstreamHandle, now time.Tim fn() } - instance, err := tracker.nextHeartbeat(now, handle.Hello(), c.authID) + instance, err := tracker.nextHeartbeat(now, handle.Hello(), c.authID, handle.AuditQueueStatus()) if err != nil { slog.WarnContext(c.closeContext, "Failed to construct next heartbeat value for instance (this is a bug)", "server_id", handle.Hello().GetServerID(), diff --git a/lib/inventory/controller_test.go b/lib/inventory/controller_test.go index 477d695bd6af..47e39e9cd9bc 100644 --- a/lib/inventory/controller_test.go +++ b/lib/inventory/controller_test.go @@ -1826,6 +1826,125 @@ func testAgentMetadata(t *testing.T) { }, 10*time.Second, 200*time.Millisecond) } +func TestInstanceStatus(t *testing.T) { + tests := []struct { + name string + supportsInstanceStatus bool + expectStatus bool + }{ + { + name: "reported when auth advertises support", + supportsInstanceStatus: true, + expectStatus: true, + }, + { + name: "withheld when auth does not advertise support", + supportsInstanceStatus: false, + expectStatus: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + synctest.Test(t, func(t *testing.T) { + testInstanceStatus(t, test.supportsInstanceStatus, test.expectStatus) + }) + }) + } +} + +func testInstanceStatus(t *testing.T, supportsInstanceStatus, expectStatus bool) { + const serverID = "test-instance" + const peerAddr = "1.2.3.4:456" + + events := make(chan testEvent, 1024) + statusSignal := make(chan struct{}, 1) + + auth := &fakeAuth{} + + controller := NewController( + auth, + usagereporter.DiscardUsageReporter{}, + withInstanceHBInterval(time.Millisecond*200), + withTestEventsChannel(events), + ) + defer controller.Close() + + upstream, downstream := client.InventoryControlStreamPipe(client.ICSPipePeerAddr(peerAddr)) + upstreamHello := proto.UpstreamInventoryHello_builder{ + ServerID: serverID, + Version: teleport.Version, + Services: types.SystemRoles{types.RoleNode}.StringSlice(), + }.Build() + downstreamHello := proto.DownstreamInventoryHello_builder{ + Version: teleport.Version, + ServerID: "auth", + Capabilities: proto.DownstreamInventoryHello_SupportedCapabilities_builder{ + InstanceStatus: supportsInstanceStatus, + }.Build(), + }.Build() + + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + + inventoryHandle, err := NewDownstreamHandle( + func(ctx context.Context) (client.DownstreamInventoryControlStream, error) { + return downstream, nil + }, + func(ctx context.Context) (*proto.UpstreamInventoryHello, error) { + return upstreamHello, nil + }, + withMetadataGetter(func(ctx context.Context) (*metadata.Metadata, error) { + return &metadata.Metadata{}, nil + }), + WithAuditQueueStatusGetter(func(ctx context.Context) *types.AuditQueueStatus { + return &types.AuditQueueStatus{ + PendingCount: 7, + DeadLetterCount: 3, + CorruptCount: 1, + OldestPendingAgeSeconds: 42, + } + }), + WithAuditQueueStatusSignal(statusSignal), + ) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, inventoryHandle.Close()) }) + + select { + case msg := <-upstream.Recv(): + require.Equal(t, upstreamHello, msg) + case <-ctx.Done(): + require.Fail(t, "never got upstream hello") + } + require.NoError(t, upstream.Send(ctx, downstreamHello)) + controller.RegisterControlStream(upstream, upstreamHello) + statusSignal <- struct{}{} + + handle, ok := controller.GetControlStream(serverID) + require.True(t, ok) + upstreamHandle, ok := handle.(*upstreamHandle) + require.True(t, ok) + + if !expectStatus { + require.Never(t, func() bool { + return upstreamHandle.AuditQueueStatus() != nil + }, 5*time.Second, 200*time.Millisecond) + return + } + + require.EventuallyWithT(t, func(t *assert.CollectT) { + status := upstreamHandle.AuditQueueStatus() + if !assert.NotNil(t, status) { + return + } + assert.Equal(t, int64(7), status.PendingCount) + assert.Equal(t, int64(3), status.DeadLetterCount) + assert.Equal(t, int64(1), status.CorruptCount) + assert.Equal(t, int64(42), status.OldestPendingAgeSeconds) + }, 10*time.Second, 200*time.Millisecond) +} + func TestGoodbye(t *testing.T) { tests := []struct { name string diff --git a/lib/inventory/inventory.go b/lib/inventory/inventory.go index e7ac047b30ad..6b9c8a02fbc8 100644 --- a/lib/inventory/inventory.go +++ b/lib/inventory/inventory.go @@ -98,8 +98,10 @@ type DownstreamSender interface { } type downstreamHandleOptions struct { - metadataGetter func(ctx context.Context) (*metadata.Metadata, error) - clock clockwork.Clock + metadataGetter func(ctx context.Context) (*metadata.Metadata, error) + auditQueueStatusGetter func(ctx context.Context) *types.AuditQueueStatus + auditQueueStatusSignal <-chan struct{} + clock clockwork.Clock } func (options *downstreamHandleOptions) SetDefaults() { @@ -126,6 +128,18 @@ func WithDownstreamClock(clock clockwork.Clock) DownstreamHandleOption { } } +func WithAuditQueueStatusGetter(getter func(ctx context.Context) *types.AuditQueueStatus) DownstreamHandleOption { + return func(opts *downstreamHandleOptions) { + opts.auditQueueStatusGetter = getter + } +} + +func WithAuditQueueStatusSignal(signal <-chan struct{}) DownstreamHandleOption { + return func(opts *downstreamHandleOptions) { + opts.auditQueueStatusSignal = signal + } +} + // NewDownstreamHandle creates a new downstream inventory control handle which will create control streams via the // supplied create func and manage hello exchange with the supplied upstream hello. func NewDownstreamHandle(fn DownstreamCreateFunc, hello HelloGetter, opts ...DownstreamHandleOption) (DownstreamHandle, error) { @@ -160,33 +174,38 @@ func NewDownstreamHandle(fn DownstreamCreateFunc, hello HelloGetter, opts ...Dow } handle := &downstreamHandle{ - senderC: make(chan DownstreamSender), - pingHandlers: make(map[uint64]DownstreamPingHandler), - closeContext: ctx, - cancel: cancel, - metadataGetter: options.metadataGetter, - clock: options.clock, - helloGetter: cachedHelloGetter, + senderC: make(chan DownstreamSender), + pingHandlers: make(map[uint64]DownstreamPingHandler), + closeContext: ctx, + cancel: cancel, + metadataGetter: options.metadataGetter, + auditQueueStatusGetter: options.auditQueueStatusGetter, + auditQueueStatusSignal: options.auditQueueStatusSignal, + clock: options.clock, + helloGetter: cachedHelloGetter, } go handle.run(fn) go handle.autoEmitMetadata() go handle.autoEmitGoodbye() + go handle.autoEmitInstanceStatus() return handle, nil } type downstreamHandle struct { - sender atomic.Pointer[downstreamSender] - mu sync.Mutex - handlerNonce uint64 - pingHandlers map[uint64]DownstreamPingHandler - senderC chan DownstreamSender - closeContext context.Context - cancel context.CancelFunc - upstreamSSHLabels map[string]string - metadataGetter func(ctx context.Context) (*metadata.Metadata, error) - clock clockwork.Clock - helloGetter HelloGetter - goodbye atomic.Pointer[proto.UpstreamInventoryGoodbye] + sender atomic.Pointer[downstreamSender] + mu sync.Mutex + handlerNonce uint64 + pingHandlers map[uint64]DownstreamPingHandler + senderC chan DownstreamSender + closeContext context.Context + cancel context.CancelFunc + upstreamSSHLabels map[string]string + metadataGetter func(ctx context.Context) (*metadata.Metadata, error) + auditQueueStatusGetter func(ctx context.Context) *types.AuditQueueStatus + auditQueueStatusSignal <-chan struct{} + clock clockwork.Clock + helloGetter HelloGetter + goodbye atomic.Pointer[proto.UpstreamInventoryGoodbye] } func (h *downstreamHandle) closing() bool { @@ -265,6 +284,53 @@ func (h *downstreamHandle) autoEmitMetadata() { } } +func (h *downstreamHandle) autoEmitInstanceStatus() { + if h.auditQueueStatusGetter == nil || h.auditQueueStatusSignal == nil { + return + } + + send := func(sender DownstreamSender) { + status := h.auditQueueStatusGetter(h.CloseContext()) + if status == nil { + return + } + msg := proto.InstanceStatus_builder{AuditQueue: status}.Build() + if err := sender.Send(h.CloseContext(), msg); err != nil && !errors.Is(err, context.Canceled) { + slog.WarnContext(h.CloseContext(), "Failed to send instance status", "error", err) + } + } + + for { + var sender DownstreamSender + select { + case sender = <-h.Sender(): + case <-h.CloseContext().Done(): + return + } + + if !sender.Hello().GetCapabilities().GetInstanceStatus() { + select { + case <-sender.Done(): + continue + case <-h.CloseContext().Done(): + return + } + } + + streamLoop: + for { + send(sender) + select { + case <-h.auditQueueStatusSignal: + case <-sender.Done(): + break streamLoop + case <-h.CloseContext().Done(): + return + } + } + } +} + func (h *downstreamHandle) run(fn DownstreamCreateFunc) { retry := utils.NewDefaultLinear(h.clock) for { @@ -636,7 +702,7 @@ func (i *instanceStateTracker) WithLock(fn func()) { } // nextHeartbeat calculates the next heartbeat value. *Must* be called only while lock is held. -func (i *instanceStateTracker) nextHeartbeat(now time.Time, hello *proto.UpstreamInventoryHello, authID string) (types.Instance, error) { +func (i *instanceStateTracker) nextHeartbeat(now time.Time, hello *proto.UpstreamInventoryHello, authID string, auditQueueStatus *types.AuditQueueStatus) (types.Instance, error) { var lastMeasurement *types.SystemClockMeasurement if !i.pingResponse.systemClock.IsZero() { lastMeasurement = &types.SystemClockMeasurement{ @@ -661,6 +727,7 @@ func (i *instanceStateTracker) nextHeartbeat(now time.Time, hello *proto.Upstrea ExternalUpgraderVersion: vc.Normalize(hello.GetExternalUpgraderVersion()), LastMeasurement: lastMeasurement, UpdaterInfo: hello.GetUpdaterInfo(), + AuditQueueStatus: auditQueueStatus, }) if err != nil { return nil, trace.Wrap(err) @@ -687,8 +754,9 @@ type upstreamHandle struct { hello *proto.UpstreamInventoryHello registrationTime time.Time - agentMetadata atomic.Pointer[proto.UpstreamInventoryAgentMetadata] - goodbye atomic.Pointer[proto.UpstreamInventoryGoodbye] + agentMetadata atomic.Pointer[proto.UpstreamInventoryAgentMetadata] + auditQueueStatus atomic.Pointer[types.AuditQueueStatus] + goodbye atomic.Pointer[proto.UpstreamInventoryGoodbye] pingC chan pingRequest @@ -828,6 +896,18 @@ func (h *upstreamHandle) setAgentMetadata(agentMD *proto.UpstreamInventoryAgentM h.agentMetadata.Store(agentMD) } +// AuditQueueStatus returns the most recent audit-queue depth reported by this +// instance, or nil if none has been reported. +func (h *upstreamHandle) AuditQueueStatus() *types.AuditQueueStatus { + return h.auditQueueStatus.Load() +} + +// setAuditQueueStatus stores the latest audit-queue depth reported by this +// instance. +func (h *upstreamHandle) setAuditQueueStatus(status *types.AuditQueueStatus) { + h.auditQueueStatus.Store(status) +} + // HasService is a helper for checking if a given service is associated with this // stream. func (h *upstreamHandle) HasService(service types.SystemRole) bool { diff --git a/lib/service/auditqueue_status_test.go b/lib/service/auditqueue_status_test.go new file mode 100644 index 000000000000..5825db8c482d --- /dev/null +++ b/lib/service/auditqueue_status_test.go @@ -0,0 +1,98 @@ +/* + * Teleport + * Copyright (C) 2026 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 service + +import ( + "context" + "errors" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/lib/events/auditqueue" +) + +type fakeEmitter struct { + stats auditqueue.Stats + err error +} + +func (f *fakeEmitter) Stats(context.Context) (auditqueue.Stats, error) { + return f.stats, f.err +} + +func (f *fakeEmitter) Shutdown(context.Context) error { + return nil +} + +func (f *fakeEmitter) Close() error { + return nil +} + +func TestAuditQueueStatusAggregation(t *testing.T) { + ctx := t.Context() + process := &TeleportProcess{logger: slog.Default()} + + // No emitters registered -> nil status (audit queue disabled / not present). + require.Nil(t, process.AuditQueueStatus(ctx)) + + now := time.Now() + a := &fakeEmitter{stats: auditqueue.Stats{ + PendingCount: 3, + DeadLetterCount: 1, + CorruptCount: 4, + OldestPendingTime: now.Add(-10 * time.Minute), + }} + b := &fakeEmitter{stats: auditqueue.Stats{ + PendingCount: 5, + DeadLetterCount: 2, + CorruptCount: 6, + OldestPendingTime: now.Add(-20 * time.Minute), + OldestDeadLetterTime: now.Add(-time.Hour), + }} + process.registerEmitter(a) + process.registerEmitter(b) + + // Depth is summed across all registered emitters; the oldest ages take + // the largest non-zero value, ignoring emitters with empty queues. + status := process.AuditQueueStatus(ctx) + require.NotNil(t, status) + require.Equal(t, int64(8), status.PendingCount) + require.Equal(t, int64(3), status.DeadLetterCount) + require.Equal(t, int64(10), status.CorruptCount) + require.InDelta(t, 20*60, status.OldestPendingAgeSeconds, 5) + require.InDelta(t, 60*60, status.OldestDeadLetterAgeSeconds, 5) + + // An erroring getter is skipped, not fatal. + c := &fakeEmitter{err: errors.New("queue closed")} + process.registerEmitter(c) + status = process.AuditQueueStatus(ctx) + require.Equal(t, int64(8), status.PendingCount) + require.Equal(t, int64(3), status.DeadLetterCount) + require.Equal(t, int64(10), status.CorruptCount) + + // Unregistering drops the emitter from the sum. + process.unregisterEmitter(b) + status = process.AuditQueueStatus(ctx) + require.Equal(t, int64(3), status.PendingCount) + require.Equal(t, int64(1), status.DeadLetterCount) + require.Equal(t, int64(4), status.CorruptCount) +} diff --git a/lib/service/service.go b/lib/service/service.go index eadde605b5b7..ae39254755a9 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -739,6 +739,14 @@ type TeleportProcess struct { // logger is a process-local slog.Logger. logger *slog.Logger + // emittersMu guards emitters. + emittersMu sync.Mutex + // emitters holds the audit emitters the process has created, keyed by the + // emitter pointer. AuditQueueStatus sums their queue depths so the instance + // heartbeat reports the process total. + emitters map[processEmitter]struct{} + auditQueueStatusSignalC chan struct{} + // reporter is used to report some in memory stats reporter *backend.Reporter @@ -1358,23 +1366,24 @@ func NewTeleport(cfg *servicecfg.Config) (_ *TeleportProcess, err error) { scopesFeatures := cfg.ScopesFeatures process := &TeleportProcess{ - PluginRegistry: cfg.PluginRegistry, - Clock: cfg.Clock, - Supervisor: supervisor, - Config: cfg, - instanceConnectorReady: make(chan struct{}), - instanceRoles: make(map[types.SystemRole]string), - hostedPluginRoles: make(map[types.SystemRole]string), - connectors: make(map[types.SystemRole]*Connector), - importedDescriptors: cfg.FileDescriptors, - storage: store, - rotationCache: rotationCache, - id: processID, - logger: cfg.Logger, - cloudLabels: cloudLabels, - scopesFeatures: scopesFeatures, - TracingProvider: tracing.NoopProvider(), - metricsRegistry: metricsRegistry, + PluginRegistry: cfg.PluginRegistry, + Clock: cfg.Clock, + Supervisor: supervisor, + Config: cfg, + instanceConnectorReady: make(chan struct{}), + instanceRoles: make(map[types.SystemRole]string), + auditQueueStatusSignalC: make(chan struct{}, 1), + hostedPluginRoles: make(map[types.SystemRole]string), + connectors: make(map[types.SystemRole]*Connector), + importedDescriptors: cfg.FileDescriptors, + storage: store, + rotationCache: rotationCache, + id: processID, + logger: cfg.Logger, + cloudLabels: cloudLabels, + scopesFeatures: scopesFeatures, + TracingProvider: tracing.NoopProvider(), + metricsRegistry: metricsRegistry, SyncGatherers: metrics.NewSyncGatherers( rootMetricRegistry, prometheus.DefaultGatherer, @@ -1464,6 +1473,8 @@ func NewTeleport(cfg *servicecfg.Config) (_ *TeleportProcess, err error) { process.makeInventoryControlStreamWhenReady, getHello, inventory.WithDownstreamClock(process.Clock), + inventory.WithAuditQueueStatusGetter(process.AuditQueueStatus), + inventory.WithAuditQueueStatusSignal(process.auditQueueStatusSignalC), ) if err != nil { return nil, trace.Wrap(err, "building inventory handle") @@ -3500,7 +3511,7 @@ func isAuditQueueEnabled() bool { func (process *TeleportProcess) NewAsyncEmitter(clt apievents.Emitter) (*events.CheckingAsyncEmitter, error) { // Wrap the AsyncEmitter in a CheckingEmitter to ensure event fields are // properly set before inserting events into the queue. - return events.NewCheckingAsyncEmitter( + emitter, err := events.NewCheckingAsyncEmitter( events.CheckingEmitterConfig{ Clock: process.Clock, }, @@ -3512,16 +3523,90 @@ func (process *TeleportProcess) NewAsyncEmitter(clt apievents.Emitter) (*events. AuditQueueBackends: process.auditQueueBackends(), }, ) + if err != nil { + return nil, trace.Wrap(err) + } + process.registerEmitter(emitter) + return emitter, nil +} + +type processEmitter interface { + Stats(ctx context.Context) (auditqueue.Stats, error) + Shutdown(context.Context) error + Close() error +} + +func (process *TeleportProcess) registerEmitter(emitter processEmitter) { + process.emittersMu.Lock() + defer process.emittersMu.Unlock() + if process.emitters == nil { + process.emitters = make(map[processEmitter]struct{}) + } + process.emitters[emitter] = struct{}{} +} + +func (process *TeleportProcess) unregisterEmitter(emitter processEmitter) { + process.emittersMu.Lock() + defer process.emittersMu.Unlock() + delete(process.emitters, emitter) +} + +func (process *TeleportProcess) registeredEmitters() []processEmitter { + process.emittersMu.Lock() + defer process.emittersMu.Unlock() + + emitters := make([]processEmitter, 0, len(process.emitters)) + for e := range process.emitters { + emitters = append(emitters, e) + } + + return emitters +} + +func (process *TeleportProcess) AuditQueueStatus(ctx context.Context) *types.AuditQueueStatus { + emitters := process.registeredEmitters() + + if len(emitters) == 0 { + return nil + } + now := time.Now() + var status types.AuditQueueStatus + for _, e := range emitters { + stats, err := e.Stats(ctx) + if err != nil { + process.logger.DebugContext(ctx, "Failed to read audit queue stats for heartbeat.", "error", err) + continue + } + status.PendingCount += stats.PendingCount + status.DeadLetterCount += stats.DeadLetterCount + status.CorruptCount += stats.CorruptCount + if !stats.OldestPendingTime.IsZero() { + if age := int64(now.Sub(stats.OldestPendingTime).Seconds()); age > status.OldestPendingAgeSeconds { + status.OldestPendingAgeSeconds = age + } + } + if !stats.OldestDeadLetterTime.IsZero() { + if age := int64(now.Sub(stats.OldestDeadLetterTime).Seconds()); age > status.OldestDeadLetterAgeSeconds { + status.OldestDeadLetterAgeSeconds = age + } + } + } + return &status } func (process *TeleportProcess) newAuthFallbackEmitter(primary apievents.Emitter) (*events.FallbackEmitter, error) { - return events.NewFallbackEmitter(events.FallbackEmitterConfig{ + emitter, err := events.NewFallbackEmitter(events.FallbackEmitterConfig{ Primary: primary, DataDir: process.Config.DataDir, EnableAuditQueue: isAuditQueueEnabled(), AuditQueueCfg: process.auditQueueConfig(), AuditQueueBackends: process.auditQueueBackends(), }) + if err != nil { + return nil, trace.Wrap(err) + } + process.registerEmitter(emitter) + return emitter, nil } func (process *TeleportProcess) auditQueueConfig() auditqueue.Config { @@ -3533,6 +3618,14 @@ func (process *TeleportProcess) auditQueueConfig() auditqueue.Config { DeadLetterSweepInterval: process.Config.AuditQueue.DeadLetterSweepInterval, OrphanScanInterval: process.Config.AuditQueue.OrphanScanInterval, Synchronous: process.Config.AuditQueue.Synchronous, + OnStatsUpdated: process.signalAuditQueueStatus, + } +} + +func (process *TeleportProcess) signalAuditQueueStatus() { + select { + case process.auditQueueStatusSignalC <- struct{}{}: + default: } } @@ -7291,18 +7384,13 @@ func (process *TeleportProcess) initApps() { }) } -// drainableEmitter is an audit emitter whose queue can be drained on shutdown. -type drainableEmitter interface { - Shutdown(context.Context) error - Close() error -} - const emitterDrainTimeout = time.Hour // shutdownEmitter drains the emitter's queue to the audit backend when payload // carries a graceful shutdown context, otherwise it closes the emitter // immediately. -func shutdownEmitter(process *TeleportProcess, emitter drainableEmitter, payload any, logger *slog.Logger) { +func shutdownEmitter(process *TeleportProcess, emitter processEmitter, payload any, logger *slog.Logger) { + process.unregisterEmitter(emitter) if payload == nil { warnOnErr(process.ExitContext(), emitter.Close(), logger) return diff --git a/tool/tctl/common/inventory_command.go b/tool/tctl/common/inventory_command.go index 7d2fc62b5df1..7c6f21ea79b6 100644 --- a/tool/tctl/common/inventory_command.go +++ b/tool/tctl/common/inventory_command.go @@ -24,9 +24,12 @@ import ( "encoding/json" "fmt" "os" + "slices" "strings" + "time" "github.com/alecthomas/kingpin/v2" + "github.com/dustin/go-humanize" "github.com/gravitational/trace" "github.com/gravitational/teleport" @@ -62,6 +65,7 @@ type InventoryCommand struct { upgrader string updateGroup string + sort string inventoryStatus *kingpin.CmdClause inventoryList *kingpin.CmdClause @@ -85,6 +89,7 @@ func (c *InventoryCommand) Initialize(app *kingpin.Application, _ *tctlcfg.Globa c.inventoryList.Flag("format", "Output format.").Default(teleport.Text).EnumVar(&c.format, teleport.Text, teleport.JSON) c.inventoryList.Flag("upgrader", "Filter output by upgrader (kube,unit,none)").StringVar(&c.upgrader) c.inventoryList.Flag("update-group", "Filter output by update group").StringVar(&c.updateGroup) + c.inventoryList.Flag("sort", "Sort output by column. Supported: audit-queue (largest audit queue backlog first, including dead-letter and corrupt events).").EnumVar(&c.sort, "", "audit-queue") c.inventoryPing = inventory.Command("ping", "Ping locally connected instance.") c.inventoryPing.Arg("server-id", "ID of target server").Required().StringVar(&c.serverID) @@ -238,7 +243,11 @@ func (c *InventoryCommand) List(ctx context.Context, client *authclient.Client) switch c.format { case teleport.Text: - table := asciitable.MakeTable([]string{"Server ID", "Hostname", "Services", "Agent Version", "Upgrader", "Upgrader Version", "Update Group"}) + type instanceRow struct { + cells []string + auditQueueBacklog int64 + } + var rows []instanceRow for instances.Next() { instance := instances.Item() @@ -268,14 +277,24 @@ func (c *InventoryCommand) List(ctx context.Context, client *authclient.Client) updateGroup = updateInfo.UpdateGroup } - table.AddRow([]string{ - instance.GetName(), - instance.GetHostname(), - strings.Join(services, ","), - instance.GetTeleportVersion(), - upgrader, - upgraderVersion, - updateGroup, + var auditQueueBacklog int64 + auditQueue := formatAuditQueue(instance.GetAuditQueueStatus()) + if aq := instance.GetAuditQueueStatus(); aq != nil { + auditQueueBacklog = aq.PendingCount + aq.DeadLetterCount + aq.CorruptCount + } + + rows = append(rows, instanceRow{ + auditQueueBacklog: auditQueueBacklog, + cells: []string{ + instance.GetName(), + instance.GetHostname(), + strings.Join(services, ","), + instance.GetTeleportVersion(), + upgrader, + upgraderVersion, + updateGroup, + auditQueue, + }, }) } @@ -283,6 +302,18 @@ func (c *InventoryCommand) List(ctx context.Context, client *authclient.Client) return trace.Wrap(err) } + if c.sort == "audit-queue" { + slices.SortStableFunc(rows, func(a, b instanceRow) int { + // Largest audit queue backlog first. + return cmp.Compare(b.auditQueueBacklog, a.auditQueueBacklog) + }) + } + + table := asciitable.MakeTable([]string{"Server ID", "Hostname", "Services", "Agent Version", "Upgrader", "Upgrader Version", "Update Group", "Audit Queue"}) + for _, row := range rows { + table.AddRow(row.cells) + } + _, err := table.AsBuffer().WriteTo(os.Stdout) return trace.Wrap(err) case teleport.JSON: @@ -296,6 +327,37 @@ func (c *InventoryCommand) List(ctx context.Context, client *authclient.Client) } } +const minDisplayedAuditQueueAge = 5 * time.Minute + +func formatAuditQueue(aq *types.AuditQueueStatus) string { + if aq == nil { + return "" + } + minAge := int64(minDisplayedAuditQueueAge.Seconds()) + out := fmt.Sprintf("%d", aq.PendingCount) + if aq.OldestPendingAgeSeconds >= minAge { + out += fmt.Sprintf(" (oldest %s)", formatAge(aq.OldestPendingAgeSeconds)) + } + if aq.DeadLetterCount > 0 { + out += fmt.Sprintf(" (%d DL", aq.DeadLetterCount) + if aq.OldestDeadLetterAgeSeconds >= minAge { + out += fmt.Sprintf(", oldest %s", formatAge(aq.OldestDeadLetterAgeSeconds)) + } + out += ")" + } + if aq.CorruptCount > 0 { + out += fmt.Sprintf(" (%d corrupt)", aq.CorruptCount) + } + return out +} + +// formatAge renders an age in seconds as a coarse duration, e.g. "5 minutes" +// or "1 hour". +func formatAge(seconds int64) string { + base := time.Unix(0, 0) + return strings.TrimSpace(humanize.RelTime(base, base.Add(time.Duration(seconds)*time.Second), "", "")) +} + func (c *InventoryCommand) Ping(ctx context.Context, client *authclient.Client) error { rsp, err := client.PingInventory(ctx, proto.InventoryPingRequest{ ServerID: c.serverID, diff --git a/tool/tctl/common/inventory_command_test.go b/tool/tctl/common/inventory_command_test.go new file mode 100644 index 000000000000..77dd7d0294e5 --- /dev/null +++ b/tool/tctl/common/inventory_command_test.go @@ -0,0 +1,105 @@ +/* + * Teleport + * Copyright (C) 2026 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 common + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestFormatAuditQueue(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + status *types.AuditQueueStatus + want string + }{ + { + name: "not reported", + status: nil, + want: "", + }, + { + name: "empty queue", + status: &types.AuditQueueStatus{}, + want: "0", + }, + { + name: "pending only", + status: &types.AuditQueueStatus{PendingCount: 12}, + want: "12", + }, + { + name: "pending with age", + status: &types.AuditQueueStatus{ + PendingCount: 12, + OldestPendingAgeSeconds: int64((5 * time.Minute).Seconds()), + }, + want: "12 (oldest 5 minutes)", + }, + { + name: "young ages are hidden", + status: &types.AuditQueueStatus{ + PendingCount: 12, + DeadLetterCount: 3, + OldestPendingAgeSeconds: int64((4 * time.Minute).Seconds()), + OldestDeadLetterAgeSeconds: int64((2 * time.Minute).Seconds()), + }, + want: "12 (3 DL)", + }, + { + name: "dead letter with age", + status: &types.AuditQueueStatus{ + PendingCount: 2, + DeadLetterCount: 3, + OldestDeadLetterAgeSeconds: int64((48 * time.Hour).Seconds()), + }, + want: "2 (3 DL, oldest 2 days)", + }, + { + name: "dead letter without age", + status: &types.AuditQueueStatus{ + PendingCount: 0, + DeadLetterCount: 3, + }, + want: "0 (3 DL)", + }, + { + name: "all parts", + status: &types.AuditQueueStatus{ + PendingCount: 12, + DeadLetterCount: 3, + CorruptCount: 1, + OldestPendingAgeSeconds: int64(time.Hour.Seconds()), + OldestDeadLetterAgeSeconds: int64((30 * 24 * time.Hour).Seconds()), + }, + want: "12 (oldest 1 hour) (3 DL, oldest 1 month) (1 corrupt)", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, formatAuditQueue(tc.status)) + }) + } +} From 8a625e2c40eccdcb954b8068f5cc374fd0cf1e3a Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Wed, 12 Aug 2026 23:46:31 +0000 Subject: [PATCH 279/347] waiting for kube server to land in unified resource cache before proceeding with test cases (#69619) --- lib/auth/auth_with_roles_test.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index f111691dfb5a..fb3d0444652d 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -14410,7 +14410,11 @@ func TestScopedUserCertGeneration(t *testing.T) { client, err := srv.NewClient(ident) require.NoError(t, err) - createKubeServer(t, srv.Auth(), []string{"kube-cluster"}, "kube-host", scope) + const ( + kubeClusterName = "kube-cluster" + kubeHostName = "kube-host" + ) + createKubeServer(t, srv.Auth(), []string{kubeClusterName}, kubeHostName, scope) scopedApp, err := types.NewAppV3(types.Metadata{ Name: "test-app", @@ -14425,6 +14429,17 @@ func TestScopedUserCertGeneration(t *testing.T) { _, err = srv.Auth().UpsertApplicationServer(ctx, scopedAppServer) require.NoError(t, err) + // wait for kube server to appear in the unified resource cache, otherwise tests can be flaky + require.EventuallyWithT(t, func(collectT *assert.CollectT) { + for ks, err := range srv.Auth().UnifiedResourceCache.KubernetesServers(t.Context(), services.UnifiedResourcesIterateParams{}) { + require.NoError(collectT, err) + if ks.GetCluster().GetName() == kubeClusterName && ks.GetHostID() == kubeHostName && ks.GetScope() == scope { + return + } + } + require.Fail(collectT, "kube cluster not found") + }, time.Second*10, time.Millisecond*10, "kube cluster %s.%s was never found in unified resource cache", kubeHostName, kubeClusterName) + tts := []struct { name string req proto.UserCertsRequest @@ -14438,7 +14453,7 @@ func TestScopedUserCertGeneration(t *testing.T) { TLSPublicKey: tlsPubKey, Username: username, Usage: proto.UserCertsRequest_Kubernetes, - KubernetesCluster: "kube-cluster", + KubernetesCluster: kubeClusterName, Expires: time.Now().Add(time.Hour), }, }, @@ -14450,7 +14465,7 @@ func TestScopedUserCertGeneration(t *testing.T) { Username: username, Usage: proto.UserCertsRequest_Kubernetes, RequesterName: proto.UserCertsRequest_TSH_KUBE_LOCAL_PROXY, - KubernetesCluster: "kube-cluster", + KubernetesCluster: kubeClusterName, Expires: time.Now().Add(time.Hour * 24 * 7), }, assertCert: func(t *testing.T, cert *x509.Certificate) { @@ -14471,7 +14486,7 @@ func TestScopedUserCertGeneration(t *testing.T) { TLSPublicKey: tlsPubKey, Username: "some-other-user", Usage: proto.UserCertsRequest_Kubernetes, - KubernetesCluster: "kube-cluster", + KubernetesCluster: kubeClusterName, Expires: time.Now().Add(time.Hour), }, assertErr: func(t *testing.T, err error) { @@ -14487,7 +14502,7 @@ func TestScopedUserCertGeneration(t *testing.T) { TLSPublicKey: tlsPubKey, Username: username, Usage: proto.UserCertsRequest_Kubernetes, - KubernetesCluster: "kube-cluster", + KubernetesCluster: kubeClusterName, Expires: time.Now().Add(time.Hour), UseRoleRequests: true, RoleRequests: []string{role.GetName()}, From 17df2634f568c93b6818078c566fb779869c38f5 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:21:42 +0000 Subject: [PATCH 280/347] Terraform: convert ui config to generic provider (#69507) Contributes to https://github.com/gravitational/teleport/issues/69301. --- integrations/terraform/gen/main.go | 19 - .../legacy/data_source_teleport_ui_config.go | 89 ---- .../provider/internal/legacy/registry.go | 2 - .../legacy/resource_teleport_ui_config.go | 388 ------------------ .../provider/internal/resources/ui_config.go | 67 +++ .../provider/internal/teleport/ui_config.go | 68 +++ integrations/terraform/provider/provider.go | 2 + .../terraform/testlib/ui_config_test.go | 15 +- 8 files changed, 142 insertions(+), 508 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_ui_config.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_ui_config.go create mode 100644 integrations/terraform/provider/internal/resources/ui_config.go create mode 100644 integrations/terraform/provider/internal/teleport/ui_config.go diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 403c1f022a50..2fa5825ab9a8 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -350,23 +350,6 @@ var ( HasCheckAndSetDefaults: true, } - uiConfig = payload{ - Name: "UIConfig", - TypeName: "UIConfigV1", - VarName: "uiConfig", - IfaceName: "UIConfig", - GetMethod: "GetUIConfig", - CreateMethod: "SetUIConfig", - UpdateMethod: "SetUIConfig", - DeleteMethod: "DeleteUIConfig", - ID: `"ui_config"`, - Kind: "ui_config", - HasStaticID: false, - TerraformResourceType: "teleport_ui_config", - HasCheckAndSetDefaults: true, - GetCanReturnNil: true, - } - autoUpdateVersion = payload{ Name: "AutoUpdateVersion", TypeName: "AutoUpdateVersion", @@ -709,8 +692,6 @@ func genTFSchema() { generateDataSource(samlIdPServiceProvider, pluralDataSource) generateResource(provisionToken, pluralResource) generateDataSource(provisionToken, pluralDataSource) - generateResource(uiConfig, singularResource) - generateDataSource(uiConfig, singularDataSource) generateResource(autoUpdateVersion, singularResource) generateDataSource(autoUpdateVersion, singularDataSource) generateResource(autoUpdateConfig, singularResource) diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_ui_config.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_ui_config.go deleted file mode 100755 index a315417d2177..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_ui_config.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 legacy - -import ( - "context" - - apitypes "github.com/gravitational/teleport/api/types" - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportUIConfigType is the data source metadata type -type dataSourceTeleportUIConfigType struct{} - -// dataSourceTeleportUIConfig is the resource -type dataSourceTeleportUIConfig struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportUIConfigType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaUIConfigV1(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportUIConfigType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportUIConfig{ - p: p.(Provider), - }, nil -} - -// Read reads teleport UIConfig -func (r dataSourceTeleportUIConfig) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { - uiConfigI, err := r.p.Client().GetUIConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - uiConfig := uiConfigI.(*apitypes.UIConfigV1) - diags := tfschema.CopyUIConfigV1ToTerraform(ctx, uiConfig, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - id := uiConfig.GetName() - 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/provider/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index dfd9de86ee15..c04d5a4e2871 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -27,7 +27,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_provision_token": dataSourceTeleportProvisionTokenType{}, "teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": dataSourceTeleportSAMLIdPServiceProviderType{}, - "teleport_ui_config": dataSourceTeleportUIConfigType{}, "teleport_autoupdate_version": dataSourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": dataSourceTeleportAutoUpdateConfigType{}, "teleport_vnet_config": dataSourceTeleportVnetConfigType{}, @@ -53,7 +52,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_provision_token": resourceTeleportProvisionTokenType{}, "teleport_oidc_connector": resourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": resourceTeleportSAMLIdPServiceProviderType{}, - "teleport_ui_config": resourceTeleportUIConfigType{}, "teleport_bot": resourceTeleportBotType{}, "teleport_autoupdate_version": resourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": resourceTeleportAutoUpdateConfigType{}, diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_ui_config.go b/integrations/terraform/provider/internal/legacy/resource_teleport_ui_config.go deleted file mode 100755 index 2af06306fdea..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_ui_config.go +++ /dev/null @@ -1,388 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - - apitypes "github.com/gravitational/teleport/api/types" - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/gravitational/teleport/integrations/terraform/tfschema" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportUIConfigType is the resource metadata type -type resourceTeleportUIConfigType struct{} - -// resourceTeleportUIConfig is the resource -type resourceTeleportUIConfig struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportUIConfigType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfschema.GenSchemaUIConfigV1(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportUIConfigType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportUIConfig{ - p: p.(Provider), - }, nil -} - -// Create creates the UIConfig -func (r resourceTeleportUIConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - 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 - } - - uiConfig := &apitypes.UIConfigV1{} - diags = tfschema.CopyUIConfigV1FromTerraform(ctx, plan, uiConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := uiConfig.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting UIConfig defaults", trace.Wrap(err), "ui_config")) - return - } - - - - uiConfigBefore, err := r.p.Client().GetUIConfig(ctx) - if err != nil && !trace.IsNotFound(err) { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - if uiConfigBefore == nil || trace.IsNotFound(err) { - uiConfigBefore = &apitypes.UIConfigV1{} - } - - err = r.p.Client().SetUIConfig(ctx, uiConfig) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating UIConfig", trace.Wrap(err), "ui_config")) - return - } - - var uiConfigI apitypes.UIConfig - - // Try getting the resource until it exists and is different than the previous ones. - // There are two types of singleton resources: - // - the ones who can deleted and return a trace.NotFoundErr - // - the ones who cannot be deleted, only reset. In this case, the resource revision is used to know if the change got applied. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - uiConfigI, err = r.p.Client().GetUIConfig(ctx) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(ctx.Err()), "ui_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading UIConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "ui_config") - return - } - continue - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - previousMetadata := uiConfigBefore.GetMetadata() - currentMetadata := uiConfigI.GetMetadata() - if previousMetadata.GetRevision() != currentMetadata.GetRevision() || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(ctx.Err()), "ui_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading UIConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "ui_config") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - uiConfig, ok := uiConfigI.(*apitypes.UIConfigV1) - if !ok { - resp.Diagnostics.Append( - tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Errorf("Can not convert %T to UIConfigV1", uiConfigI), "ui_config"), - ) - return - } - - diags = tfschema.CopyUIConfigV1ToTerraform(ctx, uiConfig, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: "ui_config"} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport UIConfig -func (r resourceTeleportUIConfig) 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 - } - - uiConfigI, err := r.p.Client().GetUIConfig(ctx) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - - uiConfig := uiConfigI.(*apitypes.UIConfigV1) - diags = tfschema.CopyUIConfigV1ToTerraform(ctx, uiConfig, &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 UIConfig -func (r resourceTeleportUIConfig) 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 - } - - uiConfig := &apitypes.UIConfigV1{} - diags = tfschema.CopyUIConfigV1FromTerraform(ctx, plan, uiConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - err := uiConfig.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating UIConfig", trace.Wrap(err), "ui_config")) - return - } - - uiConfigBefore, err := r.p.Client().GetUIConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - err = r.p.Client().SetUIConfig(ctx, uiConfig) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating UIConfig", trace.Wrap(err), "ui_config")) - return - } - var uiConfigI apitypes.UIConfig - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - uiConfigI, err = r.p.Client().GetUIConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - if uiConfigBefore.GetMetadata().Revision != uiConfigI.GetMetadata().Revision || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(ctx.Err()), "ui_config")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading UIConfig (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "ui_config") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading UIConfig", trace.Wrap(err), "ui_config")) - return - } - - - uiConfig = uiConfigI.(*apitypes.UIConfigV1) - diags = tfschema.CopyUIConfigV1ToTerraform(ctx, uiConfig, &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 UIConfig -func (r resourceTeleportUIConfig) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - err := r.p.Client().DeleteUIConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting UIConfig", trace.Wrap(err), "ui_config")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports UIConfig state -func (r resourceTeleportUIConfig) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - uiConfigI, err := r.p.Client().GetUIConfig(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating UIConfig", trace.Wrap(err), "ui_config")) - return - } - uiConfig := uiConfigI.(*apitypes.UIConfigV1) - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = tfschema.CopyUIConfigV1ToTerraform(ctx, uiConfig, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := uiConfig.GetName() - - state.Attrs["id"] = types.String{Value: id} - - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// ModifyPlan modifies the planned value, normalizing null values. -func (r resourceTeleportUIConfig) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { - // If the entire plan is null, the resource is planned for destruction. - if req.Plan.Raw.IsNull() { - return - } - - // If the state is null, the resource is being created. No need to modify plan. - if req.State.Raw.IsNull() { - return - } - - var config types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - uiConfig := &apitypes.UIConfigV1{} - resp.Diagnostics.Append(tfschema.CopyUIConfigV1FromTerraform(ctx, config, uiConfig)...) - if resp.Diagnostics.HasError() { - return - } - - uiConfigResource := uiConfig - - if err := uiConfigResource.CheckAndSetDefaults(); err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting UIConfig defaults", trace.Wrap(err), "ui_config")) - return - } - - uiConfig = uiConfigResource - - const preserveUnknown = true - resp.Diagnostics.Append(tfschema.CopyUIConfigV1ToTerraformPreserveUnknown(ctx, uiConfig, &config, preserveUnknown)...) - if resp.Diagnostics.HasError() { - return - } - - var plan types.Object - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - plan.Attrs["spec"] = config.Attrs["spec"] - - resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) -} diff --git a/integrations/terraform/provider/internal/resources/ui_config.go b/integrations/terraform/provider/internal/resources/ui_config.go new file mode 100644 index 000000000000..bcc2abf60121 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/ui_config.go @@ -0,0 +1,67 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + "github.com/gravitational/teleport/integrations/terraform/tfschema" +) + +// uiConfigID is the static identifier used in the terraform state. +// Note that types.MetaNameUIConfig is "ui-config" which can't be used here +// without causing churn. +const uiConfigID = "ui_config" + +// NewUIConfigDataSourceType returns the web ui config data source type. +func NewUIConfigDataSourceType() tfdriver.DataSourceType[types.UIConfigV1, tfdriver.SingletonIdentifier] { + return tfdriver.DataSourceType[types.UIConfigV1, tfdriver.SingletonIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[types.UIConfigV1, tfdriver.SingletonIdentifier] { + return teleport.NewUIConfigClient(clientFromProvider(p)) + }, + Kind: types.KindUIConfig, + Codec: tfdriver.DataSourceCodecFuncs[types.UIConfigV1]{ + SchemaFunc: tfschema.GenSchemaUIConfigV1, + ToStateFunc: tfschema.CopyUIConfigV1ToTerraform, + }, + Identifier: tfdriver.SingletonIdentifierFromName(uiConfigID), + } +} + +// NewUIConfigResourceType returns the web ui config resource type. +func NewUIConfigResourceType() tfdriver.ResourceType[types.UIConfigV1, tfdriver.SingletonIdentifier] { + return tfdriver.ResourceType[types.UIConfigV1, tfdriver.SingletonIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[types.UIConfigV1, tfdriver.SingletonIdentifier] { + return teleport.NewUIConfigClient(clientFromProvider(p)) + }, + Kind: types.KindUIConfig, + Codec: tfdriver.ResourceCodecFuncs[types.UIConfigV1]{ + SchemaFunc: tfschema.GenSchemaUIConfigV1, + ToStateFunc: tfschema.CopyUIConfigV1ToTerraform, + FromPlanFunc: tfschema.CopyUIConfigV1FromTerraform, + }, + Normalizer: tfdriver.CheckAndSetDefaults[types.UIConfigV1](), + Identifier: tfdriver.SingletonIdentifierPolicy[types.UIConfigV1](uiConfigID), + ResourceRevision: func(st *types.UIConfigV1) string { + return st.GetMetadata().Revision + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/ui_config.go b/integrations/terraform/provider/internal/teleport/ui_config.go new file mode 100644 index 000000000000..29aa42aaf32d --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/ui_config.go @@ -0,0 +1,68 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewUIConfigClient returns a Teleport client to interact with the web ui configuration. +func NewUIConfigClient(c *client.Client) UIConfigClient { + return UIConfigClient{client: c} +} + +// UIConfigClient manages the Teleport web ui configuration. +type UIConfigClient struct { + client *client.Client +} + +// Get reads the Teleport web ui configuration. +func (r UIConfigClient) Get(ctx context.Context, _ tfdriver.SingletonIdentifier) (*types.UIConfigV1, error) { + cfg, err := r.client.GetUIConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + uiConfig, ok := cfg.(*types.UIConfigV1) + if !ok { + return nil, trace.BadParameter("unexpected UI config type %T", cfg) + } + + return uiConfig, nil +} + +// Create sets the Teleport web ui configuration. +func (r UIConfigClient) Create(ctx context.Context, cfg *types.UIConfigV1) error { + return trace.Wrap(r.client.SetUIConfig(ctx, cfg)) +} + +// Upsert sets the Teleport web ui configuration. +func (r UIConfigClient) Upsert(ctx context.Context, cfg *types.UIConfigV1) error { + return trace.Wrap(r.client.SetUIConfig(ctx, cfg)) +} + +// Delete removes the Teleport web ui configuration. +func (r UIConfigClient) Delete(ctx context.Context, _ tfdriver.SingletonIdentifier) error { + return trace.Wrap(r.client.DeleteUIConfig(ctx)) +} diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 22435ce40562..6ff79875cc66 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -590,6 +590,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_static_host_user": resources.NewStaticHostUserResourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterResourceType(), "teleport_trusted_device": resources.NewTrustedDeviceResourceType(), + "teleport_ui_config": resources.NewUIConfigResourceType(), "teleport_user": resources.NewUserResourceType(), "teleport_workload_identity": resources.NewWorkloadIdentityResourceType(), } @@ -630,6 +631,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_static_host_user": resources.NewStaticHostUserDataSourceType(), "teleport_trusted_cluster": resources.NewTrustedClusterDataSourceType(), "teleport_trusted_device": resources.NewTrustedDeviceDataSourceType(), + "teleport_ui_config": resources.NewUIConfigDataSourceType(), "teleport_user": resources.NewUserDataSourceType(), "teleport_workload_identity": resources.NewWorkloadIdentityDataSourceType(), } diff --git a/integrations/terraform/testlib/ui_config_test.go b/integrations/terraform/testlib/ui_config_test.go index 909257e0b025..1b9885de32a7 100644 --- a/integrations/terraform/testlib/ui_config_test.go +++ b/integrations/terraform/testlib/ui_config_test.go @@ -17,7 +17,6 @@ limitations under the License. package testlib import ( - "context" "time" "github.com/gravitational/trace" @@ -65,12 +64,8 @@ func (s *TerraformSuiteOSS) TestUIConfig() { } func (s *TerraformSuiteOSS) TestImportUIConfig() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - r := "teleport_ui_config" - id := "test_import" - name := r + "." + id + name := r + "." + "ui_config" uiConfig := &types.UIConfigV1{ Spec: types.UIConfigSpecV1{ @@ -82,11 +77,11 @@ func (s *TerraformSuiteOSS) TestImportUIConfig() { err := uiConfig.CheckAndSetDefaults() require.NoError(s.T(), err) - err = s.client.SetUIConfig(ctx, uiConfig) + err = s.client.SetUIConfig(s.T().Context(), uiConfig) require.NoError(s.T(), err) require.Eventually(s.T(), func() bool { - _, err := s.client.GetUIConfig(ctx) + _, err := s.client.GetUIConfig(s.T().Context()) if trace.IsNotFound(err) { return false } @@ -99,10 +94,10 @@ func (s *TerraformSuiteOSS) TestImportUIConfig() { IsUnitTest: true, Steps: []resource.TestStep{ { - Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`, + Config: s.terraformConfig + "\n" + `resource "` + r + `" "ui_config" { }`, ResourceName: name, ImportState: true, - ImportStateId: id, + ImportStateId: "ui_config", ImportStateCheck: func(state []*terraform.InstanceState) error { require.Equal(s.T(), "ui_config", state[0].Attributes["kind"]) require.Equal(s.T(), "1000", state[0].Attributes["spec.scrollback_lines"]) From ee42a3c788bbd338d3345ac8fa087bfb87939d21 Mon Sep 17 00:00:00 2001 From: mica Date: Thu, 13 Aug 2026 00:45:18 +0000 Subject: [PATCH 281/347] docs: address codex feedback for username trait in PingFederate role (#69589) * docs: address codex feedback for username trait in PingFederate role example * codex feedback, updating version necessary for user.metadata.name * Update docs/pages/zero-trust-access/sso/integrate-idp/ping-federate-oidc.mdx Co-authored-by: Paul Gottschling --------- Co-authored-by: Paul Gottschling --- .../sso/integrate-idp/ping-federate-oidc.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/pages/zero-trust-access/sso/integrate-idp/ping-federate-oidc.mdx b/docs/pages/zero-trust-access/sso/integrate-idp/ping-federate-oidc.mdx index f4cf579290f7..de22a4de3522 100644 --- a/docs/pages/zero-trust-access/sso/integrate-idp/ping-federate-oidc.mdx +++ b/docs/pages/zero-trust-access/sso/integrate-idp/ping-federate-oidc.mdx @@ -142,7 +142,7 @@ metadata: name: developer spec: allow: - logins: ['ubuntu', 'ec2-user', '{{internal.username}}'] + logins: ['ubuntu', 'ec2-user'] node_labels: env: dev kubernetes_groups: ['developers'] @@ -152,9 +152,10 @@ spec: max_session_ttl: 8h ``` -``{{internal.username}}`` is a Teleport template variable that resolves to the authenticated -user's Teleport username at login time. It allows the role to grant a login on the target host -that matches the user's Teleport identity without hardcoding a username. Remove it if you want to restrict logins to only the explicitly listed values. +This example grants the `ubuntu` and `ec2-user` logins to anyone with the +`developer` role. If you'd rather grant a login that matches each user's own +Teleport username instead of a fixed list, you can add `{{user.metadata.name}}` +to the logins list. This variable requires Teleport v18.7.6 or later. Apply the role: From 0d76b1dee6b32d0579a1415ad4439238de163248 Mon Sep 17 00:00:00 2001 From: Julia Ogris Date: Thu, 13 Aug 2026 01:39:25 +0000 Subject: [PATCH 282/347] auth: Add v9 downgrade property tests (#68916) Exercise `maybeDowngradeRoleVersionToV8` with property tests so the guarantee holds for arbitrary roles, not just the handful of hand-written roles the example test checks. The downgrade is a security control. A pre-v19 agent cannot enforce a v9 role's `app_resources` restriction, so the downgraded v8 copy must move the role's allow app selector to the deny side and never leave one of its own apps reachable. The allow-to-deny move branches on whether a deny selector already exists, which is where a fail-open bug would go unnoticed. Generate random allow and deny label selectors and candidate apps, then assert three properties. Every app the role allowed before the downgrade is denied after. Every app it already denied stays denied. The input role comes back unmodified, because the caller passes the cache's shared role. Add apps matching each selector, and the deny expression, to every generated set, because random apps match a keyed selector in under a tenth of draws and the assertion body is otherwise skipped. Draw globs as selector values, since `services.MatchLabels` matches them as regular expressions and a downgrade that dropped them would serve a copy denying nothing. Cover the AND-to-OR downgrade in a second property, which adds a label expression on each side. An allow rule needs both `app_labels` and `app_labels_expression` to match while a deny rule needs either, so moving them across separately denies more apps than the role allowed. Add an example test for that over-deny, where a wildcard label set moved to the deny side blocks apps outside the allow rule. Use `pgregory.net/rapid`, already a direct dependency of this branch. --- .../grpcserver_downgrade_property_test.go | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 lib/auth/grpcserver_downgrade_property_test.go diff --git a/lib/auth/grpcserver_downgrade_property_test.go b/lib/auth/grpcserver_downgrade_property_test.go new file mode 100644 index 000000000000..cb2766879389 --- /dev/null +++ b/lib/auth/grpcserver_downgrade_property_test.go @@ -0,0 +1,319 @@ +/* + * Teleport + * Copyright (C) 2026 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 auth_test + +import ( + "fmt" + "slices" + "strings" + "testing" + + "github.com/coreos/go-semver/semver" + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "github.com/gravitational/teleport/api/types" + apiutils "github.com/gravitational/teleport/api/utils" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/label" + "github.com/gravitational/teleport/lib/utils" +) + +// A small value set, so generated selectors and app labels overlap often. +// Selector values may be globs, which services.MatchLabels matches as +// regular expressions. App labels are always literal, and no literal +// matches a glob, which pickMatchingAppLabels relies on. +var ( + appLabelKeys = []string{"env", "vendor", "team", "region"} + appLabelValues = []string{"prod", "dev", "gitlab", "github", "blue", "green", "us", "eu"} + appLabelSelectorValues = slices.Concat(appLabelValues, []string{"prod-*", "web-*"}) +) + +// genAppLabelSelector draws a wildcard, an empty selector, or a few keys +// with one or two values each. An empty selector matches no app. Sample +// return values: +// +// types.Labels{"*": {"*"}} +// types.Labels{} +// types.Labels{"env": {"prod"}, "team": {"blue", "web-*"}} +func genAppLabelSelector(t *rapid.T) types.Labels { + if rapid.Bool().Draw(t, "wildcard") { + return types.Labels{types.Wildcard: []string{types.Wildcard}} + } + keyGen := rapid.SliceOfNDistinct(rapid.SampledFrom(appLabelKeys), 0, len(appLabelKeys), rapid.ID[string]) + valueGen := rapid.SliceOfNDistinct(rapid.SampledFrom(appLabelSelectorValues), 1, 2, rapid.ID[string]) + selector := types.Labels{} + for _, key := range keyGen.Draw(t, "sel_keys") { + selector[key] = valueGen.Draw(t, "sel_vals_"+key) + } + return selector +} + +// genAppLabels draws the labels of one candidate app. Sample return values: +// +// map[string]string{} +// map[string]string{"env": "prod"} +// map[string]string{"env": "dev", "region": "us"} +func genAppLabels(t *rapid.T) map[string]string { + keyGen := rapid.SliceOfNDistinct(rapid.SampledFrom(appLabelKeys), 0, len(appLabelKeys), rapid.ID[string]) + labels := map[string]string{} + for _, key := range keyGen.Draw(t, "app_keys") { + labels[key] = rapid.SampledFrom(appLabelValues).Draw(t, "app_val_"+key) + } + return labels +} + +// genAppLabelSets draws the labels of one to eight candidate apps. Sample +// return value: +// +// []map[string]string{{"env": "prod"}, {}, {"team": "blue"}} +func genAppLabelSets(t *rapid.T) []map[string]string { + return rapid.SliceOfN(rapid.Custom(genAppLabels), 1, 8).Draw(t, "app_label_sets") +} + +// pickMatchingAppLabels builds app labels that the selector matches and +// that also satisfy the label expression. A glob selector value is turned +// into a literal that matches it. It returns nil when no app can satisfy +// both, and for an empty selector with no expression. +// +// {"env": {"prod", "dev"}} -> {"env": "prod"} +// {"env": {"prod"}, "team": {"blue"}} -> {"env": "prod", "team": "blue"} +// {"env": {"prod-*"}} -> {"env": "prod-x"} +// {"*": {"*"}} and region == "us" -> {"region": "us"} +// {"env": {"prod", "dev"}} and env == "dev" -> {"env": "dev"} +// {"region": {"eu"}} and region == "us" -> nil +func pickMatchingAppLabels(selector types.Labels, exprKey, exprValue string) map[string]string { + labels := map[string]string{} + for key, values := range selector { + if key == types.Wildcard { + continue + } + value := values[0] + if strings.HasSuffix(value, "*") { + value = strings.TrimSuffix(value, "*") + "x" + } + labels[key] = value + } + if exprKey == "" { + if len(selector) == 0 { + return nil + } + return labels + } + if values, ok := selector[exprKey]; ok { + match, err := utils.SliceMatchesRegex(exprValue, values) + if err != nil || !match { + return nil + } + } + labels[exprKey] = exprValue + return labels +} + +// genPreV19ClientVersion draws a version below minSupportedRoleV9Version. +// The version comparison drops any pre-release suffix, so a pre-release +// client downgrades like a release one. +func genPreV19ClientVersion(t *rapid.T) *semver.Version { + major := rapid.Int64Range(1, 18).Draw(t, "major") + minor := rapid.Int64Range(0, 20).Draw(t, "minor") + patch := rapid.Int64Range(0, 20).Draw(t, "patch") + preRelease := rapid.SampledFrom([]string{"", "dev.1", "beta.3"}).Draw(t, "pre_release") + return &semver.Version{Major: major, Minor: minor, Patch: patch, PreRelease: semver.PreRelease(preRelease)} +} + +// TestProperty_DowngradeV8_DeniesEveryPreviouslyAllowedApp asserts three +// properties: +// - The downgraded copy denies every app the v9 role allowed. +// - The downgraded copy still denies every app the v9 role denied. +// - The input role is never modified (it is shared with other callers). +// +// The properties call the downgrade directly. TestRoleVersionV9Downgrade +// covers the wiring that reaches it. +func TestProperty_DowngradeV8_DeniesEveryPreviouslyAllowedApp(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + allowLabels := genAppLabelSelector(t) + denyLabels := genAppLabelSelector(t) + appLabelSets := genAppLabelSets(t) + clientVersion := genPreV19ClientVersion(t) + + // Random apps rarely match a keyed selector, so add one that does + // for each side. + if match := pickMatchingAppLabels(allowLabels, "", ""); match != nil { + appLabelSets = append(appLabelSets, match) + } + if match := pickMatchingAppLabels(denyLabels, "", ""); match != nil { + appLabelSets = append(appLabelSets, match) + } + + // None of these is a pure allow_all, so the downgrade strips the + // allow app labels. + allowRules := rapid.SampledFrom([][]types.AppResource{nil, {{}}, {{AllowAll: true}, {}}}).Draw(t, "allow_rules") + denyRules := rapid.SampledFrom([][]types.AppResource{nil, {{}}}).Draw(t, "deny_rules") + require.False(t, types.AppResourcesAllowAll(allowRules, denyRules), "generator produced a pure allow_all role") + + // The labels map must be non-empty. The downgrade writes into it in + // place, which a shallow copy would leak back to the caller. + metadata := types.Metadata{Name: "dev", Labels: map[string]string{"owner": "core"}} + role := &types.RoleV6{ + Kind: types.KindRole, + Metadata: metadata, + Version: types.V9, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{AppLabels: allowLabels, AppResources: allowRules}, + Deny: types.RoleConditions{AppLabels: denyLabels, AppResources: denyRules}, + }, + } + + before := apiutils.CloneProtoMsg(role) + got := auth.MaybeDowngradeRoleVersionToV8(t.Context(), role, clientVersion) + + require.Equal(t, types.V8, got.GetVersion()) + require.Nil(t, got.Spec.Allow.AppResources) + require.Nil(t, got.Spec.Deny.AppResources) + require.Empty(t, got.Spec.Allow.AppLabels) + require.NotEmpty(t, got.GetMetadata().Labels[types.TeleportDowngradedLabel]) + require.Equal(t, "core", got.GetMetadata().Labels["owner"]) + + require.Equal(t, before, apiutils.CloneProtoMsg(role), "downgrade modified the input role") + + denyMatchers := types.LabelMatchers{Labels: got.Spec.Deny.AppLabels, Expression: got.Spec.Deny.AppLabelsExpression} + for _, appLabels := range appLabelSets { + allowedBefore, _, err := services.MatchLabels(allowLabels, appLabels) + require.NoError(t, err) + deniedBefore, _, err := services.MatchLabels(denyLabels, appLabels) + require.NoError(t, err) + if !allowedBefore && !deniedBefore { + continue + } + deniedAfter, _, err := services.CheckLabelsMatch(types.Deny, denyMatchers, "", nil, label.MapLabelGetter(appLabels), false) + require.NoError(t, err) + require.True(t, deniedAfter, + "app allowed or denied before downgrade must be denied after: app=%v allowLabels=%v denyLabels=%v resultDeny=%v", + appLabels, allowLabels, denyLabels, got.Spec.Deny.AppLabels) + } + }) +} + +// TestProperty_DowngradeV8_FailsClosedWithBothSelectors asserts that the +// downgraded copy denies every app the v9 role allowed or denied, whether +// the role selects by app_labels, app_labels_expression, or both, and +// that the input role is never modified. +func TestProperty_DowngradeV8_FailsClosedWithBothSelectors(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + allowLabels := genAppLabelSelector(t) + exprKey, exprValue, allowExpression := "", "", "" + if rapid.Bool().Draw(t, "has_allow_expression") { + exprKey = rapid.SampledFrom(appLabelKeys).Draw(t, "expr_key") + exprValue = rapid.SampledFrom(appLabelValues).Draw(t, "expr_value") + allowExpression = fmt.Sprintf("labels[%q] == %q", exprKey, exprValue) + } + denyLabels := rapid.SampledFrom([]types.Labels{nil, {"region": []string{"us"}}}).Draw(t, "deny_labels") + denyExprKey, denyExprValue := "region", "eu" + denyExprText := fmt.Sprintf("labels[%q] == %q", denyExprKey, denyExprValue) + denyExpression := rapid.SampledFrom([]string{"", denyExprText}).Draw(t, "deny_expression") + appLabelSets := genAppLabelSets(t) + clientVersion := genPreV19ClientVersion(t) + + // Random apps rarely match a keyed selector, so add one for the + // allow selector, the deny selector, and the deny expression. + if match := pickMatchingAppLabels(allowLabels, exprKey, exprValue); match != nil { + appLabelSets = append(appLabelSets, match) + } + if match := pickMatchingAppLabels(denyLabels, "", ""); match != nil { + appLabelSets = append(appLabelSets, match) + } + if denyExpression != "" { + appLabelSets = append(appLabelSets, map[string]string{denyExprKey: denyExprValue}) + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Metadata: types.Metadata{Name: "dev"}, + Version: types.V9, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{AppLabels: allowLabels, AppLabelsExpression: allowExpression, AppResources: []types.AppResource{{}}}, + Deny: types.RoleConditions{AppLabels: denyLabels, AppLabelsExpression: denyExpression}, + }, + } + + before := apiutils.CloneProtoMsg(role) + got := auth.MaybeDowngradeRoleVersionToV8(t.Context(), role, clientVersion) + require.Equal(t, types.V8, got.GetVersion()) + require.Empty(t, got.Spec.Allow.AppLabels) + require.Empty(t, got.Spec.Allow.AppLabelsExpression) + require.Equal(t, before, apiutils.CloneProtoMsg(role), "downgrade modified the input role") + + allowMatchers := types.LabelMatchers{Labels: allowLabels, Expression: allowExpression} + inputDenyMatchers := types.LabelMatchers{Labels: denyLabels, Expression: denyExpression} + denyMatchers := types.LabelMatchers{Labels: got.Spec.Deny.AppLabels, Expression: got.Spec.Deny.AppLabelsExpression} + for _, appLabels := range appLabelSets { + app := label.MapLabelGetter(appLabels) + allowed, _, err := services.CheckLabelsMatch(types.Allow, allowMatchers, "", nil, app, false) + require.NoError(t, err) + deniedBefore, _, err := services.CheckLabelsMatch(types.Deny, inputDenyMatchers, "", nil, app, false) + require.NoError(t, err) + if !allowed && !deniedBefore { + continue + } + denied, _, err := services.CheckLabelsMatch(types.Deny, denyMatchers, "", nil, app, false) + require.NoError(t, err) + require.True(t, denied, + "app allowed or denied before downgrade must be denied after: app=%v allowLabels=%v allowExpr=%q denyLabels=%v denyExpr=%q resultDeny=%v resultDenyExpr=%q", + appLabels, allowLabels, allowExpression, denyLabels, denyExpression, got.Spec.Deny.AppLabels, got.Spec.Deny.AppLabelsExpression) + } + }) +} + +// TestDowngradeV8_ConservativeOverDeny asserts that the downgraded copy +// can also deny certain apps the v9 role never allowed. +// +// An allow rule needs both app_labels and app_labels_expression to match, +// while a deny rule needs only one, so moving them across cannot preserve +// the allow set exactly. Over-denying is the deliberate and documented +// choice. +func TestDowngradeV8_ConservativeOverDeny(t *testing.T) { + role := &types.RoleV6{ + Kind: types.KindRole, + Metadata: types.Metadata{Name: "dev"}, + Version: types.V9, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + AppLabelsExpression: `labels["env"] == "prod"`, + AppResources: []types.AppResource{{}}, + }, + }, + } + got := auth.MaybeDowngradeRoleVersionToV8(t.Context(), role, &semver.Version{Major: 18, Minor: 1, Patch: 2}) + denyMatchers := types.LabelMatchers{Labels: got.Spec.Deny.AppLabels, Expression: got.Spec.Deny.AppLabelsExpression} + + assertDenied := func(t *testing.T, labels map[string]string) { + t.Helper() + app := label.MapLabelGetter(labels) + denied, _, err := services.CheckLabelsMatch(types.Deny, denyMatchers, "", nil, app, false) + require.NoError(t, err) + require.True(t, denied) + } + + // An app the role allowed. + assertDenied(t, map[string]string{"env": "prod"}) + // An app the role never allowed, denied by the moved wildcard labels. + assertDenied(t, map[string]string{"env": "dev"}) +} From aecf425fe8d56f4ae0c53330bef964725adfe565 Mon Sep 17 00:00:00 2001 From: Sakshyam Shah Date: Thu, 13 Aug 2026 01:53:49 +0000 Subject: [PATCH 283/347] docs: entra id metrics (#69343) * entra id metrics docs * format table width * explain integration headings * fix typo emmited -> emitted * add relative link to entra integration docs --- docs/pages/includes/metrics.mdx | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/pages/includes/metrics.mdx b/docs/pages/includes/metrics.mdx index d675f465f11e..635a91bc894b 100644 --- a/docs/pages/includes/metrics.mdx +++ b/docs/pages/includes/metrics.mdx @@ -264,6 +264,62 @@ clients/Teleport proxies to the Teleport `kubernetes_service`. | `teleport_kubernetes_server_join_in_flight_sessions` | gauge | Teleport Kubernetes Service | Number of active joining sessions. | | `teleport_kubernetes_server_join_sessions_total` | counter | Teleport Kubernetes Service | Total number of joining sessions. | +## Integrations + +The following metrics are emitted by Teleport hosted plugins and integrations. + +### Microsoft Entra ID integration + +The following metrics are emitted by the [Teleport Microsoft Entra ID integration service](../identity-governance/integrations/entra-id/entra-id.mdx). + +#### Resource discovery + +| Name | Type | Component | Description | +|-------------------------------------------------------------|-------|---------------|------------------------------------------------------------------------------| +| `teleport_plugin_entra_id_directory_discovered_groups` | gauge | Teleport Auth | Number of Entra ID groups discovered during the most recent sync. | +| `teleport_plugin_entra_id_directory_discovered_users` | gauge | Teleport Auth | Number of Entra ID users discovered during the most recent sync. | +| `teleport_plugin_entra_id_directory_discovered_memberships` | gauge | Teleport Auth | Number of Entra ID group memberships discovered during the most recent sync. | + +#### Resource reconciliation + + +| Name | Type | Component | Description | +|-----------------------------------------------------------------------|-----------|---------------|---------------------------------------------------------------------------------------------------------| +| `teleport_plugin_entra_id_directory_reconciliation_count` | counter | Teleport Auth | Number of times the Entra ID directory reconciliation ran. Labeled with `result` (`success` or `error`). | +| `teleport_plugin_entra_id_directory_reconciliation_duration_seconds` | histogram | Teleport Auth | Duration of each Entra ID directory reconciliation run. Labeled with `section`. | + +The `section` label on `teleport_plugin_entra_id_directory_reconciliation_duration_seconds` breaks a sync down into its phases: + +| Value | Description | +|---------------------------|-------------------------------------------------------------------------------------------------------------| +| `read_entra_groups` | Reading groups from the Microsoft Graph API. Emitted only on full sync. | +| `read_entra_groups_delta` | Reading group changes from the Microsoft Graph API. Emitted only on delta sync. | +| `read_entra_members` | Reading group memberships from the Microsoft Graph API. Emitted only on full sync. | +| `read_entra_users` | Reading users from the Microsoft Graph API. Emitted only on full sync. | +| `read_entra_users_delta` | Reading user changes from the Microsoft Graph API. Emitted only on delta sync. | +| `reconcile_users` | Reconciling discovered users into the Teleport backend. Emitted on full and delta sync. | +| `reconcile_access_lists` | Reconciling discovered groups into Teleport Access Lists. Emitted on full and delta sync. | +| `total` | End-to-end duration of the reconciliation run, covering all sections above. Emitted on full and delta sync. | + +Reconciliation is also measured for each individual resource: + +| Name | Type | Component | Description | +|---------------------------------------------------------------------------------|-----------|---------------|------------------------------------------------------------------------------------------------| +| `teleport_plugin_entra_id_directory_user_reconciliation_total` | counter | Teleport Auth | Total number of individual user reconciliations. Labeled with `operation` and `result`. | +| `teleport_plugin_entra_id_directory_user_reconciliation_duration_seconds` | histogram | Teleport Auth | Duration of individual user reconciliations, in seconds. Labeled with `operation`. | +| `teleport_plugin_entra_id_directory_accesslist_reconciliation_total` | counter | Teleport Auth | Total number of individual Access List reconciliations. Labeled with `operation` and `result`. | +| `teleport_plugin_entra_id_directory_accesslist_reconciliation_duration_seconds` | histogram | Teleport Auth | Duration of individual Access List reconciliations, in seconds. Labeled with `operation`. | +| `teleport_plugin_entra_id_directory_reconciled_nested_member_total` | counter | Teleport Auth | Number of Teleport nested Access Lists reconciled. Labeled with `result`. | +| `teleport_plugin_entra_id_directory_reconciled_nested_member_duration_seconds` | histogram | Teleport Auth | Duration of each nested Access List reconciliation. | + +The `operation` and `result` labels break down into the following values: + +| Label | Values | +|-------------|------------------------------------------------------------------------------------------------------------------| +| `operation` | `create`, `update`, or `delete`. | +| `result` | `success`, `error`, or `noop`. `noop` is emitted when a user or Access List scheduled for deletion is not found. | + + ## All Teleport instances | Name | Type | Component | Description | From 37c3a8a456ec4fc26674e7fd112466688f730bb6 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Thu, 13 Aug 2026 02:43:07 +0000 Subject: [PATCH 284/347] Terraform: convert auto update version to generic provider (#69514) Contributes to https://github.com/gravitational/teleport/issues/69301. --- integrations/terraform/gen/main.go | 29 -- ...data_source_teleport_autoupdate_version.go | 89 ---- .../provider/internal/legacy/registry.go | 2 - .../resource_teleport_autoupdate_version.go | 379 ------------------ .../internal/resources/autoupdate_version.go | 66 +++ .../internal/teleport/autoupdate_version.go | 71 ++++ .../provider/internal/tfdriver/normalize.go | 33 ++ .../internal/tfdriver/normalize_test.go | 30 ++ integrations/terraform/provider/provider.go | 2 + .../testlib/autoupdate_version_test.go | 25 +- 10 files changed, 212 insertions(+), 514 deletions(-) delete mode 100755 integrations/terraform/provider/internal/legacy/data_source_teleport_autoupdate_version.go delete mode 100755 integrations/terraform/provider/internal/legacy/resource_teleport_autoupdate_version.go create mode 100644 integrations/terraform/provider/internal/resources/autoupdate_version.go create mode 100644 integrations/terraform/provider/internal/teleport/autoupdate_version.go diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 2fa5825ab9a8..864964056024 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -350,33 +350,6 @@ var ( HasCheckAndSetDefaults: true, } - autoUpdateVersion = payload{ - Name: "AutoUpdateVersion", - TypeName: "AutoUpdateVersion", - VarName: "autoUpdateVersion", - GetMethod: "GetAutoUpdateVersion", - CreateMethod: "CreateAutoUpdateVersion", - UpsertMethodArity: 2, - UpdateMethod: "UpsertAutoUpdateVersion", - DeleteMethod: "DeleteAutoUpdateVersion", - ID: "autoUpdateVersion.Metadata.Name", - Kind: "autoupdate_version", - HasStaticID: false, - ProtoPackage: "autoupdatev1", - ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1", - SchemaPackage: "schemav1", - SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/autoupdate/v1", - TerraformResourceType: "teleport_autoupdate_version", - // 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. - ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""}, - ForceSetKind: "apitypes.KindAutoUpdateVersion", - DefaultName: "apitypes.MetaNameAutoUpdateVersion", - } - autoUpdateConfig = payload{ Name: "AutoUpdateConfig", TypeName: "AutoUpdateConfig", @@ -692,8 +665,6 @@ func genTFSchema() { generateDataSource(samlIdPServiceProvider, pluralDataSource) generateResource(provisionToken, pluralResource) generateDataSource(provisionToken, pluralDataSource) - generateResource(autoUpdateVersion, singularResource) - generateDataSource(autoUpdateVersion, singularDataSource) generateResource(autoUpdateConfig, singularResource) generateDataSource(autoUpdateConfig, singularDataSource) generateResource(discoveryConfig, pluralResource) diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_autoupdate_version.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_autoupdate_version.go deleted file mode 100755 index fe892b0b3714..000000000000 --- a/integrations/terraform/provider/internal/legacy/data_source_teleport_autoupdate_version.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 legacy - -import ( - "context" - - - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/autoupdate/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// dataSourceTeleportAutoUpdateVersionType is the data source metadata type -type dataSourceTeleportAutoUpdateVersionType struct{} - -// dataSourceTeleportAutoUpdateVersion is the resource -type dataSourceTeleportAutoUpdateVersion struct { - p Provider -} - -// GetSchema returns the data source schema -func (r dataSourceTeleportAutoUpdateVersionType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaAutoUpdateVersion(ctx) -} - -// NewDataSource creates the empty data source -func (r dataSourceTeleportAutoUpdateVersionType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceTeleportAutoUpdateVersion{ - p: p.(Provider), - }, nil -} - -// Read reads teleport AutoUpdateVersion -func (r dataSourceTeleportAutoUpdateVersion) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { - autoUpdateVersionI, err := r.p.Client().GetAutoUpdateVersion(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - var state types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - autoUpdateVersion := autoUpdateVersionI - diags := schemav1.CopyAutoUpdateVersionToTerraform(ctx, autoUpdateVersion, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Todo: Remove after updating terraform-plugin to >=v1.5.0. - // terraform-plugin-testing version <1.5.0 requires data resources to - // implement the 'id' attribute. - // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes - v, ok := state.Attrs["id"] - if !ok || v.IsNull() { - id := autoUpdateVersion.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/provider/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go index c04d5a4e2871..7ec1a13e1bd9 100644 --- a/integrations/terraform/provider/internal/legacy/registry.go +++ b/integrations/terraform/provider/internal/legacy/registry.go @@ -27,7 +27,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType { "teleport_provision_token": dataSourceTeleportProvisionTokenType{}, "teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": dataSourceTeleportSAMLIdPServiceProviderType{}, - "teleport_autoupdate_version": dataSourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": dataSourceTeleportAutoUpdateConfigType{}, "teleport_vnet_config": dataSourceTeleportVnetConfigType{}, "teleport_integration": dataSourceTeleportIntegrationType{}, @@ -53,7 +52,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType { "teleport_oidc_connector": resourceTeleportOIDCConnectorType{}, "teleport_saml_idp_service_provider": resourceTeleportSAMLIdPServiceProviderType{}, "teleport_bot": resourceTeleportBotType{}, - "teleport_autoupdate_version": resourceTeleportAutoUpdateVersionType{}, "teleport_autoupdate_config": resourceTeleportAutoUpdateConfigType{}, "teleport_vnet_config": resourceTeleportVnetConfigType{}, "teleport_integration": resourceTeleportIntegrationType{}, diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_autoupdate_version.go b/integrations/terraform/provider/internal/legacy/resource_teleport_autoupdate_version.go deleted file mode 100755 index e95faaec7e86..000000000000 --- a/integrations/terraform/provider/internal/legacy/resource_teleport_autoupdate_version.go +++ /dev/null @@ -1,379 +0,0 @@ -// 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 legacy - -import ( - "context" - "fmt" - apitypes "github.com/gravitational/teleport/api/types" - - autoupdatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" - headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" - "github.com/gravitational/trace" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - - schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/autoupdate/v1" - "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag" -) - -// resourceTeleportAutoUpdateVersionType is the resource metadata type -type resourceTeleportAutoUpdateVersionType struct{} - -// resourceTeleportAutoUpdateVersion is the resource -type resourceTeleportAutoUpdateVersion struct { - p Provider -} - -// GetSchema returns the resource schema -func (r resourceTeleportAutoUpdateVersionType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemav1.GenSchemaAutoUpdateVersion(ctx) -} - -// NewResource creates the empty resource -func (r resourceTeleportAutoUpdateVersionType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceTeleportAutoUpdateVersion{ - p: p.(Provider), - }, nil -} - -// Create creates the AutoUpdateVersion -func (r resourceTeleportAutoUpdateVersion) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - 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 - } - - autoUpdateVersion := &autoupdatev1.AutoUpdateVersion{} - diags = schemav1.CopyAutoUpdateVersionFromTerraform(ctx, plan, autoUpdateVersion) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - autoUpdateVersion.Kind = apitypes.KindAutoUpdateVersion - - - if autoUpdateVersion.GetMetadata() == nil { - autoUpdateVersion.Metadata = &headerv1.Metadata{} - } - if autoUpdateVersion.GetMetadata().GetName() == "" { - autoUpdateVersion.Metadata.Name = apitypes.MetaNameAutoUpdateVersion - } - - autoUpdateVersionBefore, err := r.p.Client().GetAutoUpdateVersion(ctx) - if err != nil && !trace.IsNotFound(err) { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - _, err = r.p.Client().CreateAutoUpdateVersion(ctx, autoUpdateVersion) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - // Not really an inferface, just using the same name for easier templating. - var autoUpdateVersionI *autoupdatev1.AutoUpdateVersion - - - // Try getting the resource until it exists and is different than the previous ones. - // There are two types of singleton resources: - // - the ones who can deleted and return a trace.NotFoundErr - // - the ones who cannot be deleted, only reset. In this case, the resource revision is used to know if the change got applied. - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - autoUpdateVersionI, err = r.p.Client().GetAutoUpdateVersion(ctx) - if trace.IsNotFound(err) { - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(ctx.Err()), "autoupdate_version")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading AutoUpdateVersion (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "autoupdate_version") - return - } - continue - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - previousMetadata := autoUpdateVersionBefore.GetMetadata() - currentMetadata := autoUpdateVersionI.GetMetadata() - if previousMetadata.GetRevision() != currentMetadata.GetRevision() || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(ctx.Err()), "autoupdate_version")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading AutoUpdateVersion (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "autoupdate_version") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - autoUpdateVersion = autoUpdateVersionI - - - diags = schemav1.CopyAutoUpdateVersionToTerraform(ctx, autoUpdateVersion, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - plan.Attrs["id"] = types.String{Value: autoUpdateVersion.Metadata.Name} - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read reads teleport AutoUpdateVersion -func (r resourceTeleportAutoUpdateVersion) 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 - } - - autoUpdateVersionI, err := r.p.Client().GetAutoUpdateVersion(ctx) - if trace.IsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - autoUpdateVersion := autoUpdateVersionI - diags = schemav1.CopyAutoUpdateVersionToTerraform(ctx, autoUpdateVersion, &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 AutoUpdateVersion -func (r resourceTeleportAutoUpdateVersion) 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 - } - - autoUpdateVersion := &autoupdatev1.AutoUpdateVersion{} - diags = schemav1.CopyAutoUpdateVersionFromTerraform(ctx, plan, autoUpdateVersion) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - autoUpdateVersion.Kind = apitypes.KindAutoUpdateVersion - if autoUpdateVersion.GetMetadata() == nil { - autoUpdateVersion.Metadata = &headerv1.Metadata{} - } - if autoUpdateVersion.GetMetadata().GetName() == "" { - autoUpdateVersion.Metadata.Name = apitypes.MetaNameAutoUpdateVersion - } - - autoUpdateVersionBefore, err := r.p.Client().GetAutoUpdateVersion(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - _, err = r.p.Client().UpsertAutoUpdateVersion(ctx, autoUpdateVersion) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - var autoUpdateVersionI *autoupdatev1.AutoUpdateVersion - - tries := 0 - retry, err := r.p.Retry() - if err != nil { - return - } - for { - tries = tries + 1 - autoUpdateVersionI, err = r.p.Client().GetAutoUpdateVersion(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - if autoUpdateVersionBefore.GetMetadata().Revision != autoUpdateVersionI.GetMetadata().Revision || false { - break - } - select { - case <-ctx.Done(): - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(ctx.Err()), "autoupdate_version")) - return - case <-retry.After(): - } - if tries >= r.p.MaxRetries() { - diagMessage := fmt.Sprintf("Error reading AutoUpdateVersion (tried %d times) - state outdated, please import resource", tries) - resp.Diagnostics.AddError(diagMessage, "autoupdate_version") - return - } - } - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - autoUpdateVersion = autoUpdateVersionI - diags = schemav1.CopyAutoUpdateVersionToTerraform(ctx, autoUpdateVersion, &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 AutoUpdateVersion -func (r resourceTeleportAutoUpdateVersion) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - err := r.p.Client().DeleteAutoUpdateVersion(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - - resp.State.RemoveResource(ctx) -} - -// ImportState imports AutoUpdateVersion state -func (r resourceTeleportAutoUpdateVersion) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - autoUpdateVersionI, err := r.p.Client().GetAutoUpdateVersion(ctx) - if err != nil { - resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AutoUpdateVersion", trace.Wrap(err), "autoupdate_version")) - return - } - autoUpdateVersion := autoUpdateVersionI - - var state types.Object - - diags := resp.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - diags = schemav1.CopyAutoUpdateVersionToTerraform(ctx, autoUpdateVersion, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - id := autoUpdateVersion.Metadata.Name - - state.Attrs["id"] = types.String{Value: id} - - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// ModifyPlan modifies the planned value, normalizing null values. -func (r resourceTeleportAutoUpdateVersion) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { - // If the entire plan is null, the resource is planned for destruction. - if req.Plan.Raw.IsNull() { - return - } - - // If the state is null, the resource is being created. No need to modify plan. - if req.State.Raw.IsNull() { - return - } - - var config types.Object - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - autoUpdateVersion := &autoupdatev1.AutoUpdateVersion{} - resp.Diagnostics.Append(schemav1.CopyAutoUpdateVersionFromTerraform(ctx, config, autoUpdateVersion)...) - if resp.Diagnostics.HasError() { - return - } - - autoUpdateVersionResource := autoUpdateVersion - autoUpdateVersionResource.Kind = apitypes.KindAutoUpdateVersion - - autoUpdateVersion = autoUpdateVersionResource - - const preserveUnknown = true - resp.Diagnostics.Append(schemav1.CopyAutoUpdateVersionToTerraformPreserveUnknown(ctx, autoUpdateVersion, &config, preserveUnknown)...) - if resp.Diagnostics.HasError() { - return - } - - var plan types.Object - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - plan.Attrs["spec"] = config.Attrs["spec"] - - resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) -} diff --git a/integrations/terraform/provider/internal/resources/autoupdate_version.go b/integrations/terraform/provider/internal/resources/autoupdate_version.go new file mode 100644 index 000000000000..1d6b66d0e8c3 --- /dev/null +++ b/integrations/terraform/provider/internal/resources/autoupdate_version.go @@ -0,0 +1,66 @@ +// Teleport +// Copyright (C) 2026 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 resources + +import ( + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + autoupdatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport" + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/autoupdate/v1" +) + +// NewAutoUpdateVersionDataSourceType returns the autoupdate version data source type. +func NewAutoUpdateVersionDataSourceType() tfdriver.DataSourceType[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier] { + return tfdriver.DataSourceType[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier]{ + NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier] { + return teleport.NewAutoUpdateVersionClient(clientFromProvider(p)) + }, + Kind: types.KindAutoUpdateVersion, + Codec: tfdriver.DataSourceCodecFuncs[autoupdatev1.AutoUpdateVersion]{ + SchemaFunc: schemav1.GenSchemaAutoUpdateVersion, + ToStateFunc: schemav1.CopyAutoUpdateVersionToTerraform, + }, + Identifier: tfdriver.SingletonIdentifierFromName(types.MetaNameAutoUpdateVersion), + } +} + +// NewAutoUpdateVersionResourceType returns the autoupdate version resource type. +func NewAutoUpdateVersionResourceType() tfdriver.ResourceType[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier] { + return tfdriver.ResourceType[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier]{ + NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[autoupdatev1.AutoUpdateVersion, tfdriver.SingletonIdentifier] { + return teleport.NewAutoUpdateVersionClient(clientFromProvider(p)) + }, + Kind: types.KindAutoUpdateVersion, + Codec: tfdriver.ResourceCodecFuncs[autoupdatev1.AutoUpdateVersion]{ + SchemaFunc: schemav1.GenSchemaAutoUpdateVersion, + ToStateFunc: schemav1.CopyAutoUpdateVersionToTerraform, + FromPlanFunc: schemav1.CopyAutoUpdateVersionFromTerraform, + }, + Normalizer: tfdriver.ResourceNormalizers[autoupdatev1.AutoUpdateVersion]{ + tfdriver.ForceKind[autoupdatev1.AutoUpdateVersion](types.KindAutoUpdateVersion), + tfdriver.ForceName[autoupdatev1.AutoUpdateVersion](types.MetaNameAutoUpdateVersion), + }, + Identifier: tfdriver.SingletonIdentifierPolicy[autoupdatev1.AutoUpdateVersion](types.MetaNameAutoUpdateVersion), + ResourceRevision: func(st *autoupdatev1.AutoUpdateVersion) string { + return st.GetMetadata().GetRevision() + }, + } +} diff --git a/integrations/terraform/provider/internal/teleport/autoupdate_version.go b/integrations/terraform/provider/internal/teleport/autoupdate_version.go new file mode 100644 index 000000000000..4ddaa1408a2f --- /dev/null +++ b/integrations/terraform/provider/internal/teleport/autoupdate_version.go @@ -0,0 +1,71 @@ +// Teleport +// Copyright (C) 2026 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 teleport + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + autoupdatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + + "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver" +) + +// NewAutoUpdateVersionClient returns a Teleport client to interact with the autoupdate version. +func NewAutoUpdateVersionClient(c *client.Client) AutoUpdateVersionClient { + return AutoUpdateVersionClient{client: c} +} + +// AutoUpdateVersionClient manages the Teleport autoupdate version. +type AutoUpdateVersionClient struct { + client *client.Client +} + +// Get reads the Teleport autoupdate version. +func (r AutoUpdateVersionClient) Get(ctx context.Context, _ tfdriver.SingletonIdentifier) (*autoupdatev1.AutoUpdateVersion, error) { + version, err := r.client.GetAutoUpdateVersion(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + return version, nil +} + +// Create creates the Teleport autoupdate version. +func (r AutoUpdateVersionClient) Create(ctx context.Context, version *autoupdatev1.AutoUpdateVersion) error { + if _, err := r.client.CreateAutoUpdateVersion(ctx, version); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Upsert updates the Teleport autoupdate version. +func (r AutoUpdateVersionClient) Upsert(ctx context.Context, version *autoupdatev1.AutoUpdateVersion) error { + if _, err := r.client.UpsertAutoUpdateVersion(ctx, version); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// Delete removes the Teleport autoupdate version. +func (r AutoUpdateVersionClient) Delete(ctx context.Context, _ tfdriver.SingletonIdentifier) error { + return trace.Wrap(r.client.DeleteAutoUpdateVersion(ctx)) +} diff --git a/integrations/terraform/provider/internal/tfdriver/normalize.go b/integrations/terraform/provider/internal/tfdriver/normalize.go index ba43e2d21ba6..a6e1931c7ab3 100644 --- a/integrations/terraform/provider/internal/tfdriver/normalize.go +++ b/integrations/terraform/provider/internal/tfdriver/normalize.go @@ -20,6 +20,8 @@ import ( "context" "github.com/gravitational/trace" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" ) // ResourceNormalizer prepares a Teleport resource before create and update. @@ -131,3 +133,34 @@ func ForceKindFunc[T any](setKind func(*T)) ResourceNormalizer[T] { Update: setKindFunc, } } + +// ForceName sets a fixed resource name on create and update. +func ForceName[T any](name string) ResourceNormalizer[T] { + set := func(resource *T) error { + r, ok := any(resource).(interface { + GetMetadata() *headerv1.Metadata + SetMetadata(*headerv1.Metadata) + }) + if !ok { + return trace.BadParameter("%T does not expose header metadata", resource) + } + + metadata := r.GetMetadata() + if metadata == nil { + metadata = &headerv1.Metadata{} + r.SetMetadata(metadata) + } + metadata.SetName(name) + + return nil + } + + return ResourceNormalizerFuncs[T]{ + Create: func(_ context.Context, resource *T) error { + return set(resource) + }, + Update: func(_ context.Context, resource *T) error { + return set(resource) + }, + } +} diff --git a/integrations/terraform/provider/internal/tfdriver/normalize_test.go b/integrations/terraform/provider/internal/tfdriver/normalize_test.go index b2e0a8be352d..c135ec850b03 100644 --- a/integrations/terraform/provider/internal/tfdriver/normalize_test.go +++ b/integrations/terraform/provider/internal/tfdriver/normalize_test.go @@ -22,8 +22,22 @@ import ( "testing" "github.com/stretchr/testify/require" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" ) +type nameableTestResource struct { + metadata *headerv1.Metadata +} + +func (r *nameableTestResource) GetMetadata() *headerv1.Metadata { + return r.metadata +} + +func (r *nameableTestResource) SetMetadata(metadata *headerv1.Metadata) { + r.metadata = metadata +} + type normalizerTestResource struct { kind string defaulted bool @@ -99,6 +113,19 @@ func TestResourceNormalizersStopsOnError(t *testing.T) { require.Empty(t, resource.kind) } +func TestForceName(t *testing.T) { + // ForceName populates name if not present. + resource := &nameableTestResource{} + require.NoError(t, ForceName[nameableTestResource]("foo").NormalizeCreate(t.Context(), resource)) + require.Equal(t, "foo", resource.GetMetadata().GetName()) + + // ForceName overrides the name but preserves other existing fields. + resource = &nameableTestResource{metadata: &headerv1.Metadata{Name: "llama", Description: "keep"}} + require.NoError(t, ForceName[nameableTestResource]("foo").NormalizeUpdate(t.Context(), resource)) + require.Equal(t, "foo", resource.GetMetadata().GetName()) + require.Equal(t, "keep", resource.GetMetadata().GetDescription()) +} + func TestSpecificNormalizersRejectUnsupportedResource(t *testing.T) { type unsupported struct{} @@ -107,4 +134,7 @@ func TestSpecificNormalizersRejectUnsupportedResource(t *testing.T) { err = ForceKind[unsupported]("kind").NormalizeCreate(t.Context(), &unsupported{}) require.Error(t, err) + + err = ForceName[unsupported]("name").NormalizeCreate(t.Context(), &unsupported{}) + require.Error(t, err) } diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 6ff79875cc66..1103ee9cd097 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -569,6 +569,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_access_monitoring_rule": resources.NewAccessMonitoringRuleResourceType(), "teleport_app": resources.NewAppResourceType(), "teleport_auth_preference": resources.NewAuthPreferenceResourceType(), + "teleport_autoupdate_version": resources.NewAutoUpdateVersionResourceType(), "teleport_classifier": resources.NewClassifierResourceType(), "teleport_cluster_networking_config": resources.NewClusterNetworkingConfigResourceType(), "teleport_database": resources.NewDatabaseResourceType(), @@ -610,6 +611,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_access_monitoring_rule": resources.NewAccessMonitoringRuleDataSourceType(), "teleport_app": resources.NewAppDataSourceType(), "teleport_auth_preference": resources.NewAuthPreferenceDataSourceType(), + "teleport_autoupdate_version": resources.NewAutoUpdateVersionDataSourceType(), "teleport_cluster_networking_config": resources.NewClusterNetworkingConfigDataSourceType(), "teleport_classifier": resources.NewClassifierDataSourceType(), "teleport_database": resources.NewDatabaseDataSourceType(), diff --git a/integrations/terraform/testlib/autoupdate_version_test.go b/integrations/terraform/testlib/autoupdate_version_test.go index 161d16f06fea..f517f20ecb94 100644 --- a/integrations/terraform/testlib/autoupdate_version_test.go +++ b/integrations/terraform/testlib/autoupdate_version_test.go @@ -17,11 +17,11 @@ limitations under the License. package testlib import ( - "context" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" @@ -71,12 +71,8 @@ func (s *TerraformSuiteOSS) TestAutoUpdateVersion() { } func (s *TerraformSuiteOSS) TestImportAutoUpdateVersion() { - ctx, cancel := context.WithCancel(context.Background()) - s.T().Cleanup(cancel) - r := "teleport_autoupdate_version" - id := "test_import" - name := r + "." + id + name := r + ".test" testStartVersion := "1.2.3" testTargetVersion := "1.2.4" @@ -97,25 +93,24 @@ func (s *TerraformSuiteOSS) TestImportAutoUpdateVersion() { ) require.NoError(s.T(), err) - autoUpdateVersion, err = s.client.CreateAutoUpdateVersion(ctx, autoUpdateVersion) + autoUpdateVersion, err = s.client.CreateAutoUpdateVersion(s.T().Context(), autoUpdateVersion) require.NoError(s.T(), err) - require.Eventually(s.T(), func() bool { - autoUpdateVersionCurrent, err := s.client.GetAutoUpdateVersion(ctx) - require.NoError(s.T(), err) - - return autoUpdateVersion.GetMetadata().GetRevision() != autoUpdateVersionCurrent.GetMetadata().GetName() - }, 5*time.Second, time.Second) + require.EventuallyWithT(s.T(), func(t *assert.CollectT) { + autoUpdateVersionCurrent, err := s.client.GetAutoUpdateVersion(s.T().Context()) + require.NoError(t, err) + require.Equal(t, autoUpdateVersion.GetMetadata().GetRevision(), autoUpdateVersionCurrent.GetMetadata().GetRevision()) + }, 15*time.Second, 100*time.Millisecond) resource.Test(s.T(), resource.TestCase{ ProtoV6ProviderFactories: s.terraformProviders, IsUnitTest: true, Steps: []resource.TestStep{ { - Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`, + Config: s.terraformConfig + "\n" + `resource "` + r + `" "test" { }`, ResourceName: name, ImportState: true, - ImportStateId: id, + ImportStateId: "autoupdate-version", ImportStateCheck: func(state []*terraform.InstanceState) error { require.Equal(s.T(), "autoupdate_version", state[0].Attributes["kind"]) require.Equal(s.T(), testStartVersion, state[0].Attributes["spec.agents.start_version"]) From dce19fa553cd62066b57fb7e8dfd793cc6ed21db Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 13 Aug 2026 08:15:04 +0000 Subject: [PATCH 285/347] Deprecate recording of join metadata into bot instance record (#69571) * Deprecate recording of join metadata into bot instance record * update docs * Store bound keyapir data * regen docs --- .../v1/bot_instance_protohybrid.pb.go | 20 ++- .../v1/bot_instance_protoopaque.pb.go | 15 +- .../v1/join_attrs_protohybrid.pb.go | 165 ++++++++++++++++-- .../v1/join_attrs_protoopaque.pb.go | 158 +++++++++++++++-- .../teleport/machineid/v1/bot_instance.proto | 5 +- .../workloadidentity/v1/join_attrs.proto | 14 ++ .../teleport-resources/bot-instance.mdx | 23 ++- .../workload-identity/attributes.mdx | 11 ++ lib/auth/join.go | 24 --- lib/boundkeypair/claims.go | 14 ++ lib/join/boundkeypair/boundkeypair.go | 2 +- lib/join/join_bound_keypair_test.go | 10 ++ lib/join/server_boundkeypair.go | 22 ++- tool/tctl/common/bots_command.go | 2 + 14 files changed, 411 insertions(+), 74 deletions(-) diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance_protohybrid.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance_protohybrid.pb.go index 5b16a4104b28..24bfb7e1de20 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance_protohybrid.pb.go @@ -758,7 +758,10 @@ type BotInstanceStatusAuthentication struct { // Deprecated: prefer using join_attrs.meta.join_token_name JoinToken string `protobuf:"bytes,3,opt,name=join_token,json=joinToken,proto3" json:"join_token,omitempty"` // The metadata sourced from the join method. - // Deprecated: prefer using join_attrs. + // Deprecated: prefer using join_attrs. No longer written since v19; only + // present on records written by older Auth Service versions. + // + // Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. Metadata *structpb.Struct `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` // On each renewal, this generation is incremented. For delegated join // methods, this counter is not checked during renewal. For the `token` join @@ -824,6 +827,7 @@ func (x *BotInstanceStatusAuthentication) GetJoinToken() string { return "" } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) GetMetadata() *structpb.Struct { if x != nil { return x.Metadata @@ -864,6 +868,7 @@ func (x *BotInstanceStatusAuthentication) SetJoinToken(v string) { x.JoinToken = v } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) SetMetadata(v *structpb.Struct) { x.Metadata = v } @@ -890,6 +895,7 @@ func (x *BotInstanceStatusAuthentication) HasAuthenticatedAt() bool { return x.AuthenticatedAt != nil } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) HasMetadata() bool { if x == nil { return false @@ -908,6 +914,7 @@ func (x *BotInstanceStatusAuthentication) ClearAuthenticatedAt() { x.AuthenticatedAt = nil } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) ClearMetadata() { x.Metadata = nil } @@ -930,7 +937,10 @@ type BotInstanceStatusAuthentication_builder struct { // Deprecated: prefer using join_attrs.meta.join_token_name JoinToken string // The metadata sourced from the join method. - // Deprecated: prefer using join_attrs. + // Deprecated: prefer using join_attrs. No longer written since v19; only + // present on records written by older Auth Service versions. + // + // Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. Metadata *structpb.Struct // On each renewal, this generation is incremented. For delegated join // methods, this counter is not checked during renewal. For the `token` join @@ -1362,14 +1372,14 @@ const file_teleport_machineid_v1_bot_instance_proto_rawDesc = "" + " \x01(\tR\x0fexternalUpdater\x128\n" + "\x18external_updater_version\x18\v \x01(\tR\x16externalUpdaterVersion\x127\n" + "\fupdater_info\x18\f \x01(\v2\x14.types.UpdaterV2InfoR\vupdaterInfo\x122\n" + - "\x04kind\x18\r \x01(\x0e2\x1e.teleport.machineid.v1.BotKindR\x04kind\"\xf7\x02\n" + + "\x04kind\x18\r \x01(\x0e2\x1e.teleport.machineid.v1.BotKindR\x04kind\"\xfb\x02\n" + "\x1fBotInstanceStatusAuthentication\x12E\n" + "\x10authenticated_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x0fauthenticatedAt\x12\x1f\n" + "\vjoin_method\x18\x02 \x01(\tR\n" + "joinMethod\x12\x1d\n" + "\n" + - "join_token\x18\x03 \x01(\tR\tjoinToken\x123\n" + - "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x1e\n" + + "join_token\x18\x03 \x01(\tR\tjoinToken\x127\n" + + "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructB\x02\x18\x01R\bmetadata\x12\x1e\n" + "\n" + "generation\x18\x05 \x01(\x05R\n" + "generation\x12\x1d\n" + diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance_protoopaque.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance_protoopaque.pb.go index ef7adb538e96..d330dc10a959 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance_protoopaque.pb.go @@ -770,6 +770,7 @@ func (x *BotInstanceStatusAuthentication) GetJoinToken() string { return "" } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) GetMetadata() *structpb.Struct { if x != nil { return x.xxx_hidden_Metadata @@ -810,6 +811,7 @@ func (x *BotInstanceStatusAuthentication) SetJoinToken(v string) { x.xxx_hidden_JoinToken = v } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) SetMetadata(v *structpb.Struct) { x.xxx_hidden_Metadata = v } @@ -836,6 +838,7 @@ func (x *BotInstanceStatusAuthentication) HasAuthenticatedAt() bool { return x.xxx_hidden_AuthenticatedAt != nil } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) HasMetadata() bool { if x == nil { return false @@ -854,6 +857,7 @@ func (x *BotInstanceStatusAuthentication) ClearAuthenticatedAt() { x.xxx_hidden_AuthenticatedAt = nil } +// Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. func (x *BotInstanceStatusAuthentication) ClearMetadata() { x.xxx_hidden_Metadata = nil } @@ -876,7 +880,10 @@ type BotInstanceStatusAuthentication_builder struct { // Deprecated: prefer using join_attrs.meta.join_token_name JoinToken string // The metadata sourced from the join method. - // Deprecated: prefer using join_attrs. + // Deprecated: prefer using join_attrs. No longer written since v19; only + // present on records written by older Auth Service versions. + // + // Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto. Metadata *structpb.Struct // On each renewal, this generation is incremented. For delegated join // methods, this counter is not checked during renewal. For the `token` join @@ -1312,14 +1319,14 @@ const file_teleport_machineid_v1_bot_instance_proto_rawDesc = "" + " \x01(\tR\x0fexternalUpdater\x128\n" + "\x18external_updater_version\x18\v \x01(\tR\x16externalUpdaterVersion\x127\n" + "\fupdater_info\x18\f \x01(\v2\x14.types.UpdaterV2InfoR\vupdaterInfo\x122\n" + - "\x04kind\x18\r \x01(\x0e2\x1e.teleport.machineid.v1.BotKindR\x04kind\"\xf7\x02\n" + + "\x04kind\x18\r \x01(\x0e2\x1e.teleport.machineid.v1.BotKindR\x04kind\"\xfb\x02\n" + "\x1fBotInstanceStatusAuthentication\x12E\n" + "\x10authenticated_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x0fauthenticatedAt\x12\x1f\n" + "\vjoin_method\x18\x02 \x01(\tR\n" + "joinMethod\x12\x1d\n" + "\n" + - "join_token\x18\x03 \x01(\tR\tjoinToken\x123\n" + - "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x1e\n" + + "join_token\x18\x03 \x01(\tR\tjoinToken\x127\n" + + "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructB\x02\x18\x01R\bmetadata\x12\x1e\n" + "\n" + "generation\x18\x05 \x01(\x05R\n" + "generation\x12\x1d\n" + diff --git a/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protohybrid.pb.go b/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protohybrid.pb.go index 97a15a429f50..27ae54ffd58c 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protohybrid.pb.go @@ -72,7 +72,9 @@ type JoinAttrs struct { // Attributes that are specific to the Env0 (`env0`) join method. Env0 *JoinAttrsEnv0 `protobuf:"bytes,15,opt,name=env0,proto3" json:"env0,omitempty"` // Attributes that are specific to the generic OIDC (`generic_oidc`) join method. - GenericOidc *JoinAttrsGenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3" json:"generic_oidc,omitempty"` + GenericOidc *JoinAttrsGenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3" json:"generic_oidc,omitempty"` + // Attributes that are specific to the Bound Keypair (`bound_keypair`) join method. + BoundKeypair *JoinAttrsBoundKeypair `protobuf:"bytes,17,opt,name=bound_keypair,json=boundKeypair,proto3" json:"bound_keypair,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -214,6 +216,13 @@ func (x *JoinAttrs) GetGenericOidc() *JoinAttrsGenericOIDC { return nil } +func (x *JoinAttrs) GetBoundKeypair() *JoinAttrsBoundKeypair { + if x != nil { + return x.BoundKeypair + } + return nil +} + func (x *JoinAttrs) SetMeta(v *JoinAttrsMeta) { x.Meta = v } @@ -278,6 +287,10 @@ func (x *JoinAttrs) SetGenericOidc(v *JoinAttrsGenericOIDC) { x.GenericOidc = v } +func (x *JoinAttrs) SetBoundKeypair(v *JoinAttrsBoundKeypair) { + x.BoundKeypair = v +} + func (x *JoinAttrs) HasMeta() bool { if x == nil { return false @@ -390,6 +403,13 @@ func (x *JoinAttrs) HasGenericOidc() bool { return x.GenericOidc != nil } +func (x *JoinAttrs) HasBoundKeypair() bool { + if x == nil { + return false + } + return x.BoundKeypair != nil +} + func (x *JoinAttrs) ClearMeta() { x.Meta = nil } @@ -454,6 +474,10 @@ func (x *JoinAttrs) ClearGenericOidc() { x.GenericOidc = nil } +func (x *JoinAttrs) ClearBoundKeypair() { + x.BoundKeypair = nil +} + type JoinAttrs_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -490,6 +514,8 @@ type JoinAttrs_builder struct { Env0 *JoinAttrsEnv0 // Attributes that are specific to the generic OIDC (`generic_oidc`) join method. GenericOidc *JoinAttrsGenericOIDC + // Attributes that are specific to the Bound Keypair (`bound_keypair`) join method. + BoundKeypair *JoinAttrsBoundKeypair } func (b0 JoinAttrs_builder) Build() *JoinAttrs { @@ -512,6 +538,7 @@ func (b0 JoinAttrs_builder) Build() *JoinAttrs { x.AzureDevops = b.AzureDevops x.Env0 = b.Env0 x.GenericOidc = b.GenericOidc + x.BoundKeypair = b.BoundKeypair return m0 } @@ -3246,11 +3273,107 @@ func (b0 JoinAttrsGenericOIDC_builder) Build() *JoinAttrsGenericOIDC { return m0 } +// Attributes that are specific to the Bound Keypair (`bound_keypair`) join +// method. +type JoinAttrsBoundKeypair struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The bound public key verified during this join, in SSH authorized_keys + // format. + PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // The token's configured recovery mode, e.g. `standard` or `relaxed`. + RecoveryMode string `protobuf:"bytes,2,opt,name=recovery_mode,json=recoveryMode,proto3" json:"recovery_mode,omitempty"` + // The token's recovery counter after this join. + RecoveryCount uint32 `protobuf:"varint,3,opt,name=recovery_count,json=recoveryCount,proto3" json:"recovery_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JoinAttrsBoundKeypair) Reset() { + *x = JoinAttrsBoundKeypair{} + mi := &file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JoinAttrsBoundKeypair) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinAttrsBoundKeypair) ProtoMessage() {} + +func (x *JoinAttrsBoundKeypair) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *JoinAttrsBoundKeypair) GetPublicKey() string { + if x != nil { + return x.PublicKey + } + return "" +} + +func (x *JoinAttrsBoundKeypair) GetRecoveryMode() string { + if x != nil { + return x.RecoveryMode + } + return "" +} + +func (x *JoinAttrsBoundKeypair) GetRecoveryCount() uint32 { + if x != nil { + return x.RecoveryCount + } + return 0 +} + +func (x *JoinAttrsBoundKeypair) SetPublicKey(v string) { + x.PublicKey = v +} + +func (x *JoinAttrsBoundKeypair) SetRecoveryMode(v string) { + x.RecoveryMode = v +} + +func (x *JoinAttrsBoundKeypair) SetRecoveryCount(v uint32) { + x.RecoveryCount = v +} + +type JoinAttrsBoundKeypair_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The bound public key verified during this join, in SSH authorized_keys + // format. + PublicKey string + // The token's configured recovery mode, e.g. `standard` or `relaxed`. + RecoveryMode string + // The token's recovery counter after this join. + RecoveryCount uint32 +} + +func (b0 JoinAttrsBoundKeypair_builder) Build() *JoinAttrsBoundKeypair { + m0 := &JoinAttrsBoundKeypair{} + b, x := &b0, m0 + _, _ = b, x + x.PublicKey = b.PublicKey + x.RecoveryMode = b.RecoveryMode + x.RecoveryCount = b.RecoveryCount + return m0 +} + var File_teleport_workloadidentity_v1_join_attrs_proto protoreflect.FileDescriptor const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\n" + - "-teleport/workloadidentity/v1/join_attrs.proto\x12\x1cteleport.workloadidentity.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xb1\t\n" + + "-teleport/workloadidentity/v1/join_attrs.proto\x12\x1cteleport.workloadidentity.v1\x1a\x1cgoogle/protobuf/struct.proto\"\x8b\n" + + "\n" + "\tJoinAttrs\x12?\n" + "\x04meta\x18\x01 \x01(\v2+.teleport.workloadidentity.v1.JoinAttrsMetaR\x04meta\x12E\n" + "\x06gitlab\x18\x02 \x01(\v2-.teleport.workloadidentity.v1.JoinAttrsGitLabR\x06gitlab\x12E\n" + @@ -3270,7 +3393,8 @@ const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\x06oracle\x18\r \x01(\v2-.teleport.workloadidentity.v1.JoinAttrsOracleR\x06oracle\x12U\n" + "\fazure_devops\x18\x0e \x01(\v22.teleport.workloadidentity.v1.JoinAttrsAzureDevopsR\vazureDevops\x12?\n" + "\x04env0\x18\x0f \x01(\v2+.teleport.workloadidentity.v1.JoinAttrsEnv0R\x04env0\x12U\n" + - "\fgeneric_oidc\x18\x10 \x01(\v22.teleport.workloadidentity.v1.JoinAttrsGenericOIDCR\vgenericOidc\"X\n" + + "\fgeneric_oidc\x18\x10 \x01(\v22.teleport.workloadidentity.v1.JoinAttrsGenericOIDCR\vgenericOidc\x12X\n" + + "\rbound_keypair\x18\x11 \x01(\v23.teleport.workloadidentity.v1.JoinAttrsBoundKeypairR\fboundKeypair\"X\n" + "\rJoinAttrsMeta\x12&\n" + "\x0fjoin_token_name\x18\x01 \x01(\tR\rjoinTokenName\x12\x1f\n" + "\vjoin_method\x18\x02 \x01(\tR\n" + @@ -3417,9 +3541,14 @@ const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\x0edeployer_email\x18\f \x01(\tR\rdeployerEmail\x12\x19\n" + "\benv0_tag\x18\r \x01(\tR\aenv0Tag\"G\n" + "\x14JoinAttrsGenericOIDC\x12/\n" + - "\x06claims\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x06claimsBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3" + "\x06claims\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x06claims\"\x82\x01\n" + + "\x15JoinAttrsBoundKeypair\x12\x1d\n" + + "\n" + + "public_key\x18\x01 \x01(\tR\tpublicKey\x12#\n" + + "\rrecovery_mode\x18\x02 \x01(\tR\frecoveryMode\x12%\n" + + "\x0erecovery_count\x18\x03 \x01(\rR\rrecoveryCountBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3" -var file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_teleport_workloadidentity_v1_join_attrs_proto_goTypes = []any{ (*JoinAttrs)(nil), // 0: teleport.workloadidentity.v1.JoinAttrs (*JoinAttrsMeta)(nil), // 1: teleport.workloadidentity.v1.JoinAttrsMeta @@ -3442,7 +3571,8 @@ var file_teleport_workloadidentity_v1_join_attrs_proto_goTypes = []any{ (*JoinAttrsAzureDevopsPipeline)(nil), // 18: teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline (*JoinAttrsEnv0)(nil), // 19: teleport.workloadidentity.v1.JoinAttrsEnv0 (*JoinAttrsGenericOIDC)(nil), // 20: teleport.workloadidentity.v1.JoinAttrsGenericOIDC - (*structpb.Struct)(nil), // 21: google.protobuf.Struct + (*JoinAttrsBoundKeypair)(nil), // 21: teleport.workloadidentity.v1.JoinAttrsBoundKeypair + (*structpb.Struct)(nil), // 22: google.protobuf.Struct } var file_teleport_workloadidentity_v1_join_attrs_proto_depIdxs = []int32{ 1, // 0: teleport.workloadidentity.v1.JoinAttrs.meta:type_name -> teleport.workloadidentity.v1.JoinAttrsMeta @@ -3461,16 +3591,17 @@ var file_teleport_workloadidentity_v1_join_attrs_proto_depIdxs = []int32{ 17, // 13: teleport.workloadidentity.v1.JoinAttrs.azure_devops:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevops 19, // 14: teleport.workloadidentity.v1.JoinAttrs.env0:type_name -> teleport.workloadidentity.v1.JoinAttrsEnv0 20, // 15: teleport.workloadidentity.v1.JoinAttrs.generic_oidc:type_name -> teleport.workloadidentity.v1.JoinAttrsGenericOIDC - 11, // 16: teleport.workloadidentity.v1.JoinAttrsGCP.gce:type_name -> teleport.workloadidentity.v1.JoinAttrsGCPGCE - 14, // 17: teleport.workloadidentity.v1.JoinAttrsKubernetes.service_account:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesServiceAccount - 13, // 18: teleport.workloadidentity.v1.JoinAttrsKubernetes.pod:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesPod - 18, // 19: teleport.workloadidentity.v1.JoinAttrsAzureDevops.pipeline:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline - 21, // 20: teleport.workloadidentity.v1.JoinAttrsGenericOIDC.claims:type_name -> google.protobuf.Struct - 21, // [21:21] is the sub-list for method output_type - 21, // [21:21] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 21, // 16: teleport.workloadidentity.v1.JoinAttrs.bound_keypair:type_name -> teleport.workloadidentity.v1.JoinAttrsBoundKeypair + 11, // 17: teleport.workloadidentity.v1.JoinAttrsGCP.gce:type_name -> teleport.workloadidentity.v1.JoinAttrsGCPGCE + 14, // 18: teleport.workloadidentity.v1.JoinAttrsKubernetes.service_account:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesServiceAccount + 13, // 19: teleport.workloadidentity.v1.JoinAttrsKubernetes.pod:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesPod + 18, // 20: teleport.workloadidentity.v1.JoinAttrsAzureDevops.pipeline:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline + 22, // 21: teleport.workloadidentity.v1.JoinAttrsGenericOIDC.claims:type_name -> google.protobuf.Struct + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_teleport_workloadidentity_v1_join_attrs_proto_init() } @@ -3484,7 +3615,7 @@ func file_teleport_workloadidentity_v1_join_attrs_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc), len(file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc)), NumEnums: 0, - NumMessages: 21, + NumMessages: 22, NumExtensions: 0, NumServices: 0, }, diff --git a/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protoopaque.pb.go b/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protoopaque.pb.go index 400ec9794e16..608991f9b3b7 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/join_attrs_protoopaque.pb.go @@ -56,6 +56,7 @@ type JoinAttrs struct { xxx_hidden_AzureDevops *JoinAttrsAzureDevops `protobuf:"bytes,14,opt,name=azure_devops,json=azureDevops,proto3"` xxx_hidden_Env0 *JoinAttrsEnv0 `protobuf:"bytes,15,opt,name=env0,proto3"` xxx_hidden_GenericOidc *JoinAttrsGenericOIDC `protobuf:"bytes,16,opt,name=generic_oidc,json=genericOidc,proto3"` + xxx_hidden_BoundKeypair *JoinAttrsBoundKeypair `protobuf:"bytes,17,opt,name=bound_keypair,json=boundKeypair,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -197,6 +198,13 @@ func (x *JoinAttrs) GetGenericOidc() *JoinAttrsGenericOIDC { return nil } +func (x *JoinAttrs) GetBoundKeypair() *JoinAttrsBoundKeypair { + if x != nil { + return x.xxx_hidden_BoundKeypair + } + return nil +} + func (x *JoinAttrs) SetMeta(v *JoinAttrsMeta) { x.xxx_hidden_Meta = v } @@ -261,6 +269,10 @@ func (x *JoinAttrs) SetGenericOidc(v *JoinAttrsGenericOIDC) { x.xxx_hidden_GenericOidc = v } +func (x *JoinAttrs) SetBoundKeypair(v *JoinAttrsBoundKeypair) { + x.xxx_hidden_BoundKeypair = v +} + func (x *JoinAttrs) HasMeta() bool { if x == nil { return false @@ -373,6 +385,13 @@ func (x *JoinAttrs) HasGenericOidc() bool { return x.xxx_hidden_GenericOidc != nil } +func (x *JoinAttrs) HasBoundKeypair() bool { + if x == nil { + return false + } + return x.xxx_hidden_BoundKeypair != nil +} + func (x *JoinAttrs) ClearMeta() { x.xxx_hidden_Meta = nil } @@ -437,6 +456,10 @@ func (x *JoinAttrs) ClearGenericOidc() { x.xxx_hidden_GenericOidc = nil } +func (x *JoinAttrs) ClearBoundKeypair() { + x.xxx_hidden_BoundKeypair = nil +} + type JoinAttrs_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -473,6 +496,8 @@ type JoinAttrs_builder struct { Env0 *JoinAttrsEnv0 // Attributes that are specific to the generic OIDC (`generic_oidc`) join method. GenericOidc *JoinAttrsGenericOIDC + // Attributes that are specific to the Bound Keypair (`bound_keypair`) join method. + BoundKeypair *JoinAttrsBoundKeypair } func (b0 JoinAttrs_builder) Build() *JoinAttrs { @@ -495,6 +520,7 @@ func (b0 JoinAttrs_builder) Build() *JoinAttrs { x.xxx_hidden_AzureDevops = b.AzureDevops x.xxx_hidden_Env0 = b.Env0 x.xxx_hidden_GenericOidc = b.GenericOidc + x.xxx_hidden_BoundKeypair = b.BoundKeypair return m0 } @@ -3079,11 +3105,103 @@ func (b0 JoinAttrsGenericOIDC_builder) Build() *JoinAttrsGenericOIDC { return m0 } +// Attributes that are specific to the Bound Keypair (`bound_keypair`) join +// method. +type JoinAttrsBoundKeypair struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3"` + xxx_hidden_RecoveryMode string `protobuf:"bytes,2,opt,name=recovery_mode,json=recoveryMode,proto3"` + xxx_hidden_RecoveryCount uint32 `protobuf:"varint,3,opt,name=recovery_count,json=recoveryCount,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JoinAttrsBoundKeypair) Reset() { + *x = JoinAttrsBoundKeypair{} + mi := &file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JoinAttrsBoundKeypair) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinAttrsBoundKeypair) ProtoMessage() {} + +func (x *JoinAttrsBoundKeypair) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *JoinAttrsBoundKeypair) GetPublicKey() string { + if x != nil { + return x.xxx_hidden_PublicKey + } + return "" +} + +func (x *JoinAttrsBoundKeypair) GetRecoveryMode() string { + if x != nil { + return x.xxx_hidden_RecoveryMode + } + return "" +} + +func (x *JoinAttrsBoundKeypair) GetRecoveryCount() uint32 { + if x != nil { + return x.xxx_hidden_RecoveryCount + } + return 0 +} + +func (x *JoinAttrsBoundKeypair) SetPublicKey(v string) { + x.xxx_hidden_PublicKey = v +} + +func (x *JoinAttrsBoundKeypair) SetRecoveryMode(v string) { + x.xxx_hidden_RecoveryMode = v +} + +func (x *JoinAttrsBoundKeypair) SetRecoveryCount(v uint32) { + x.xxx_hidden_RecoveryCount = v +} + +type JoinAttrsBoundKeypair_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The bound public key verified during this join, in SSH authorized_keys + // format. + PublicKey string + // The token's configured recovery mode, e.g. `standard` or `relaxed`. + RecoveryMode string + // The token's recovery counter after this join. + RecoveryCount uint32 +} + +func (b0 JoinAttrsBoundKeypair_builder) Build() *JoinAttrsBoundKeypair { + m0 := &JoinAttrsBoundKeypair{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_PublicKey = b.PublicKey + x.xxx_hidden_RecoveryMode = b.RecoveryMode + x.xxx_hidden_RecoveryCount = b.RecoveryCount + return m0 +} + var File_teleport_workloadidentity_v1_join_attrs_proto protoreflect.FileDescriptor const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\n" + - "-teleport/workloadidentity/v1/join_attrs.proto\x12\x1cteleport.workloadidentity.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xb1\t\n" + + "-teleport/workloadidentity/v1/join_attrs.proto\x12\x1cteleport.workloadidentity.v1\x1a\x1cgoogle/protobuf/struct.proto\"\x8b\n" + + "\n" + "\tJoinAttrs\x12?\n" + "\x04meta\x18\x01 \x01(\v2+.teleport.workloadidentity.v1.JoinAttrsMetaR\x04meta\x12E\n" + "\x06gitlab\x18\x02 \x01(\v2-.teleport.workloadidentity.v1.JoinAttrsGitLabR\x06gitlab\x12E\n" + @@ -3103,7 +3221,8 @@ const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\x06oracle\x18\r \x01(\v2-.teleport.workloadidentity.v1.JoinAttrsOracleR\x06oracle\x12U\n" + "\fazure_devops\x18\x0e \x01(\v22.teleport.workloadidentity.v1.JoinAttrsAzureDevopsR\vazureDevops\x12?\n" + "\x04env0\x18\x0f \x01(\v2+.teleport.workloadidentity.v1.JoinAttrsEnv0R\x04env0\x12U\n" + - "\fgeneric_oidc\x18\x10 \x01(\v22.teleport.workloadidentity.v1.JoinAttrsGenericOIDCR\vgenericOidc\"X\n" + + "\fgeneric_oidc\x18\x10 \x01(\v22.teleport.workloadidentity.v1.JoinAttrsGenericOIDCR\vgenericOidc\x12X\n" + + "\rbound_keypair\x18\x11 \x01(\v23.teleport.workloadidentity.v1.JoinAttrsBoundKeypairR\fboundKeypair\"X\n" + "\rJoinAttrsMeta\x12&\n" + "\x0fjoin_token_name\x18\x01 \x01(\tR\rjoinTokenName\x12\x1f\n" + "\vjoin_method\x18\x02 \x01(\tR\n" + @@ -3250,9 +3369,14 @@ const file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc = "" + "\x0edeployer_email\x18\f \x01(\tR\rdeployerEmail\x12\x19\n" + "\benv0_tag\x18\r \x01(\tR\aenv0Tag\"G\n" + "\x14JoinAttrsGenericOIDC\x12/\n" + - "\x06claims\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x06claimsBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3" + "\x06claims\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x06claims\"\x82\x01\n" + + "\x15JoinAttrsBoundKeypair\x12\x1d\n" + + "\n" + + "public_key\x18\x01 \x01(\tR\tpublicKey\x12#\n" + + "\rrecovery_mode\x18\x02 \x01(\tR\frecoveryMode\x12%\n" + + "\x0erecovery_count\x18\x03 \x01(\rR\rrecoveryCountBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3" -var file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_teleport_workloadidentity_v1_join_attrs_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_teleport_workloadidentity_v1_join_attrs_proto_goTypes = []any{ (*JoinAttrs)(nil), // 0: teleport.workloadidentity.v1.JoinAttrs (*JoinAttrsMeta)(nil), // 1: teleport.workloadidentity.v1.JoinAttrsMeta @@ -3275,7 +3399,8 @@ var file_teleport_workloadidentity_v1_join_attrs_proto_goTypes = []any{ (*JoinAttrsAzureDevopsPipeline)(nil), // 18: teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline (*JoinAttrsEnv0)(nil), // 19: teleport.workloadidentity.v1.JoinAttrsEnv0 (*JoinAttrsGenericOIDC)(nil), // 20: teleport.workloadidentity.v1.JoinAttrsGenericOIDC - (*structpb.Struct)(nil), // 21: google.protobuf.Struct + (*JoinAttrsBoundKeypair)(nil), // 21: teleport.workloadidentity.v1.JoinAttrsBoundKeypair + (*structpb.Struct)(nil), // 22: google.protobuf.Struct } var file_teleport_workloadidentity_v1_join_attrs_proto_depIdxs = []int32{ 1, // 0: teleport.workloadidentity.v1.JoinAttrs.meta:type_name -> teleport.workloadidentity.v1.JoinAttrsMeta @@ -3294,16 +3419,17 @@ var file_teleport_workloadidentity_v1_join_attrs_proto_depIdxs = []int32{ 17, // 13: teleport.workloadidentity.v1.JoinAttrs.azure_devops:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevops 19, // 14: teleport.workloadidentity.v1.JoinAttrs.env0:type_name -> teleport.workloadidentity.v1.JoinAttrsEnv0 20, // 15: teleport.workloadidentity.v1.JoinAttrs.generic_oidc:type_name -> teleport.workloadidentity.v1.JoinAttrsGenericOIDC - 11, // 16: teleport.workloadidentity.v1.JoinAttrsGCP.gce:type_name -> teleport.workloadidentity.v1.JoinAttrsGCPGCE - 14, // 17: teleport.workloadidentity.v1.JoinAttrsKubernetes.service_account:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesServiceAccount - 13, // 18: teleport.workloadidentity.v1.JoinAttrsKubernetes.pod:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesPod - 18, // 19: teleport.workloadidentity.v1.JoinAttrsAzureDevops.pipeline:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline - 21, // 20: teleport.workloadidentity.v1.JoinAttrsGenericOIDC.claims:type_name -> google.protobuf.Struct - 21, // [21:21] is the sub-list for method output_type - 21, // [21:21] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 21, // 16: teleport.workloadidentity.v1.JoinAttrs.bound_keypair:type_name -> teleport.workloadidentity.v1.JoinAttrsBoundKeypair + 11, // 17: teleport.workloadidentity.v1.JoinAttrsGCP.gce:type_name -> teleport.workloadidentity.v1.JoinAttrsGCPGCE + 14, // 18: teleport.workloadidentity.v1.JoinAttrsKubernetes.service_account:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesServiceAccount + 13, // 19: teleport.workloadidentity.v1.JoinAttrsKubernetes.pod:type_name -> teleport.workloadidentity.v1.JoinAttrsKubernetesPod + 18, // 20: teleport.workloadidentity.v1.JoinAttrsAzureDevops.pipeline:type_name -> teleport.workloadidentity.v1.JoinAttrsAzureDevopsPipeline + 22, // 21: teleport.workloadidentity.v1.JoinAttrsGenericOIDC.claims:type_name -> google.protobuf.Struct + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_teleport_workloadidentity_v1_join_attrs_proto_init() } @@ -3317,7 +3443,7 @@ func file_teleport_workloadidentity_v1_join_attrs_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc), len(file_teleport_workloadidentity_v1_join_attrs_proto_rawDesc)), NumEnums: 0, - NumMessages: 21, + NumMessages: 22, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/machineid/v1/bot_instance.proto b/api/proto/teleport/machineid/v1/bot_instance.proto index f48357a859fc..7376f703223c 100644 --- a/api/proto/teleport/machineid/v1/bot_instance.proto +++ b/api/proto/teleport/machineid/v1/bot_instance.proto @@ -139,8 +139,9 @@ message BotInstanceStatusAuthentication { // Deprecated: prefer using join_attrs.meta.join_token_name string join_token = 3; // The metadata sourced from the join method. - // Deprecated: prefer using join_attrs. - google.protobuf.Struct metadata = 4; + // Deprecated: prefer using join_attrs. No longer written since v19; only + // present on records written by older Auth Service versions. + google.protobuf.Struct metadata = 4 [deprecated = true]; // On each renewal, this generation is incremented. For delegated join // methods, this counter is not checked during renewal. For the `token` join diff --git a/api/proto/teleport/workloadidentity/v1/join_attrs.proto b/api/proto/teleport/workloadidentity/v1/join_attrs.proto index 703a1bc26f30..71ca90decb96 100644 --- a/api/proto/teleport/workloadidentity/v1/join_attrs.proto +++ b/api/proto/teleport/workloadidentity/v1/join_attrs.proto @@ -55,6 +55,8 @@ message JoinAttrs { JoinAttrsEnv0 env0 = 15; // Attributes that are specific to the generic OIDC (`generic_oidc`) join method. JoinAttrsGenericOIDC generic_oidc = 16; + // Attributes that are specific to the Bound Keypair (`bound_keypair`) join method. + JoinAttrsBoundKeypair bound_keypair = 17; } // The collection of attributes that result from the join process but are not @@ -417,3 +419,15 @@ message JoinAttrsGenericOIDC { // A complete set of token claims. google.protobuf.Struct claims = 1; } + +// Attributes that are specific to the Bound Keypair (`bound_keypair`) join +// method. +message JoinAttrsBoundKeypair { + // The bound public key verified during this join, in SSH authorized_keys + // format. + string public_key = 1; + // The token's configured recovery mode, e.g. `standard` or `relaxed`. + string recovery_mode = 2; + // The token's recovery counter after this join. + uint32 recovery_count = 3; +} diff --git a/docs/pages/reference/infrastructure-as-code/teleport-resources/bot-instance.mdx b/docs/pages/reference/infrastructure-as-code/teleport-resources/bot-instance.mdx index aa89abc311ed..b23013461e3e 100644 --- a/docs/pages/reference/infrastructure-as-code/teleport-resources/bot-instance.mdx +++ b/docs/pages/reference/infrastructure-as-code/teleport-resources/bot-instance.mdx @@ -158,7 +158,7 @@ join_attrs: # [...] |join_attrs|The attributes generated during the join process. Typically, this is information from the join attestation process itself. This field will eventually replace the `metadata` field, which is structureless.|[Join Attrs](#join-attrs)| |join_method|The join method used for this join or renewal. Deprecated: prefer using join_attrs.meta.join_method|string| |join_token|The join token used for this join or renewal. This is only populated for delegated join methods as the value for `token` join methods is sensitive. Deprecated: prefer using join_attrs.meta.join_token_name|string| -|metadata|The metadata sourced from the join method. Deprecated: prefer using join_attrs.|| +|metadata|The metadata sourced from the join method. Deprecated: prefer using join_attrs. No longer written since v19; only present on records written by older Auth Service versions. Deprecated: Marked as deprecated in teleport/machineid/v1/bot_instance.proto.|| |public_key|The public key of the Bot instance. This must be a PEM wrapped, PKIX DER encoded public key. This provides consistency and supports multiple types of public key algorithm.|base64-encoded string| ## Bot Instance Status Heartbeat @@ -229,6 +229,7 @@ oracle: # [...] azure_devops: # [...] env0: # [...] generic_oidc: # [...] +bound_keypair: # [...] ``` |Field Name|Description|Type| @@ -236,6 +237,7 @@ generic_oidc: # [...] |azure|Attributes that are specific to the Azure (`azure`) join method.|[Join Attrs Azure](#join-attrs-azure)| |azure_devops|Attributes that are specific to the Azure Devops (`azure_devops`) join method.|[Join Attrs Azure Devops](#join-attrs-azure-devops)| |bitbucket|Attributes that are specific to the Bitbucket (`bitbucket`) join method.|[Join Attrs Bitbucket](#join-attrs-bitbucket)| +|bound_keypair|Attributes that are specific to the Bound Keypair (`bound_keypair`) join method.|[Join Attrs Bound Keypair](#join-attrs-bound-keypair)| |circleci|Attributes that are specific to the CircleCI (`circleci`) join method.|[Join Attrs CircleCI](#join-attrs-circleci)| |env0|Attributes that are specific to the Env0 (`env0`) join method.|[Join Attrs Env0](#join-attrs-env0)| |gcp|Attributes that are specific to the GCP (`gcp`) join method.|[Join Attrs GCP](#join-attrs-gcp)| @@ -363,6 +365,25 @@ branch_name: "string" |sub|The `sub` claim of the Bitbucket JWT that was used to join.|string| |workspace_uuid|The UUID of the workspace the pipeline belongs to.|string| +## Join Attrs Bound Keypair + +Attributes that are specific to the Bound Keypair (`bound_keypair`) join method. + + +Example: + +```yaml +public_key: "string" +recovery_mode: "string" +recovery_count: 1 +``` + +|Field Name|Description|Type| +|---|---|---| +|public_key|The bound public key verified during this join, in SSH authorized_keys format.|string| +|recovery_count|The token's recovery counter after this join.|number| +|recovery_mode|The token's configured recovery mode, e.g. `standard` or `relaxed`.|string| + ## Join Attrs CircleCI Attributes that are specific to the CircleCI (`circleci`) join method. These are mapped from the claims of the JWT issued by CircleCI to runs, which is documented at: https://circleci.com/docs/openid-connect-tokens/ diff --git a/docs/pages/reference/machine-workload-identity/workload-identity/attributes.mdx b/docs/pages/reference/machine-workload-identity/workload-identity/attributes.mdx index 461a41a257de..27f7da5286bb 100644 --- a/docs/pages/reference/machine-workload-identity/workload-identity/attributes.mdx +++ b/docs/pages/reference/machine-workload-identity/workload-identity/attributes.mdx @@ -81,6 +81,17 @@ documentation is available at https://support.atlassian.com/bitbucket-cloud/docs | `join.bitbucket.deployment_environment_uuid` | The UUID of the deployment environment the pipeline is running against. | | `join.bitbucket.branch_name` | The name of the branch the pipeline is running against. | +### `join.bound_keypair` + +These attributes are present if the Bot joined using the Bound Keypair join +method. + +| Field | Description | +|-------------------------------------|-------------------------------------------------------------------------| +| `join.bound_keypair.public_key` | The bound public key verified during the join, in SSH authorized_keys format. | +| `join.bound_keypair.recovery_mode` | The join token's configured recovery mode, e.g. `standard` or `relaxed`. | +| `join.bound_keypair.recovery_count` | The join token's recovery counter after the join. | + ### `join.circleci` These attributes are present if the Bot joined using the CircleCI join method. diff --git a/lib/auth/join.go b/lib/auth/join.go index 1e9cb1e2f5c9..20eef9c6ee65 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -20,7 +20,6 @@ package auth import ( "context" - "encoding/json" "errors" "log/slog" "maps" @@ -30,7 +29,6 @@ import ( "github.com/gravitational/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/client/proto" @@ -429,13 +427,6 @@ func (a *Server) GenerateBotCertsForJoin( JoinAttrs: params.Attrs, }.Build() - var err error - // TODO(noah): In v19, we can drop writing to the deprecated Metadata field. - auth.Metadata, err = rawJoinAttrsToGoogleStruct(params.RawJoinClaims) - if err != nil { - a.logger.WarnContext(ctx, "Unable to encode struct value for join metadata", "error", err) - } - botUserName, err := botUserNameFromToken(token) if err != nil { return nil, "", trace.Wrap(err) @@ -679,18 +670,3 @@ func (a *Server) emitJoinEvent(ctx context.Context, token provision.Token, param a.logger.WarnContext(ctx, "Failed to emit instance join event", "error", err) } } - -func rawJoinAttrsToGoogleStruct(in any) (*structpb.Struct, error) { - if in == nil { - return nil, nil - } - attrBytes, err := json.Marshal(in) - if err != nil { - return nil, trace.Wrap(err, "marshaling join attributes") - } - out := &structpb.Struct{} - if err := out.UnmarshalJSON(attrBytes); err != nil { - return nil, trace.Wrap(err, "unmarshaling join attributes") - } - return out, nil -} diff --git a/lib/boundkeypair/claims.go b/lib/boundkeypair/claims.go index 788776dc1e35..d4e56399c8fc 100644 --- a/lib/boundkeypair/claims.go +++ b/lib/boundkeypair/claims.go @@ -18,6 +18,10 @@ package boundkeypair +import ( + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" +) + // Claims contains extended claims resulting from bound keypair joining type Claims struct { // PublicKey is the verified public key trusted at the end of the joining @@ -30,3 +34,13 @@ type Claims struct { // attempt. RecoveryMode RecoveryMode `json:"recovery_mode"` } + +// JoinAttrs returns the protobuf representation of the claims for inclusion +// in a bot's join attributes. +func (c *Claims) JoinAttrs() *workloadidentityv1pb.JoinAttrsBoundKeypair { + return workloadidentityv1pb.JoinAttrsBoundKeypair_builder{ + PublicKey: c.PublicKey, + RecoveryMode: string(c.RecoveryMode), + RecoveryCount: c.RecoveryCount, + }.Build() +} diff --git a/lib/join/boundkeypair/boundkeypair.go b/lib/join/boundkeypair/boundkeypair.go index f508ed181c63..d0feaee73049 100644 --- a/lib/join/boundkeypair/boundkeypair.go +++ b/lib/join/boundkeypair/boundkeypair.go @@ -846,7 +846,7 @@ type JoinParams struct { // validator, used to override the validator in tests. CreateBoundKeypairValidator CreateBoundKeypairValidator // GenerateBotCerts is a function that generates bot certificates. - GenerateBotCerts func(ctx context.Context, previousBotInstanceID string, claims any) (*messages.Certificates, string, error) + GenerateBotCerts func(ctx context.Context, previousBotInstanceID string, claims *boundkeypair.Claims) (*messages.Certificates, string, error) // GenerateHostCerts is a function that generates host certificates. Unlike // bots, hosts do not maintain an ID lineage and are expected to retain // their host IDs indefinitely (or else start over from a clean slate diff --git a/lib/join/join_bound_keypair_test.go b/lib/join/join_bound_keypair_test.go index f16df018aeb4..b4a451e67295 100644 --- a/lib/join/join_bound_keypair_test.go +++ b/lib/join/join_bound_keypair_test.go @@ -443,6 +443,16 @@ func TestJoinBoundKeypair(t *testing.T) { require.Equal(t, uint32(1), v2.Status.BoundKeypair.RecoveryCount) require.NotEmpty(t, v2.Status.BoundKeypair.BoundBotInstanceID) require.NotEmpty(t, v2.Status.BoundKeypair.BoundPublicKey) + + bi, err := authServer.BotInstance.GetBotInstance(t.Context(), machineidv1pb.GetBotInstanceRequest_builder{ + BotName: "test", + InstanceId: v2.Status.BoundKeypair.BoundBotInstanceID, + }.Build()) + require.NoError(t, err) + attrs := bi.GetStatus().GetInitialAuthentication().GetJoinAttrs().GetBoundKeypair() + require.Equal(t, v2.Status.BoundKeypair.BoundPublicKey, attrs.GetPublicKey()) + require.Equal(t, boundkeypair.RecoveryModeInsecure, attrs.GetRecoveryMode()) + require.Equal(t, uint32(1), attrs.GetRecoveryCount()) }, }, { diff --git a/lib/join/server_boundkeypair.go b/lib/join/server_boundkeypair.go index 3bc1871b3cc2..753bcd60b4b5 100644 --- a/lib/join/server_boundkeypair.go +++ b/lib/join/server_boundkeypair.go @@ -23,7 +23,9 @@ import ( "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" "github.com/gravitational/teleport/api/types" + libboundkeypair "github.com/gravitational/teleport/lib/boundkeypair" "github.com/gravitational/teleport/lib/join/boundkeypair" "github.com/gravitational/teleport/lib/join/internal/authz" "github.com/gravitational/teleport/lib/join/internal/diagnostic" @@ -88,13 +90,19 @@ func (s *Server) handleBoundKeypairJoin( rotationResp, err := messages.RecvRequest[*messages.BoundKeypairRotationResponse](stream) return rotationResp, trace.Wrap(err) } - generateBotCerts := func(ctx context.Context, previousBotInstanceID string, claims any) (*messages.Certificates, string, error) { + generateBotCerts := func(ctx context.Context, previousBotInstanceID string, claims *libboundkeypair.Claims) (*messages.Certificates, string, error) { + var workloadIDAttrs *workloadidentityv1.JoinAttrs + if claims != nil { + workloadIDAttrs = workloadidentityv1.JoinAttrs_builder{ + BoundKeypair: claims.JoinAttrs(), + }.Build() + } botCertsParams, err := makeBotCertsParams( diag, authCtx, boundKeypairInit.ClientParams.BotParams, claims, - nil, // TODO(timothyb89): workload id claims + workloadIDAttrs, ) if err != nil { return nil, "", trace.Wrap(err) @@ -244,13 +252,19 @@ func AdaptRegisterUsingBoundKeypairMethod( PreviousJoinState: req.PreviousJoinState, } - generateBotCerts := func(ctx context.Context, previousBotInstanceID string, claims any) (*messages.Certificates, string, error) { + generateBotCerts := func(ctx context.Context, previousBotInstanceID string, claims *libboundkeypair.Claims) (*messages.Certificates, string, error) { + var workloadIDAttrs *workloadidentityv1.JoinAttrs + if claims != nil { + workloadIDAttrs = workloadidentityv1.JoinAttrs_builder{ + BoundKeypair: claims.JoinAttrs(), + }.Build() + } botCertsParams, err := makeBotCertsParams( diag, authCtx, clientParams.BotParams, claims, - nil, // TODO(timothyb89): workload id claims + workloadIDAttrs, ) if err != nil { return nil, "", trace.Wrap(err) diff --git a/tool/tctl/common/bots_command.go b/tool/tctl/common/bots_command.go index 8a9bcb02c37f..97ab11e2ac29 100644 --- a/tool/tctl/common/bots_command.go +++ b/tool/tctl/common/bots_command.go @@ -1302,6 +1302,8 @@ func formatBotInstanceAuthentication(record *machineidv1pb.BotInstanceStatusAuth table.AddRow([]string{"Authenticated At:", record.GetAuthenticatedAt().AsTime().Format(time.RFC3339)}) table.AddRow([]string{"Join Method:", cmp.Or(record.GetJoinAttrs().GetMeta().GetJoinMethod(), record.GetJoinMethod())}) table.AddRow([]string{"Join Token:", cmp.Or(record.GetJoinAttrs().GetMeta().GetJoinTokenName(), record.GetJoinToken())}) + //nolint:staticcheck // fallback for records written before join_attrs + // existed. TODO(noah): DELETE IN V20.0.0 var meta fmt.Stringer = record.GetMetadata() if attrs := record.GetJoinAttrs(); attrs != nil { meta = attrs From ac5bdf78840681b46f6a3ca0f6679ad593944392 Mon Sep 17 00:00:00 2001 From: Bartosz Leper Date: Thu, 13 Aug 2026 08:18:51 +0000 Subject: [PATCH 286/347] Fix TestNewWebSessionScopedTrustedDeviceRequirement flakiness (#69506) --- lib/auth/auth_test.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index 729a7467520c..f030dc81dd8e 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -94,6 +94,7 @@ import ( "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/modules/modulestest" "github.com/gravitational/teleport/lib/scopes" + scopedaccess "github.com/gravitational/teleport/lib/scopes/access" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/local" @@ -3810,10 +3811,10 @@ func TestNewWebSessionScopedTrustedDeviceRequirement(t *testing.T) { Assignment: scopedaccessv1pb.ScopedRoleAssignment_builder{ Kind: "scoped_role_assignment", Metadata: headerv1.Metadata_builder{ - Name: uuid.NewString(), + Name: "some-scoped-role-assignment", }.Build(), Scope: "/foo", - SubKind: "dynamic", + SubKind: scopedaccess.SubKindDynamic, Spec: scopedaccessv1pb.ScopedRoleAssignmentSpec_builder{ User: "test-user", Assignments: []*scopedaccessv1pb.Assignment{ @@ -3828,12 +3829,19 @@ func TestNewWebSessionScopedTrustedDeviceRequirement(t *testing.T) { }.Build()) require.NoError(t, err) - // Wait for scoped access cache to see the assignment. - pin := scopesv1pb.Pin_builder{Kind: scopesv1pb.PinKind_PIN_KIND_USER, Scope: "/foo/bar"}.Build() + // Wait for scoped access cache to see the role and the assignment. require.EventuallyWithT(t, func(ct *assert.CollectT) { - err := p.a.ScopedAccessCache.PopulatePinnedAssignmentsForUser(ctx, "test-user", pin) - assert.NoError(ct, err) - assert.NotNil(ct, pin.GetAssignmentTree()) + _, err := p.a.ScopedAccessCache.GetScopedRole(ctx, scopedaccessv1pb.GetScopedRoleRequest_builder{ + Name: "some-scoped-role", + Scope: "/foo", + }.Build()) + require.NoError(ct, err) + _, err = p.a.ScopedAccessCache.GetScopedRoleAssignment(ctx, scopedaccessv1pb.GetScopedRoleAssignmentRequest_builder{ + Name: "some-scoped-role-assignment", + Scope: "/foo", + SubKind: scopedaccess.SubKindDynamic, + }.Build()) + require.NoError(ct, err) }, 15*time.Second, 100*time.Millisecond) ws, _, err := p.a.NewWebSession(ctx, auth.NewWebSessionRequest{ From b7dea667466be4efd3c10f58f029a78cb1589f92 Mon Sep 17 00:00:00 2001 From: Tiago Scolari <38940+tscolari@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:56:17 +0000 Subject: [PATCH 287/347] MWI: issuance: Add UsageApp support to IssueScopedBotCerts (#69449) * proto: add UsageApp to IssueScopedBotCertsRequest proto * issuance: add UsageApp support to IssueScopedBotCerts Implement the server-side handling for the UsageApp usage in IssueScopedBotCerts. When a scoped bot requests app-routed certificates, the service now: - Validates the requested app scope against the caller's scope pin - Resolves the app via RangeApplicationServersWithName, ensuring it exists in the exact scope claimed - Rejects Identity Center account apps (not routable, no scoped role support for account assignments) - Creates a scoped app session via CreateAppSessionFromReq - Annotates the output certificate with app routing and session info * issuance: ensure field error messages are consistent * issuance: remove unnecessay TestIssueScopedBotCerts_UsageApp_ScopeHierarchy test cases These tests were a left over when previous code was fetching and validating exact scope matching. Since that code was removed, I've removed the exact match cases and moved the other 2 up to the TestIssueScopedBotCerts_UsageApp tests. * linter: Fix imports * tests: Use t.Context in internal t.Run calls * issuance: Validate the PublicAddr is present in UsageApp * tests: convert scoped IssueScopedBotCerts failure tests for UsageApp to table tests * issuance: UsageApp validates the PublicAddr against ScopedAppPublicAddrValid * test: add explicit test in IssueScopedBotCerts to check scope presence --- .../issuance/v1/service_protohybrid.pb.go | 192 ++++++++- .../issuance/v1/service_protoopaque.pb.go | 186 +++++++- api/proto/teleport/issuance/v1/service.proto | 15 + lib/auth/issuance/v1/service.go | 39 +- lib/auth/issuance/v1/service_test.go | 407 +++++++++++++++++- lib/auth/issuance/v1/usage_app.go | 127 ++++++ 6 files changed, 895 insertions(+), 71 deletions(-) create mode 100644 lib/auth/issuance/v1/usage_app.go diff --git a/api/gen/proto/go/teleport/issuance/v1/service_protohybrid.pb.go b/api/gen/proto/go/teleport/issuance/v1/service_protohybrid.pb.go index 6d04c7a06b36..af1153a766fa 100644 --- a/api/gen/proto/go/teleport/issuance/v1/service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/issuance/v1/service_protohybrid.pb.go @@ -57,6 +57,7 @@ type IssueScopedBotCertsRequest struct { // Types that are valid to be assigned to Usage: // // *IssueScopedBotCertsRequest_Identity + // *IssueScopedBotCertsRequest_App Usage isIssueScopedBotCertsRequest_Usage `protobuf_oneof:"usage"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -124,6 +125,15 @@ func (x *IssueScopedBotCertsRequest) GetIdentity() *UsageIdentity { return nil } +func (x *IssueScopedBotCertsRequest) GetApp() *UsageApp { + if x != nil { + if x, ok := x.Usage.(*IssueScopedBotCertsRequest_App); ok { + return x.App + } + } + return nil +} + func (x *IssueScopedBotCertsRequest) SetSshPublicKey(v []byte) { if v == nil { v = []byte{} @@ -150,6 +160,14 @@ func (x *IssueScopedBotCertsRequest) SetIdentity(v *UsageIdentity) { x.Usage = &IssueScopedBotCertsRequest_Identity{v} } +func (x *IssueScopedBotCertsRequest) SetApp(v *UsageApp) { + if v == nil { + x.Usage = nil + return + } + x.Usage = &IssueScopedBotCertsRequest_App{v} +} + func (x *IssueScopedBotCertsRequest) HasTtl() bool { if x == nil { return false @@ -172,6 +190,14 @@ func (x *IssueScopedBotCertsRequest) HasIdentity() bool { return ok } +func (x *IssueScopedBotCertsRequest) HasApp() bool { + if x == nil { + return false + } + _, ok := x.Usage.(*IssueScopedBotCertsRequest_App) + return ok +} + func (x *IssueScopedBotCertsRequest) ClearTtl() { x.Ttl = nil } @@ -186,8 +212,15 @@ func (x *IssueScopedBotCertsRequest) ClearIdentity() { } } +func (x *IssueScopedBotCertsRequest) ClearApp() { + if _, ok := x.Usage.(*IssueScopedBotCertsRequest_App); ok { + x.Usage = nil + } +} + const IssueScopedBotCertsRequest_Usage_not_set_case case_IssueScopedBotCertsRequest_Usage = 0 const IssueScopedBotCertsRequest_Identity_case case_IssueScopedBotCertsRequest_Usage = 4 +const IssueScopedBotCertsRequest_App_case case_IssueScopedBotCertsRequest_Usage = 5 func (x *IssueScopedBotCertsRequest) WhichUsage() case_IssueScopedBotCertsRequest_Usage { if x == nil { @@ -196,6 +229,8 @@ func (x *IssueScopedBotCertsRequest) WhichUsage() case_IssueScopedBotCertsReques switch x.Usage.(type) { case *IssueScopedBotCertsRequest_Identity: return IssueScopedBotCertsRequest_Identity_case + case *IssueScopedBotCertsRequest_App: + return IssueScopedBotCertsRequest_App_case default: return IssueScopedBotCertsRequest_Usage_not_set_case } @@ -220,6 +255,7 @@ type IssueScopedBotCertsRequest_builder struct { // Fields of oneof Usage: Identity *UsageIdentity + App *UsageApp // -- end of Usage } @@ -233,6 +269,9 @@ func (b0 IssueScopedBotCertsRequest_builder) Build() *IssueScopedBotCertsRequest if b.Identity != nil { x.Usage = &IssueScopedBotCertsRequest_Identity{b.Identity} } + if b.App != nil { + x.Usage = &IssueScopedBotCertsRequest_App{b.App} + } return m0 } @@ -254,8 +293,14 @@ type IssueScopedBotCertsRequest_Identity struct { Identity *UsageIdentity `protobuf:"bytes,4,opt,name=identity,proto3,oneof"` } +type IssueScopedBotCertsRequest_App struct { + App *UsageApp `protobuf:"bytes,5,opt,name=app,proto3,oneof"` +} + func (*IssueScopedBotCertsRequest_Identity) isIssueScopedBotCertsRequest_Usage() {} +func (*IssueScopedBotCertsRequest_App) isIssueScopedBotCertsRequest_Usage() {} + // The simplest possible usage mode for a certificate, returning certificates // appropriate for SSH or API authn. type UsageIdentity struct { @@ -301,6 +346,102 @@ func (b0 UsageIdentity_builder) Build() *UsageIdentity { return m0 } +// UsageApp defines the mode +type UsageApp struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // name is the name of the application to route to. + // Must resolve to an application within the scope to which + // the calling bot is pinned. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // public_addr is the application public address. + PublicAddr string `protobuf:"bytes,2,opt,name=public_addr,json=publicAddr,proto3" json:"public_addr,omitempty"` + // scope is the scope of the target application. + Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UsageApp) Reset() { + *x = UsageApp{} + mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UsageApp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsageApp) ProtoMessage() {} + +func (x *UsageApp) ProtoReflect() protoreflect.Message { + mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *UsageApp) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UsageApp) GetPublicAddr() string { + if x != nil { + return x.PublicAddr + } + return "" +} + +func (x *UsageApp) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *UsageApp) SetName(v string) { + x.Name = v +} + +func (x *UsageApp) SetPublicAddr(v string) { + x.PublicAddr = v +} + +func (x *UsageApp) SetScope(v string) { + x.Scope = v +} + +type UsageApp_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // name is the name of the application to route to. + // Must resolve to an application within the scope to which + // the calling bot is pinned. + Name string + // public_addr is the application public address. + PublicAddr string + // scope is the scope of the target application. + Scope string +} + +func (b0 UsageApp_builder) Build() *UsageApp { + m0 := &UsageApp{} + b, x := &b0, m0 + _, _ = b, x + x.Name = b.Name + x.PublicAddr = b.PublicAddr + x.Scope = b.Scope + return m0 +} + // Response for IssueScopedBotCerts type IssueScopedBotCertsResponse struct { state protoimpl.MessageState `protogen:"hybrid.v1"` @@ -312,7 +453,7 @@ type IssueScopedBotCertsResponse struct { func (x *IssueScopedBotCertsResponse) Reset() { *x = IssueScopedBotCertsResponse{} - mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -324,7 +465,7 @@ func (x *IssueScopedBotCertsResponse) String() string { func (*IssueScopedBotCertsResponse) ProtoMessage() {} func (x *IssueScopedBotCertsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -385,7 +526,7 @@ type Certs struct { func (x *Certs) Reset() { *x = Certs{} - mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -397,7 +538,7 @@ func (x *Certs) String() string { func (*Certs) ProtoMessage() {} func (x *Certs) ProtoReflect() protoreflect.Message { - mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -458,14 +599,20 @@ var File_teleport_issuance_v1_service_proto protoreflect.FileDescriptor const file_teleport_issuance_v1_service_proto_rawDesc = "" + "\n" + - "\"teleport/issuance/v1/service.proto\x12\x14teleport.issuance.v1\x1a\x1egoogle/protobuf/duration.proto\"\xe1\x01\n" + + "\"teleport/issuance/v1/service.proto\x12\x14teleport.issuance.v1\x1a\x1egoogle/protobuf/duration.proto\"\x95\x02\n" + "\x1aIssueScopedBotCertsRequest\x12$\n" + "\x0essh_public_key\x18\x01 \x01(\fR\fsshPublicKey\x12$\n" + "\x0etls_public_key\x18\x02 \x01(\fR\ftlsPublicKey\x12+\n" + "\x03ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x12A\n" + - "\bidentity\x18\x04 \x01(\v2#.teleport.issuance.v1.UsageIdentityH\x00R\bidentityB\a\n" + + "\bidentity\x18\x04 \x01(\v2#.teleport.issuance.v1.UsageIdentityH\x00R\bidentity\x122\n" + + "\x03app\x18\x05 \x01(\v2\x1e.teleport.issuance.v1.UsageAppH\x00R\x03appB\a\n" + "\x05usage\"\x0f\n" + - "\rUsageIdentity\"P\n" + + "\rUsageIdentity\"U\n" + + "\bUsageApp\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" + + "\vpublic_addr\x18\x02 \x01(\tR\n" + + "publicAddr\x12\x14\n" + + "\x05scope\x18\x03 \x01(\tR\x05scope\"P\n" + "\x1bIssueScopedBotCertsResponse\x121\n" + "\x05certs\x18\x01 \x01(\v2\x1b.teleport.issuance.v1.CertsR\x05certs\"+\n" + "\x05Certs\x12\x10\n" + @@ -474,25 +621,27 @@ const file_teleport_issuance_v1_service_proto_rawDesc = "" + "\x0fIssuanceService\x12z\n" + "\x13IssueScopedBotCerts\x120.teleport.issuance.v1.IssueScopedBotCertsRequest\x1a1.teleport.issuance.v1.IssueScopedBotCertsResponseBVZTgithub.com/gravitational/teleport/api/gen/proto/go/teleport/issuance/v1;issuancev1pbb\x06proto3" -var file_teleport_issuance_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_teleport_issuance_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_teleport_issuance_v1_service_proto_goTypes = []any{ (*IssueScopedBotCertsRequest)(nil), // 0: teleport.issuance.v1.IssueScopedBotCertsRequest (*UsageIdentity)(nil), // 1: teleport.issuance.v1.UsageIdentity - (*IssueScopedBotCertsResponse)(nil), // 2: teleport.issuance.v1.IssueScopedBotCertsResponse - (*Certs)(nil), // 3: teleport.issuance.v1.Certs - (*durationpb.Duration)(nil), // 4: google.protobuf.Duration + (*UsageApp)(nil), // 2: teleport.issuance.v1.UsageApp + (*IssueScopedBotCertsResponse)(nil), // 3: teleport.issuance.v1.IssueScopedBotCertsResponse + (*Certs)(nil), // 4: teleport.issuance.v1.Certs + (*durationpb.Duration)(nil), // 5: google.protobuf.Duration } var file_teleport_issuance_v1_service_proto_depIdxs = []int32{ - 4, // 0: teleport.issuance.v1.IssueScopedBotCertsRequest.ttl:type_name -> google.protobuf.Duration + 5, // 0: teleport.issuance.v1.IssueScopedBotCertsRequest.ttl:type_name -> google.protobuf.Duration 1, // 1: teleport.issuance.v1.IssueScopedBotCertsRequest.identity:type_name -> teleport.issuance.v1.UsageIdentity - 3, // 2: teleport.issuance.v1.IssueScopedBotCertsResponse.certs:type_name -> teleport.issuance.v1.Certs - 0, // 3: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:input_type -> teleport.issuance.v1.IssueScopedBotCertsRequest - 2, // 4: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:output_type -> teleport.issuance.v1.IssueScopedBotCertsResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 2: teleport.issuance.v1.IssueScopedBotCertsRequest.app:type_name -> teleport.issuance.v1.UsageApp + 4, // 3: teleport.issuance.v1.IssueScopedBotCertsResponse.certs:type_name -> teleport.issuance.v1.Certs + 0, // 4: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:input_type -> teleport.issuance.v1.IssueScopedBotCertsRequest + 3, // 5: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:output_type -> teleport.issuance.v1.IssueScopedBotCertsResponse + 5, // [5:6] is the sub-list for method output_type + 4, // [4:5] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_issuance_v1_service_proto_init() } @@ -502,6 +651,7 @@ func file_teleport_issuance_v1_service_proto_init() { } file_teleport_issuance_v1_service_proto_msgTypes[0].OneofWrappers = []any{ (*IssueScopedBotCertsRequest_Identity)(nil), + (*IssueScopedBotCertsRequest_App)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -509,7 +659,7 @@ func file_teleport_issuance_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_issuance_v1_service_proto_rawDesc), len(file_teleport_issuance_v1_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/issuance/v1/service_protoopaque.pb.go b/api/gen/proto/go/teleport/issuance/v1/service_protoopaque.pb.go index 82d9a0abb5bb..540e34ce0db2 100644 --- a/api/gen/proto/go/teleport/issuance/v1/service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/issuance/v1/service_protoopaque.pb.go @@ -103,6 +103,15 @@ func (x *IssueScopedBotCertsRequest) GetIdentity() *UsageIdentity { return nil } +func (x *IssueScopedBotCertsRequest) GetApp() *UsageApp { + if x != nil { + if x, ok := x.xxx_hidden_Usage.(*issueScopedBotCertsRequest_App); ok { + return x.App + } + } + return nil +} + func (x *IssueScopedBotCertsRequest) SetSshPublicKey(v []byte) { if v == nil { v = []byte{} @@ -129,6 +138,14 @@ func (x *IssueScopedBotCertsRequest) SetIdentity(v *UsageIdentity) { x.xxx_hidden_Usage = &issueScopedBotCertsRequest_Identity{v} } +func (x *IssueScopedBotCertsRequest) SetApp(v *UsageApp) { + if v == nil { + x.xxx_hidden_Usage = nil + return + } + x.xxx_hidden_Usage = &issueScopedBotCertsRequest_App{v} +} + func (x *IssueScopedBotCertsRequest) HasTtl() bool { if x == nil { return false @@ -151,6 +168,14 @@ func (x *IssueScopedBotCertsRequest) HasIdentity() bool { return ok } +func (x *IssueScopedBotCertsRequest) HasApp() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_Usage.(*issueScopedBotCertsRequest_App) + return ok +} + func (x *IssueScopedBotCertsRequest) ClearTtl() { x.xxx_hidden_Ttl = nil } @@ -165,8 +190,15 @@ func (x *IssueScopedBotCertsRequest) ClearIdentity() { } } +func (x *IssueScopedBotCertsRequest) ClearApp() { + if _, ok := x.xxx_hidden_Usage.(*issueScopedBotCertsRequest_App); ok { + x.xxx_hidden_Usage = nil + } +} + const IssueScopedBotCertsRequest_Usage_not_set_case case_IssueScopedBotCertsRequest_Usage = 0 const IssueScopedBotCertsRequest_Identity_case case_IssueScopedBotCertsRequest_Usage = 4 +const IssueScopedBotCertsRequest_App_case case_IssueScopedBotCertsRequest_Usage = 5 func (x *IssueScopedBotCertsRequest) WhichUsage() case_IssueScopedBotCertsRequest_Usage { if x == nil { @@ -175,6 +207,8 @@ func (x *IssueScopedBotCertsRequest) WhichUsage() case_IssueScopedBotCertsReques switch x.xxx_hidden_Usage.(type) { case *issueScopedBotCertsRequest_Identity: return IssueScopedBotCertsRequest_Identity_case + case *issueScopedBotCertsRequest_App: + return IssueScopedBotCertsRequest_App_case default: return IssueScopedBotCertsRequest_Usage_not_set_case } @@ -199,6 +233,7 @@ type IssueScopedBotCertsRequest_builder struct { // Fields of oneof xxx_hidden_Usage: Identity *UsageIdentity + App *UsageApp // -- end of xxx_hidden_Usage } @@ -212,6 +247,9 @@ func (b0 IssueScopedBotCertsRequest_builder) Build() *IssueScopedBotCertsRequest if b.Identity != nil { x.xxx_hidden_Usage = &issueScopedBotCertsRequest_Identity{b.Identity} } + if b.App != nil { + x.xxx_hidden_Usage = &issueScopedBotCertsRequest_App{b.App} + } return m0 } @@ -233,8 +271,14 @@ type issueScopedBotCertsRequest_Identity struct { Identity *UsageIdentity `protobuf:"bytes,4,opt,name=identity,proto3,oneof"` } +type issueScopedBotCertsRequest_App struct { + App *UsageApp `protobuf:"bytes,5,opt,name=app,proto3,oneof"` +} + func (*issueScopedBotCertsRequest_Identity) isIssueScopedBotCertsRequest_Usage() {} +func (*issueScopedBotCertsRequest_App) isIssueScopedBotCertsRequest_Usage() {} + // The simplest possible usage mode for a certificate, returning certificates // appropriate for SSH or API authn. type UsageIdentity struct { @@ -280,6 +324,97 @@ func (b0 UsageIdentity_builder) Build() *UsageIdentity { return m0 } +// UsageApp defines the mode +type UsageApp struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Name string `protobuf:"bytes,1,opt,name=name,proto3"` + xxx_hidden_PublicAddr string `protobuf:"bytes,2,opt,name=public_addr,json=publicAddr,proto3"` + xxx_hidden_Scope string `protobuf:"bytes,3,opt,name=scope,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UsageApp) Reset() { + *x = UsageApp{} + mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UsageApp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsageApp) ProtoMessage() {} + +func (x *UsageApp) ProtoReflect() protoreflect.Message { + mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *UsageApp) GetName() string { + if x != nil { + return x.xxx_hidden_Name + } + return "" +} + +func (x *UsageApp) GetPublicAddr() string { + if x != nil { + return x.xxx_hidden_PublicAddr + } + return "" +} + +func (x *UsageApp) GetScope() string { + if x != nil { + return x.xxx_hidden_Scope + } + return "" +} + +func (x *UsageApp) SetName(v string) { + x.xxx_hidden_Name = v +} + +func (x *UsageApp) SetPublicAddr(v string) { + x.xxx_hidden_PublicAddr = v +} + +func (x *UsageApp) SetScope(v string) { + x.xxx_hidden_Scope = v +} + +type UsageApp_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // name is the name of the application to route to. + // Must resolve to an application within the scope to which + // the calling bot is pinned. + Name string + // public_addr is the application public address. + PublicAddr string + // scope is the scope of the target application. + Scope string +} + +func (b0 UsageApp_builder) Build() *UsageApp { + m0 := &UsageApp{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Name = b.Name + x.xxx_hidden_PublicAddr = b.PublicAddr + x.xxx_hidden_Scope = b.Scope + return m0 +} + // Response for IssueScopedBotCerts type IssueScopedBotCertsResponse struct { state protoimpl.MessageState `protogen:"opaque.v1"` @@ -290,7 +425,7 @@ type IssueScopedBotCertsResponse struct { func (x *IssueScopedBotCertsResponse) Reset() { *x = IssueScopedBotCertsResponse{} - mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -302,7 +437,7 @@ func (x *IssueScopedBotCertsResponse) String() string { func (*IssueScopedBotCertsResponse) ProtoMessage() {} func (x *IssueScopedBotCertsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_issuance_v1_service_proto_msgTypes[2] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -361,7 +496,7 @@ type Certs struct { func (x *Certs) Reset() { *x = Certs{} - mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +508,7 @@ func (x *Certs) String() string { func (*Certs) ProtoMessage() {} func (x *Certs) ProtoReflect() protoreflect.Message { - mi := &file_teleport_issuance_v1_service_proto_msgTypes[3] + mi := &file_teleport_issuance_v1_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -434,14 +569,20 @@ var File_teleport_issuance_v1_service_proto protoreflect.FileDescriptor const file_teleport_issuance_v1_service_proto_rawDesc = "" + "\n" + - "\"teleport/issuance/v1/service.proto\x12\x14teleport.issuance.v1\x1a\x1egoogle/protobuf/duration.proto\"\xe1\x01\n" + + "\"teleport/issuance/v1/service.proto\x12\x14teleport.issuance.v1\x1a\x1egoogle/protobuf/duration.proto\"\x95\x02\n" + "\x1aIssueScopedBotCertsRequest\x12$\n" + "\x0essh_public_key\x18\x01 \x01(\fR\fsshPublicKey\x12$\n" + "\x0etls_public_key\x18\x02 \x01(\fR\ftlsPublicKey\x12+\n" + "\x03ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x12A\n" + - "\bidentity\x18\x04 \x01(\v2#.teleport.issuance.v1.UsageIdentityH\x00R\bidentityB\a\n" + + "\bidentity\x18\x04 \x01(\v2#.teleport.issuance.v1.UsageIdentityH\x00R\bidentity\x122\n" + + "\x03app\x18\x05 \x01(\v2\x1e.teleport.issuance.v1.UsageAppH\x00R\x03appB\a\n" + "\x05usage\"\x0f\n" + - "\rUsageIdentity\"P\n" + + "\rUsageIdentity\"U\n" + + "\bUsageApp\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" + + "\vpublic_addr\x18\x02 \x01(\tR\n" + + "publicAddr\x12\x14\n" + + "\x05scope\x18\x03 \x01(\tR\x05scope\"P\n" + "\x1bIssueScopedBotCertsResponse\x121\n" + "\x05certs\x18\x01 \x01(\v2\x1b.teleport.issuance.v1.CertsR\x05certs\"+\n" + "\x05Certs\x12\x10\n" + @@ -450,25 +591,27 @@ const file_teleport_issuance_v1_service_proto_rawDesc = "" + "\x0fIssuanceService\x12z\n" + "\x13IssueScopedBotCerts\x120.teleport.issuance.v1.IssueScopedBotCertsRequest\x1a1.teleport.issuance.v1.IssueScopedBotCertsResponseBVZTgithub.com/gravitational/teleport/api/gen/proto/go/teleport/issuance/v1;issuancev1pbb\x06proto3" -var file_teleport_issuance_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_teleport_issuance_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_teleport_issuance_v1_service_proto_goTypes = []any{ (*IssueScopedBotCertsRequest)(nil), // 0: teleport.issuance.v1.IssueScopedBotCertsRequest (*UsageIdentity)(nil), // 1: teleport.issuance.v1.UsageIdentity - (*IssueScopedBotCertsResponse)(nil), // 2: teleport.issuance.v1.IssueScopedBotCertsResponse - (*Certs)(nil), // 3: teleport.issuance.v1.Certs - (*durationpb.Duration)(nil), // 4: google.protobuf.Duration + (*UsageApp)(nil), // 2: teleport.issuance.v1.UsageApp + (*IssueScopedBotCertsResponse)(nil), // 3: teleport.issuance.v1.IssueScopedBotCertsResponse + (*Certs)(nil), // 4: teleport.issuance.v1.Certs + (*durationpb.Duration)(nil), // 5: google.protobuf.Duration } var file_teleport_issuance_v1_service_proto_depIdxs = []int32{ - 4, // 0: teleport.issuance.v1.IssueScopedBotCertsRequest.ttl:type_name -> google.protobuf.Duration + 5, // 0: teleport.issuance.v1.IssueScopedBotCertsRequest.ttl:type_name -> google.protobuf.Duration 1, // 1: teleport.issuance.v1.IssueScopedBotCertsRequest.identity:type_name -> teleport.issuance.v1.UsageIdentity - 3, // 2: teleport.issuance.v1.IssueScopedBotCertsResponse.certs:type_name -> teleport.issuance.v1.Certs - 0, // 3: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:input_type -> teleport.issuance.v1.IssueScopedBotCertsRequest - 2, // 4: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:output_type -> teleport.issuance.v1.IssueScopedBotCertsResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 2: teleport.issuance.v1.IssueScopedBotCertsRequest.app:type_name -> teleport.issuance.v1.UsageApp + 4, // 3: teleport.issuance.v1.IssueScopedBotCertsResponse.certs:type_name -> teleport.issuance.v1.Certs + 0, // 4: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:input_type -> teleport.issuance.v1.IssueScopedBotCertsRequest + 3, // 5: teleport.issuance.v1.IssuanceService.IssueScopedBotCerts:output_type -> teleport.issuance.v1.IssueScopedBotCertsResponse + 5, // [5:6] is the sub-list for method output_type + 4, // [4:5] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_issuance_v1_service_proto_init() } @@ -478,6 +621,7 @@ func file_teleport_issuance_v1_service_proto_init() { } file_teleport_issuance_v1_service_proto_msgTypes[0].OneofWrappers = []any{ (*issueScopedBotCertsRequest_Identity)(nil), + (*issueScopedBotCertsRequest_App)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -485,7 +629,7 @@ func file_teleport_issuance_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_issuance_v1_service_proto_rawDesc), len(file_teleport_issuance_v1_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/teleport/issuance/v1/service.proto b/api/proto/teleport/issuance/v1/service.proto index 1612b0c6d36c..25a813a2625f 100644 --- a/api/proto/teleport/issuance/v1/service.proto +++ b/api/proto/teleport/issuance/v1/service.proto @@ -65,6 +65,7 @@ message IssueScopedBotCertsRequest { // any usage-specific options. At least one of the usage values must be set. oneof usage { UsageIdentity identity = 4; + UsageApp app = 5; } } @@ -74,6 +75,20 @@ message UsageIdentity { // Reserved for future identity-usage-specific options. } +// UsageApp defines the mode +message UsageApp { + // name is the name of the application to route to. + // Must resolve to an application within the scope to which + // the calling bot is pinned. + string name = 1; + + // public_addr is the application public address. + string public_addr = 2; + + // scope is the scope of the target application. + string scope = 3; +} + // Response for IssueScopedBotCerts message IssueScopedBotCertsResponse { // Issued certificates. diff --git a/lib/auth/issuance/v1/service.go b/lib/auth/issuance/v1/service.go index 373040d19fc3..1b6c6f4bf193 100644 --- a/lib/auth/issuance/v1/service.go +++ b/lib/auth/issuance/v1/service.go @@ -27,6 +27,7 @@ import ( issuancev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/issuance/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/internal/cert" + sessionreq "github.com/gravitational/teleport/lib/auth/internal/session" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/scopes" @@ -36,6 +37,8 @@ import ( type authServer interface { AccessCheckerForScope(ctx context.Context, scope string, userState services.UserState, allowedResourceAccessIDs []types.ResourceAccessID) (*services.ScopedAccessCheckerContext, error) GenerateUserCerts(ctx context.Context, req cert.Request) (*proto.Certs, error) + CreateAppSessionFromReq(ctx context.Context, req sessionreq.NewAppSessionRequest) (types.WebSession, error) + GetClusterName(ctx context.Context) (types.ClusterName, error) } // Service implements teleport.issuance.v1.IssuanceService @@ -128,7 +131,11 @@ func (s *Service) IssueScopedBotCerts( switch req.WhichUsage() { case issuancev1pb.IssueScopedBotCertsRequest_Identity_case: - // no special handling. + // no special handling. + case issuancev1pb.IssueScopedBotCertsRequest_App_case: + if err := validateUsageApp(req); err != nil { + return nil, trace.Wrap(err) + } default: return nil, trace.BadParameter( "unsupported or unspecified usage variant", @@ -202,17 +209,6 @@ func (s *Service) IssueScopedBotCerts( SSHPublicKey: req.GetSshPublicKey(), TLSPublicKey: req.GetTlsPublicKey(), - // Explicitly set BotInternal to false as these certs are intended for - // outputs/services and not use by the bot internally. This prevents - // using them further issuance. - BotInternal: false, - // Explicitly reject use for further issuance - DisallowReissue: true, - - // We do not pass traits from the Bot user here. This is because we do - // not currently support traits for scoped Bots. Users shouldn't be able - // to set these due to validation but this acts as a back-stop. - // Propagate certain attributes from current identity to generated // certificates JoinAttributes: currentIdentity.JoinAttributes, @@ -226,6 +222,25 @@ func (s *Service) IssueScopedBotCerts( JoinToken: currentIdentity.JoinToken, } + // Apply per-usage alterations to the cert request. + switch req.WhichUsage() { + case issuancev1pb.IssueScopedBotCertsRequest_App_case: + if err := s.applyUsageApp(ctx, user, botScope, currentIdentity, req.GetApp(), ttl, &certReq); err != nil { + return nil, trace.Wrap(err, "configuring App Usage into the certificate request") + } + } + + // Explicitly set BotInternal to false as these certs are intended for + // outputs/services and not use by the bot internally. This prevents + // using them further issuance. + certReq.BotInternal = false + // Explicitly reject use for further issuance + certReq.DisallowReissue = true + // We do not pass traits from the Bot user here. This is because we do + // not currently support traits for scoped Bots. Users shouldn't be able + // to set these due to validation but this acts as a back-stop. + certReq.Traits = nil + // nb(strideynet): One day, we'll want to pull more of the logic around // cert generation into this package rather than invoking this via the // auth server struct. diff --git a/lib/auth/issuance/v1/service_test.go b/lib/auth/issuance/v1/service_test.go index 0dd56a6fa876..151c0738ea94 100644 --- a/lib/auth/issuance/v1/service_test.go +++ b/lib/auth/issuance/v1/service_test.go @@ -35,6 +35,7 @@ import ( headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" issuancev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/issuance/v1" + labelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" "github.com/gravitational/teleport/api/types" @@ -46,6 +47,7 @@ import ( "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/scopes" scopedaccess "github.com/gravitational/teleport/lib/scopes/access" + scopedapp "github.com/gravitational/teleport/lib/scopes/app" "github.com/gravitational/teleport/lib/tlsca" ) @@ -80,7 +82,7 @@ func newTestTLSServerWithScopesFeatures(t testing.TB, scopesFeatures scopes.Feat return srv } -func TestIssueScopedBotCerts(t *testing.T) { +func TestIssueScopedBotCerts_UsageIdentity(t *testing.T) { t.Parallel() ctx := t.Context() @@ -165,7 +167,7 @@ func TestIssueScopedBotCerts(t *testing.T) { now := srv.Clock().Now() t.Run("success", func(t *testing.T) { - resp, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + resp, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(requestedTTL), @@ -202,7 +204,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("excessive TTL rejected", func(t *testing.T) { - _, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(defaults.MaxRenewableCertTTL + time.Hour), @@ -212,7 +214,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("missing usage rejected", func(t *testing.T) { - _, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(time.Hour), @@ -221,7 +223,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("ssh only", func(t *testing.T) { - resp, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + resp, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, Ttl: durationpb.New(requestedTTL), Identity: &issuancev1pb.UsageIdentity{}, @@ -233,7 +235,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("tls only", func(t *testing.T) { - resp, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + resp, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(requestedTTL), Identity: &issuancev1pb.UsageIdentity{}, @@ -245,7 +247,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("missing keys rejected", func(t *testing.T) { - _, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ Ttl: durationpb.New(requestedTTL), Identity: &issuancev1pb.UsageIdentity{}, }.Build()) @@ -254,7 +256,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("zero ttl rejected", func(t *testing.T) { - _, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(0), @@ -265,7 +267,7 @@ func TestIssueScopedBotCerts(t *testing.T) { }) t.Run("negative ttl rejected", func(t *testing.T) { - _, err := issuanceClient.IssueScopedBotCerts(ctx, issuancev1pb.IssueScopedBotCertsRequest_builder{ + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ SshPublicKey: sshPubKeyBytes, TlsPublicKey: tlsPubKeyPEM, Ttl: durationpb.New(-time.Hour), @@ -382,7 +384,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { }.Build() t.Run("non-bot user without scope", func(t *testing.T) { - _, err := adminClient.IssuanceClient().IssueScopedBotCerts(ctx, req) + _, err := adminClient.IssuanceClient().IssueScopedBotCerts(t.Context(), req) require.True( t, trace.IsAccessDenied(err), @@ -392,10 +394,10 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { t.Run("non-bot user with scope", func(t *testing.T) { // Create a regular user with a scoped role assignment. - user, err := authtest.CreateUser(ctx, srv.Auth(), "scoped-user") + user, err := authtest.CreateUser(t.Context(), srv.Auth(), "scoped-user") require.NoError(t, err) - userSRAResp, err := scopedSvc.CreateScopedRoleAssignment(ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ + userSRAResp, err := scopedSvc.CreateScopedRoleAssignment(t.Context(), scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ Kind: scopedaccess.KindScopedRoleAssignment, Version: types.V1, @@ -421,7 +423,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _ = scopedUserClient.Close() }) - _, err = scopedUserClient.IssuanceClient().IssueScopedBotCerts(ctx, req) + _, err = scopedUserClient.IssuanceClient().IssueScopedBotCerts(t.Context(), req) require.True( t, trace.IsAccessDenied(err), @@ -431,7 +433,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { t.Run("unscoped bot", func(t *testing.T) { // Create an unscoped bot. - unscopedBot, err := adminClient.BotServiceClient().CreateBot(ctx, machineidv1pb.CreateBotRequest_builder{ + unscopedBot, err := adminClient.BotServiceClient().CreateBot(t.Context(), machineidv1pb.CreateBotRequest_builder{ Bot: machineidv1pb.Bot_builder{ Kind: types.KindBot, Version: types.V1, @@ -449,7 +451,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _ = botClient.Close() }) - _, err = botClient.IssuanceClient().IssueScopedBotCerts(ctx, req) + _, err = botClient.IssuanceClient().IssueScopedBotCerts(t.Context(), req) require.True( t, trace.IsAccessDenied(err), @@ -464,7 +466,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _ = botClient.Close() }) - _, err = botClient.IssuanceClient().IssueScopedBotCerts(ctx, req) + _, err = botClient.IssuanceClient().IssueScopedBotCerts(t.Context(), req) require.True( t, trace.IsAccessDenied(err), @@ -482,7 +484,7 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { _ = botClient.Close() }) - _, err = botClient.IssuanceClient().IssueScopedBotCerts(ctx, req) + _, err = botClient.IssuanceClient().IssueScopedBotCerts(t.Context(), req) require.True( t, trace.IsAccessDenied(err), @@ -491,6 +493,377 @@ func TestIssueScopedBotCerts_Unauthorized(t *testing.T) { }) } +func TestIssueScopedBotCerts_UsageApp(t *testing.T) { + t.Parallel() + + ctx := t.Context() + srv := newTestTLSServerWithScopesFeatures(t, scopes.Features{Enabled: true}) + + const ( + botScope = "/test-scope" + childScope = "/test-scope/child" + ) + + adminClient, err := srv.NewClient(authtest.TestAdmin()) + require.NoError(t, err) + t.Cleanup(func() { _ = adminClient.Close() }) + + // Create a scoped role with app access. + scopedSvc := adminClient.ScopedAccessServiceClient() + _, err = scopedSvc.CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1.ScopedRole_builder{ + Kind: scopedaccess.KindScopedRole, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "bot-role", + }.Build(), + Scope: botScope, + Spec: scopedaccessv1.ScopedRoleSpec_builder{ + AssignableScopes: []string{botScope}, + App: scopedaccessv1.ScopedRoleApp_builder{ + Labels: []*labelv1.Label{ + labelv1.Label_builder{ + Name: types.Wildcard, + Values: []string{types.Wildcard}, + }.Build(), + }, + }.Build(), + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + + // Create a scoped bot. + bot, err := adminClient.BotServiceClient().CreateBot(ctx, machineidv1pb.CreateBotRequest_builder{ + Bot: machineidv1pb.Bot_builder{ + Kind: types.KindBot, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "test-bot", + }.Build(), + Scope: botScope, + Spec: &machineidv1pb.BotSpec{}, + }.Build(), + }.Build()) + require.NoError(t, err) + + // Create a scoped role assignment for the bot. + sraResp, err := scopedSvc.CreateScopedRoleAssignment(ctx, scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: uuid.NewString(), + }.Build(), + Scope: botScope, + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + Bot: scopes.QualifiedName{Scope: botScope, Name: bot.GetMetadata().GetName()}.String(), + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{Role: botScope + "::bot-role", Scope: botScope}.Build(), + }, + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + waitForSRACache(t, srv, sraResp) + + // Create a scoped app and register it. + app, err := types.NewAppV3(types.Metadata{ + Name: "test-app", + }, types.AppSpecV3{ + URI: "http://localhost:8080", + PublicAddr: scopedapp.ScopedAppPublicAddr(botScope, "test-app", "proxy.example.com"), + }) + require.NoError(t, err) + app.Scope = botScope + appServer, err := types.NewAppServerV3FromApp(app, "test-app-host", "test-app-hostid") + require.NoError(t, err) + appServer.Scope = botScope + _, err = srv.Auth().UpsertApplicationServer(ctx, appServer) + require.NoError(t, err) + + // Create a client with a scoped bot internal identity. + botClient, err := srv.NewClient( + authtest.TestScopedBot(t, scopes.QualifiedName{Scope: botScope, Name: bot.GetMetadata().GetName()}, true), + ) + require.NoError(t, err) + t.Cleanup(func() { _ = botClient.Close() }) + + // Generate a key pair for the request. + key, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) + require.NoError(t, err) + tlsPubKeyPEM, err := keys.MarshalPublicKey(key.Public()) + require.NoError(t, err) + sshPubKey, err := ssh.NewPublicKey(key.Public()) + require.NoError(t, err) + sshPubKeyBytes := ssh.MarshalAuthorizedKey(sshPubKey) + + issuanceClient := issuancev1pb.NewIssuanceServiceClient(botClient.GetConnection()) + requestedTTL := time.Hour + + t.Run("success", func(t *testing.T) { + resp, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ + SshPublicKey: sshPubKeyBytes, + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: app.GetPublicAddr(), + Scope: botScope, + }.Build(), + }.Build()) + require.NoError(t, err) + require.NotNil(t, resp.GetCerts()) + require.NotEmpty(t, resp.GetCerts().GetTls()) + + // Parse the returned TLS cert and verify app-specific identity properties. + tlsCert, err := tlsca.ParseCertificatePEM(resp.GetCerts().GetTls()) + require.NoError(t, err) + identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter) + require.NoError(t, err) + + assert.False(t, identity.BotInternal, "output cert should not be bot-internal") + assert.True(t, identity.DisallowReissue, "output cert should disallow reissue") + assert.Equal(t, "test-bot", identity.BotName) + assert.Equal(t, "test-app", identity.RouteToApp.Name) + assert.Equal(t, app.GetPublicAddr(), identity.RouteToApp.PublicAddr) + assert.Equal(t, srv.ClusterName(), identity.RouteToApp.ClusterName, "ClusterName should be resolved server-side via auth.GetClusterName()") + assert.NotEmpty(t, identity.RouteToApp.SessionID, "app session should have been created") + assert.Equal(t, botScope, identity.RouteToApp.Scope) + }) + + t.Run("failures", func(t *testing.T) { + testCases := map[string]struct { + req *issuancev1pb.IssueScopedBotCertsRequest + traceErrCheck func(error) bool + errMsg string + }{ + "missing app name rejected": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Scope: botScope, + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "app.name: is required", + }, + "missing scope rejected": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + Scope: "", + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "app.scope", + }, + "invalid scope rejected": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + Scope: "not-a-scope", + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "app.scope", + }, + "tls public key required for app usage": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + SshPublicKey: sshPubKeyBytes, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: app.GetPublicAddr(), + Scope: botScope, + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "tls_public_key: is required for app usage", + }, + "public_addr required for app usage": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + SshPublicKey: sshPubKeyBytes, + Ttl: durationpb.New(requestedTTL), + TlsPublicKey: tlsPubKeyPEM, + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: "", + Scope: botScope, + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "app.public_addr: is required", + }, + "app scope outside pinned scope rejected by not matching the scoped public addr subdomain": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: app.GetPublicAddr(), + Scope: "/other-scope", + }.Build(), + }.Build(), + traceErrCheck: trace.IsBadParameter, + errMsg: "app.public_addr: is not valid for given app name and scope", + }, + "app scope outside pinned scope with matching the scoped public addr subdomain gets rejected": { + req: issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: scopedapp.ScopedAppPublicAddr("/other-scope", "test-app", "proxy.example.com"), + Scope: "/other-scope", + }.Build(), + }.Build(), + traceErrCheck: trace.IsAccessDenied, + errMsg: "other-scope", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + _, err := issuanceClient.IssueScopedBotCerts(t.Context(), tc.req) + require.Error(t, err) + require.True(t, tc.traceErrCheck(err), "error is not of the expected type: %v", err) + require.ErrorContains(t, err, tc.errMsg) + }) + } + }) + + // Register a child-scope app for hierarchy tests. + childApp, err := types.NewAppV3(types.Metadata{ + Name: "child-app", + }, types.AppSpecV3{ + URI: "http://localhost:8082", + PublicAddr: scopedapp.ScopedAppPublicAddr(childScope, "child-app", "proxy.example.com"), + }) + require.NoError(t, err) + childApp.Scope = childScope + childAppServer, err := types.NewAppServerV3FromApp(childApp, "child-app-host", "child-app-hostid") + require.NoError(t, err) + childAppServer.Scope = childScope + _, err = srv.Auth().UpsertApplicationServer(ctx, childAppServer) + require.NoError(t, err) + + t.Run("parent-scoped bot accesses child-scope app", func(t *testing.T) { + // Bot pinned to /test-scope can access app in /test-scope/child + // because /test-scope/child is a descendant of /test-scope. + resp, err := issuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "child-app", + PublicAddr: childApp.GetPublicAddr(), + Scope: childScope, + }.Build(), + }.Build()) + require.NoError(t, err) + require.NotNil(t, resp.GetCerts()) + require.NotEmpty(t, resp.GetCerts().GetTls()) + + tlsCert, err := tlsca.ParseCertificatePEM(resp.GetCerts().GetTls()) + require.NoError(t, err) + identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter) + require.NoError(t, err) + assert.Equal(t, "child-app", identity.RouteToApp.Name) + assert.Equal(t, childScope, identity.RouteToApp.Scope) + }) + + t.Run("child-scoped bot rejected for parent-scope app", func(t *testing.T) { + // Bot pinned to /test-scope/child requests an app in /test-scope. + // /test-scope is NOT a descendant of /test-scope/child, so the + // scope pin check rejects. + + // Create a scoped role at child scope for the child bot. + _, err := scopedSvc.CreateScopedRole(t.Context(), scopedaccessv1.CreateScopedRoleRequest_builder{ + Role: scopedaccessv1.ScopedRole_builder{ + Kind: scopedaccess.KindScopedRole, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "child-bot-role", + }.Build(), + Scope: childScope, + Spec: scopedaccessv1.ScopedRoleSpec_builder{ + AssignableScopes: []string{childScope}, + App: scopedaccessv1.ScopedRoleApp_builder{ + Labels: []*labelv1.Label{ + labelv1.Label_builder{ + Name: types.Wildcard, + Values: []string{types.Wildcard}, + }.Build(), + }, + }.Build(), + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + + // Create a child-scoped bot. + childBot, err := adminClient.BotServiceClient().CreateBot(t.Context(), machineidv1pb.CreateBotRequest_builder{ + Bot: machineidv1pb.Bot_builder{ + Kind: types.KindBot, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: "child-bot", + }.Build(), + Scope: childScope, + Spec: &machineidv1pb.BotSpec{}, + }.Build(), + }.Build()) + require.NoError(t, err) + + childSRAResp, err := scopedSvc.CreateScopedRoleAssignment(t.Context(), scopedaccessv1.CreateScopedRoleAssignmentRequest_builder{ + Assignment: scopedaccessv1.ScopedRoleAssignment_builder{ + Kind: scopedaccess.KindScopedRoleAssignment, + SubKind: scopedaccess.SubKindDynamic, + Version: types.V1, + Metadata: headerv1.Metadata_builder{ + Name: uuid.NewString(), + }.Build(), + Scope: childScope, + Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{ + Bot: scopes.QualifiedName{Scope: childScope, Name: childBot.GetMetadata().GetName()}.String(), + Assignments: []*scopedaccessv1.Assignment{ + scopedaccessv1.Assignment_builder{ + Role: scopes.QualifiedName{Scope: childScope, Name: "child-bot-role"}.String(), + Scope: childScope, + }.Build(), + }, + }.Build(), + }.Build(), + }.Build()) + require.NoError(t, err) + waitForSRACache(t, srv, childSRAResp) + + childBotClient, err := srv.NewClient( + authtest.TestScopedBot(t, scopes.QualifiedName{Scope: childScope, Name: childBot.GetMetadata().GetName()}, true), + ) + require.NoError(t, err) + t.Cleanup(func() { _ = childBotClient.Close() }) + + childIssuanceClient := issuancev1pb.NewIssuanceServiceClient(childBotClient.GetConnection()) + _, err = childIssuanceClient.IssueScopedBotCerts(t.Context(), issuancev1pb.IssueScopedBotCertsRequest_builder{ + TlsPublicKey: tlsPubKeyPEM, + Ttl: durationpb.New(requestedTTL), + App: issuancev1pb.UsageApp_builder{ + Name: "test-app", + PublicAddr: app.GetPublicAddr(), + Scope: botScope, + }.Build(), + }.Build()) + require.True(t, trace.IsAccessDenied(err), "expected access denied at scope pin check, got: %v", err) + }) +} + func waitForSRACache(t *testing.T, srv *authtest.TLSServer, resps ...*scopedaccessv1.CreateScopedRoleAssignmentResponse) { t.Helper() ctx := t.Context() diff --git a/lib/auth/issuance/v1/usage_app.go b/lib/auth/issuance/v1/usage_app.go new file mode 100644 index 000000000000..4f1592f58be0 --- /dev/null +++ b/lib/auth/issuance/v1/usage_app.go @@ -0,0 +1,127 @@ +/* + * Teleport + * Copyright (C) 2026 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 issuancev1 + +import ( + "context" + "time" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" + issuancev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/issuance/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/internal/cert" + sessionreq "github.com/gravitational/teleport/lib/auth/internal/session" + "github.com/gravitational/teleport/lib/scopes" + appscopes "github.com/gravitational/teleport/lib/scopes/app" + "github.com/gravitational/teleport/lib/tlsca" +) + +// applyUsageApp will create a new scoped session with the application and +// append to the cert.Request the relevant information. +func (s *Service) applyUsageApp( + ctx context.Context, + user types.User, + botScope string, + currentIdentity tlsca.Identity, + usageApp *issuancev1pb.UsageApp, + ttl time.Duration, + certReq *cert.Request, +) error { + // Ensure the current identity has scope to perform this operation. + pinnedScope := currentIdentity.ScopePin.GetScope() + if !scopes.ResourceScope(usageApp.GetScope()).IsSubjectToScopeOfEffect(pinnedScope) { + return trace.AccessDenied("app scope %q is not equivalent to or descendant of pinned scope %q", usageApp.GetScope(), pinnedScope) + } + + cn, err := s.authServer.GetClusterName(ctx) + if err != nil { + return trace.Wrap(err, "resolving cluster name") + } + + // CreateAppSessionFromReq performs some authorization internally, but NOT explicit app-level RBAC. + // The explicit app-level authorization (CheckAccessToApp) happens at connection time in the App Service. + ws, err := s.authServer.CreateAppSessionFromReq(ctx, sessionreq.NewAppSessionRequest{ + NewWebSessionRequest: sessionreq.NewWebSessionRequest{ + User: user.GetName(), + LoginIP: currentIdentity.LoginIP, + SessionTTL: ttl, + // No Traits, Roles, AccessRequests, or RequestedResourceAccessIDs: + // scoped bots have none, and CreateAppSessionFromReq rejects + // resource access IDs for scope-pinned identities. + + // AttestWebSession must be true so that clusters enforcing a + // hardware private key policy (e.g. hardware_key_touch) do not + // reject this session. The session's private key is generated + // and held exclusively by the Auth server — marking it as a + // web_session attestation tells GenerateUserCerts that no + // physical hardware key is needed to satisfy the policy. + // Without this, CreateAppSessionFromReq would fail with a + // PrivateKeyPolicyError on any cluster with a non-"none" policy. + + AttestWebSession: true, + }, + + AppName: usageApp.GetName(), + PublicAddr: usageApp.GetPublicAddr(), + ClusterName: cn.GetClusterName(), + TargetScope: usageApp.GetScope(), + + // Identity drives the scoped access checker context. + Identity: currentIdentity, + BotName: currentIdentity.BotName, + BotInstanceID: currentIdentity.BotInstanceID, + BotScope: botScope, + }) + if err != nil { + return trace.Wrap(err, "creating app session") + } + + // Annotate the certificate with the session information. + certReq.Usage = []string{teleport.UsageAppsOnly} + certReq.AppSessionID = ws.GetName() + certReq.AppName = usageApp.GetName() + certReq.AppPublicAddr = usageApp.GetPublicAddr() + certReq.AppClusterName = cn.GetClusterName() + certReq.TargetScope = usageApp.GetScope() + + return nil +} + +func validateUsageApp(req *issuancev1pb.IssueScopedBotCertsRequest) error { + app := req.GetApp() + if app.GetName() == "" { + return trace.BadParameter("app.name: is required") + } + if err := scopes.StrongValidate(app.GetScope()); err != nil { + return trace.Wrap(err, "app.scope") + } + if len(req.GetTlsPublicKey()) == 0 { + return trace.BadParameter("tls_public_key: is required for app usage") + } + if app.GetPublicAddr() == "" { + return trace.BadParameter("app.public_addr: is required") + } + if !appscopes.ScopedAppPublicAddrValid(app.GetScope(), app.GetName(), app.GetPublicAddr()) { + return trace.BadParameter("app.public_addr: is not valid for given app name and scope") + } + + return nil +} From bbe840d16541b54d4c3c396276d6206a53e74fd8 Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Thu, 13 Aug 2026 09:08:09 +0000 Subject: [PATCH 288/347] docs: Add 'sudo' to teleport-update uninstall command (#69618) --- docs/pages/installation/uninstall-teleport.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/installation/uninstall-teleport.mdx b/docs/pages/installation/uninstall-teleport.mdx index a1db8d657db7..3cbdfae4a5b1 100644 --- a/docs/pages/installation/uninstall-teleport.mdx +++ b/docs/pages/installation/uninstall-teleport.mdx @@ -94,7 +94,7 @@ Follow the steps for your operating system to remove Teleport binaries. First, uninstall the Teleport automatic updater (`teleport-update`) and the Teleport versions it potentially installed: ```code - teleport-update uninstall + sudo teleport-update uninstall ``` Then, follow the instructions for your Linux distribution: From 25a7be07dfa0f4d4380c63eb152d0e4d3af1dd42 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 13 Aug 2026 10:11:50 +0000 Subject: [PATCH 289/347] Connect: add e2e tests for kube access (#65929) * Add kube fixture * Make items in unified resources more semantically accurate so they are easier to find * Add shell utility that executes a command and waits for its completion * Add kube tests * Use unique pod name * Use `tsh kubectl` * Ignore kubeconfigs in git * Set up kube cluster only for Connect * Remove stale kind cluster before calling create * Handle Docker networking * Use stable image * Fix race condition on launch * Do not download `busybox` image, exec into kind's internal pod * Disable tests to avoid hitting rate limiter * Disable kube fixture for now * Lint --- e2e/.gitignore | 1 + e2e/README.md | 11 +- e2e/config/teleport.yaml.tmpl | 7 + e2e/helpers/connect.ts | 3 + e2e/helpers/fixtures.ts | 1 + e2e/helpers/pages/Terminal.ts | 55 +++++- e2e/helpers/pages/UnifiedResources.ts | 12 +- e2e/runner/fixtures/fixtures.go | 1 + e2e/runner/go.mod | 8 +- e2e/runner/go.sum | 8 + e2e/runner/instance.go | 12 ++ e2e/runner/kube.go | 187 ++++++++++++++++++ e2e/runner/main.go | 33 +++- e2e/runner/teleport.go | 26 +++ e2e/tests/connect/kube.spec.ts | 133 +++++++++++++ .../UnifiedResources/CardsView/CardsView.tsx | 9 +- .../CardsView/ResourceCard.tsx | 3 +- 17 files changed, 494 insertions(+), 16 deletions(-) create mode 100644 e2e/runner/kube.go create mode 100644 e2e/tests/connect/kube.spec.ts diff --git a/e2e/.gitignore b/e2e/.gitignore index 99d2769cf101..1e1c20c2de19 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -9,6 +9,7 @@ certs/ data/ config/*.yaml node/*.yaml +kube/*-kubeconfig *.log .auth/ !config/resources/*.yaml diff --git a/e2e/README.md b/e2e/README.md index 9cda112d707b..80106e7c26f5 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -60,11 +60,12 @@ automatically starts the required infrastructure. Available fixtures: -| Fixture | Description | -|----------------|------------------------------------------------------------------------------------| -| `ssh-node` | Start and connect a Teleport SSH node (runs in Docker) | -| `ssh-node-bpf` | A second node, `docker-node-bpf`, with Enhanced Session Recording enabled. | -| `connect` | Build Teleport Connect. Auto-detected from Connect test helpers. | +| Fixture | Description | +|----------------|-----------------------------------------------------------------------------------------------| +| `ssh-node` | Start and connect a Teleport SSH node (runs in Docker) | +| `ssh-node-bpf` | A second node, `docker-node-bpf`, with Enhanced Session Recording enabled. | +| `kube` | Start a kind-backed Kubernetes fixture + enable Teleport `kubernetes_service` in main process | +| `connect` | Build Teleport Connect. Auto-detected from Connect test helpers. | Fixtures can also be enabled manually with `--with-` flags (e.g. `--with-ssh-node`, `--with-connect`), which is useful for modes like `--codegen` or `--browse` where auto-detection does not run. diff --git a/e2e/config/teleport.yaml.tmpl b/e2e/config/teleport.yaml.tmpl index 0dde943dc485..75a16279d17c 100644 --- a/e2e/config/teleport.yaml.tmpl +++ b/e2e/config/teleport.yaml.tmpl @@ -30,6 +30,13 @@ auth_service: ssh_service: enabled: "no" +{{- if .KubeConfigPath }} +kubernetes_service: + enabled: "yes" + listen_addr: 0.0.0.0:{{ .KubeServerPort }} + kubeconfig_file: {{ .KubeConfigPath }} +{{- end }} + proxy_service: enabled: "yes" public_addr: localhost:{{ .ProxyPort }} diff --git a/e2e/helpers/connect.ts b/e2e/helpers/connect.ts index b7f662447db0..90b88d204179 100644 --- a/e2e/helpers/connect.ts +++ b/e2e/helpers/connect.ts @@ -99,6 +99,7 @@ export async function login( .filter({ hasText: 'Add Cluster' }); // The 'Connect' button is visible only when no clusters are present. + await expect(connectButton.or(addClusterItem)).toBeVisible(); if (await connectButton.isVisible()) { await connectButton.click(); } else { @@ -120,6 +121,7 @@ export async function login( export interface App { electronApp: ElectronApplication; page: Page; + userDataDir: string; appConfigPath: string; } @@ -153,6 +155,7 @@ export const test = fixtureBase.extend<{ } await use({ electronApp: launchedApp.electronApp, + userDataDir: temp.path, page: launchedApp.page, appConfigPath, }); diff --git a/e2e/helpers/fixtures.ts b/e2e/helpers/fixtures.ts index 2d26bffbc42a..ecdd63221bdc 100644 --- a/e2e/helpers/fixtures.ts +++ b/e2e/helpers/fixtures.ts @@ -23,6 +23,7 @@ import { test as base } from '@playwright/test'; export type Fixture = | 'ssh-node' | 'ssh-node-bpf' + | 'kube' | 'connect'; export const test = base.extend<{ fixtures: Fixture[] }>({ diff --git a/e2e/helpers/pages/Terminal.ts b/e2e/helpers/pages/Terminal.ts index 6a7877ade63a..7b2b6b4d7394 100644 --- a/e2e/helpers/pages/Terminal.ts +++ b/e2e/helpers/pages/Terminal.ts @@ -28,7 +28,7 @@ export class TerminalPage { constructor(private page: Page) { this.input = page.getByRole('textbox', { name: 'Terminal input' }); - this.terminal = page.getByTestId('terminal'); + this.terminal = page.locator('.xterm'); } async waitForReady() { @@ -39,6 +39,59 @@ export class TerminalPage { await this.input.pressSequentially(command + '\n'); } + /** + * Executes a shell command in the terminal and waits for its completion. + * Appends an exit-code marker so output can be matched to this specific command. + * + * @returns The trimmed output of the command. + * @throws {Error} If the exit code cannot be parsed or is not 0. + */ + async execAndWait( + command: string, + options: { timeout?: number } = {} + ): Promise { + // Generate a unique marker per command + const marker = `__EXIT_${crypto.randomUUID().split('-').at(0)}__=`; + const fullCommand = `${command}; echo "${marker}$?"`; + const terminalRows = this.terminal.locator('.xterm-rows'); + + await this.input.pressSequentially(`${fullCommand}\n`); + + // Wait for the marker plus the exit code + const markerWithExitCodeRegex = new RegExp(`${marker}\\d+`); + await expect(terminalRows).toContainText(markerWithExitCodeRegex, { + timeout: options?.timeout || TERMINAL_TIMEOUT, + }); + + const text = (await terminalRows.textContent()) || ''; + + const fullCommandIndex = text.lastIndexOf(fullCommand); + const markerIndex = text.lastIndexOf(marker); + + // Extract output between the typed command and the exit marker + const output = text + .slice(fullCommandIndex + fullCommand.length, markerIndex) + .trim(); + + // Extract the exit code immediately following the marker + const exitCodeMatch = text + .slice(markerIndex + marker.length) + .match(/^(\d+)/); + if (!exitCodeMatch) { + throw new Error(`Could not find exit code, output: ${output}`); + } + + const exitCode = Number(exitCodeMatch[1]); + + if (exitCode !== 0) { + throw new Error( + `Command failed with ${exitCode} exit code, output: ${output}` + ); + } + + return output; + } + async waitForText(text: string) { await expect(this.terminal).toContainText(text, { timeout: TERMINAL_TIMEOUT, diff --git a/e2e/helpers/pages/UnifiedResources.ts b/e2e/helpers/pages/UnifiedResources.ts index 4ce5d819f81f..bc35f6c934c0 100644 --- a/e2e/helpers/pages/UnifiedResources.ts +++ b/e2e/helpers/pages/UnifiedResources.ts @@ -34,7 +34,7 @@ export class UnifiedResourcesPage { */ async connect(serverName: string, login: string) { const row = this.page - .locator('div') + .locator('li') .filter({ has: this.page.getByText(serverName, { exact: true }) }) .filter({ has: this.page.getByRole('button', { name: 'Connect' }) }) .last(); @@ -47,4 +47,14 @@ export class UnifiedResourcesPage { return new TerminalPage(popup); } + + async connectWithoutLogin(resourceName: string | RegExp): Promise { + const locator = this.page + .locator('li') + .filter({ hasText: resourceName }) + .filter({ has: this.page.getByRole('button', { name: 'Connect' }) }) + .first(); + + await locator.getByRole('button', { name: 'Connect' }).click(); + } } diff --git a/e2e/runner/fixtures/fixtures.go b/e2e/runner/fixtures/fixtures.go index b41479a0fb4c..f377e19268d7 100644 --- a/e2e/runner/fixtures/fixtures.go +++ b/e2e/runner/fixtures/fixtures.go @@ -24,6 +24,7 @@ var ( SSHNode = register("ssh-node") // SSHNodeBPF runs a second node, docker-node-bpf, with Enhanced Session Recording enabled. SSHNodeBPF = register("ssh-node-bpf") + Kube = register("kube") Connect = register("connect") ) diff --git a/e2e/runner/go.mod b/e2e/runner/go.mod index c65bcfacc415..0a794072e8f3 100644 --- a/e2e/runner/go.mod +++ b/e2e/runner/go.mod @@ -20,9 +20,12 @@ require ( golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.22.0 gopkg.in/yaml.v3 v3.0.1 + k8s.io/client-go v0.36.2 + sigs.k8s.io/kind v0.32.0 ) require ( + al.essio.dev/pkg/shellescape v1.6.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect @@ -171,6 +174,7 @@ require ( github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect github.com/in-toto/attestation v1.2.0 // indirect github.com/in-toto/in-toto-golang v0.11.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -210,6 +214,7 @@ require ( github.com/openai/openai-go/v3 v3.8.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible // indirect + github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.8 // indirect @@ -234,6 +239,8 @@ require ( github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sijms/go-ora/v2 v2.9.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef // indirect @@ -287,7 +294,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.36.2 // indirect k8s.io/apimachinery v0.36.2 // indirect - k8s.io/client-go v0.36.2 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect diff --git a/e2e/runner/go.sum b/e2e/runner/go.sum index e036b7c94088..c37ba96c88fa 100644 --- a/e2e/runner/go.sum +++ b/e2e/runner/go.sum @@ -243,6 +243,7 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -462,6 +463,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f h1:o2yGZLlsOj5H5uvtQNEdi6DeA0GbUP3lm0gWW5RvY0s= github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f/go.mod h1:H3K1Iu/utuCfa10JO+GsmKUYSWi7ug57Rk6GaDRHaaQ= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -685,6 +688,8 @@ github.com/oracle/oci-go-sdk/v65 v65.117.1/go.mod h1:oo33NDf2XPqx3/N6oLG4jFlrqJ0 github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo= github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -772,6 +777,7 @@ github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= @@ -1071,6 +1077,8 @@ sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9 sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kind v0.32.0 h1:p9hscbj98u/qyrjVpjId86LI70nQmbSsipV7wCG10Xk= +sigs.k8s.io/kind v0.32.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= diff --git a/e2e/runner/instance.go b/e2e/runner/instance.go index d7aab60c98c2..239e4cee1404 100644 --- a/e2e/runner/instance.go +++ b/e2e/runner/instance.go @@ -31,6 +31,7 @@ type testInstance struct { proxyPort int authPort int sshPorts []int + kubePort int e2eDir string dataDir string tctlBin string @@ -38,6 +39,7 @@ type testInstance struct { teleportConfigPath string teleport *teleportInstance nodes []*dockerNode + kube *kubeCluster } // start starts the Teleport instance and SSH node for this test instance. @@ -51,6 +53,12 @@ func (inst *testInstance) start(ctx context.Context) error { } }() + if inst.kube != nil { + if err = inst.kube.start(ctx); err != nil { + return fmt.Errorf("failed to start kube fixture for %s: %w", inst.browser, err) + } + } + if inst.teleport != nil { if err = inst.teleport.start(ctx); err != nil { return fmt.Errorf("failed to start Teleport for %s: %w", inst.browser, err) @@ -85,6 +93,10 @@ func (inst *testInstance) stop() { node.stop(context.Background()) } + if inst.kube != nil { + inst.kube.stop() + } + if inst.teleport != nil { inst.teleport.stop() } diff --git a/e2e/runner/kube.go b/e2e/runner/kube.go new file mode 100644 index 000000000000..3ce157b94d49 --- /dev/null +++ b/e2e/runner/kube.go @@ -0,0 +1,187 @@ +/** + * Teleport + * Copyright (C) 2026 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 main + +import ( + "context" + "fmt" + "log/slog" + "net" + "net/url" + "os" + "path/filepath" + + "k8s.io/client-go/tools/clientcmd" + kindv1alpha4 "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" + kindcluster "sigs.k8s.io/kind/pkg/cluster" + kindlog "sigs.k8s.io/kind/pkg/log" +) + +type kubeCluster struct { + log *slog.Logger + name string + kubeconfigPath string + // dockerEndpointHost is the hostname from a remote DOCKER_HOST (for example + // "docker" from tcp://docker:2375). Enables the kube cluster to be + // reachable outside the container. + dockerEndpointHost string + provider *kindcluster.Provider +} + +func (k *kubeCluster) start(ctx context.Context) error { + if err := os.MkdirAll(filepath.Dir(k.kubeconfigPath), 0o755); err != nil { + return err + } + + k.provider = kindcluster.NewProvider( + kindcluster.ProviderWithDocker(), + kindcluster.ProviderWithLogger(kindSlogLogger{log: k.log}), + ) + k.log.InfoContext(ctx, "starting kube cluster", "name", k.name) + + if err := k.provider.Delete(k.name, k.kubeconfigPath); err != nil { + return fmt.Errorf("failed to delete stale kube cluster %s: %w", k.name, err) + } + + cfg := &kindv1alpha4.Cluster{} + if k.dockerEndpointHost != "" { + cfg.Networking.APIServerAddress = "0.0.0.0" + } + if err := k.provider.Create( + k.name, + kindcluster.CreateWithV1Alpha4Config(cfg), + kindcluster.CreateWithKubeconfigPath(k.kubeconfigPath), + ); err != nil { + return fmt.Errorf("creating kube cluster %q: %w", k.name, err) + } + + if k.dockerEndpointHost != "" { + if err := rewriteKubeconfigServerHost(k.kubeconfigPath, k.dockerEndpointHost); err != nil { + return fmt.Errorf("rewriting kubeconfig server host: %w", err) + } + } + + k.log.InfoContext(ctx, "kube cluster is ready", "name", k.name, "kubeconfig", k.kubeconfigPath) + return nil +} + +func rewriteKubeconfigServerHost(kubeconfigPath, targetHost string) error { + config, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + return fmt.Errorf("loading kubeconfig %q: %w", kubeconfigPath, err) + } + + for name, cluster := range config.Clusters { + serverURL, err := url.Parse(cluster.Server) + if err != nil { + return fmt.Errorf("parsing kubeconfig server URL for cluster %q: %w", name, err) + } + + // Rewrite wildcard bind addresses. + if serverURL.Hostname() != "0.0.0.0" { + continue + } + + port := serverURL.Port() + if port == "" { + serverURL.Host = targetHost + } else { + serverURL.Host = net.JoinHostPort(targetHost, port) + } + // Replace the address with the Docker endpoint host so the API server is reachable from the Teleport container. + cluster.Server = serverURL.String() + // kind's API server cert includes "localhost" in SANs, but not the Docker host name. + // Pinning TLSServerName to localhost avoids x509 hostname mismatch failures. + cluster.TLSServerName = "localhost" + } + + return clientcmd.WriteToFile(*config, kubeconfigPath) +} + +func (k *kubeCluster) stop() { + if k.provider == nil { + return + } + + ctx := context.Background() + k.log.InfoContext(ctx, "deleting kube cluster", "name", k.name) + if err := k.provider.Delete(k.name, k.kubeconfigPath); err != nil { + k.log.WarnContext(ctx, "failed to delete kube cluster", "name", k.name, "error", err) + } + + if err := os.Remove(k.kubeconfigPath); err != nil && !os.IsNotExist(err) { + k.log.WarnContext(ctx, "failed to remove kubeconfig", "path", k.kubeconfigPath, "error", err) + } +} + +type kindSlogLogger struct { + log *slog.Logger +} + +func (k kindSlogLogger) Warn(message string) { + //nolint:sloglint // message cannot be constant + k.log.WarnContext(context.Background(), message) +} + +func (k kindSlogLogger) Warnf(format string, args ...interface{}) { + //nolint:sloglint // message cannot be constant + k.log.WarnContext(context.Background(), fmt.Sprintf(format, args...)) +} + +func (k kindSlogLogger) Error(message string) { + //nolint:sloglint // message cannot be constant + k.log.ErrorContext(context.Background(), message) +} + +func (k kindSlogLogger) Errorf(format string, args ...interface{}) { + //nolint:sloglint // message cannot be constant + k.log.ErrorContext(context.Background(), fmt.Sprintf(format, args...)) +} + +func (k kindSlogLogger) V(level kindlog.Level) kindlog.InfoLogger { + return kindSlogInfoLogger{ + log: k.log, + level: level, + } +} + +type kindSlogInfoLogger struct { + log *slog.Logger + level kindlog.Level +} + +func (k kindSlogInfoLogger) Enabled() bool { + return k.log.Enabled(context.Background(), k.slogLevel()) +} + +func (k kindSlogInfoLogger) Info(message string) { + //nolint:sloglint // message cannot be constant + k.log.Log(context.Background(), k.slogLevel(), message) +} + +func (k kindSlogInfoLogger) Infof(format string, args ...interface{}) { + k.Info(fmt.Sprintf(format, args...)) +} + +func (k kindSlogInfoLogger) slogLevel() slog.Level { + if k.level == 0 { + return slog.LevelInfo + } + return slog.LevelDebug +} diff --git a/e2e/runner/main.go b/e2e/runner/main.go index b896562f51da..0e954001dac3 100644 --- a/e2e/runner/main.go +++ b/e2e/runner/main.go @@ -254,9 +254,12 @@ func run(flags *e2eFlags, mode runMode, e2eDir string, isCI bool) error { portTargets = append(portTargets, &inst.sshPorts[i]) } } + if fixtures.Kube.Enabled { + portTargets = append(portTargets, &inst.kubePort) + } } if ci := config.connectInstance; ci != nil { - portTargets = append(portTargets, &ci.proxyPort, &ci.authPort) + portTargets = append(portTargets, &ci.proxyPort, &ci.authPort, &ci.kubePort) } if err := allocatePorts(portTargets...); err != nil { @@ -264,10 +267,10 @@ func run(flags *e2eFlags, mode runMode, e2eDir string, isCI bool) error { } for _, inst := range config.instances { - inst.log.DebugContext(ctx, "allocated ports", "proxy", inst.proxyPort, "auth", inst.authPort, "ssh", inst.sshPorts) + inst.log.DebugContext(ctx, "allocated ports", "proxy", inst.proxyPort, "auth", inst.authPort, "ssh", inst.sshPorts, "kube", inst.kubePort) } if ci := config.connectInstance; ci != nil { - ci.log.DebugContext(ctx, "allocated ports", "proxy", ci.proxyPort, "auth", ci.authPort) + ci.log.DebugContext(ctx, "allocated ports", "proxy", ci.proxyPort, "auth", ci.authPort, "kube", ci.kubePort) } if sshNodeEnabled() { @@ -375,16 +378,40 @@ func run(flags *e2eFlags, mode runMode, e2eDir string, isCI bool) error { slog.DebugContext(ctx, "generated bootstrap state", "path", stateFile) for _, inst := range allInstances { + kubeConfigPath := "" + // TODO(gzdunek): Currently only Connect tests kube access, so skip setting up kube clusters + // for Web UI and slowing down the tests. + // Replace this with project/browser-scoped fixture discovery so each instance gets only the fixtures + // required by its test set. + if fixtures.Kube.Enabled && inst.browser == "connect" { + name := "teleport-e2e-kube-" + inst.browser + kubeConfigPath = filepath.Join(e2eDir, "kube", inst.browser+"-kubeconfig") + + dockerEndpointHost, err := resolveDockerEndpointHost() + if err != nil { + return fmt.Errorf("resolving docker endpoint host: %w", err) + } + + inst.kube = &kubeCluster{ + log: inst.log, + name: name, + dockerEndpointHost: dockerEndpointHost, + kubeconfigPath: kubeConfigPath, + } + } + outPath := filepath.Join(e2eDir, "config", inst.browser+"-teleport.yaml") tcfg, err := generateTeleportConfig(config.teleportConfigTemplate, outPath, &TeleportConfig{ ClusterName: clusterName, DataDir: inst.dataDir, AuthServerPort: inst.authPort, ProxyPort: inst.proxyPort, + KubeServerPort: inst.kubePort, KeyFilePath: filepath.Join(config.certsDir, keyFileName), CertFilePath: filepath.Join(config.certsDir, certFileName), LicenseFile: config.licenseFile, LogLevel: config.teleportLogLevel, + KubeConfigPath: kubeConfigPath, }) if err != nil { return fmt.Errorf("failed to generate Teleport config for %s: %w", inst.browser, err) diff --git a/e2e/runner/teleport.go b/e2e/runner/teleport.go index 1d3116df9dea..6e0c04a5ee94 100644 --- a/e2e/runner/teleport.go +++ b/e2e/runner/teleport.go @@ -159,10 +159,12 @@ type TeleportConfig struct { DataDir string AuthServerPort int ProxyPort int + KubeServerPort int KeyFilePath string CertFilePath string LicenseFile string LogLevel string + KubeConfigPath string } func generateTeleportConfig(templatePath, outPath string, data *TeleportConfig) (string, error) { @@ -211,6 +213,30 @@ func resolveDockerHost() (string, error) { return conn.LocalAddr().(*net.UDPAddr).IP.String(), nil } +// resolveDockerEndpointHost returns the hostname portion of a remote Docker +// endpoint (for example "docker" from DOCKER_HOST=tcp://docker:2375). +// Returns empty string when Docker is local (unix/npipe/default). +func resolveDockerEndpointHost() (string, error) { + dockerHost := os.Getenv("DOCKER_HOST") + if dockerHost == "" { + return "", nil + } + + u, err := url.Parse(dockerHost) + if err != nil { + return "", fmt.Errorf("parsing DOCKER_HOST: %w", err) + } + if u.Scheme != "tcp" { + return "", nil + } + + host := u.Hostname() + if host == "" { + return "", fmt.Errorf("tcp DOCKER_HOST has empty hostname: %q", dockerHost) + } + return host, nil +} + func generateStateFile(templatePath string, state *stateConfig) (string, error) { return renderTemplate(templatePath, state) } diff --git a/e2e/tests/connect/kube.spec.ts b/e2e/tests/connect/kube.spec.ts new file mode 100644 index 000000000000..4e66f39e23e6 --- /dev/null +++ b/e2e/tests/connect/kube.spec.ts @@ -0,0 +1,133 @@ +/* + * Teleport + * Copyright (C) 2026 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 fs from 'node:fs/promises'; + +import { test, expect } from '@gravitational/e2e/helpers/connect'; +import { TerminalPage } from '@gravitational/e2e/helpers/pages/Terminal'; +import { UnifiedResourcesPage } from '@gravitational/e2e/helpers/pages/UnifiedResources'; + +test.use({ + autoLogin: true, + fixtures: [ + // TODO(gzdunek): Uncomment when enabling these tests. + /*'kube'*/ + ], + user: { + roles: ['access'], + traits: { kubernetes_groups: ['system:masters'] }, + }, +}); + +const kubeResourceName = /teleport-e2e-kube-/i; +const kubePromptText = 'Try "kubectl version" to test the connection.'; + +async function openKubeTerminal( + resources: UnifiedResourcesPage, + terminal: TerminalPage +): Promise { + await resources.connectWithoutLogin(kubeResourceName); + await terminal.waitForText(kubePromptText); +} + +// TODO(gzdunek): Re-enable these tests once Connect E2E logins reuse authenticated state +// instead of hitting the rate limiter. +test.skip('creates kubeconfig under user data and reuses for the same kube session', async ({ + app, +}) => { + const { page, userDataDir } = app; + const resources = new UnifiedResourcesPage(page); + const terminal = new TerminalPage(page); + + await openKubeTerminal(resources, terminal); + const kubeTab = page.locator( + '[role="tab"][data-doc-kind="doc.gateway_kube"]' + ); + await expect(kubeTab).toBeVisible(); + + const pathBeforeTabClose = await terminal.execAndWait('echo $KUBECONFIG'); + expect(pathBeforeTabClose.startsWith(userDataDir)).toBe(true); + + await expect(kubeTab).toBeVisible(); + await kubeTab.locator('.close').click(); + await expect(kubeTab).toHaveCount(0); + + await openKubeTerminal(resources, terminal); + await expect(kubeTab).toBeVisible(); + const pathAfterTabReopen = await terminal.execAndWait('echo $KUBECONFIG'); + + expect(pathBeforeTabClose).toBe(pathAfterTabReopen); +}); + +test.skip('closing connection removes kubeconfig file', async ({ app }) => { + const { page } = app; + const resources = new UnifiedResourcesPage(page); + const terminal = new TerminalPage(page); + + await openKubeTerminal(resources, terminal); + + const kubeconfigPath = await terminal.execAndWait('echo $KUBECONFIG'); + + expect(await fileExists(kubeconfigPath)).toBe(true); + + await page.getByTitle(/Open Connections/).click(); + const kubeConnection = page.locator('li').filter({ hasText: /KUBE/ }).first(); + await expect(kubeConnection).toBeVisible(); + + const disconnectButton = kubeConnection.getByTitle(/Disconnect /).first(); + await expect(disconnectButton).toBeVisible(); + await disconnectButton.click(); + + const removeButton = kubeConnection.getByTitle(/Remove /).first(); + await expect(removeButton).toBeVisible(); + await removeButton.click(); + + await expect.poll(() => fileExists(kubeconfigPath)).toBe(false); +}); + +test.skip('exec into a pod', async ({ app }) => { + const { page } = app; + const resources = new UnifiedResourcesPage(page); + const terminal = new TerminalPage(page); + // Use `tsh kubectl` because `kubectl` is not available in CI. + const kubectlCommand = `"$E2E_CONNECT_TSH_BIN" kubectl`; + await openKubeTerminal(resources, terminal); + + // Reuse kind's kube-proxy pod to test exec without pulling an additional image. + const versionOutput = await terminal.execAndWait( + `${kubectlCommand} exec --stdin --tty --namespace kube-system daemonset/kube-proxy -- /usr/local/bin/kube-proxy --version` + ); + expect(versionOutput).toContain('Kubernetes v'); +}); + +async function fileExists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch (e: unknown) { + if ( + typeof e === 'object' && + e !== null && + 'code' in e && + (e as { code?: string }).code === 'ENOENT' + ) { + return false; + } + throw e; + } +} diff --git a/web/packages/shared/components/UnifiedResources/CardsView/CardsView.tsx b/web/packages/shared/components/UnifiedResources/CardsView/CardsView.tsx index 60dc6ee79b3c..fa816360e973 100644 --- a/web/packages/shared/components/UnifiedResources/CardsView/CardsView.tsx +++ b/web/packages/shared/components/UnifiedResources/CardsView/CardsView.tsx @@ -18,8 +18,6 @@ import styled from 'styled-components'; -import { Flex } from 'design'; - import { LoadingSkeleton } from '../shared/LoadingSkeleton'; import { ResourceViewProps } from '../types'; import { FETCH_MORE_SIZE } from '../UnifiedResources'; @@ -40,7 +38,7 @@ export function CardsView({ resourceLabelConfig, }: ResourceViewProps) { return ( - + {mappedResources.map( ({ item, key, onShowStatusInfo, showingStatusInfo }) => ( props.theme.space[2]}px; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); + margin: 0; + padding: 0; `; diff --git a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx index 9c0b455a3449..6f0dea1d1097 100644 --- a/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx +++ b/web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx @@ -379,11 +379,12 @@ const WarningRightEdgeBadgeIcon = ({ * TODO(bl-nero): Known issue: this doesn't really work well with one-column * layout; */ -const CardContainer = styled(Box)<{ +const CardContainer = styled.li<{ showingStatusInfo: boolean; showHoverState: boolean; }>` height: 110px; + list-style: none; position: relative; .resource-health-status-svg { From 6fc6dc073396adc8dcac6bf59c2714327b38e393 Mon Sep 17 00:00:00 2001 From: Michael Patterson-Muir Date: Thu, 13 Aug 2026 13:59:03 +0000 Subject: [PATCH 290/347] discovery: add configurable AWS SSM install timeout (#69102) --- .../aws-ec2-advanced-config.mdx | 28 ++ lib/srv/discovery/status.go | 63 ++- lib/srv/discovery/status_test.go | 65 +++ lib/srv/server/ssm_install.go | 133 +++-- lib/srv/server/ssm_install_test.go | 458 +++++++++++++++++- 5 files changed, 678 insertions(+), 69 deletions(-) diff --git a/docs/pages/includes/auto-discovery/aws-ec2-advanced-config.mdx b/docs/pages/includes/auto-discovery/aws-ec2-advanced-config.mdx index 532732a639f0..877484a5f838 100644 --- a/docs/pages/includes/auto-discovery/aws-ec2-advanced-config.mdx +++ b/docs/pages/includes/auto-discovery/aws-ec2-advanced-config.mdx @@ -2,6 +2,34 @@ (!docs/pages/includes/auto-discovery/server-advanced-config.mdx matcher="aws"!) +### Configure the SSM installation timeout + +By default, the Discovery Service waits up to five minutes for an SSM +installation attempt. It allows an additional minute for AWS to publish the +command's terminal state and reserves up to one more minute to collect and +report the result. + +Set `TELEPORT_UNSTABLE_AWS_INSTALL_TIMEOUT` on the Discovery Service to change +the five-minute timeout. The value must use Go duration syntax, such as +`3m45s`. Values are limited to a minimum of 10 seconds and a maximum of 90 +minutes. Invalid values use the five-minute default. + +For example: + +```code +TELEPORT_UNSTABLE_AWS_INSTALL_TIMEOUT=3m45s +``` + + + +This environment variable is unstable and may change in a future release. + +The timeout bounds how long the Discovery Service waits for the installation. +It does not override the execution timeout in a custom SSM document or +necessarily cancel a command that AWS has already accepted. + + + ### Use a custom installation script (!docs/pages/includes/server-access/custom-installer-aws-config.mdx!) diff --git a/lib/srv/discovery/status.go b/lib/srv/discovery/status.go index 17b6c4e0acac..4a613064f993 100644 --- a/lib/srv/discovery/status.go +++ b/lib/srv/discovery/status.go @@ -382,42 +382,39 @@ func (ars *awsResourcesStatus) incrementEnrolled(g awsResourceGroup, count int) // ReportEC2SSMInstallationResult is called when discovery gets the result of running the installation script in a EC2 instance. // It will emit an audit event with the result and update the DiscoveryConfig status func (s *Server) ReportEC2SSMInstallationResult(ctx context.Context, result *server.SSMInstallationResult) error { - if err := s.Emitter.EmitAuditEvent(ctx, result.SSMRunEvent); err != nil { - return trace.Wrap(err) - } - // Only failed runs are counted. // Successful ones only mean that the teleport was installed in the target host. // If they succeed in joining the cluster, during the next iteration, they will be countd as "enrolled" - if result.SSMRunEvent.Metadata.Code == libevents.SSMRunSuccessCode { - return nil - } - - s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfigName: result.DiscoveryConfigName, - integration: result.IntegrationName, - }, 1) - - s.awsEC2Tasks.addFailedEnrollment( - awsEC2TaskKey{ - integration: result.IntegrationName, - issueType: result.IssueType, - accountID: result.SSMRunEvent.AccountID, - region: result.SSMRunEvent.Region, - ssmDocument: result.SSMDocumentName, - installerScript: result.InstallerScript, - }, - usertasksv1.DiscoverEC2Instance_builder{ - InvocationUrl: result.SSMRunEvent.InvocationURL, - DiscoveryConfig: result.DiscoveryConfigName, - DiscoveryGroup: s.DiscoveryGroup, - SyncTime: timestamppb.New(s.clock.Now()), - InstanceId: result.SSMRunEvent.InstanceID, - Name: result.InstanceName, - }.Build(), - ) - - return nil + if result.SSMRunEvent.Metadata.Code != libevents.SSMRunSuccessCode { + // Record failure status before emitting the audit event. Audit emission + // errors are reported to the caller, but must not hide the failed + // enrollment from DiscoveryConfig status or UserTasks. + s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ + discoveryConfigName: result.DiscoveryConfigName, + integration: result.IntegrationName, + }, 1) + + s.awsEC2Tasks.addFailedEnrollment( + awsEC2TaskKey{ + integration: result.IntegrationName, + issueType: result.IssueType, + accountID: result.SSMRunEvent.AccountID, + region: result.SSMRunEvent.Region, + ssmDocument: result.SSMDocumentName, + installerScript: result.InstallerScript, + }, + usertasksv1.DiscoverEC2Instance_builder{ + InvocationUrl: result.SSMRunEvent.InvocationURL, + DiscoveryConfig: result.DiscoveryConfigName, + DiscoveryGroup: s.DiscoveryGroup, + SyncTime: timestamppb.New(s.clock.Now()), + InstanceId: result.SSMRunEvent.InstanceID, + Name: result.InstanceName, + }.Build(), + ) + } + + return trace.Wrap(s.Emitter.EmitAuditEvent(ctx, result.SSMRunEvent)) } // awsEC2Tasks contains the Discover EC2 User Tasks that must be reported to the user. diff --git a/lib/srv/discovery/status_test.go b/lib/srv/discovery/status_test.go index 610a1492f52e..d4c17b3b6d89 100644 --- a/lib/srv/discovery/status_test.go +++ b/lib/srv/discovery/status_test.go @@ -20,6 +20,7 @@ package discovery import ( "context" + "errors" "maps" "slices" "strings" @@ -37,10 +38,74 @@ import ( usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/discoveryconfig" + apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/usertasks" + libevents "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/srv/server" "github.com/gravitational/teleport/lib/utils/log/logtest" ) +type failingEmitter struct { + err error +} + +func (e failingEmitter) EmitAuditEvent(context.Context, apievents.AuditEvent) error { + return e.err +} + +func TestReportEC2SSMInstallationResultRecordsFailureOnAuditError(t *testing.T) { + emitErr := errors.New("audit emission failed") + clock := clockwork.NewFakeClock() + group := awsResourceGroup{ + discoveryConfigName: "discovery-config", + integration: "integration", + } + s := &Server{ + Config: &Config{ + Emitter: failingEmitter{err: emitErr}, + DiscoveryGroup: "discovery-group", + clock: clock, + }, + awsEC2ResourcesStatus: newAWSResourceStatusCollector("ec2"), + } + s.awsEC2ResourcesStatus.iterationStarted([]awsResourceGroup{group}, clock.Now()) + + result := &server.SSMInstallationResult{ + SSMRunEvent: &apievents.SSMRun{ + Metadata: apievents.Metadata{ + Code: libevents.SSMRunFailCode, + }, + AccountID: "123456789012", + Region: "eu-west-2", + InstanceID: "i-1234567890abcdef0", + InvocationURL: "https://example.com/invocation", + }, + IntegrationName: group.integration, + DiscoveryConfigName: group.discoveryConfigName, + IssueType: usertasks.AutoDiscoverEC2IssueSSMInvocationFailure, + SSMDocumentName: "document", + InstallerScript: "installer", + InstanceName: "instance", + } + + err := s.ReportEC2SSMInstallationResult(t.Context(), result) + require.ErrorIs(t, err, emitErr) + require.Equal(t, 1, s.awsEC2ResourcesStatus.awsResourcesResults[group].failed) + + taskKey := awsEC2TaskKey{ + integration: result.IntegrationName, + issueType: result.IssueType, + accountID: result.SSMRunEvent.AccountID, + region: result.SSMRunEvent.Region, + ssmDocument: result.SSMDocumentName, + installerScript: result.InstallerScript, + } + task := s.awsEC2Tasks.instancesIssues[taskKey] + require.NotNil(t, task) + require.Contains(t, task.GetInstances(), result.SSMRunEvent.InstanceID) + require.Contains(t, s.awsEC2Tasks.issuesSyncQueue, taskKey) +} + func TestTruncateErrorMessage(t *testing.T) { for _, tt := range []struct { name string diff --git a/lib/srv/server/ssm_install.go b/lib/srv/server/ssm_install.go index 33097968ea27..f11c4de6c5dd 100644 --- a/lib/srv/server/ssm_install.go +++ b/lib/srv/server/ssm_install.go @@ -24,6 +24,7 @@ import ( "fmt" "log/slog" "maps" + "os" "slices" "strings" "time" @@ -45,6 +46,18 @@ import ( ) const ( + // awsInstallTimeoutEnvVar configures how long an AWS SSM installation + // attempt may execute before timing out. + awsInstallTimeoutEnvVar = "TELEPORT_UNSTABLE_AWS_INSTALL_TIMEOUT" + + defaultAWSInstallTimeout = 5 * time.Minute + minAWSInstallTimeout = 10 * time.Second + maxAWSInstallTimeout = 90 * time.Minute + // awsSSMResultGracePeriod gives SSM time to publish the terminal command + // status and Teleport time to collect the final result after installation + // timeout. It is applied once for each phase. + awsSSMResultGracePeriod = time.Minute + // waiterTimedOutErrorMessage is the error message returned by the AWS SDK command // executed waiter when it times out. waiterTimedOutErrorMessage = "exceeded max wait time for CommandExecuted waiter" @@ -53,19 +66,6 @@ const ( //nolint:misspell // ignore Cancelled and Cancelling // executed waiter when the command state transitions to one of Cancelled, TimedOut, Failed or Cancelling. waiterTransitionedToFailureErrorMessage = "waiter state transitioned to Failure" - // waitTimeoutPad is extra waiter headroom beyond the installer's join-failure timeout. - // The installer decides success/failure based on whether join completes in time. - // This pad avoids reporting a temporary "still running" SSM state as the final outcome. - // Tradeoff: EC2 install handling is still synchronous in discovery, so a wedged - // SSM invocation can delay later groups until waitTimeout elapses. - // - // TODO(carlisia): Decouple SSM result waiting from synchronous discovery - // iterations so a wedged invocation does not stall later EC2 groups. - waitTimeoutPad = 10 * time.Minute - - // waitTimeout is how long we wait for AWS to report a terminal command state - // for the installer result. - waitTimeout = installer.JoinFailureTimeout + waitTimeoutPad // maxSSMRunOutputChars limits stdout/stderr size while preserving the most recent diagnostics. // 24_000 matches the documented per-field cap for SSMRun stdout/stderr in the event schema, @@ -73,6 +73,14 @@ const ( maxSSMRunOutputChars = 24_000 ) +func getAWSInstallTimeout() time.Duration { + if timeout, err := time.ParseDuration(os.Getenv(awsInstallTimeoutEnvVar)); err == nil { + // Keep the timeout bounds consistent with Azure VM installation timeouts. + return min(maxAWSInstallTimeout, max(minAWSInstallTimeout, timeout)) + } + return defaultAWSInstallTimeout +} + // SSMClient is the subset of the AWS SSM API required for EC2 discovery. type SSMClient interface { ssm.DescribeInstanceInformationAPIClient @@ -231,8 +239,21 @@ func (si *SSMInstaller) Run(ctx context.Context, req SSMRunRequest) error { return nil } + installTimeout := getAWSInstallTimeout() + waiterMaxWait := installTimeout + awsSSMResultGracePeriod + // EC2 install handling remains synchronous in discovery, so a wedged SSM + // invocation can delay later groups until the configured timeout and SSM + // status headroom elapse. + // TODO(carlisia): Decouple SSM result waiting from synchronous discovery + // iterations so a wedged invocation does not stall later EC2 groups. + // Give SSM time to publish a terminal state after the install timeout, then + // keep the context alive beyond the waiter so Teleport can collect and + // report that state. + installCtx, cancel := context.WithTimeout(ctx, waiterMaxWait+awsSSMResultGracePeriod) + defer cancel() + validInstanceIDs := instanceIDsFrom(validInstances) - output, err := req.SSM.SendCommand(ctx, &ssm.SendCommandInput{ + output, err := req.SSM.SendCommand(installCtx, &ssm.SendCommandInput{ DocumentName: aws.String(req.DocumentName), InstanceIds: validInstanceIDs, Parameters: params, @@ -260,7 +281,7 @@ func (si *SSMInstaller) Run(ctx context.Context, req SSMRunRequest) error { // As a best effort, we try to call ssm.SendCommand again but this time without the "sshdConfigPath" param // We must not remove the Param "sshdConfigPath" beforehand because customers might be using custom SSM Documents for ec2 auto discovery. delete(params, ParamSSHDConfigPath) - output, err = req.SSM.SendCommand(ctx, &ssm.SendCommandInput{ + output, err = req.SSM.SendCommand(installCtx, &ssm.SendCommandInput{ DocumentName: aws.String(req.DocumentName), InstanceIds: validInstanceIDs, Parameters: params, @@ -270,8 +291,11 @@ func (si *SSMInstaller) Run(ctx context.Context, req SSMRunRequest) error { } } - g, ctx := errgroup.WithContext(ctx) - g.SetLimit(10) + installDeadline, _ := installCtx.Deadline() + g, groupCtx := errgroup.WithContext(ctx) + // SendCommand accepts at most awsEC2APIChunkSize instances. Start every + // waiter in the batch so the configured timeout bounds the whole batch. + g.SetLimit(awsEC2APIChunkSize) for instanceID, instanceName := range validInstances { metadata := instanceMetadata{ InstanceID: instanceID, @@ -283,7 +307,40 @@ func (si *SSMInstaller) Run(ctx context.Context, req SSMRunRequest) error { } } g.Go(func() error { - return trace.Wrap(si.checkCommand(ctx, req, output.Command.CommandId, metadata)) + log := si.Logger.With( + "command_id", aws.ToString(output.Command.CommandId), + "instance_id", metadata.InstanceID, + ) + // Give every instance an independent context with the same absolute + // deadline. A local waiter or collection failure must not cancel the + // other instances in this SendCommand batch. + instanceCtx, cancel := context.WithDeadline(groupCtx, installDeadline) + defer cancel() + + result, err := si.checkCommand(instanceCtx, req, output.Command.CommandId, metadata, waiterMaxWait) + if err != nil { + // Preserve caller cancellation as a Run error. Once SendCommand has + // succeeded, all other errors describe this instance only. + if ctxErr := groupCtx.Err(); ctxErr != nil { + return trace.Wrap(ctxErr) + } + + log.WarnContext(ctx, + "Failed to collect SSM installation result", + "error", err, + ) + result = failedSSMCommandInstallationResult(req, output.Command.CommandId, metadata) + } + + if err := si.ReportSSMInstallationResultFunc(groupCtx, result); err != nil { + // Reporting is also instance-local. Returning this error would cause + // discovery to mark every instance in the batch as failed again. + log.ErrorContext(ctx, + "Failed to report SSM installation result", + "error", err, + ) + } + return nil }) } return trace.Wrap(g.Wait()) @@ -323,6 +380,17 @@ func invalidSSMInstanceInstallationResult(req SSMRunRequest, instanceMetadata in } } +func failedSSMCommandInstallationResult(req SSMRunRequest, commandID *string, instanceMetadata instanceMetadata) *SSMInstallationResult { + result := invalidSSMInstanceInstallationResult( + req, + instanceMetadata, + "Failed to collect the SSM command result.", + usertasks.AutoDiscoverEC2IssueSSMInvocationFailure, + ) + result.SSMRunEvent.CommandID = aws.ToString(commandID) + return result +} + func (si *SSMInstaller) emitInvalidInstanceEvents(ctx context.Context, req SSMRunRequest, instanceIDsState *instanceIDsSSMState) error { var errs []error for instanceID, instanceName := range instanceIDsState.missing { @@ -452,11 +520,11 @@ func (si *SSMInstaller) describeSSMAgentState(ctx context.Context, req SSMRunReq return ret, nil } -func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, commandID *string, instanceMetadata instanceMetadata) error { +func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, commandID *string, instanceMetadata instanceMetadata, waiterMaxWait time.Duration) (*SSMInstallationResult, error) { err := si.getWaiter(req.SSM).Wait(ctx, &ssm.GetCommandInvocationInput{ CommandId: commandID, InstanceId: aws.String(instanceMetadata.InstanceID), - }, waitTimeout) + }, waiterMaxWait) switch { case err == nil: // Command executed successfully. @@ -472,9 +540,14 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com // The command might still be Pending or InProgress. // Ignoring this error allows us to report that status back to the user. + case errors.Is(err, context.DeadlineExceeded) && ctx.Err() == nil: + // The waiter can return a wrapped context deadline error when its + // internal timeout expires. The outer context remains available for + // collecting and reporting this instance's current invocation state. + default: // For every other unknown error, return the error. - return trace.Wrap(err) + return nil, trace.Wrap(err) } invocationSteps, err := si.getInvocationSteps(ctx, req, commandID, aws.String(instanceMetadata.InstanceID)) @@ -491,7 +564,7 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com invocationSteps = awslib.EC2DiscoverySSMDocumentSteps case err != nil: - return trace.Wrap(err) + return nil, trace.Wrap(err) } for i, step := range invocationSteps { @@ -504,22 +577,22 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com // If that's the case, emit an event with the overall invocation result (ignoring specific steps' stdout and stderr). outcome, err = si.getCommandStepOutcome(ctx, "" /*no step*/, req, commandID, instanceMetadata) if err != nil { - return trace.Wrap(err) + return nil, trace.Wrap(err) } - return trace.Wrap(si.reportCommandStepOutcome(ctx, req, instanceMetadata, outcome)) + return commandStepInstallationResult(req, instanceMetadata, outcome), nil } - return trace.Wrap(err) + return nil, trace.Wrap(err) } // Emit an event for the first failed step or for the latest step. lastStep := i+1 == len(invocationSteps) if outcome.SSMRunEvent.Metadata.Code != libevents.SSMRunSuccessCode || lastStep { - return trace.Wrap(si.reportCommandStepOutcome(ctx, req, instanceMetadata, outcome)) + return commandStepInstallationResult(req, instanceMetadata, outcome), nil } } - return nil + return nil, trace.BadParameter("no command invocation steps were found") } func (si *SSMInstaller) getInvocationSteps(ctx context.Context, req SSMRunRequest, commandID, instanceID *string) ([]string, error) { @@ -579,8 +652,8 @@ type commandStepOutcome struct { IssueType string } -func (si *SSMInstaller) reportCommandStepOutcome(ctx context.Context, req SSMRunRequest, instanceMetadata instanceMetadata, outcome commandStepOutcome) error { - return si.ReportSSMInstallationResultFunc(ctx, &SSMInstallationResult{ +func commandStepInstallationResult(req SSMRunRequest, instanceMetadata instanceMetadata, outcome commandStepOutcome) *SSMInstallationResult { + return &SSMInstallationResult{ SSMRunEvent: outcome.SSMRunEvent, IntegrationName: req.IntegrationName, DiscoveryConfigName: req.DiscoveryConfigName, @@ -588,7 +661,7 @@ func (si *SSMInstaller) reportCommandStepOutcome(ctx context.Context, req SSMRun SSMDocumentName: req.DocumentName, InstallerScript: req.InstallerScriptName(), InstanceName: instanceMetadata.InstanceName, - }) + } } func (si *SSMInstaller) getCommandStepOutcome(ctx context.Context, step string, req SSMRunRequest, commandID *string, instanceMetadata instanceMetadata) (commandStepOutcome, error) { diff --git a/lib/srv/server/ssm_install_test.go b/lib/srv/server/ssm_install_test.go index bd942e8cc9d4..1cee09c5a8f6 100644 --- a/lib/srv/server/ssm_install_test.go +++ b/lib/srv/server/ssm_install_test.go @@ -23,7 +23,9 @@ import ( "fmt" "slices" "strings" + "sync" "testing" + "testing/synctest" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -32,6 +34,7 @@ import ( "github.com/gravitational/trace" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/usertasks" libevent "github.com/gravitational/teleport/lib/events" @@ -41,11 +44,18 @@ import ( type mockSSMClient struct { SSMClient - commandOutput *ssm.SendCommandOutput - waiterTimeout bool - commandInvokeOutput map[string]*ssm.GetCommandInvocationOutput - describeOutput *ssm.DescribeInstanceInformationOutput - listCommandInvocations *ssm.ListCommandInvocationsOutput + commandOutput *ssm.SendCommandOutput + waiterTimeout bool + waiterMaxWaitError error + waitForContext bool + waitForContextInstance map[string]bool + waiterCompletionDelay time.Duration + waiterStarted chan struct{} + getCommandWaitForContextInstance map[string]bool + commandInvokeOutput map[string]*ssm.GetCommandInvocationOutput + commandInvokeByInstance map[string]*ssm.GetCommandInvocationOutput + describeOutput *ssm.DescribeInstanceInformationOutput + listCommandInvocations *ssm.ListCommandInvocationsOutput } func TestTrimToRecentTail(t *testing.T) { @@ -118,6 +128,46 @@ const docWithoutSSHDConfigPathParam = "ssmdocument-without-sshdConfigPath-param" const docWithoutEnvParam = "ssmdocument-without-env-param" +func TestGetAWSInstallTimeout(t *testing.T) { + tests := []struct { + name string + value string + want time.Duration + }{ + { + name: "unset uses default", + want: defaultAWSInstallTimeout, + }, + { + name: "valid duration", + value: "3m45s", + want: 3*time.Minute + 45*time.Second, + }, + { + name: "invalid duration uses default", + value: "invalid", + want: defaultAWSInstallTimeout, + }, + { + name: "duration is clamped to minimum", + value: "1s", + want: minAWSInstallTimeout, + }, + { + name: "duration is clamped to maximum", + value: "2h", + want: maxAWSInstallTimeout, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(awsInstallTimeoutEnvVar, tt.value) + require.Equal(t, tt.want, getAWSInstallTimeout()) + }) + } +} + func (sm *mockSSMClient) SendCommand(_ context.Context, input *ssm.SendCommandInput, _ ...func(*ssm.Options)) (*ssm.SendCommandOutput, error) { if _, hasExtraParam := input.Parameters["sshdConfigPath"]; hasExtraParam && aws.ToString(input.DocumentName) == docWithoutSSHDConfigPathParam { return nil, fmt.Errorf("InvalidParameters: document %s does not support parameters", docWithoutSSHDConfigPathParam) @@ -130,7 +180,14 @@ func (sm *mockSSMClient) SendCommand(_ context.Context, input *ssm.SendCommandIn return sm.commandOutput, nil } -func (sm *mockSSMClient) GetCommandInvocation(_ context.Context, input *ssm.GetCommandInvocationInput, _ ...func(*ssm.Options)) (*ssm.GetCommandInvocationOutput, error) { +func (sm *mockSSMClient) GetCommandInvocation(ctx context.Context, input *ssm.GetCommandInvocationInput, _ ...func(*ssm.Options)) (*ssm.GetCommandInvocationOutput, error) { + if sm.getCommandWaitForContextInstance[aws.ToString(input.InstanceId)] { + <-ctx.Done() + return nil, ctx.Err() + } + if result, found := sm.commandInvokeByInstance[aws.ToString(input.InstanceId)]; found { + return result, nil + } if stepResult, found := sm.commandInvokeOutput[aws.ToString(input.PluginName)]; found { return stepResult, nil } @@ -152,6 +209,31 @@ func (sm *mockSSMClient) ListCommandInvocations(_ context.Context, input *ssm.Li } func (sm *mockSSMClient) Wait(ctx context.Context, params *ssm.GetCommandInvocationInput, maxWaitDur time.Duration, optFns ...func(*ssm.CommandExecutedWaiterOptions)) error { + if sm.waiterStarted != nil { + sm.waiterStarted <- struct{}{} + } + + if sm.waiterCompletionDelay > 0 { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(sm.waiterCompletionDelay): + return nil + } + } + + if sm.waitForContext || sm.waitForContextInstance[aws.ToString(params.InstanceId)] { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(maxWaitDur): + if sm.waiterMaxWaitError != nil { + return sm.waiterMaxWaitError + } + return trace.Errorf(waiterTimedOutErrorMessage) + } + } + if sm.waiterTimeout { return trace.Errorf(waiterTimedOutErrorMessage) } @@ -169,11 +251,375 @@ func (sm *mockSSMClient) Wait(ctx context.Context, params *ssm.GetCommandInvocat return nil } +func TestSSMInstallerHungCommandReportsPerInstanceResults(t *testing.T) { + const installTimeout = 3*time.Minute + 45*time.Second + t.Setenv(awsInstallTimeoutEnvVar, installTimeout.String()) + + synctest.Test(t, func(t *testing.T) { + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waitForContextInstance: map[string]bool{ + "instance-hung": true, + }, + commandInvokeByInstance: map[string]*ssm.GetCommandInvocationOutput{ + "instance-success": { + Status: ssmtypes.CommandInvocationStatusSuccess, + ResponseCode: 0, + }, + "instance-hung": { + Status: ssmtypes.CommandInvocationStatusInProgress, + ResponseCode: -1, + }, + }, + } + + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: []EC2Instance{ + {InstanceID: "instance-success"}, + {InstanceID: "instance-hung"}, + }, + }) + require.NoError(t, err) + + require.Len(t, installationResults.installations, 2) + resultsByInstance := make(map[string]*SSMInstallationResult, 2) + for _, result := range installationResults.installations { + resultsByInstance[result.SSMRunEvent.InstanceID] = result + } + require.Equal(t, string(ssmtypes.CommandInvocationStatusSuccess), resultsByInstance["instance-success"].SSMRunEvent.Status) + require.Equal(t, libevent.SSMRunSuccessCode, resultsByInstance["instance-success"].SSMRunEvent.Metadata.Code) + require.Equal(t, string(ssmtypes.CommandInvocationStatusInProgress), resultsByInstance["instance-hung"].SSMRunEvent.Status) + require.Equal(t, libevent.SSMRunFailCode, resultsByInstance["instance-hung"].SSMRunEvent.Metadata.Code) + }) +} + +func TestSSMInstallerBatchCompletesWithinSingleWaitPeriod(t *testing.T) { + const installTimeout = 3*time.Minute + 45*time.Second + t.Setenv(awsInstallTimeoutEnvVar, installTimeout.String()) + + synctest.Test(t, func(t *testing.T) { + start := time.Now() + instances := make([]EC2Instance, 0, awsEC2APIChunkSize) + commandInvokeByInstance := make(map[string]*ssm.GetCommandInvocationOutput, awsEC2APIChunkSize) + for i := range awsEC2APIChunkSize { + instanceID := fmt.Sprintf("instance-%d", i) + instances = append(instances, EC2Instance{InstanceID: instanceID}) + commandInvokeByInstance[instanceID] = &ssm.GetCommandInvocationOutput{ + Status: ssmtypes.CommandInvocationStatusInProgress, + ResponseCode: -1, + } + } + + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waitForContext: true, + commandInvokeByInstance: commandInvokeByInstance, + } + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: instances, + }) + require.NoError(t, err) + require.Equal(t, installTimeout+awsSSMResultGracePeriod, time.Since(start)) + require.Len(t, installationResults.installations, awsEC2APIChunkSize) + + for _, result := range installationResults.installations { + require.Equal(t, string(ssmtypes.CommandInvocationStatusInProgress), result.SSMRunEvent.Status) + require.Equal(t, libevent.SSMRunFailCode, result.SSMRunEvent.Metadata.Code) + } + }) +} + +func TestSSMInstallerCollectionTimeoutIsInstanceLocal(t *testing.T) { + const installTimeout = 3*time.Minute + 45*time.Second + const blockedInstanceID = "instance-blocked" + t.Setenv(awsInstallTimeoutEnvVar, installTimeout.String()) + + synctest.Test(t, func(t *testing.T) { + start := time.Now() + instances := make([]EC2Instance, 0, awsEC2APIChunkSize) + commandInvokeByInstance := make(map[string]*ssm.GetCommandInvocationOutput, awsEC2APIChunkSize) + for i := range awsEC2APIChunkSize - 1 { + instanceID := fmt.Sprintf("instance-%d", i) + instances = append(instances, EC2Instance{InstanceID: instanceID}) + commandInvokeByInstance[instanceID] = &ssm.GetCommandInvocationOutput{ + Status: ssmtypes.CommandInvocationStatusSuccess, + ResponseCode: 0, + } + } + instances = append(instances, EC2Instance{InstanceID: blockedInstanceID}) + + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waitForContext: true, + getCommandWaitForContextInstance: map[string]bool{ + blockedInstanceID: true, + }, + commandInvokeByInstance: commandInvokeByInstance, + } + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: instances, + }) + require.NoError(t, err) + require.Equal(t, installTimeout+2*awsSSMResultGracePeriod, time.Since(start)) + + require.Len(t, installationResults.installations, awsEC2APIChunkSize) + resultsByInstance := make(map[string]*SSMInstallationResult, awsEC2APIChunkSize) + for _, result := range installationResults.installations { + instanceID := result.SSMRunEvent.InstanceID + require.NotContains(t, resultsByInstance, instanceID) + resultsByInstance[instanceID] = result + } + + for _, instance := range instances { + result := resultsByInstance[instance.InstanceID] + require.NotNil(t, result) + if instance.InstanceID == blockedInstanceID { + require.Equal(t, libevent.SSMRunFailCode, result.SSMRunEvent.Metadata.Code) + require.Equal(t, int64(-1), result.SSMRunEvent.ExitCode) + require.Equal(t, usertasks.AutoDiscoverEC2IssueSSMInvocationFailure, result.IssueType) + continue + } + + require.Equal(t, libevent.SSMRunSuccessCode, result.SSMRunEvent.Metadata.Code) + require.Equal(t, string(ssmtypes.CommandInvocationStatusSuccess), result.SSMRunEvent.Status) + } + }) +} + +func TestSSMInstallerReportErrorIsInstanceLocal(t *testing.T) { + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + commandInvokeByInstance: map[string]*ssm.GetCommandInvocationOutput{ + "instance-report-error": { + Status: ssmtypes.CommandInvocationStatusSuccess, + ResponseCode: 0, + }, + "instance-success": { + Status: ssmtypes.CommandInvocationStatusSuccess, + ResponseCode: 0, + }, + }, + } + + var mu sync.Mutex + reportedInstances := make(map[string]int, 2) + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: func(_ context.Context, result *SSMInstallationResult) error { + mu.Lock() + defer mu.Unlock() + reportedInstances[result.SSMRunEvent.InstanceID]++ + if result.SSMRunEvent.InstanceID == "instance-report-error" { + return fmt.Errorf("report failed") + } + return nil + }, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: []EC2Instance{ + {InstanceID: "instance-report-error"}, + {InstanceID: "instance-success"}, + }, + }) + require.NoError(t, err) + require.Equal(t, map[string]int{ + "instance-report-error": 1, + "instance-success": 1, + }, reportedInstances) +} + +func TestSSMInstallerWaitsThroughStatusHeadroom(t *testing.T) { + const installTimeout = 3*time.Minute + 45*time.Second + const terminalStatusDelay = installTimeout + 30*time.Second + t.Setenv(awsInstallTimeoutEnvVar, installTimeout.String()) + + synctest.Test(t, func(t *testing.T) { + start := time.Now() + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waiterCompletionDelay: terminalStatusDelay, + commandInvokeByInstance: map[string]*ssm.GetCommandInvocationOutput{ + "instance-id": { + Status: ssmtypes.CommandInvocationStatusTimedOut, + ResponseCode: -1, + }, + }, + } + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: []EC2Instance{ + {InstanceID: "instance-id"}, + }, + }) + require.NoError(t, err) + require.Equal(t, terminalStatusDelay, time.Since(start)) + + require.Len(t, installationResults.installations, 1) + require.Equal(t, string(ssmtypes.CommandInvocationStatusTimedOut), installationResults.installations[0].SSMRunEvent.Status) + require.Equal(t, libevent.SSMRunFailCode, installationResults.installations[0].SSMRunEvent.Metadata.Code) + }) +} + +func TestSSMInstallerCallerCancellation(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waitForContext: true, + waiterStarted: make(chan struct{}, 1), + } + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + errCh := make(chan error, 1) + go func() { + errCh <- inst.Run(ctx, SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: []EC2Instance{ + {InstanceID: "instance-id"}, + }, + }) + }() + + <-client.waiterStarted + cancel() + require.ErrorIs(t, <-errCh, context.Canceled) + require.Empty(t, installationResults.installations) + }) +} + +func TestSSMInstallerHonorsTimeoutAboveLegacyWaiterLimit(t *testing.T) { + t.Setenv(awsInstallTimeoutEnvVar, maxAWSInstallTimeout.String()) + + synctest.Test(t, func(t *testing.T) { + start := time.Now() + client := &mockSSMClient{ + commandOutput: &ssm.SendCommandOutput{ + Command: &ssmtypes.Command{ + CommandId: aws.String("command-id"), + }, + }, + waitForContext: true, + waiterMaxWaitError: fmt.Errorf("request canceled while waiting: %w", context.DeadlineExceeded), + commandInvokeByInstance: map[string]*ssm.GetCommandInvocationOutput{ + "instance-id": { + Status: ssmtypes.CommandInvocationStatusInProgress, + ResponseCode: -1, + }, + }, + } + installationResults := &mockInstallationResults{} + inst, err := NewSSMInstaller(SSMInstallerConfig{ + ReportSSMInstallationResultFunc: installationResults.ReportInstallationResult, + getWaiter: func(SSMClient) commandWaiter { + return client + }, + }) + require.NoError(t, err) + + err = inst.Run(t.Context(), SSMRunRequest{ + DocumentName: types.AWSSSMDocumentRunShellScript, + SSM: client, + Instances: []EC2Instance{ + {InstanceID: "instance-id"}, + }, + }) + require.NoError(t, err) + require.Equal(t, maxAWSInstallTimeout+awsSSMResultGracePeriod, time.Since(start)) + + require.Len(t, installationResults.installations, 1) + require.Equal(t, string(ssmtypes.CommandInvocationStatusInProgress), installationResults.installations[0].SSMRunEvent.Status) + }) +} + type mockInstallationResults struct { + mu sync.Mutex installations []*SSMInstallationResult } func (me *mockInstallationResults) ReportInstallationResult(ctx context.Context, result *SSMInstallationResult) error { + me.mu.Lock() + defer me.mu.Unlock() me.installations = append(me.installations, result) return nil } From 05419f28b6d31b2c76bef339eaaf6ed5d4ccf4aa Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Thu, 13 Aug 2026 14:29:20 +0000 Subject: [PATCH 291/347] support `where` RBAC on application session recordings (#69630) * support `where` RBAC on application session recordings application session recordings are backed by AppSessionChunk events, but those events were never wired into the session recording RBAC path, so `where` clauses on `kind: session` rules could not match them. Fixes https://github.com/gravitational/teleport/issues/67775 Signed-off-by: Tiago Silva * fix unit test * handle feedback --------- Signed-off-by: Tiago Silva --- .../teleport/legacy/types/events/events.proto | 4 + api/types/events/api.go | 2 + api/types/events/events.pb.go | 3215 +++++++++-------- .../pages/reference/access-controls/roles.mdx | 19 +- integration/appaccess/appaccess_test.go | 2 + integration/appaccess/pack.go | 8 +- lib/events/api.go | 2 + lib/services/parser.go | 4 +- lib/services/role_test.go | 117 + lib/services/session_recording.go | 17 + lib/services/session_recording_test.go | 35 + lib/srv/app/common/audit.go | 23 + lib/srv/app/server_test.go | 12 +- 13 files changed, 1868 insertions(+), 1592 deletions(-) diff --git a/api/proto/teleport/legacy/types/events/events.proto b/api/proto/teleport/legacy/types/events/events.proto index 8c3605241f10..d71db199ea3f 100644 --- a/api/proto/teleport/legacy/types/events/events.proto +++ b/api/proto/teleport/legacy/types/events/events.proto @@ -3279,6 +3279,10 @@ message AppSessionChunk { (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; + // Participants is the list of participants in a session. + // App sessions only have one participant but we keep it a repeated field + // to keep compatibility with other session end events. + repeated string Participants = 8 [(gogoproto.jsontag) = "participants,omitempty"]; } // AppSessionRequest is an HTTP request and response. diff --git a/api/types/events/api.go b/api/types/events/api.go index 54307bd95fa4..51737032bdcb 100644 --- a/api/types/events/api.go +++ b/api/types/events/api.go @@ -126,4 +126,6 @@ const ( EventProtocolTDP = "tdp" // EventProtocolDB specifies database as a type of captured protocol EventProtocolDB = "db" + // EventProtocolApp specifies app as a type of captured protocol + EventProtocolApp = "app" ) diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 878645aa0d87..6495bc32522e 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -5658,7 +5658,11 @@ type AppSessionChunk struct { // application log chunk. SessionChunkID string `protobuf:"bytes,6,opt,name=SessionChunkID,proto3" json:"session_chunk_id"` // App is a common application resource metadata. - AppMetadata `protobuf:"bytes,7,opt,name=App,proto3,embedded=App" json:""` + AppMetadata `protobuf:"bytes,7,opt,name=App,proto3,embedded=App" json:""` + // Participants is the list of participants in a session. + // App sessions only have one participant but we keep it a repeated field + // to keep compatibility with other session end events. + Participants []string `protobuf:"bytes,8,rep,name=Participants,proto3" json:"participants,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -21886,1591 +21890,1591 @@ func init() { } var fileDescriptor_007ba1c3d6266d56 = []byte{ - // 25344 bytes of a gzipped FileDescriptorProto + // 25336 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xfd, 0x79, 0x7c, 0x24, 0xc9, 0x75, 0x18, 0x08, 0xa3, 0x0e, 0x14, 0x80, 0x87, 0x3b, 0xfa, 0xaa, 0xe9, 0xe9, 0x9e, 0x9a, 0xa9, 0x99, 0x69, 0x76, 0x93, 0x33, 0xdd, 0x33, 0x3d, 0x07, 0x39, 0x9c, 0x21, 0x67, 0x0a, 0xd7, 0xa0, 0xba, 0x81, 0x46, 0x4d, 0x16, 0xba, 0x7b, 0x48, 0x8a, 0x2c, 0x25, 0x2a, 0x03, 0x40, 0x0e, 0xaa, - 0x32, 0x8b, 0x99, 0x59, 0x8d, 0xc6, 0xf8, 0xf3, 0x67, 0xdd, 0x12, 0x25, 0x92, 0xa2, 0x49, 0xcb, - 0x94, 0x25, 0xcb, 0x2b, 0xc9, 0xb2, 0x57, 0x96, 0x64, 0x69, 0xe5, 0xf5, 0x5a, 0xb7, 0x2c, 0x89, - 0xf6, 0x8a, 0xba, 0x0f, 0x5b, 0x5a, 0xad, 0x24, 0x83, 0x5a, 0xd9, 0xb2, 0xbc, 0x58, 0xdb, 0xab, - 0xd5, 0x61, 0xc9, 0xb2, 0xa5, 0xdd, 0x5f, 0xbc, 0x88, 0xcc, 0x8c, 0x8c, 0xcc, 0x2a, 0x9c, 0x23, - 0x34, 0xa6, 0xf1, 0x4f, 0x37, 0xea, 0xbd, 0x17, 0x2f, 0x33, 0x5f, 0xbc, 0x88, 0x78, 0x11, 0xf1, - 0x0e, 0xb8, 0xe4, 0xd1, 0x06, 0x6d, 0xd9, 0x8e, 0x77, 0xa5, 0x41, 0x57, 0xf4, 0xfa, 0xc6, 0x15, - 0x6f, 0xa3, 0x45, 0xdd, 0x2b, 0xf4, 0x0e, 0xb5, 0x3c, 0xff, 0xbf, 0xcb, 0x2d, 0xc7, 0xf6, 0x6c, - 0x92, 0xe3, 0xbf, 0xce, 0x9e, 0x5c, 0xb1, 0x57, 0x6c, 0x04, 0x5d, 0x61, 0x7f, 0x71, 0xec, 0xd9, - 0x73, 0x2b, 0xb6, 0xbd, 0xd2, 0xa0, 0x57, 0xf0, 0xd7, 0x52, 0x7b, 0xf9, 0x8a, 0xeb, 0x39, 0xed, - 0xba, 0x27, 0xb0, 0x05, 0x15, 0xeb, 0x99, 0x4d, 0xea, 0x7a, 0x7a, 0xb3, 0x25, 0x08, 0x1e, 0x52, - 0x09, 0xd6, 0x1d, 0xbd, 0xd5, 0xa2, 0x8e, 0x78, 0xf8, 0xd9, 0x77, 0x04, 0xef, 0xa9, 0xd7, 0xeb, - 0xd4, 0x75, 0x1b, 0xa6, 0xeb, 0x5d, 0xb9, 0xf3, 0xb4, 0xf4, 0x4b, 0x10, 0x3e, 0x92, 0xfc, 0x41, - 0xf8, 0xaf, 0x20, 0x79, 0x32, 0x99, 0xc4, 0x7f, 0xa2, 0xf2, 0xe8, 0xe2, 0xa7, 0xd3, 0xd0, 0x3f, - 0x4f, 0x3d, 0xdd, 0xd0, 0x3d, 0x9d, 0x9c, 0x83, 0xde, 0xb2, 0x65, 0xd0, 0xbb, 0xf9, 0xd4, 0xc3, - 0xa9, 0x8b, 0x99, 0x89, 0xdc, 0xd6, 0x66, 0x21, 0x4d, 0x4d, 0x8d, 0x03, 0xc9, 0x79, 0xc8, 0x2e, - 0x6e, 0xb4, 0x68, 0x3e, 0xfd, 0x70, 0xea, 0xe2, 0xc0, 0xc4, 0xc0, 0xd6, 0x66, 0xa1, 0x17, 0x85, - 0xa6, 0x21, 0x98, 0x3c, 0x02, 0xe9, 0xf2, 0x54, 0x3e, 0x83, 0xc8, 0xf1, 0xad, 0xcd, 0xc2, 0x70, - 0xdb, 0x34, 0x9e, 0xb0, 0x9b, 0xa6, 0x47, 0x9b, 0x2d, 0x6f, 0x43, 0x4b, 0x97, 0xa7, 0xc8, 0x05, - 0xc8, 0x4e, 0xda, 0x06, 0xcd, 0x67, 0x91, 0x88, 0x6c, 0x6d, 0x16, 0x46, 0xea, 0xb6, 0x41, 0x25, - 0x2a, 0xc4, 0x93, 0x57, 0x20, 0xbb, 0x68, 0x36, 0x69, 0xbe, 0xf7, 0xe1, 0xd4, 0xc5, 0xc1, 0xab, - 0x67, 0x2f, 0x73, 0xf1, 0x5d, 0xf6, 0xc5, 0x77, 0x79, 0xd1, 0x97, 0xef, 0xc4, 0xd8, 0xe7, 0x37, - 0x0b, 0x3d, 0x5b, 0x9b, 0x85, 0x2c, 0x13, 0xf9, 0xa7, 0xbe, 0x50, 0x48, 0x69, 0xd8, 0x92, 0xbc, - 0x04, 0x83, 0x93, 0x8d, 0xb6, 0xeb, 0x51, 0xe7, 0x86, 0xde, 0xa4, 0xf9, 0x1c, 0x3e, 0xf0, 0xec, - 0xd6, 0x66, 0xe1, 0x74, 0x9d, 0x83, 0x6b, 0x96, 0xde, 0x94, 0x1f, 0x2c, 0x93, 0x17, 0x7f, 0x30, - 0x05, 0xa3, 0x55, 0xea, 0xba, 0xa6, 0x6d, 0x05, 0xb2, 0x79, 0x1c, 0x06, 0x04, 0xa8, 0x3c, 0x85, - 0xf2, 0x19, 0x98, 0xe8, 0xdb, 0xda, 0x2c, 0x64, 0x5c, 0xd3, 0xd0, 0x42, 0x0c, 0x79, 0x0a, 0xfa, - 0x6e, 0x9b, 0xde, 0xea, 0xfc, 0x4c, 0x49, 0xc8, 0xe9, 0xf4, 0xd6, 0x66, 0x81, 0xac, 0x9b, 0xde, - 0x6a, 0xad, 0xb9, 0xac, 0x4b, 0x0f, 0xf4, 0xc9, 0xc8, 0x1c, 0x8c, 0x55, 0x1c, 0xf3, 0x8e, 0xee, - 0xd1, 0xeb, 0x74, 0xa3, 0x62, 0x37, 0xcc, 0xfa, 0x86, 0x90, 0xe2, 0xc3, 0x5b, 0x9b, 0x85, 0x73, - 0x2d, 0x8e, 0xab, 0xad, 0xd1, 0x8d, 0x5a, 0x0b, 0xb1, 0x12, 0x93, 0x58, 0xcb, 0xe2, 0x2f, 0x01, - 0x0c, 0xdd, 0x74, 0xa9, 0x13, 0xbc, 0xf7, 0x05, 0xc8, 0xb2, 0xdf, 0xe2, 0x95, 0x51, 0xe6, 0x6d, - 0x97, 0x3a, 0xb2, 0xcc, 0x19, 0x9e, 0x5c, 0x82, 0xde, 0x39, 0x7b, 0xc5, 0xb4, 0xc4, 0x6b, 0x9f, - 0xd8, 0xda, 0x2c, 0x8c, 0x36, 0x18, 0x40, 0xa2, 0xe4, 0x14, 0xe4, 0xfd, 0x30, 0x54, 0x6e, 0x32, - 0x1d, 0xb2, 0x2d, 0xdd, 0xb3, 0x1d, 0xf1, 0xb6, 0x28, 0x5d, 0x53, 0x82, 0x4b, 0x0d, 0x23, 0xf4, - 0xe4, 0xbd, 0x00, 0xa5, 0xdb, 0x55, 0xcd, 0x6e, 0xd0, 0x92, 0x76, 0x43, 0x28, 0x03, 0xb6, 0xd6, - 0xd7, 0xdd, 0x9a, 0x63, 0x37, 0x68, 0x4d, 0x77, 0xe4, 0xc7, 0x4a, 0xd4, 0x64, 0x1a, 0x46, 0x4a, - 0x38, 0x2a, 0x34, 0xfa, 0xd1, 0x36, 0x75, 0x3d, 0x37, 0xdf, 0xfb, 0x70, 0xe6, 0xe2, 0xc0, 0xc4, - 0xf9, 0xad, 0xcd, 0xc2, 0x03, 0x7c, 0xbc, 0xd4, 0x1c, 0x81, 0x92, 0x58, 0x28, 0x8d, 0xc8, 0x04, - 0x0c, 0x97, 0xde, 0x6c, 0x3b, 0xb4, 0x6c, 0x50, 0xcb, 0x33, 0xbd, 0x0d, 0xa1, 0x21, 0xe7, 0xb6, - 0x36, 0x0b, 0x79, 0x9d, 0x21, 0x6a, 0xa6, 0xc0, 0x48, 0x4c, 0xa2, 0x4d, 0xc8, 0x02, 0x8c, 0xbf, - 0x3a, 0x59, 0xa9, 0x52, 0xe7, 0x8e, 0x59, 0xa7, 0xa5, 0x7a, 0xdd, 0x6e, 0x5b, 0x5e, 0xbe, 0x0f, - 0xf9, 0x3c, 0xb2, 0xb5, 0x59, 0x38, 0xbf, 0x52, 0x6f, 0xd5, 0x5c, 0x8e, 0xad, 0xe9, 0x1c, 0x2d, - 0x31, 0x8b, 0xb7, 0x25, 0x1f, 0x84, 0xe1, 0x45, 0x87, 0x69, 0xa1, 0x31, 0x45, 0x19, 0x3c, 0xdf, - 0x8f, 0xfa, 0x7f, 0xfa, 0xb2, 0x98, 0xa9, 0x38, 0xd4, 0xef, 0x59, 0xfe, 0xb2, 0x1e, 0x6f, 0x50, - 0x33, 0x10, 0x27, 0xbf, 0x6c, 0x84, 0x15, 0xa1, 0x90, 0x67, 0x1f, 0x6f, 0x3a, 0xd4, 0x88, 0x69, - 0xdb, 0x00, 0xbe, 0xf3, 0xa5, 0xad, 0xcd, 0xc2, 0xe3, 0x8e, 0xa0, 0xa9, 0x75, 0x55, 0xbb, 0x8e, - 0xac, 0xc8, 0x34, 0xf4, 0x33, 0x6d, 0xba, 0x6e, 0x5a, 0x46, 0x1e, 0x1e, 0x4e, 0x5d, 0x1c, 0xb9, - 0x3a, 0xe6, 0xbf, 0xbd, 0x0f, 0x9f, 0x38, 0xb3, 0xb5, 0x59, 0x38, 0xc1, 0x74, 0xb0, 0xb6, 0x66, - 0x5a, 0xf2, 0x14, 0x11, 0x34, 0x65, 0xa3, 0x68, 0xc2, 0xf6, 0x70, 0xe8, 0x0e, 0x86, 0xa3, 0x68, - 0xc9, 0xf6, 0xd4, 0x61, 0xeb, 0x93, 0x91, 0x49, 0x18, 0x9e, 0xb0, 0xbd, 0xb2, 0xe5, 0x7a, 0xba, - 0x55, 0xa7, 0xe5, 0xa9, 0xfc, 0x10, 0xb6, 0x43, 0xb5, 0x60, 0xed, 0x4c, 0x81, 0xa9, 0x45, 0x26, - 0xa5, 0x68, 0x1b, 0x32, 0x0f, 0xc0, 0x5e, 0x61, 0xc1, 0x31, 0xd9, 0x40, 0x18, 0xc6, 0xf7, 0x27, - 0xf2, 0xfb, 0x73, 0xcc, 0xc4, 0x03, 0x5b, 0x9b, 0x85, 0x53, 0xf8, 0x05, 0x36, 0x02, 0x64, 0x5d, - 0x0d, 0xc9, 0xc8, 0xf3, 0x30, 0xc0, 0x7e, 0x31, 0xd5, 0x75, 0xf3, 0x23, 0xa8, 0xa6, 0xf9, 0xad, - 0xcd, 0xc2, 0x49, 0x6c, 0xc9, 0xf4, 0x5c, 0xd6, 0xd0, 0x90, 0x94, 0x7c, 0x26, 0xc5, 0xdf, 0x63, - 0xd1, 0xd1, 0x4d, 0xcf, 0xcd, 0x8f, 0xa2, 0x16, 0x9c, 0xba, 0x1c, 0xcc, 0xdc, 0x73, 0xfa, 0x12, - 0x6d, 0xdc, 0xd2, 0x1b, 0x6d, 0xea, 0x4e, 0x7c, 0x11, 0x9b, 0x00, 0x7f, 0x73, 0xb3, 0xf0, 0xe2, - 0x8a, 0xe9, 0xad, 0xb6, 0x97, 0x2e, 0xd7, 0xed, 0xe6, 0x95, 0x15, 0x47, 0xbf, 0x63, 0x7a, 0xba, - 0x67, 0xda, 0x96, 0xde, 0xb8, 0x12, 0xae, 0x2d, 0x2d, 0x53, 0x59, 0x0c, 0x2e, 0xf3, 0x27, 0x04, - 0x5f, 0xe3, 0xe1, 0x4f, 0xf5, 0x6b, 0x38, 0x11, 0x29, 0xc3, 0x28, 0xfb, 0x25, 0x4f, 0xab, 0x63, - 0x28, 0xe3, 0xc2, 0xd6, 0x66, 0xe1, 0x41, 0x6c, 0xdf, 0x61, 0x6e, 0x55, 0xdb, 0x91, 0x27, 0x61, - 0xc0, 0xad, 0xdb, 0x2d, 0x5a, 0x6b, 0x99, 0x56, 0x7e, 0x1c, 0x3f, 0x2f, 0x50, 0x93, 0x2a, 0x43, - 0x54, 0x4c, 0x4b, 0xeb, 0x77, 0xc5, 0x5f, 0xe4, 0x49, 0xc8, 0x4d, 0x50, 0xbd, 0x59, 0x9e, 0xca, - 0x13, 0x7c, 0xe0, 0xa9, 0xad, 0xcd, 0xc2, 0xf8, 0x12, 0xd5, 0x9b, 0xd1, 0xce, 0x14, 0x44, 0x64, - 0x1e, 0xc6, 0x26, 0x6c, 0x0f, 0xf9, 0x2c, 0x2c, 0x8b, 0xbe, 0x3c, 0x11, 0x0e, 0x4b, 0xa6, 0x0d, - 0xfc, 0xe9, 0xf6, 0x72, 0xbc, 0xff, 0x62, 0x4d, 0x8b, 0x7f, 0x9a, 0x85, 0x11, 0x36, 0x50, 0xa5, - 0x39, 0xb5, 0xc4, 0x96, 0x07, 0x06, 0x61, 0x5f, 0xe3, 0xb6, 0xf4, 0x3a, 0x15, 0xd3, 0x2b, 0xaa, - 0xb6, 0xe5, 0x03, 0x65, 0x11, 0x28, 0xf4, 0xe4, 0x12, 0xf4, 0x73, 0x50, 0x79, 0x4a, 0xcc, 0xb8, - 0xc3, 0x5b, 0x9b, 0x85, 0x01, 0x17, 0x61, 0x35, 0xd3, 0xd0, 0x02, 0x34, 0x9b, 0xf2, 0xf8, 0xdf, - 0xb3, 0xb6, 0xeb, 0x31, 0xe6, 0x62, 0xc2, 0x45, 0xdd, 0x16, 0x0d, 0x56, 0x05, 0x4a, 0x9e, 0xf2, - 0xa2, 0x8d, 0xc8, 0x0b, 0x00, 0x1c, 0x52, 0x32, 0x0c, 0x47, 0xcc, 0xba, 0xa8, 0xc8, 0x82, 0x85, - 0x6e, 0x18, 0xf2, 0x94, 0x2d, 0x11, 0x93, 0x26, 0x0c, 0xf1, 0x5f, 0xa8, 0x79, 0x7c, 0xca, 0x1d, - 0xbc, 0x7a, 0x31, 0xe8, 0xb2, 0x88, 0x74, 0x2e, 0xcb, 0xa4, 0xd3, 0x96, 0xe7, 0x6c, 0x4c, 0x14, - 0xc4, 0x2a, 0x7d, 0x46, 0x3c, 0xaa, 0x81, 0x38, 0x79, 0x7d, 0x90, 0xdb, 0xb0, 0xc5, 0x7b, 0xc6, - 0x76, 0xd6, 0x75, 0xc7, 0xa0, 0xc6, 0xc4, 0x86, 0xbc, 0x78, 0x2f, 0xfb, 0xe0, 0xda, 0x92, 0x3c, - 0x1f, 0xc9, 0xe4, 0x6c, 0x26, 0xe0, 0xdc, 0xaa, 0xed, 0x25, 0x9c, 0x87, 0xfa, 0x62, 0xd2, 0x72, - 0xdb, 0x4b, 0xea, 0xdc, 0x13, 0x6d, 0xc3, 0xd6, 0x07, 0x0e, 0xb8, 0x45, 0x1d, 0xb6, 0xb2, 0xe3, - 0x54, 0x2c, 0xd6, 0x07, 0xc1, 0xe4, 0x0e, 0xc7, 0xc4, 0x79, 0x88, 0x26, 0x67, 0x5f, 0x86, 0xf1, - 0x98, 0x28, 0xc8, 0x18, 0x64, 0xd6, 0xe8, 0x06, 0x57, 0x17, 0x8d, 0xfd, 0x49, 0x4e, 0x42, 0xef, - 0x1d, 0x36, 0x96, 0xb9, 0x1a, 0x68, 0xfc, 0xc7, 0x7b, 0xd3, 0xef, 0x49, 0x31, 0x33, 0x84, 0x4c, - 0xda, 0x96, 0x45, 0xeb, 0x9e, 0x6c, 0x89, 0x3c, 0x0f, 0x03, 0x73, 0x76, 0x5d, 0x6f, 0x60, 0x3f, - 0x72, 0xbd, 0xc3, 0x69, 0x85, 0x75, 0xe0, 0xe5, 0x06, 0xc3, 0xc8, 0xd3, 0x4a, 0x40, 0xca, 0x14, - 0x40, 0xa3, 0x4d, 0xdb, 0xa3, 0xd8, 0x30, 0x1d, 0x2a, 0x00, 0x36, 0x74, 0x10, 0x25, 0x2b, 0x40, - 0x48, 0x4c, 0xae, 0x40, 0x7f, 0x85, 0x19, 0x5f, 0x75, 0xbb, 0x21, 0x94, 0x0f, 0xed, 0x03, 0x34, - 0xc8, 0xe4, 0x09, 0xdc, 0x27, 0x2a, 0xce, 0xc2, 0xc8, 0x64, 0xc3, 0xa4, 0x96, 0x27, 0xbf, 0x35, - 0x9b, 0x06, 0x4a, 0x2b, 0xd4, 0xf2, 0xe4, 0xb7, 0xc6, 0x89, 0x43, 0x67, 0x50, 0x75, 0x32, 0x44, - 0xd2, 0xe2, 0x2f, 0x66, 0xe0, 0x81, 0xeb, 0xed, 0x25, 0xea, 0x58, 0xd4, 0xa3, 0xae, 0x98, 0x45, - 0x02, 0xae, 0x37, 0x60, 0x3c, 0x86, 0x14, 0xdc, 0xd1, 0x7a, 0x5a, 0x0b, 0x90, 0xfe, 0xe4, 0x24, - 0x2f, 0xc1, 0xb1, 0xa6, 0x64, 0x16, 0x46, 0x43, 0x20, 0x7b, 0x09, 0x37, 0x9f, 0xc6, 0x89, 0xfb, - 0xa1, 0xad, 0xcd, 0xc2, 0x59, 0x89, 0x1b, 0x7b, 0x6d, 0x59, 0x83, 0xd5, 0x66, 0xe4, 0x3a, 0x8c, - 0x85, 0xa0, 0x57, 0x1d, 0xbb, 0xdd, 0x72, 0xf3, 0x19, 0x64, 0x85, 0xf3, 0xa5, 0xc4, 0x6a, 0x05, - 0x91, 0xf2, 0x1c, 0xa4, 0x36, 0x24, 0x5f, 0x91, 0x92, 0xb9, 0x89, 0x51, 0x98, 0xc5, 0x51, 0xf8, - 0x6e, 0x7f, 0x14, 0x76, 0x14, 0xd2, 0x65, 0xb5, 0xa5, 0x18, 0x94, 0xca, 0x6b, 0xc4, 0x06, 0x65, - 0xec, 0x89, 0x67, 0x27, 0xe1, 0x54, 0x22, 0xaf, 0x5d, 0x69, 0xf5, 0xef, 0x65, 0x64, 0x2e, 0x15, - 0xdb, 0x08, 0x3a, 0x73, 0x41, 0xee, 0xcc, 0x8a, 0x6d, 0xe0, 0x1a, 0x93, 0x0a, 0x67, 0x6e, 0xe9, - 0x65, 0x5b, 0xb6, 0xa1, 0xae, 0x32, 0xf1, 0xb6, 0xe4, 0x23, 0x70, 0x3a, 0x06, 0xe4, 0xd3, 0x35, - 0xd7, 0xfe, 0x0b, 0x5b, 0x9b, 0x85, 0x62, 0x02, 0x57, 0x75, 0xf6, 0xee, 0xc0, 0x85, 0xe8, 0x70, - 0x46, 0x92, 0xba, 0x6d, 0x79, 0xba, 0x69, 0x89, 0xa5, 0x91, 0x8f, 0x92, 0x77, 0x6c, 0x6d, 0x16, - 0x1e, 0x95, 0x75, 0xd0, 0xa7, 0x51, 0x5f, 0xbe, 0x13, 0x1f, 0x62, 0x40, 0x3e, 0x01, 0x55, 0x6e, - 0xea, 0x2b, 0xfe, 0x36, 0xea, 0xe2, 0xd6, 0x66, 0xe1, 0xb1, 0xc4, 0x67, 0x98, 0x8c, 0x4a, 0x36, - 0xdb, 0x3a, 0x71, 0x22, 0x1a, 0x90, 0x10, 0x77, 0xc3, 0x36, 0x28, 0x7e, 0x43, 0x2f, 0xf2, 0x2f, - 0x6e, 0x6d, 0x16, 0x1e, 0x92, 0xf8, 0x5b, 0xb6, 0x41, 0xd5, 0xd7, 0x4f, 0x68, 0x5d, 0xfc, 0x9d, - 0x0c, 0x9c, 0xbe, 0x6d, 0x5a, 0x86, 0xbd, 0xee, 0x4e, 0x51, 0x77, 0xcd, 0xb3, 0x5b, 0x41, 0x47, - 0x7f, 0x08, 0x4e, 0x45, 0x31, 0xc2, 0x10, 0x16, 0x9d, 0xfd, 0xf8, 0xd6, 0x66, 0xe1, 0x91, 0x75, - 0x4e, 0x50, 0x33, 0x38, 0x85, 0x6f, 0x49, 0x4b, 0x0f, 0x4d, 0xe6, 0xc1, 0x56, 0x0f, 0x01, 0x91, - 0xe6, 0x39, 0x5c, 0x3d, 0x7c, 0x56, 0xca, 0x4a, 0x27, 0x93, 0x93, 0x67, 0x21, 0x37, 0x65, 0x37, - 0x75, 0xd3, 0x12, 0x3d, 0x88, 0x33, 0x7e, 0xf0, 0x2e, 0x88, 0x91, 0x4d, 0x0e, 0x4e, 0xcb, 0x9e, - 0x29, 0x5e, 0x06, 0xf7, 0x5a, 0xd2, 0x96, 0xc6, 0x6f, 0xaa, 0xec, 0xb9, 0x64, 0x72, 0xd2, 0x86, - 0x61, 0xf1, 0x0a, 0x91, 0xf5, 0xf5, 0x69, 0x7f, 0x64, 0x27, 0x4b, 0xf1, 0x72, 0xa4, 0x0d, 0x1f, - 0xd3, 0xf8, 0xb6, 0xfe, 0x67, 0xc6, 0x06, 0x74, 0xf4, 0x29, 0x67, 0x5f, 0x01, 0x12, 0x67, 0xb1, - 0xbb, 0x05, 0x2a, 0x03, 0x0f, 0x55, 0x4b, 0xf3, 0x73, 0x65, 0xc3, 0xdf, 0xca, 0x54, 0x1c, 0xfb, - 0x8e, 0x69, 0x48, 0x13, 0xf4, 0x12, 0x9c, 0x51, 0x50, 0xd3, 0xb8, 0x7b, 0x0a, 0x36, 0xd1, 0xa8, - 0xbe, 0xfe, 0x36, 0xa9, 0x25, 0x68, 0x6a, 0x7c, 0x8b, 0x15, 0xb5, 0xef, 0x3a, 0x31, 0x62, 0xc3, - 0x50, 0x41, 0x55, 0x57, 0x6d, 0xc7, 0xab, 0xb7, 0x3d, 0xd1, 0xfb, 0x38, 0x0c, 0x63, 0xcf, 0x70, - 0x05, 0x51, 0x97, 0x47, 0xf8, 0x7c, 0xc8, 0xc7, 0x52, 0x30, 0x56, 0xf2, 0x3c, 0xc7, 0x5c, 0x6a, - 0x7b, 0x74, 0x5e, 0x6f, 0xb5, 0x4c, 0x6b, 0x05, 0xa7, 0xf3, 0xc1, 0xab, 0x2f, 0x05, 0x66, 0x50, - 0x57, 0x49, 0x5c, 0x56, 0x9b, 0x4b, 0xb3, 0xb0, 0xee, 0xa3, 0x6a, 0x4d, 0x8e, 0x93, 0x67, 0x61, - 0xb5, 0x1d, 0x9b, 0x85, 0x13, 0x79, 0xed, 0xaa, 0xeb, 0x3e, 0x9d, 0x81, 0x73, 0x0b, 0x6b, 0x9e, - 0xae, 0x51, 0xd7, 0x6e, 0x3b, 0x75, 0xea, 0xde, 0x6c, 0x19, 0xba, 0x47, 0xc3, 0xc9, 0xb8, 0x00, - 0xbd, 0x25, 0xc3, 0xa0, 0x06, 0xb2, 0xeb, 0xe5, 0xc7, 0x3d, 0x3a, 0x03, 0x68, 0x1c, 0x4e, 0x1e, - 0x87, 0x3e, 0xd1, 0x06, 0xb9, 0xf7, 0x4e, 0x0c, 0x6e, 0x6d, 0x16, 0xfa, 0xda, 0x1c, 0xa4, 0xf9, - 0x38, 0x46, 0x36, 0x45, 0x1b, 0x94, 0x91, 0x65, 0x42, 0x32, 0x83, 0x83, 0x34, 0x1f, 0x47, 0x5e, - 0x83, 0x11, 0x64, 0x1b, 0xbc, 0x8f, 0x58, 0xde, 0x4e, 0xfa, 0xd2, 0x95, 0x5f, 0x96, 0x5b, 0x1f, - 0xf8, 0x36, 0x35, 0xc7, 0x6f, 0xa0, 0x29, 0x0c, 0xc8, 0x6d, 0x18, 0x13, 0x2f, 0x11, 0x32, 0xed, - 0xed, 0xc2, 0x14, 0xb7, 0x15, 0xe2, 0xfd, 0x25, 0xb6, 0x31, 0x26, 0x8c, 0xb1, 0x78, 0xed, 0x90, - 0x71, 0x6e, 0x3b, 0xc6, 0xe2, 0x8b, 0x65, 0xc6, 0x2a, 0x93, 0xe2, 0x07, 0x60, 0x48, 0x6e, 0x48, - 0x4e, 0xe3, 0x91, 0x1a, 0x1f, 0x27, 0x78, 0x18, 0x67, 0x1a, 0x78, 0x8e, 0xf6, 0x34, 0x4e, 0x71, - 0x75, 0xc7, 0x6c, 0x31, 0xc3, 0x50, 0x28, 0xf9, 0xe8, 0xd6, 0x66, 0x61, 0xd0, 0x08, 0xc1, 0x9a, - 0x4c, 0x53, 0xfc, 0x2f, 0x29, 0x38, 0xcd, 0x78, 0x97, 0x5c, 0xd7, 0x5c, 0xb1, 0x9a, 0xb2, 0x65, - 0xf6, 0x04, 0xe4, 0xaa, 0xf8, 0x3c, 0xf1, 0xa4, 0x93, 0x5b, 0x9b, 0x85, 0x31, 0xfe, 0x06, 0xf2, - 0x54, 0xc7, 0x69, 0x82, 0xf3, 0xa4, 0xf4, 0x36, 0xe7, 0x49, 0x6c, 0xd7, 0xe2, 0xe9, 0x8e, 0x67, - 0x5a, 0x2b, 0x55, 0x4f, 0xf7, 0xda, 0x6e, 0x64, 0xd7, 0x22, 0x30, 0x35, 0x17, 0x51, 0x91, 0x5d, - 0x4b, 0xa4, 0x11, 0x79, 0x19, 0x86, 0xa6, 0x2d, 0x23, 0x64, 0xc2, 0xa7, 0xd6, 0x07, 0xd9, 0x66, - 0x82, 0x22, 0x3c, 0xce, 0x22, 0xd2, 0xa0, 0xf8, 0x1d, 0x69, 0xc8, 0xf3, 0xc3, 0x9f, 0x39, 0xd3, - 0xf5, 0xe6, 0x69, 0x73, 0x49, 0x9a, 0x9d, 0x66, 0xfc, 0xd3, 0x24, 0x86, 0x93, 0xcc, 0x0d, 0xb4, - 0xf6, 0xc4, 0x69, 0x52, 0xc3, 0x74, 0x63, 0xc7, 0x0e, 0x4a, 0x2b, 0x52, 0x86, 0x3e, 0xce, 0x99, - 0x9b, 0x8b, 0x83, 0x57, 0xf3, 0xbe, 0x22, 0xa8, 0x8f, 0xe6, 0xca, 0xd0, 0xe4, 0xc4, 0xf2, 0x41, - 0x86, 0x68, 0xcf, 0xb6, 0xd9, 0x61, 0x9b, 0x45, 0xd3, 0x6b, 0xf8, 0xeb, 0x3c, 0x9f, 0x29, 0xa4, - 0x77, 0xf2, 0x18, 0x52, 0x36, 0x41, 0x95, 0x76, 0xe4, 0x9d, 0x30, 0x2e, 0xd3, 0xe3, 0xa6, 0x97, - 0x2f, 0xea, 0xda, 0xa8, 0x1e, 0xd0, 0xe2, 0x6e, 0xb7, 0xf8, 0xb9, 0x0c, 0x8c, 0xa9, 0xef, 0x4a, - 0x6e, 0x43, 0xff, 0x35, 0xdb, 0xb4, 0xa8, 0xb1, 0x60, 0xa1, 0x60, 0xba, 0x9f, 0xc5, 0xfa, 0xbb, - 0xbc, 0x13, 0x6f, 0x60, 0x9b, 0x9a, 0xbc, 0x37, 0xc2, 0xa3, 0xd9, 0x80, 0x19, 0xf9, 0x20, 0x0c, - 0xb0, 0xdd, 0xc5, 0x1d, 0xe4, 0x9c, 0xde, 0x96, 0xf3, 0xc3, 0x82, 0xf3, 0x49, 0x87, 0x37, 0x8a, - 0xb3, 0x0e, 0xd9, 0x31, 0x75, 0xd6, 0xa8, 0xee, 0xda, 0xfe, 0x0a, 0x8e, 0xea, 0xec, 0x20, 0x44, - 0x56, 0x67, 0x4e, 0xc3, 0x36, 0x45, 0xfc, 0x63, 0xb1, 0xf7, 0xa5, 0x5d, 0x31, 0xef, 0x22, 0xb5, - 0xe3, 0x25, 0x62, 0x62, 0xc1, 0xa8, 0xe8, 0xc7, 0x55, 0xb3, 0x85, 0xfb, 0x49, 0x14, 0xee, 0xc8, - 0xd5, 0x0b, 0x97, 0xfd, 0x33, 0x97, 0xcb, 0xd2, 0x09, 0xfe, 0x9d, 0xa7, 0x2f, 0xcf, 0x07, 0xe4, - 0x78, 0x10, 0x86, 0x43, 0x41, 0x61, 0x21, 0x2b, 0x59, 0x33, 0x42, 0x4e, 0x1e, 0x81, 0x21, 0xf1, - 0x52, 0xbc, 0x27, 0x71, 0x5f, 0xac, 0x0d, 0x72, 0x18, 0xef, 0xc5, 0xaf, 0x4a, 0xc3, 0x93, 0x61, - 0x2f, 0x6a, 0xf4, 0x8e, 0x49, 0xd7, 0xc3, 0x87, 0x8a, 0x53, 0x3b, 0x36, 0xf8, 0xdd, 0xc9, 0x55, - 0xdd, 0x5a, 0xa1, 0x06, 0xb9, 0x04, 0xbd, 0xfc, 0x7c, 0x2a, 0x85, 0x7b, 0x13, 0x9c, 0x58, 0xd5, - 0xa3, 0x29, 0x4e, 0x41, 0x6c, 0xc8, 0x89, 0x13, 0x29, 0xae, 0xe3, 0xa5, 0xb8, 0x8e, 0xef, 0xe0, - 0x89, 0xe2, 0xcc, 0x89, 0xaf, 0x7e, 0xd8, 0x37, 0xb1, 0x53, 0x27, 0xf1, 0x98, 0xb3, 0x2f, 0xc0, - 0xa0, 0x44, 0xbc, 0xab, 0xe5, 0xed, 0x87, 0x7b, 0xe5, 0x51, 0xef, 0xbf, 0x96, 0x18, 0xf5, 0x57, - 0xd8, 0x68, 0x75, 0x5d, 0x66, 0x42, 0xa7, 0xc2, 0x03, 0xa5, 0x26, 0x07, 0x45, 0xc7, 0x24, 0x82, - 0xc8, 0x33, 0xd0, 0xcf, 0x59, 0x04, 0x87, 0x35, 0x78, 0xd0, 0xe3, 0x20, 0x2c, 0x6a, 0xa4, 0x04, - 0x84, 0xe4, 0xbb, 0x52, 0x70, 0xbe, 0xab, 0x24, 0x50, 0x3f, 0x07, 0xaf, 0x3e, 0xb7, 0x27, 0x31, - 0x4e, 0x3c, 0xb9, 0xb5, 0x59, 0xb8, 0x24, 0x29, 0x8f, 0x23, 0xd1, 0xd4, 0xea, 0x9c, 0x48, 0x7a, - 0xaf, 0xee, 0xaf, 0xc2, 0x76, 0x4a, 0xfc, 0xa1, 0x33, 0x78, 0x78, 0x6e, 0xd5, 0x37, 0xfc, 0x97, - 0xcc, 0x86, 0x3b, 0x25, 0xf1, 0xbd, 0xcb, 0x3e, 0x49, 0xc2, 0x63, 0x3a, 0x70, 0x21, 0x75, 0x38, - 0xc3, 0x31, 0x53, 0xfa, 0xc6, 0xc2, 0xf2, 0xbc, 0x6d, 0x79, 0xab, 0xfe, 0x03, 0x7a, 0xe5, 0xd3, - 0x67, 0x7c, 0x80, 0xa1, 0x6f, 0xd4, 0xec, 0xe5, 0x5a, 0x93, 0x51, 0x25, 0x3c, 0xa3, 0x13, 0x27, - 0xb6, 0xe4, 0x88, 0x69, 0xc0, 0x9f, 0x8c, 0x73, 0xe1, 0xdd, 0x80, 0x3f, 0x75, 0xc4, 0xa7, 0x5e, - 0xa5, 0x51, 0xd2, 0x0c, 0xdc, 0xb7, 0xc7, 0x19, 0xf8, 0x59, 0x38, 0x8d, 0x63, 0x95, 0x2d, 0xfb, - 0x91, 0xc7, 0xe7, 0xfb, 0xd9, 0x9b, 0x69, 0x27, 0x39, 0x36, 0xfa, 0x02, 0xc5, 0x32, 0x0c, 0xcd, - 0xd9, 0xf5, 0xb5, 0x40, 0x5f, 0x5f, 0x80, 0xdc, 0xa2, 0xee, 0xac, 0x50, 0x0f, 0x3b, 0x63, 0xf0, - 0xea, 0xf8, 0x65, 0x7e, 0xe1, 0xc7, 0x88, 0x38, 0x62, 0x62, 0x44, 0xcc, 0x90, 0x39, 0x0f, 0x7f, - 0x6b, 0xa2, 0x41, 0xf1, 0xc7, 0x73, 0x30, 0x24, 0x2e, 0xa7, 0x70, 0x61, 0x25, 0xef, 0x0d, 0xaf, - 0xfb, 0xc4, 0x94, 0x1e, 0x9c, 0xbc, 0x06, 0x17, 0x0b, 0x43, 0x8c, 0xd9, 0x2f, 0x6f, 0x16, 0x52, - 0x5b, 0x9b, 0x85, 0x1e, 0xad, 0x5f, 0x3a, 0xc2, 0x09, 0x97, 0x7e, 0xc9, 0xd6, 0x91, 0xaf, 0x9b, - 0x94, 0xb6, 0xdc, 0x14, 0x78, 0x19, 0xfa, 0xc4, 0x3b, 0x08, 0x95, 0x3f, 0x13, 0x9e, 0x1c, 0x46, - 0x2e, 0xd9, 0x94, 0xd6, 0x7e, 0x2b, 0xf2, 0x12, 0xe4, 0xf8, 0x49, 0x9a, 0x10, 0xc0, 0xe9, 0xe4, - 0x93, 0x47, 0xa5, 0xb9, 0x68, 0x43, 0x66, 0x01, 0xc2, 0x53, 0xb4, 0xe0, 0x4e, 0x51, 0x70, 0x88, - 0x9f, 0xaf, 0x29, 0x5c, 0xa4, 0xb6, 0xe4, 0x79, 0x18, 0x5a, 0xa4, 0x4e, 0xd3, 0xb4, 0xf4, 0x46, - 0xd5, 0x7c, 0xd3, 0xbf, 0x56, 0x44, 0x1b, 0xc8, 0x35, 0xdf, 0x94, 0x35, 0x21, 0x42, 0x47, 0x3e, - 0x9c, 0x74, 0x4a, 0xd5, 0x87, 0x2f, 0xf2, 0xc8, 0xb6, 0xc7, 0x37, 0xca, 0xfb, 0x24, 0x1c, 0x5a, - 0xbd, 0x06, 0xc3, 0x91, 0x03, 0x0a, 0x71, 0x6f, 0x74, 0x3e, 0xce, 0x5a, 0x3a, 0x6d, 0x51, 0xd8, - 0x46, 0x39, 0xb0, 0xa1, 0x54, 0xb6, 0x4c, 0xcf, 0xd4, 0x1b, 0x93, 0x76, 0xb3, 0xa9, 0x5b, 0x46, - 0x7e, 0x20, 0x1c, 0x4a, 0x26, 0xc7, 0xd4, 0xea, 0x1c, 0x25, 0x0f, 0xa5, 0x68, 0x23, 0x72, 0x1d, - 0xc6, 0x44, 0x1f, 0x6a, 0xb4, 0x6e, 0x3b, 0xcc, 0x2c, 0xc3, 0x6b, 0x21, 0x31, 0x96, 0x5c, 0x8e, - 0xab, 0x39, 0x3e, 0x52, 0xde, 0xf7, 0xa8, 0x0d, 0xd9, 0xb4, 0x5d, 0xb6, 0xee, 0x98, 0x6c, 0x27, - 0x31, 0x84, 0x2f, 0x83, 0xd3, 0xb6, 0xc9, 0x41, 0xf2, 0xb4, 0x2d, 0xa8, 0x24, 0x4b, 0x60, 0x78, - 0x7b, 0x4b, 0xe0, 0x5a, 0xb6, 0x7f, 0x70, 0x6c, 0x48, 0xbd, 0x28, 0x2c, 0xfe, 0xa3, 0x0c, 0x0c, - 0x8a, 0x37, 0x61, 0xd6, 0xcb, 0xf1, 0xf8, 0xd9, 0xcf, 0xf8, 0x49, 0x1c, 0x07, 0xb9, 0x83, 0x1a, - 0x07, 0xc5, 0x8f, 0xa7, 0x83, 0xc9, 0xae, 0xe2, 0x98, 0xd6, 0xfe, 0x26, 0xbb, 0x0b, 0x00, 0x93, - 0xab, 0x6d, 0x6b, 0x8d, 0x3b, 0x44, 0xa4, 0x43, 0x87, 0x88, 0xba, 0xa9, 0x49, 0x18, 0x72, 0x1e, - 0xb2, 0x53, 0x8c, 0x3f, 0xeb, 0x99, 0xa1, 0x89, 0x81, 0xcf, 0x73, 0x4e, 0xa9, 0x27, 0x35, 0x04, - 0xb3, 0x6d, 0xf4, 0xc4, 0x86, 0x47, 0xf9, 0xc6, 0x25, 0xc3, 0xb7, 0xd1, 0x4b, 0x0c, 0xa0, 0x71, - 0x38, 0x79, 0x16, 0xc6, 0xa7, 0x68, 0x43, 0xdf, 0x98, 0x37, 0x1b, 0x0d, 0xd3, 0xa5, 0x75, 0xdb, - 0x32, 0x5c, 0x14, 0xb2, 0x78, 0x5c, 0xd3, 0xd5, 0xe2, 0x04, 0xa4, 0x08, 0xb9, 0x85, 0xe5, 0x65, - 0x97, 0x7a, 0x28, 0xbe, 0xcc, 0x04, 0xb0, 0xb9, 0xdf, 0x46, 0x88, 0x26, 0x30, 0xc5, 0xdf, 0x4f, - 0xb1, 0x7d, 0x2a, 0x1e, 0xf0, 0x84, 0x83, 0x68, 0x3f, 0x22, 0xb9, 0x14, 0xda, 0x4d, 0x69, 0xfc, - 0xda, 0x51, 0xf1, 0xb5, 0x7d, 0xc2, 0x76, 0x0a, 0x2d, 0xa6, 0xc4, 0xaf, 0xca, 0x6c, 0xf7, 0x55, - 0xcf, 0xc3, 0xe0, 0xe2, 0x54, 0x65, 0xc2, 0x7f, 0x48, 0x16, 0x1f, 0x72, 0x52, 0x3c, 0x64, 0xc8, - 0x33, 0x5a, 0x4b, 0x35, 0xff, 0x49, 0x32, 0x61, 0xf1, 0x3b, 0x32, 0x70, 0x46, 0x7c, 0xe9, 0x64, - 0xc3, 0x6c, 0x2d, 0xd9, 0xba, 0x63, 0x68, 0xb4, 0x4e, 0xcd, 0x3b, 0xf4, 0x68, 0x0e, 0xd8, 0xe8, - 0x90, 0xcb, 0xee, 0x63, 0xc8, 0x5d, 0x8d, 0x9e, 0x86, 0x72, 0x63, 0x6b, 0x8c, 0x89, 0x53, 0x3e, - 0x0d, 0x8d, 0x9e, 0x81, 0x16, 0x21, 0x37, 0x47, 0xad, 0x15, 0x6f, 0x15, 0x95, 0xab, 0x97, 0x2b, - 0x57, 0x03, 0x21, 0x9a, 0xc0, 0x48, 0x7c, 0x71, 0xe3, 0xd4, 0x17, 0xe7, 0xcb, 0x76, 0x4e, 0x9a, - 0x4c, 0x54, 0xfc, 0xb6, 0x0c, 0x9c, 0x54, 0xbb, 0xa9, 0x4a, 0x2d, 0xe3, 0xb8, 0x8f, 0xee, 0x9d, - 0x3e, 0xfa, 0xce, 0x2c, 0x3c, 0xe8, 0x1f, 0xa8, 0xaf, 0xea, 0x0e, 0x35, 0xa6, 0x4c, 0x87, 0xd6, - 0x3d, 0xdb, 0xd9, 0x38, 0xc2, 0xf6, 0xe3, 0xc1, 0x75, 0xd5, 0xb3, 0x90, 0x13, 0x07, 0x51, 0x7c, - 0x1d, 0x1c, 0x09, 0xde, 0x04, 0xa1, 0xb1, 0x15, 0x94, 0x1f, 0x62, 0x29, 0x1d, 0x9c, 0xdb, 0x49, - 0x07, 0xbf, 0x07, 0x86, 0x03, 0xd1, 0x4b, 0xdd, 0x87, 0xc6, 0xa6, 0xe1, 0x23, 0x78, 0x07, 0x46, - 0x09, 0xf1, 0x69, 0x3e, 0xa0, 0x3c, 0x85, 0xc6, 0xe0, 0xb0, 0x78, 0x5a, 0xd0, 0xce, 0x34, 0x34, - 0x99, 0x48, 0x55, 0x95, 0x81, 0x9d, 0xa8, 0xca, 0xa7, 0x7b, 0xe1, 0x6c, 0xb2, 0xaa, 0x68, 0x54, - 0x37, 0x8e, 0x35, 0xe5, 0xed, 0xa9, 0x29, 0x8f, 0x40, 0xb6, 0xa2, 0x7b, 0xab, 0x42, 0x45, 0xd0, - 0x69, 0x65, 0xd9, 0x6c, 0xd0, 0x5a, 0x4b, 0xf7, 0x56, 0x35, 0x44, 0x49, 0x73, 0x13, 0x20, 0xc7, - 0xa4, 0xb9, 0x29, 0x34, 0x60, 0x06, 0x1f, 0x4e, 0x5d, 0xcc, 0x26, 0x19, 0x30, 0xaa, 0x52, 0x0e, - 0xed, 0x44, 0x29, 0x3f, 0xd3, 0xdb, 0x69, 0xfe, 0xba, 0xed, 0x98, 0x1e, 0x3d, 0xd6, 0xca, 0x63, - 0xad, 0x3c, 0x04, 0xad, 0xfc, 0xd5, 0x34, 0x0c, 0x07, 0xfb, 0xd9, 0x37, 0x68, 0xfd, 0x70, 0xd6, - 0xd1, 0x70, 0x1b, 0x98, 0xd9, 0xf7, 0x36, 0x70, 0x3f, 0x4a, 0x58, 0x0c, 0xf6, 0xe5, 0xdc, 0xd4, - 0x41, 0x29, 0xf3, 0x7d, 0x79, 0x70, 0x2e, 0xff, 0x08, 0xf4, 0xcd, 0xeb, 0x77, 0xcd, 0x66, 0xbb, - 0x29, 0x76, 0x38, 0xe8, 0x6c, 0xdd, 0xd4, 0xef, 0x6a, 0x3e, 0xbc, 0xf8, 0xaf, 0x53, 0x30, 0x22, - 0x84, 0x2a, 0x98, 0xef, 0x4b, 0xaa, 0xa1, 0x74, 0xd2, 0xfb, 0x96, 0x4e, 0x66, 0xef, 0xd2, 0x29, - 0xfe, 0xdd, 0x0c, 0xe4, 0x67, 0xcc, 0x06, 0x5d, 0x74, 0x74, 0xcb, 0x5d, 0xa6, 0x8e, 0x38, 0x8a, - 0x98, 0x66, 0xac, 0xf6, 0xf5, 0x81, 0xd2, 0x34, 0x94, 0xde, 0xd3, 0x34, 0xf4, 0x2e, 0x18, 0x10, - 0x2f, 0x13, 0x38, 0xfa, 0xe3, 0x48, 0x73, 0x7c, 0xa0, 0x16, 0xe2, 0x19, 0x71, 0xa9, 0xd5, 0x72, - 0xec, 0x3b, 0xd4, 0xe1, 0x77, 0xb9, 0x82, 0x58, 0xf7, 0x81, 0x5a, 0x88, 0x97, 0x38, 0x53, 0xdf, - 0xfe, 0x95, 0x39, 0x53, 0x47, 0x0b, 0xf1, 0xe4, 0x22, 0xf4, 0xcf, 0xd9, 0x75, 0x74, 0x6f, 0x15, - 0x53, 0xd1, 0xd0, 0xd6, 0x66, 0xa1, 0xbf, 0x21, 0x60, 0x5a, 0x80, 0x65, 0x94, 0x53, 0xf6, 0xba, - 0xd5, 0xb0, 0x75, 0xee, 0x05, 0xd8, 0xcf, 0x29, 0x0d, 0x01, 0xd3, 0x02, 0x2c, 0xa3, 0x64, 0x32, - 0x47, 0xef, 0xca, 0xfe, 0x90, 0xe7, 0xb2, 0x80, 0x69, 0x01, 0xb6, 0xf8, 0x7d, 0x59, 0xa6, 0xbd, - 0xae, 0xf9, 0xe6, 0x7d, 0xbf, 0x96, 0x84, 0x03, 0xa6, 0x77, 0x0f, 0x03, 0xe6, 0xbe, 0x39, 0x4b, - 0x2d, 0xfe, 0x69, 0x1f, 0x80, 0x90, 0xfe, 0xf4, 0xf1, 0x66, 0x77, 0x7f, 0x5a, 0x33, 0x05, 0xe3, - 0xd3, 0xd6, 0xaa, 0x6e, 0xd5, 0xa9, 0x11, 0x9e, 0x28, 0xe7, 0x70, 0x68, 0x63, 0x88, 0x00, 0x15, - 0xc8, 0xf0, 0x48, 0x59, 0x8b, 0x37, 0x20, 0x4f, 0xc3, 0x60, 0xd9, 0xf2, 0xa8, 0xa3, 0xd7, 0x3d, - 0xf3, 0x0e, 0x15, 0x53, 0x03, 0xfa, 0x4f, 0x98, 0x21, 0x58, 0x93, 0x69, 0xc8, 0xb3, 0x30, 0x54, - 0xd1, 0x1d, 0xcf, 0xac, 0x9b, 0x2d, 0xdd, 0xf2, 0xc4, 0xfd, 0x0d, 0x5f, 0xf6, 0x5b, 0x12, 0x5c, - 0x8b, 0x50, 0x91, 0x0f, 0xc3, 0x00, 0x6e, 0x9b, 0x31, 0x9a, 0x69, 0x60, 0xdb, 0x7b, 0xee, 0x47, - 0x43, 0x3f, 0x69, 0x7e, 0x30, 0x8e, 0x7e, 0x12, 0xea, 0x55, 0x77, 0xc0, 0x91, 0xbc, 0x0e, 0x7d, - 0xd3, 0x96, 0x81, 0xcc, 0x61, 0x5b, 0xe6, 0x45, 0xc1, 0xfc, 0x74, 0xc8, 0xdc, 0x6e, 0x29, 0xbc, - 0x7d, 0x76, 0xc9, 0xa3, 0x6c, 0xf0, 0xad, 0x1b, 0x65, 0x43, 0x6f, 0xc1, 0x8d, 0xc5, 0xf0, 0x41, - 0xdd, 0x58, 0x8c, 0xec, 0xf1, 0xc6, 0xa2, 0xf8, 0x83, 0x29, 0x18, 0x9c, 0xa8, 0xcc, 0x04, 0xc3, - 0xf7, 0x01, 0xc8, 0x54, 0x84, 0x43, 0x4f, 0x96, 0x1b, 0x34, 0x2d, 0xd3, 0xd0, 0x18, 0x8c, 0x5c, - 0x82, 0xfe, 0x49, 0x74, 0x04, 0x16, 0x57, 0xcc, 0x59, 0xbe, 0x00, 0xd6, 0x11, 0x86, 0xf1, 0x00, - 0x3e, 0x9a, 0x3c, 0x0e, 0x7d, 0x15, 0xc7, 0x5e, 0x71, 0xf4, 0xa6, 0x58, 0x84, 0xd1, 0xa3, 0xaa, - 0xc5, 0x41, 0x9a, 0x8f, 0x23, 0x2f, 0xc1, 0x48, 0xa9, 0x6d, 0x98, 0x5e, 0x18, 0xb5, 0x96, 0x45, - 0xbb, 0x17, 0x6f, 0x41, 0x74, 0x86, 0xa9, 0xf9, 0x5f, 0x63, 0x1a, 0x9a, 0x42, 0x5b, 0xfc, 0x4c, - 0xca, 0xdf, 0x29, 0xb0, 0xe7, 0x55, 0xdb, 0x78, 0x2b, 0x82, 0x6f, 0xde, 0xcf, 0x9f, 0xe7, 0x72, - 0x90, 0xe6, 0xe3, 0xc8, 0x25, 0xe8, 0x9d, 0x76, 0x1c, 0xdb, 0x91, 0x03, 0xc8, 0x28, 0x03, 0xc8, - 0x9e, 0x04, 0x48, 0x41, 0xde, 0x0d, 0x83, 0x7c, 0xca, 0xe2, 0xe7, 0xbc, 0x99, 0x6e, 0x97, 0xf0, - 0x32, 0x65, 0xf1, 0x73, 0x19, 0xc9, 0xe4, 0xe3, 0x1d, 0x76, 0x1f, 0x5e, 0xc8, 0x3c, 0x03, 0x99, - 0x89, 0xca, 0x8c, 0x98, 0x3f, 0x4f, 0xf8, 0x4d, 0x25, 0x45, 0x53, 0xda, 0x31, 0x6a, 0x72, 0x0e, - 0xb2, 0x15, 0xa6, 0x7c, 0x39, 0x54, 0xae, 0xfe, 0xad, 0xcd, 0x42, 0xb6, 0xc5, 0xb4, 0x0f, 0xa1, - 0x88, 0x65, 0xfb, 0x27, 0xbe, 0x49, 0xe3, 0xd8, 0x70, 0xeb, 0x74, 0x0e, 0xb2, 0x25, 0x67, 0xe5, - 0x8e, 0x98, 0xf4, 0x10, 0xab, 0x3b, 0x2b, 0x77, 0x34, 0x84, 0x92, 0x2b, 0x00, 0x1a, 0xf5, 0xda, - 0x8e, 0x85, 0xb1, 0x9d, 0x03, 0x78, 0x1c, 0x89, 0x93, 0xa9, 0x83, 0xd0, 0x5a, 0xdd, 0x36, 0xa8, - 0x26, 0x91, 0x14, 0xff, 0x41, 0x78, 0xa7, 0x36, 0x65, 0xba, 0x6b, 0xc7, 0x5d, 0xb8, 0x8b, 0x2e, - 0xd4, 0xc5, 0x89, 0x6f, 0xbc, 0x93, 0x0a, 0xd0, 0x3b, 0xd3, 0xd0, 0x57, 0x5c, 0xec, 0x43, 0xe1, - 0xb0, 0xb9, 0xcc, 0x00, 0x1a, 0x87, 0x2b, 0xfd, 0xd4, 0xbf, 0x7d, 0x3f, 0x7d, 0xb6, 0x37, 0x18, - 0x6d, 0x37, 0xa8, 0xb7, 0x6e, 0x3b, 0xc7, 0x5d, 0xb5, 0xd3, 0xae, 0xba, 0x00, 0x7d, 0x55, 0xa7, - 0x2e, 0x9d, 0x96, 0xe0, 0x76, 0xc2, 0x75, 0xea, 0xfc, 0xa4, 0xc4, 0x47, 0x32, 0xba, 0x29, 0xd7, - 0x43, 0xba, 0xbe, 0x90, 0xce, 0x70, 0x3d, 0x41, 0x27, 0x90, 0x82, 0xae, 0x62, 0x3b, 0x9e, 0xe8, - 0xb8, 0x80, 0xae, 0x65, 0x3b, 0x9e, 0xe6, 0x23, 0xc9, 0xbb, 0x00, 0x16, 0x27, 0x2b, 0x7e, 0xd0, - 0xd2, 0x40, 0xe8, 0x70, 0x2b, 0xa2, 0x95, 0x34, 0x09, 0x4d, 0x16, 0x61, 0x60, 0xa1, 0x45, 0x1d, - 0xbe, 0x93, 0xe2, 0xd1, 0x9a, 0xef, 0x50, 0x44, 0x2b, 0xfa, 0xfd, 0xb2, 0xf8, 0x3f, 0x20, 0xe7, - 0xab, 0x93, 0xed, 0xff, 0xd4, 0x42, 0x46, 0xe4, 0xdd, 0x90, 0x2b, 0x71, 0x33, 0x71, 0x10, 0x59, - 0x06, 0x22, 0xc3, 0x1d, 0x2c, 0x47, 0xf1, 0x2d, 0xbf, 0x8e, 0x7f, 0x6b, 0x82, 0xbc, 0x78, 0x09, - 0xc6, 0xd4, 0xc7, 0x90, 0x41, 0xe8, 0x9b, 0x5c, 0xb8, 0x71, 0x63, 0x7a, 0x72, 0x71, 0xac, 0x87, - 0xf4, 0x43, 0xb6, 0x3a, 0x7d, 0x63, 0x6a, 0x2c, 0x55, 0xfc, 0x1e, 0x69, 0x06, 0x61, 0xaa, 0x75, - 0x7c, 0x2b, 0xbf, 0xaf, 0xeb, 0xa7, 0x31, 0xbc, 0x8a, 0xc6, 0x03, 0x87, 0xa6, 0xe9, 0x79, 0xd4, - 0x10, 0xab, 0x04, 0x5e, 0xd5, 0x7a, 0x77, 0xb5, 0x18, 0x9e, 0x3c, 0x01, 0xc3, 0x08, 0x13, 0xb7, - 0xac, 0x7c, 0x7b, 0x2d, 0x1a, 0x38, 0x77, 0xb5, 0x28, 0xb2, 0xf8, 0x73, 0xe1, 0xc5, 0xfc, 0x1c, - 0xd5, 0x8f, 0xea, 0xa5, 0xec, 0x3d, 0xd2, 0x5f, 0xc5, 0x1f, 0xee, 0xe5, 0xa1, 0x74, 0x3c, 0x18, - 0xff, 0x30, 0x44, 0x19, 0x9e, 0x22, 0x67, 0x76, 0x71, 0x8a, 0xfc, 0x04, 0xe4, 0xe6, 0xa9, 0xb7, - 0x6a, 0xfb, 0x3e, 0x85, 0x68, 0x88, 0x36, 0x11, 0x22, 0xbb, 0xe3, 0x70, 0x1a, 0xb2, 0x06, 0xc4, - 0x8f, 0xb4, 0x0f, 0xa2, 0x1d, 0xfc, 0x53, 0xeb, 0x33, 0xb1, 0x6d, 0x4e, 0x15, 0xf3, 0x71, 0x60, - 0xac, 0xd2, 0xc9, 0x20, 0x9a, 0x42, 0x72, 0xf2, 0xfb, 0xf3, 0xcd, 0x42, 0x8e, 0xd3, 0x68, 0x09, - 0x6c, 0xc9, 0x6b, 0x30, 0x30, 0x3f, 0x53, 0x12, 0x51, 0xf7, 0xdc, 0x21, 0xe5, 0x81, 0x40, 0x8a, - 0x3e, 0x22, 0x10, 0x09, 0xc6, 0x2d, 0x36, 0x97, 0xf5, 0x78, 0xd0, 0x7d, 0xc8, 0x85, 0x69, 0x0b, - 0x8f, 0x80, 0x14, 0x87, 0x13, 0x81, 0xb6, 0x44, 0xe3, 0x22, 0x55, 0x59, 0x71, 0xac, 0xa2, 0x2d, - 0xfd, 0xfb, 0x18, 0xdd, 0x0b, 0x30, 0x5e, 0x6a, 0xb5, 0x1a, 0x26, 0x35, 0x50, 0x5f, 0xb4, 0x76, - 0x83, 0xba, 0xc2, 0x99, 0x0b, 0x83, 0xea, 0x74, 0x8e, 0xac, 0x61, 0xae, 0x87, 0x9a, 0xd3, 0x8e, - 0xba, 0xfe, 0xc6, 0xdb, 0x62, 0x6a, 0x0d, 0xce, 0xde, 0x76, 0xca, 0x53, 0xc2, 0x9d, 0x8b, 0xa7, - 0xd6, 0xf0, 0xc1, 0x51, 0x97, 0x58, 0x99, 0xbc, 0xf8, 0x3b, 0x69, 0x38, 0x3d, 0xe9, 0x50, 0xdd, - 0xa3, 0xf3, 0x33, 0xa5, 0x52, 0x1b, 0x9d, 0x37, 0x1b, 0x0d, 0x6a, 0xad, 0x1c, 0xce, 0xa4, 0xf0, - 0x22, 0x8c, 0x04, 0x2f, 0x80, 0x2e, 0xd4, 0x72, 0x78, 0x6b, 0xdd, 0xc7, 0x70, 0x87, 0x6b, 0x4d, - 0x21, 0x25, 0xd7, 0xe1, 0x44, 0x00, 0x29, 0x35, 0x1a, 0xf6, 0xba, 0x46, 0xdb, 0x2e, 0x77, 0x5b, - 0xe9, 0xe7, 0x4e, 0xe4, 0x21, 0x07, 0x9d, 0xe1, 0x6b, 0x0e, 0x23, 0xd0, 0x92, 0x5a, 0x91, 0x59, - 0xe8, 0x9f, 0x69, 0xd8, 0xeb, 0x98, 0x61, 0xa5, 0x37, 0xba, 0x70, 0xce, 0xcf, 0x94, 0x7c, 0x14, - 0x77, 0x3c, 0x5e, 0x66, 0xbc, 0xbc, 0x8d, 0x96, 0xac, 0x7a, 0x41, 0xeb, 0xe2, 0x56, 0x06, 0xce, - 0xdc, 0xd2, 0x1b, 0xa6, 0x11, 0x0a, 0x59, 0xa3, 0x6e, 0xcb, 0xb6, 0x5c, 0x7a, 0x84, 0x66, 0x8b, - 0xc8, 0x90, 0xcc, 0x1e, 0xc8, 0x90, 0x8c, 0x77, 0x76, 0xef, 0xbe, 0x3b, 0x3b, 0xb7, 0xef, 0xce, - 0xee, 0xdb, 0x57, 0x67, 0x7f, 0x36, 0x0d, 0x63, 0x7e, 0xbc, 0x90, 0x9c, 0xf3, 0x45, 0x0a, 0x66, - 0xc1, 0x33, 0x55, 0x25, 0x8e, 0x01, 0xf1, 0xa4, 0x0a, 0x7d, 0xd3, 0x77, 0x5b, 0xa6, 0x43, 0xdd, - 0x1d, 0x04, 0x61, 0x9c, 0x17, 0xe7, 0x47, 0xe3, 0x94, 0x37, 0x89, 0x1d, 0x1d, 0x71, 0x30, 0x06, - 0x7a, 0xf3, 0x88, 0xa9, 0x09, 0x3f, 0x91, 0x0d, 0x0f, 0xf4, 0x16, 0x91, 0x55, 0x91, 0xc8, 0xfd, - 0x90, 0x94, 0x3c, 0x0a, 0x99, 0xc5, 0xc5, 0x39, 0xb1, 0x36, 0x60, 0x02, 0x21, 0xcf, 0x93, 0x23, - 0xd9, 0x19, 0x96, 0x5c, 0x82, 0x5e, 0x57, 0xed, 0x39, 0x04, 0xc8, 0x87, 0x0c, 0x08, 0x28, 0xfe, - 0x76, 0x9a, 0x67, 0xd1, 0xe0, 0xb3, 0xcd, 0xa1, 0x68, 0xfe, 0x04, 0xf4, 0xfb, 0x7d, 0x23, 0x74, - 0x3f, 0x88, 0x0b, 0x52, 0xfb, 0x4c, 0x7d, 0x76, 0x10, 0x03, 0x56, 0xf0, 0x03, 0x34, 0xf8, 0x1d, - 0x0a, 0x6e, 0xeb, 0x30, 0x40, 0xc3, 0x0f, 0xcb, 0x78, 0x17, 0x0c, 0x04, 0x13, 0xac, 0x7c, 0x77, - 0x12, 0xcc, 0xc6, 0x5a, 0x88, 0x57, 0xd6, 0x95, 0xdc, 0x3e, 0xac, 0x10, 0x5f, 0xbc, 0xbc, 0x03, - 0x8f, 0xc5, 0x7b, 0xc0, 0xe2, 0xfd, 0xa4, 0x10, 0x2f, 0x8f, 0x11, 0x3c, 0xb2, 0xe2, 0x3d, 0xb0, - 0x7b, 0x83, 0xe2, 0xaf, 0xa6, 0x80, 0xb0, 0xd7, 0xaa, 0xe8, 0xae, 0xbb, 0x6e, 0x3b, 0x06, 0x0f, - 0xfa, 0x38, 0x14, 0xc1, 0x1c, 0xdc, 0x5d, 0xef, 0xd7, 0x02, 0x9c, 0x88, 0x38, 0x9c, 0x1f, 0xf1, - 0xc9, 0xea, 0x52, 0x74, 0x34, 0x75, 0x8b, 0x26, 0x7b, 0x4c, 0xbe, 0x4c, 0xee, 0x8d, 0x84, 0xb8, - 0x4a, 0xb7, 0xc8, 0x4f, 0xc2, 0x90, 0xf8, 0xc1, 0xcc, 0x02, 0xff, 0x96, 0x10, 0x47, 0xa9, 0xcb, - 0x00, 0x5a, 0x04, 0x4d, 0x9e, 0x83, 0x01, 0x36, 0x60, 0x56, 0x30, 0x2d, 0x59, 0x5f, 0x18, 0xa9, - 0x65, 0xf8, 0x40, 0x79, 0xe9, 0x09, 0x28, 0xa5, 0x40, 0x81, 0xfe, 0x1d, 0x84, 0x0c, 0x7e, 0x04, - 0x06, 0x4b, 0x96, 0x65, 0xf3, 0x04, 0x4b, 0xae, 0xb8, 0xd6, 0xe9, 0xb8, 0x25, 0x79, 0x14, 0x33, - 0xac, 0x84, 0xf4, 0x89, 0x7b, 0x12, 0x99, 0x21, 0xb9, 0xea, 0x47, 0x9b, 0x51, 0x47, 0x58, 0xd7, - 0x78, 0xb5, 0xe5, 0x08, 0x58, 0x3c, 0xd8, 0x0c, 0x3b, 0x6f, 0xb8, 0xe2, 0xd8, 0x2d, 0xdb, 0xa5, - 0x06, 0x17, 0xd4, 0x60, 0x98, 0xbd, 0xa0, 0x25, 0x10, 0x18, 0x29, 0x1b, 0x49, 0x11, 0x16, 0x69, - 0x42, 0x96, 0xe1, 0xa4, 0x7f, 0xc9, 0x1e, 0xc4, 0x24, 0x97, 0xa7, 0x5c, 0x0c, 0xb6, 0x18, 0x0c, - 0xf3, 0x60, 0x85, 0xa8, 0x89, 0x87, 0xfc, 0x2b, 0x25, 0x3f, 0xa8, 0xb9, 0x66, 0x1a, 0x72, 0x57, - 0x27, 0xf2, 0x23, 0x5f, 0x0c, 0x83, 0xf3, 0xfa, 0xdd, 0xa9, 0xb6, 0x38, 0x78, 0x1a, 0xde, 0xf9, - 0xcd, 0x55, 0x53, 0xbf, 0x5b, 0x33, 0x44, 0x3b, 0xc5, 0xfc, 0x90, 0x59, 0x92, 0x1a, 0x9c, 0xae, - 0x38, 0x76, 0xd3, 0xf6, 0xa8, 0xa1, 0x84, 0xf7, 0x8e, 0x86, 0xf9, 0x00, 0x5a, 0x82, 0xa2, 0xd6, - 0x25, 0xce, 0xb7, 0x03, 0x1b, 0xd2, 0x84, 0xd1, 0x92, 0xeb, 0xb6, 0x9b, 0x34, 0xbc, 0xdd, 0x1b, - 0xdb, 0xf6, 0x33, 0xde, 0x21, 0x1c, 0xd9, 0x1f, 0xd4, 0xb1, 0x29, 0xbf, 0xdc, 0xab, 0x79, 0xa6, - 0xfc, 0x44, 0xfc, 0x16, 0x95, 0x37, 0xeb, 0x5d, 0x5f, 0x80, 0x98, 0x7c, 0x24, 0x3f, 0x8e, 0xc3, - 0x0b, 0x7b, 0x37, 0x10, 0x3d, 0x26, 0x2e, 0x91, 0x7b, 0x37, 0xd2, 0x84, 0x7c, 0x04, 0xce, 0xc6, - 0x7a, 0x83, 0x7f, 0x15, 0xeb, 0x63, 0x12, 0x8d, 0x5a, 0x56, 0x09, 0x26, 0xb2, 0xac, 0x0b, 0xb4, - 0x2e, 0x1c, 0xc8, 0x23, 0x30, 0xe4, 0xb6, 0x97, 0xf8, 0xf1, 0x50, 0x6d, 0x69, 0x83, 0x67, 0xdc, - 0xd2, 0x06, 0x03, 0xd8, 0xc4, 0xc6, 0xb5, 0x6c, 0xff, 0xc8, 0xd8, 0xa8, 0x76, 0x26, 0x2e, 0x53, - 0x0c, 0x96, 0x2b, 0x7e, 0x4b, 0x5a, 0x99, 0x0c, 0xb9, 0x45, 0xb9, 0xaf, 0xc9, 0x50, 0x9e, 0xd4, - 0xd2, 0x7b, 0x9c, 0xd4, 0x1e, 0x8b, 0xbb, 0xbd, 0x24, 0xcc, 0x54, 0x5f, 0x0c, 0x23, 0x7e, 0x0b, - 0x7c, 0xef, 0x8d, 0x60, 0xb5, 0xeb, 0xac, 0x11, 0xe7, 0x84, 0x46, 0x8c, 0xa1, 0x49, 0xbd, 0xa1, - 0xa8, 0x81, 0xc2, 0xaf, 0xf8, 0x63, 0x29, 0x80, 0x70, 0x1c, 0x91, 0x27, 0xa3, 0x29, 0x2e, 0x53, - 0xe1, 0x55, 0xa0, 0xc8, 0x74, 0x14, 0xc9, 0x69, 0x49, 0xce, 0x41, 0x16, 0xb3, 0x61, 0xa5, 0xc3, - 0xcb, 0x83, 0x35, 0xd3, 0x32, 0x34, 0x84, 0x32, 0xac, 0x94, 0xb6, 0x06, 0xb1, 0xe8, 0xf7, 0xc2, - 0xf7, 0x09, 0x53, 0x30, 0x5a, 0x6d, 0x2f, 0xc9, 0xfa, 0x24, 0xa7, 0x38, 0x71, 0xdb, 0x4b, 0x41, - 0x56, 0x83, 0x48, 0xca, 0xb3, 0x68, 0x93, 0xe2, 0xd7, 0x48, 0x5b, 0x15, 0x5f, 0x6f, 0xc8, 0x45, - 0x48, 0x9b, 0x86, 0xe8, 0xd6, 0xa4, 0x29, 0x86, 0x2b, 0x5e, 0xda, 0x34, 0xc8, 0x3c, 0x9c, 0x68, - 0x5b, 0x6b, 0x96, 0xbd, 0x6e, 0xd5, 0xea, 0xb6, 0xe5, 0x7a, 0x8e, 0x6e, 0x5a, 0x9e, 0x1b, 0x5c, - 0x7c, 0xfb, 0xcb, 0x1c, 0x27, 0x99, 0x0c, 0x29, 0x66, 0x7b, 0x34, 0xd2, 0x8e, 0x41, 0xc9, 0x2b, - 0x30, 0xa8, 0xaf, 0x63, 0xb6, 0x1c, 0xd7, 0x6e, 0x50, 0x71, 0xb7, 0x1d, 0x5c, 0x40, 0x97, 0x6e, - 0x57, 0x27, 0x39, 0x26, 0xca, 0x09, 0xf4, 0x75, 0x57, 0x20, 0xc8, 0x3b, 0x21, 0xe3, 0xba, 0xab, - 0xe2, 0xea, 0x3a, 0x3c, 0x0c, 0xac, 0xce, 0x46, 0x9b, 0x30, 0xa2, 0x89, 0x61, 0x18, 0x94, 0x5e, - 0xba, 0x78, 0x12, 0x48, 0xfc, 0x45, 0x8b, 0x6b, 0x70, 0x2a, 0xf1, 0xb9, 0xe4, 0x02, 0x8c, 0xfa, - 0x69, 0x31, 0xd9, 0x1b, 0xb7, 0x45, 0x06, 0xad, 0x61, 0x6d, 0x98, 0x81, 0x4b, 0x8e, 0xe5, 0x4e, - 0x62, 0x02, 0xc9, 0x77, 0xc2, 0x78, 0x48, 0xd7, 0xe2, 0x0b, 0x06, 0xcf, 0x5f, 0xa5, 0x8d, 0xfa, - 0x94, 0x15, 0x0e, 0x2e, 0x7e, 0x10, 0x46, 0xa2, 0xaf, 0xca, 0x46, 0x30, 0x1e, 0x02, 0x45, 0x1f, - 0x31, 0xc8, 0x61, 0xfc, 0x01, 0x8f, 0xc3, 0x88, 0x20, 0x89, 0x72, 0x1f, 0xe6, 0x50, 0x9f, 0xf7, - 0xf7, 0xa5, 0x94, 0x91, 0x7c, 0x88, 0x56, 0xec, 0x8e, 0x46, 0x6f, 0xf1, 0x57, 0x32, 0x30, 0x58, - 0xb1, 0x1d, 0x4f, 0x24, 0x92, 0x3b, 0xda, 0x66, 0xa5, 0x74, 0xe2, 0x92, 0xdd, 0xc5, 0x89, 0xcb, - 0x39, 0xc8, 0x4a, 0xf1, 0x27, 0xfc, 0x96, 0xd7, 0x30, 0x1c, 0x0d, 0xa1, 0x6f, 0x71, 0xec, 0x5e, - 0xdc, 0x23, 0xa4, 0x6f, 0xdf, 0x7e, 0x57, 0x3f, 0x9e, 0x06, 0x78, 0xfd, 0xe9, 0xa7, 0xef, 0xe7, - 0x2e, 0xdd, 0x97, 0xb3, 0x55, 0xf1, 0x9b, 0x53, 0x30, 0x2a, 0xdc, 0x2c, 0xa4, 0x34, 0xc8, 0x7d, - 0xbe, 0x7f, 0x8d, 0xbc, 0xe2, 0x70, 0x90, 0xe6, 0xe3, 0x98, 0x0d, 0x3b, 0x7d, 0xd7, 0xf4, 0xf0, - 0xa6, 0x59, 0xca, 0x83, 0x4c, 0x05, 0x4c, 0xb6, 0x61, 0x7d, 0x3a, 0xf2, 0xa4, 0xef, 0x40, 0x92, - 0x09, 0x0d, 0x77, 0xd6, 0x60, 0x3a, 0xd1, 0x89, 0xa4, 0xf8, 0xcf, 0xb2, 0x90, 0x9d, 0xbe, 0x4b, - 0xeb, 0x47, 0xbc, 0x63, 0xa5, 0x6b, 0xa9, 0xec, 0x3e, 0xaf, 0xa5, 0xf6, 0xe2, 0x50, 0xf7, 0x72, - 0xd8, 0x9f, 0xb9, 0xe8, 0xe3, 0x95, 0x9e, 0x57, 0x1f, 0xef, 0xf7, 0xf4, 0xd1, 0xf3, 0xc7, 0xfc, - 0x97, 0x19, 0xc8, 0x54, 0x27, 0x2b, 0xc7, 0x7a, 0x73, 0xa8, 0x7a, 0xd3, 0xdd, 0xe3, 0xa8, 0x18, - 0x38, 0x11, 0xf4, 0x87, 0x21, 0x02, 0x8a, 0xbf, 0xc0, 0x1f, 0x67, 0x60, 0xa4, 0x3a, 0xb3, 0x58, - 0x91, 0xee, 0xf1, 0xae, 0x73, 0x37, 0x6e, 0x74, 0x28, 0xe6, 0x5d, 0x7a, 0x2e, 0x66, 0x7e, 0xdf, - 0x2c, 0x5b, 0xde, 0xf3, 0xcf, 0x62, 0xfa, 0x64, 0x3c, 0x66, 0xe6, 0x81, 0x22, 0xae, 0xf9, 0x26, - 0xfd, 0x56, 0x4c, 0x52, 0xe4, 0x33, 0x20, 0x2f, 0x42, 0xe6, 0xa6, 0xf0, 0xc6, 0xeb, 0xc4, 0xe7, - 0x99, 0xab, 0x9c, 0x0f, 0x9b, 0x04, 0x33, 0x6d, 0xd3, 0x40, 0x0e, 0xac, 0x15, 0x6b, 0xfc, 0xaa, - 0x30, 0x38, 0x76, 0xd4, 0x78, 0xc5, 0x6f, 0xfc, 0x6a, 0x79, 0x8a, 0x54, 0x61, 0xb0, 0x42, 0x9d, - 0xa6, 0x89, 0x1d, 0xe5, 0xcf, 0xf8, 0xdd, 0x99, 0x9c, 0xd8, 0xda, 0x2c, 0x0c, 0xb6, 0xc2, 0x46, - 0xc8, 0x4c, 0xe6, 0x42, 0x3e, 0x00, 0xc0, 0x6d, 0xb2, 0x1d, 0xa6, 0xd6, 0x3f, 0x8f, 0x07, 0x17, - 0x7c, 0x6f, 0x9c, 0xb0, 0x49, 0x95, 0x98, 0x91, 0x35, 0x18, 0x9b, 0xb7, 0x0d, 0x73, 0xd9, 0xe4, - 0x7e, 0xf7, 0xf8, 0x80, 0xdc, 0xf6, 0xde, 0xae, 0x6c, 0x2f, 0xdc, 0x94, 0xda, 0x25, 0x3d, 0x26, - 0xc6, 0xb8, 0xf8, 0xcf, 0x7b, 0x21, 0xcb, 0xba, 0xfd, 0x78, 0xfc, 0xee, 0x67, 0xfc, 0x96, 0x60, - 0xec, 0xb6, 0xed, 0xac, 0x99, 0xd6, 0x4a, 0x10, 0x46, 0x25, 0x0e, 0xd7, 0xd0, 0x0f, 0x73, 0x9d, - 0xe3, 0x6a, 0x41, 0xc4, 0x95, 0x16, 0x23, 0xdf, 0x66, 0x04, 0xbf, 0x00, 0xc0, 0x73, 0xd0, 0x20, - 0x4d, 0x7f, 0x98, 0x58, 0x8b, 0x67, 0xa8, 0xc1, 0xc8, 0x2c, 0x39, 0xb1, 0x56, 0x48, 0x4c, 0x2e, - 0xf9, 0x9e, 0x6c, 0x03, 0xe8, 0xb0, 0x8a, 0xa7, 0x88, 0xe8, 0xc9, 0x26, 0x1b, 0x01, 0xdc, 0xa7, - 0xad, 0x02, 0x20, 0x79, 0x07, 0x80, 0x22, 0x88, 0xc8, 0xe4, 0x20, 0x92, 0x24, 0x27, 0x38, 0x07, - 0x68, 0x12, 0x0f, 0xf2, 0xbc, 0xe2, 0xbe, 0x44, 0x22, 0xdc, 0x3a, 0x7a, 0x2f, 0x85, 0xee, 0xaf, - 0x43, 0xdb, 0xb9, 0xbf, 0x16, 0xbf, 0x3b, 0x03, 0x83, 0x8c, 0x5b, 0xb5, 0xdd, 0x6c, 0xea, 0xce, - 0xc6, 0xb1, 0x22, 0xef, 0x47, 0x91, 0x6b, 0x30, 0x2e, 0x47, 0x4b, 0x31, 0xc3, 0xd7, 0xcf, 0xc6, - 0x18, 0x1c, 0xf5, 0xa8, 0x04, 0xe2, 0x62, 0x95, 0xcd, 0xfb, 0x9e, 0x00, 0xe3, 0xe1, 0xa8, 0xab, - 0xc5, 0x79, 0x15, 0xbf, 0x21, 0x05, 0x63, 0x2a, 0x34, 0xd0, 0xfd, 0x54, 0xa2, 0xee, 0x3f, 0x01, - 0x03, 0xc2, 0x01, 0x4a, 0x37, 0x84, 0x37, 0xf7, 0xc8, 0xd6, 0x66, 0x01, 0x30, 0xf1, 0x47, 0xcd, - 0xa1, 0xba, 0xa1, 0x85, 0x04, 0xe4, 0x39, 0x18, 0xc2, 0x1f, 0xb7, 0x1d, 0xd3, 0xf3, 0x28, 0xef, - 0x8c, 0x2c, 0xbf, 0x01, 0xe5, 0x0d, 0xd6, 0x39, 0x42, 0x8b, 0x90, 0x15, 0x7f, 0x36, 0x0d, 0x03, - 0xd5, 0xf6, 0x92, 0xbb, 0xe1, 0x7a, 0xb4, 0x79, 0xc4, 0x75, 0xc8, 0x3f, 0x7e, 0xca, 0x26, 0x1e, - 0x3f, 0x3d, 0xea, 0x0f, 0x2d, 0xe9, 0xfa, 0x2d, 0xd8, 0x18, 0xf8, 0x3e, 0xe5, 0xa1, 0x16, 0xe5, - 0xf6, 0xb0, 0xd5, 0xf9, 0x27, 0x69, 0x18, 0xe3, 0xae, 0x37, 0x53, 0xa6, 0x5b, 0x3f, 0x80, 0x68, - 0xc2, 0xc3, 0x97, 0xe9, 0xfe, 0xdc, 0xd5, 0x76, 0x10, 0xa3, 0x59, 0xfc, 0xca, 0x0c, 0x0c, 0x96, - 0xda, 0xde, 0x6a, 0xc9, 0xc3, 0xf9, 0xed, 0x78, 0x87, 0xbd, 0x87, 0x70, 0xa6, 0x21, 0xbd, 0xd5, - 0xaa, 0x35, 0x7d, 0xa9, 0xe5, 0xa2, 0x4e, 0xc6, 0xa5, 0x56, 0xab, 0x03, 0x83, 0x41, 0x3d, 0x44, - 0x15, 0x7f, 0x26, 0xc5, 0x4b, 0x73, 0x2c, 0xda, 0x6b, 0xd4, 0x3a, 0x80, 0x1b, 0xc4, 0x83, 0x38, - 0x34, 0xf7, 0xfb, 0x33, 0xb3, 0xbb, 0xfe, 0xc4, 0x7b, 0x6f, 0xcd, 0x6e, 0xd0, 0xa3, 0xfd, 0x19, - 0x07, 0x78, 0xef, 0xed, 0x0b, 0xe4, 0x00, 0xfc, 0x2c, 0xde, 0x5e, 0x02, 0x39, 0x80, 0x33, 0xe5, - 0xb7, 0x87, 0x40, 0x3e, 0x97, 0x82, 0x81, 0x09, 0xdb, 0x3b, 0xe2, 0x03, 0x5f, 0x7c, 0xc5, 0xd1, - 0x56, 0x73, 0xff, 0x2b, 0x8e, 0xb6, 0x6e, 0x16, 0xbf, 0x31, 0x0d, 0x27, 0x45, 0xf9, 0x30, 0x71, - 0x0e, 0x77, 0x3c, 0x1d, 0x8b, 0xc1, 0x16, 0x17, 0xcd, 0xf1, 0x3c, 0x24, 0x44, 0xf3, 0xe9, 0x14, - 0x9c, 0x40, 0x5f, 0x59, 0x03, 0x0d, 0x91, 0xe0, 0xeb, 0x4e, 0x02, 0xf7, 0xa8, 0xe3, 0x29, 0xa6, - 0x35, 0xfe, 0x83, 0x14, 0x60, 0xf0, 0x0d, 0xdb, 0xb4, 0x6a, 0x3c, 0x00, 0x40, 0x64, 0x70, 0x06, - 0x06, 0x12, 0x01, 0x00, 0xe7, 0x01, 0xda, 0xae, 0xbe, 0x42, 0x6b, 0x4d, 0xdb, 0x10, 0x17, 0xd3, - 0xda, 0x00, 0x42, 0xe6, 0x6d, 0x83, 0x92, 0xc7, 0x61, 0x44, 0xc7, 0x5c, 0xf6, 0xd4, 0x10, 0xf9, - 0xb0, 0x71, 0xf3, 0xa0, 0x0d, 0xfb, 0x50, 0x9e, 0x11, 0xfb, 0x97, 0xd2, 0x30, 0x2e, 0xbd, 0x54, - 0xa8, 0xc7, 0xcd, 0x5d, 0x76, 0x56, 0x53, 0xea, 0x2c, 0x67, 0x8f, 0x9d, 0xe5, 0x48, 0x9d, 0xd5, - 0xde, 0x65, 0x67, 0xb5, 0xa3, 0xae, 0xd4, 0xbb, 0xb1, 0x51, 0xe7, 0x60, 0x48, 0x64, 0x21, 0xf6, - 0x98, 0x0c, 0x84, 0xa5, 0xfa, 0x60, 0xa4, 0xe2, 0x5a, 0xb4, 0xcf, 0x54, 0x6b, 0xd3, 0x0d, 0x49, - 0x54, 0x89, 0x86, 0x13, 0xf6, 0xb1, 0x44, 0xf7, 0x28, 0xd1, 0x2f, 0x8d, 0x4a, 0x34, 0x9c, 0x50, - 0xee, 0x1f, 0x89, 0x16, 0xbf, 0x33, 0x03, 0x27, 0xb1, 0x64, 0x8a, 0x6b, 0xda, 0xd6, 0xdb, 0x60, - 0x23, 0x43, 0xea, 0x51, 0x57, 0xc8, 0xf9, 0x04, 0x57, 0xc8, 0x3f, 0xdf, 0x2c, 0x3c, 0xbf, 0x8b, - 0xb2, 0x8d, 0x97, 0xab, 0x78, 0xd4, 0xc3, 0xb8, 0xfa, 0x4e, 0x94, 0x2e, 0xc0, 0xb5, 0x60, 0xc6, - 0x14, 0x3b, 0xf5, 0xea, 0xd6, 0x66, 0xe1, 0x94, 0x34, 0xb5, 0xee, 0xfd, 0x79, 0x21, 0x6b, 0x4d, - 0x7a, 0x4c, 0xf1, 0x5f, 0xa5, 0xe0, 0x81, 0xe8, 0x12, 0xf8, 0x76, 0xd8, 0x78, 0xfe, 0x9d, 0x34, - 0x9c, 0x7a, 0x15, 0x85, 0x13, 0xb8, 0x73, 0x1f, 0x1b, 0x3d, 0x62, 0x65, 0x4f, 0x90, 0xcd, 0xf1, - 0x76, 0xb4, 0xb3, 0x6c, 0x8e, 0x2d, 0x42, 0x21, 0x9b, 0x5f, 0x48, 0xc1, 0x89, 0x85, 0xf2, 0xd4, - 0xe4, 0xdb, 0x64, 0x44, 0xc5, 0xbf, 0xe7, 0x88, 0xef, 0x56, 0x63, 0xdf, 0x73, 0xc4, 0xf7, 0xad, - 0x9f, 0x4e, 0xc3, 0x89, 0x6a, 0x69, 0x7e, 0xee, 0xed, 0x32, 0x83, 0x4f, 0xca, 0xb1, 0x47, 0xfe, - 0x29, 0xbe, 0xb0, 0x05, 0xe4, 0xcf, 0xbc, 0x75, 0xb5, 0x73, 0x4c, 0x52, 0x5c, 0x28, 0x47, 0x7c, - 0xea, 0x3e, 0x10, 0xa1, 0x30, 0xcd, 0x8f, 0x50, 0x1f, 0x71, 0xcd, 0xff, 0x17, 0x39, 0x18, 0xbc, - 0xde, 0x5e, 0xa2, 0xc2, 0xa3, 0xf5, 0xbe, 0xbe, 0xba, 0xba, 0x0a, 0x83, 0x42, 0x0c, 0x78, 0x45, - 0x2b, 0xa5, 0x53, 0x17, 0xe9, 0x24, 0x79, 0x56, 0x58, 0x99, 0x88, 0x9c, 0x83, 0xec, 0x2d, 0xea, - 0x2c, 0xc9, 0xa9, 0x75, 0xee, 0x50, 0x67, 0x49, 0x43, 0x28, 0x99, 0x93, 0x1c, 0xd7, 0x2b, 0x65, - 0xac, 0xc9, 0x2b, 0xbc, 0x1e, 0xb0, 0xc8, 0x70, 0x10, 0x82, 0xa1, 0xb7, 0x4c, 0x5e, 0xcd, 0x57, - 0xce, 0x0a, 0xa6, 0xb6, 0x24, 0x37, 0x60, 0x3c, 0x12, 0x9a, 0x81, 0x05, 0x69, 0xfb, 0x13, 0xd8, - 0x25, 0x95, 0xa2, 0x8d, 0x37, 0x25, 0x2f, 0xc3, 0x90, 0x0f, 0x44, 0x0f, 0xff, 0x81, 0xb0, 0x44, - 0x5e, 0xc0, 0x4a, 0x29, 0x2d, 0x16, 0x69, 0x20, 0x33, 0xc0, 0x5b, 0x58, 0x48, 0x60, 0xa0, 0x04, - 0xc6, 0x44, 0x1a, 0x90, 0xe7, 0x90, 0x01, 0x46, 0x98, 0xa3, 0xc7, 0xe7, 0x20, 0xe6, 0x9d, 0xc1, - 0x1b, 0x6c, 0x47, 0xc0, 0x79, 0x76, 0xa1, 0x08, 0x19, 0x59, 0x00, 0x08, 0x3d, 0xf3, 0x84, 0x1f, - 0xfd, 0xae, 0x7d, 0x06, 0x25, 0x16, 0xb2, 0x2b, 0xc2, 0xf0, 0x5e, 0x5c, 0x11, 0x8a, 0x9f, 0xcf, - 0xc0, 0xa0, 0x74, 0xe9, 0x46, 0x9e, 0x84, 0x5c, 0xa9, 0xd5, 0xba, 0xa9, 0x95, 0xe5, 0x42, 0x61, - 0x7a, 0xab, 0x55, 0x6b, 0x3b, 0xa6, 0x1c, 0x19, 0xc6, 0x89, 0xc8, 0x24, 0x0c, 0x97, 0x5a, 0xad, - 0x4a, 0x7b, 0xa9, 0x61, 0xd6, 0xa5, 0xe2, 0xb3, 0x98, 0x82, 0x8e, 0xb5, 0x6a, 0x21, 0x46, 0xad, - 0x3f, 0x1b, 0x6d, 0x43, 0x3e, 0x82, 0x89, 0x53, 0x45, 0x25, 0x58, 0x5e, 0x62, 0xb4, 0x98, 0x70, - 0x21, 0x78, 0x39, 0x20, 0x12, 0xa5, 0x5f, 0xfd, 0x1a, 0x79, 0xec, 0x41, 0xb1, 0xd2, 0xaf, 0x21, - 0x4b, 0xf2, 0x14, 0xf4, 0x95, 0x5a, 0x2d, 0xe9, 0xba, 0x1d, 0x3d, 0x73, 0x59, 0x2b, 0xa5, 0x8f, - 0x7d, 0x32, 0xf1, 0x59, 0xc2, 0x41, 0xc7, 0x76, 0x3c, 0x1c, 0x52, 0xc3, 0xe1, 0x67, 0xf9, 0x1e, - 0x3d, 0xb6, 0x9c, 0xab, 0x50, 0x8b, 0xb6, 0x61, 0x8f, 0xf5, 0x0b, 0xb2, 0xe7, 0xc2, 0xc7, 0x26, - 0x54, 0x62, 0xf7, 0xc9, 0xce, 0xbe, 0x04, 0x23, 0xd1, 0x6f, 0xdc, 0x55, 0x05, 0xb8, 0x3f, 0x4b, - 0xa1, 0x1c, 0x8f, 0x78, 0x40, 0xe5, 0x33, 0x90, 0x29, 0xb5, 0x5a, 0x62, 0x1a, 0xdc, 0xc1, 0xbd, - 0x30, 0xa3, 0xf6, 0x3f, 0xfd, 0x88, 0x47, 0x66, 0xef, 0xe9, 0xd3, 0x3f, 0xc7, 0x3f, 0xfd, 0x68, - 0x47, 0x4d, 0x17, 0xbf, 0x29, 0x0b, 0xa3, 0xa5, 0x56, 0xeb, 0xb8, 0x70, 0xdb, 0x41, 0xa5, 0xb8, - 0x7a, 0x1a, 0x40, 0x9a, 0x95, 0xfb, 0x82, 0x14, 0x13, 0x83, 0xd2, 0x8c, 0x9c, 0x4f, 0x69, 0x12, - 0x91, 0xaf, 0x7e, 0xfd, 0xbb, 0x51, 0xbf, 0xd0, 0x09, 0x70, 0x60, 0xb7, 0x39, 0x30, 0x61, 0xc7, - 0x39, 0x30, 0xbf, 0x34, 0x83, 0xd3, 0xf1, 0x51, 0xcf, 0x28, 0x7c, 0xaf, 0xa8, 0x86, 0xe8, 0xe7, - 0xdc, 0xae, 0xa6, 0x99, 0x9f, 0xcc, 0xc8, 0x03, 0x14, 0xab, 0x81, 0x1d, 0xf7, 0x42, 0xef, 0xbe, - 0x2c, 0xfe, 0x11, 0x59, 0x98, 0x22, 0x4f, 0xa9, 0x5f, 0x8b, 0x5a, 0xa4, 0xa9, 0xad, 0x33, 0x14, - 0x26, 0xab, 0x8d, 0xd2, 0xfa, 0x7d, 0xd8, 0xb7, 0xab, 0x3e, 0xdc, 0x4c, 0x63, 0x66, 0xac, 0x20, - 0x69, 0xef, 0xfe, 0x37, 0x4e, 0x57, 0x00, 0xf8, 0x6d, 0x46, 0x10, 0xf6, 0x34, 0xcc, 0x13, 0x6c, - 0xf2, 0x12, 0xd5, 0x22, 0xc1, 0x66, 0x48, 0x12, 0x78, 0x91, 0x66, 0x12, 0xbd, 0x48, 0x2f, 0x41, - 0xbf, 0xa6, 0xaf, 0xbf, 0xd6, 0xa6, 0x22, 0x96, 0xd8, 0xcf, 0x89, 0xaf, 0xaf, 0xd7, 0x3e, 0xca, - 0x80, 0x5a, 0x80, 0x26, 0xc5, 0x20, 0xb5, 0x9a, 0xe4, 0xad, 0xc7, 0x8f, 0xff, 0x83, 0x84, 0x6a, - 0x7b, 0x51, 0x74, 0xf2, 0x5e, 0xc8, 0x94, 0x6e, 0x57, 0x85, 0x64, 0xcf, 0x4a, 0xd1, 0xb1, 0x42, - 0x5e, 0x1d, 0xdb, 0xde, 0xae, 0x16, 0xbf, 0x2f, 0x0b, 0x27, 0x43, 0x01, 0xcf, 0xcd, 0xcd, 0x4b, - 0x32, 0xde, 0xf3, 0xcd, 0xd6, 0x33, 0x90, 0xd1, 0x5b, 0x2d, 0x31, 0x50, 0x76, 0xf2, 0x15, 0x7a, - 0xab, 0x45, 0x9e, 0x85, 0x9c, 0xbb, 0x8b, 0x0c, 0x54, 0x6e, 0x10, 0x0f, 0xc9, 0x7a, 0x43, 0x76, - 0x4a, 0xe5, 0xbd, 0xd3, 0x12, 0x11, 0x2a, 0xcd, 0x8e, 0x22, 0xe7, 0xff, 0x93, 0x8b, 0xd0, 0xdf, - 0x72, 0xec, 0x3b, 0xa6, 0x41, 0x23, 0x69, 0x40, 0x7d, 0x98, 0x16, 0xfc, 0xc5, 0x16, 0x8e, 0xa6, - 0x6d, 0xd0, 0x86, 0x58, 0x9b, 0x70, 0xe1, 0x40, 0x80, 0xbc, 0x70, 0x20, 0x80, 0xbc, 0x04, 0xa3, - 0x7e, 0x59, 0x04, 0xa3, 0xc6, 0x1b, 0xf5, 0x87, 0x8d, 0x14, 0x94, 0x36, 0x12, 0x00, 0xe6, 0xb1, - 0xf5, 0x75, 0x18, 0x37, 0xad, 0x56, 0xdb, 0xe3, 0x17, 0x97, 0x22, 0xd2, 0x77, 0x00, 0x2b, 0x6c, - 0x60, 0x82, 0xeb, 0x18, 0x52, 0x8e, 0xe8, 0x46, 0x24, 0xbf, 0xec, 0xc1, 0x70, 0xe0, 0x1b, 0x40, - 0xec, 0xb6, 0xa7, 0x72, 0x03, 0xe4, 0x86, 0x5b, 0xd9, 0x38, 0x56, 0xde, 0x19, 0x73, 0x6c, 0xc8, - 0xaf, 0x78, 0x1d, 0x60, 0x76, 0x71, 0xb1, 0x32, 0x4b, 0x75, 0x26, 0x93, 0x73, 0x80, 0x4e, 0xc0, - 0xb2, 0x8f, 0x35, 0x77, 0x0a, 0x66, 0xff, 0x92, 0x42, 0xc4, 0xf2, 0xe7, 0x29, 0x41, 0x10, 0x20, - 0x36, 0x01, 0xc5, 0x6f, 0xc8, 0xc2, 0xa9, 0x50, 0xfd, 0x18, 0xdf, 0x83, 0xd0, 0x3f, 0xff, 0x56, - 0x34, 0xbd, 0xcb, 0x5b, 0xd1, 0x97, 0xa1, 0xcf, 0xdd, 0xd3, 0x4c, 0x2d, 0x5a, 0xf9, 0x8a, 0x9f, - 0xdd, 0x95, 0xe2, 0x3f, 0x09, 0xe0, 0x9f, 0x79, 0x98, 0xbe, 0xa2, 0xa2, 0x27, 0x7a, 0x08, 0xd5, - 0x82, 0xf2, 0x1d, 0x86, 0xa4, 0xd3, 0xb9, 0x8e, 0x3a, 0xfd, 0x00, 0x64, 0xda, 0x8e, 0xaf, 0xa7, - 0x98, 0xc3, 0xbc, 0xed, 0x34, 0x34, 0xf6, 0x0f, 0x79, 0x06, 0x86, 0x56, 0x3d, 0xaf, 0x55, 0x13, - 0xb9, 0x66, 0x85, 0x5a, 0xe2, 0xc9, 0x8b, 0x0c, 0xd7, 0x06, 0xd9, 0x2f, 0x3f, 0x09, 0xed, 0x0b, - 0xd0, 0xb7, 0x8a, 0xfd, 0xcd, 0xb3, 0x12, 0x4a, 0x59, 0x00, 0x42, 0x55, 0xe0, 0x41, 0xa6, 0x82, - 0x4c, 0xf3, 0xff, 0x20, 0xcf, 0x42, 0x38, 0x19, 0x0a, 0x03, 0x8a, 0xd7, 0xe5, 0xf6, 0x81, 0x72, - 0x98, 0xa9, 0x23, 0xe6, 0xca, 0xe2, 0x4f, 0xa5, 0xe1, 0xa1, 0x44, 0xbd, 0x98, 0xb0, 0x8d, 0x8d, - 0x60, 0x29, 0xdf, 0xb3, 0x82, 0x48, 0x1d, 0x9d, 0xde, 0x53, 0x47, 0x47, 0xfb, 0x2c, 0xb3, 0x5d, - 0x9f, 0x3d, 0x05, 0x83, 0x62, 0x5d, 0xc4, 0x42, 0xa5, 0xbc, 0xcc, 0x28, 0xae, 0x3a, 0x12, 0x58, - 0x83, 0x7a, 0x58, 0xb1, 0xf4, 0x31, 0xe8, 0x33, 0xdd, 0x5a, 0x43, 0x77, 0xf9, 0x46, 0x5e, 0xe4, - 0x73, 0x17, 0x20, 0x2d, 0x67, 0xba, 0x73, 0xba, 0x8b, 0x11, 0x0e, 0x81, 0x4b, 0xf2, 0x10, 0x1f, - 0x7d, 0xec, 0xb7, 0x86, 0xff, 0x16, 0xff, 0x30, 0x0b, 0xa7, 0x55, 0x21, 0x86, 0x29, 0x06, 0x0f, - 0x4f, 0x78, 0x62, 0x94, 0x64, 0xf6, 0x31, 0x4a, 0xb2, 0x3b, 0x90, 0xb8, 0xb4, 0xa0, 0x8b, 0xc3, - 0x90, 0xf8, 0x3a, 0xef, 0x86, 0xeb, 0x7c, 0xd8, 0xc2, 0xa3, 0x77, 0x3d, 0x31, 0xb8, 0xe4, 0x16, - 0x0c, 0xec, 0xb7, 0x58, 0xa4, 0x77, 0xbd, 0xd8, 0x50, 0xea, 0xdb, 0xe5, 0x50, 0xea, 0xdf, 0xe5, - 0x50, 0xba, 0x0a, 0x43, 0x2e, 0xb5, 0x0c, 0x8c, 0xc0, 0xab, 0x35, 0x5d, 0xb1, 0x22, 0xe0, 0xf3, - 0x64, 0xb8, 0x06, 0xec, 0xd7, 0xa2, 0xd9, 0xa4, 0xf3, 0xd8, 0x66, 0x5d, 0x37, 0xbd, 0xa0, 0x0d, - 0x84, 0x6d, 0x64, 0xb8, 0x06, 0xec, 0x97, 0x68, 0xf3, 0x22, 0x5b, 0xbc, 0x30, 0x2b, 0x70, 0xd0, - 0x6c, 0x10, 0x9b, 0x89, 0xc5, 0x2b, 0x82, 0xd2, 0x86, 0x05, 0x80, 0x37, 0x2e, 0x7e, 0x2e, 0x0d, - 0x85, 0x64, 0xa5, 0x3b, 0x1e, 0xba, 0x3b, 0x1f, 0xba, 0x5f, 0x9a, 0x06, 0x12, 0x37, 0xe0, 0xc8, - 0xf3, 0x30, 0x80, 0xd0, 0x15, 0xf6, 0xf9, 0xa9, 0x30, 0x6d, 0xa3, 0xbe, 0xee, 0xd6, 0x1c, 0x84, - 0x46, 0x8e, 0x13, 0x7d, 0x52, 0xf2, 0x02, 0x40, 0xe9, 0x76, 0x95, 0x6d, 0x08, 0xcc, 0xba, 0xbf, - 0x18, 0x63, 0x9c, 0x1f, 0x6b, 0xe8, 0x72, 0x70, 0x24, 0xd4, 0x2e, 0x20, 0xc6, 0x93, 0xc8, 0xdb, - 0xd5, 0x59, 0xdb, 0xf5, 0x84, 0xac, 0xf8, 0x49, 0xe4, 0xba, 0x5b, 0x5b, 0xb5, 0x5d, 0x2f, 0x72, - 0x12, 0xc9, 0xc9, 0xc8, 0x0c, 0x8c, 0x94, 0x6e, 0x57, 0x79, 0x7a, 0x24, 0x43, 0xb3, 0x1b, 0xfe, - 0x11, 0xe6, 0x43, 0x5b, 0x9b, 0x85, 0xb3, 0xac, 0x21, 0x4f, 0xad, 0x64, 0xd4, 0x1c, 0x3b, 0x52, - 0x98, 0x5f, 0x69, 0x55, 0xfc, 0x44, 0x3f, 0x8c, 0x4d, 0xe9, 0x9e, 0xbe, 0xa4, 0xbb, 0x54, 0xba, - 0xbf, 0x19, 0xf5, 0x61, 0xfe, 0xe7, 0x48, 0x72, 0x30, 0x96, 0x12, 0xbe, 0x46, 0x6d, 0x40, 0x5e, - 0x0c, 0xf9, 0x56, 0x1c, 0xdb, 0xb3, 0xeb, 0x76, 0x43, 0xc8, 0x04, 0xfb, 0xd5, 0x58, 0xaa, 0xb5, - 0x04, 0x58, 0x8b, 0x11, 0x92, 0x27, 0x60, 0xd0, 0x87, 0xdd, 0xd4, 0xca, 0x42, 0x26, 0xb8, 0x06, - 0x1b, 0x4b, 0xb5, 0xb6, 0x63, 0x6a, 0x32, 0x9a, 0xbc, 0x00, 0x43, 0xfe, 0x4f, 0xe9, 0x30, 0x17, - 0x4f, 0x10, 0x8c, 0xa5, 0xd8, 0x79, 0xbd, 0x4c, 0x2a, 0x37, 0xc5, 0x6d, 0x67, 0x6f, 0xa4, 0x29, - 0xb3, 0x57, 0x92, 0x9a, 0xe2, 0x8e, 0xf3, 0xa3, 0x30, 0xe2, 0xff, 0x16, 0x47, 0xd4, 0x3c, 0xd6, - 0xee, 0x09, 0x7f, 0xa8, 0xa8, 0x62, 0xbd, 0x1c, 0x25, 0xe7, 0x87, 0xd5, 0x0f, 0x8a, 0xc3, 0xea, - 0x13, 0xc6, 0x52, 0xfc, 0xac, 0x5a, 0x79, 0x00, 0x29, 0xc3, 0xb8, 0x0f, 0x09, 0x35, 0xb4, 0x2f, - 0xbc, 0xa3, 0x30, 0x96, 0x6a, 0x89, 0x4a, 0x1a, 0x6f, 0x45, 0x1a, 0x70, 0x2e, 0x02, 0x34, 0xdc, - 0x55, 0x73, 0xd9, 0x13, 0x17, 0x0c, 0xa2, 0xd6, 0xe0, 0xc0, 0xc4, 0xc5, 0xad, 0xcd, 0xc2, 0x63, - 0x01, 0x57, 0x4e, 0x53, 0x13, 0x49, 0x95, 0xa2, 0xa9, 0x8d, 0xbb, 0x72, 0x23, 0x55, 0x38, 0xe9, - 0xe3, 0x5f, 0x9d, 0xac, 0x54, 0x1c, 0xfb, 0x0d, 0x5a, 0xf7, 0xca, 0x53, 0xe2, 0x70, 0x08, 0xcd, - 0x6d, 0x63, 0xa9, 0xb6, 0x52, 0x6f, 0x31, 0xa5, 0x60, 0xb8, 0x28, 0xf3, 0xc4, 0xc6, 0xe4, 0x16, - 0x9c, 0x92, 0xe0, 0x65, 0xcb, 0xf5, 0x74, 0xab, 0x4e, 0x83, 0x44, 0xcc, 0x68, 0x76, 0x0b, 0xae, - 0xa6, 0x40, 0x46, 0xd9, 0x26, 0x37, 0x27, 0x2f, 0xc1, 0xb0, 0x8f, 0xe0, 0x7e, 0x6b, 0x83, 0xe8, - 0xb7, 0x86, 0x43, 0xd2, 0x58, 0xaa, 0xa9, 0x59, 0xfc, 0xa2, 0xc4, 0xb2, 0x46, 0x61, 0x52, 0xdb, - 0xa1, 0x88, 0x46, 0x29, 0xd9, 0x6b, 0x23, 0xa4, 0xe4, 0xe5, 0x50, 0xa3, 0x16, 0x1c, 0x73, 0xc5, - 0xf4, 0xab, 0xf5, 0x9f, 0x11, 0xfa, 0x61, 0x23, 0x30, 0x49, 0x3f, 0x38, 0xf9, 0xd9, 0x12, 0x9c, - 0x48, 0xd0, 0xb1, 0x5d, 0x5d, 0x16, 0x7c, 0x3c, 0x1d, 0xbe, 0xc4, 0x11, 0xbf, 0x31, 0x98, 0x80, - 0x7e, 0xff, 0x4b, 0xc4, 0x66, 0x21, 0xdf, 0x69, 0x68, 0xaa, 0x3c, 0x7c, 0x7c, 0x44, 0x1c, 0x47, - 0xfc, 0x16, 0xe1, 0x20, 0xc4, 0xf1, 0xf9, 0x54, 0x28, 0x8e, 0x23, 0x7e, 0xb3, 0xf0, 0x5d, 0xbd, - 0xe1, 0x9c, 0x74, 0x7c, 0xbd, 0x70, 0x50, 0xa7, 0x97, 0xa1, 0xcb, 0x73, 0x6e, 0x17, 0x4e, 0xe4, - 0xb2, 0x6a, 0xf6, 0xed, 0x4d, 0x35, 0xc9, 0x4b, 0x30, 0x58, 0xb1, 0x5d, 0x6f, 0xc5, 0xa1, 0x6e, - 0x25, 0xa8, 0x95, 0x8b, 0x59, 0xf9, 0x5a, 0x02, 0x5c, 0x6b, 0x45, 0x93, 0xf1, 0x4b, 0xe4, 0x52, - 0x8d, 0x82, 0x81, 0x3d, 0xd4, 0x28, 0xa8, 0x01, 0x4c, 0x96, 0x16, 0xee, 0x50, 0xc7, 0x31, 0x0d, - 0xbf, 0x00, 0xdd, 0x63, 0x01, 0x87, 0x00, 0x33, 0x49, 0x1d, 0x8f, 0x67, 0xf0, 0xa0, 0x53, 0xd4, - 0xd3, 0xcd, 0x86, 0x2b, 0xd2, 0xa5, 0xeb, 0x35, 0x5b, 0x90, 0xc8, 0xf6, 0x61, 0xd8, 0xb0, 0xf8, - 0xeb, 0x99, 0x98, 0xb2, 0xf2, 0xe3, 0xce, 0x23, 0xa9, 0xac, 0x07, 0x30, 0x13, 0x91, 0xab, 0xe1, - 0x22, 0xcd, 0xcf, 0x85, 0x7b, 0xa5, 0xa2, 0x41, 0x4b, 0xe2, 0x58, 0x38, 0x4a, 0x42, 0x3e, 0x04, - 0x67, 0x22, 0x80, 0x8a, 0xee, 0xe8, 0x4d, 0xea, 0xb1, 0x5d, 0x62, 0x2e, 0x2c, 0x03, 0xe1, 0xb7, - 0xae, 0xb5, 0x02, 0xb4, 0xd4, 0x2b, 0x9d, 0x38, 0x48, 0x9a, 0xdf, 0xb7, 0x0b, 0x67, 0xff, 0xcf, - 0x66, 0x42, 0x3b, 0x2c, 0x5a, 0xce, 0x4d, 0xa3, 0x6e, 0xbb, 0xe1, 0xdd, 0xbf, 0x1d, 0xbc, 0xb7, - 0xfa, 0xdc, 0xb3, 0x30, 0x5a, 0x5a, 0x5e, 0xa6, 0x75, 0xcf, 0x2f, 0x72, 0xe9, 0x8a, 0x02, 0x3e, - 0x7c, 0x5f, 0x24, 0x50, 0xa2, 0x68, 0xa1, 0xdc, 0xaf, 0x6a, 0xb3, 0xe2, 0x6f, 0x64, 0x21, 0x1f, - 0xec, 0x4b, 0x82, 0xe4, 0x41, 0x87, 0x68, 0x03, 0xdc, 0x13, 0xbd, 0x62, 0xc2, 0x78, 0x28, 0x0c, - 0x91, 0xb5, 0x25, 0xdf, 0x8b, 0xfb, 0x9e, 0x82, 0xca, 0x2c, 0x24, 0xe4, 0x5b, 0x9d, 0xb3, 0x62, - 0xab, 0x43, 0xc2, 0xe4, 0x4c, 0x35, 0x97, 0xb3, 0xd0, 0xe2, 0x5c, 0xc9, 0x27, 0x53, 0x70, 0xd2, - 0xef, 0x94, 0x85, 0x25, 0x66, 0xf3, 0xe3, 0xd1, 0xb8, 0xbf, 0xcd, 0x7a, 0x6f, 0xe7, 0xc7, 0xf1, - 0x4e, 0xba, 0x9c, 0xd4, 0x98, 0xbf, 0x49, 0x90, 0xad, 0x39, 0x50, 0x08, 0x1b, 0x69, 0xf8, 0xc1, - 0xbc, 0xab, 0x25, 0x3e, 0xf7, 0xec, 0xab, 0xf0, 0x40, 0x47, 0x96, 0xdb, 0xd9, 0xd8, 0xbd, 0xb2, - 0x8d, 0xfd, 0x6b, 0xa9, 0x70, 0x22, 0x52, 0x84, 0x44, 0x2e, 0x03, 0x84, 0x20, 0xb1, 0xeb, 0xc6, - 0x83, 0x93, 0x50, 0x68, 0x9a, 0x44, 0x41, 0x16, 0x20, 0x27, 0xc4, 0x92, 0x46, 0xb1, 0xbc, 0x6b, - 0x9b, 0x5e, 0xb8, 0x2c, 0xcb, 0x01, 0x77, 0xd4, 0xe2, 0x9b, 0x05, 0x9b, 0xb3, 0x2f, 0xc0, 0xe0, - 0x5e, 0xbf, 0xeb, 0x93, 0x19, 0x20, 0xf2, 0x16, 0xf9, 0x10, 0xf7, 0x0f, 0x47, 0x78, 0x0a, 0xbb, - 0x08, 0xfd, 0xec, 0x13, 0xf0, 0xc2, 0x47, 0xba, 0x2a, 0x6b, 0x0b, 0x98, 0x16, 0x60, 0xc3, 0x8a, - 0x0d, 0x7d, 0xc9, 0x15, 0x1b, 0x8a, 0xdf, 0x90, 0x81, 0xd3, 0x72, 0x87, 0x4c, 0x51, 0xac, 0xe1, - 0x7b, 0xdc, 0x29, 0x6f, 0x61, 0xa7, 0x14, 0x21, 0xc7, 0x77, 0x46, 0xa2, 0x98, 0x32, 0x3f, 0xb5, - 0x42, 0x88, 0x26, 0x30, 0xc5, 0x7f, 0x9b, 0x86, 0xe1, 0xc0, 0xfa, 0xd4, 0x1d, 0xf7, 0x3e, 0xee, - 0x8e, 0xf7, 0xc0, 0x30, 0xe6, 0xdc, 0x6f, 0x52, 0x8b, 0xe7, 0xa5, 0xef, 0x95, 0xaa, 0x9f, 0xfb, - 0x08, 0x3c, 0xc3, 0xd3, 0xa2, 0x84, 0x4c, 0xfb, 0xb9, 0xe5, 0x27, 0x55, 0x42, 0xe0, 0x66, 0x1f, - 0x87, 0x17, 0xbf, 0x3d, 0x03, 0x43, 0xbe, 0x94, 0x27, 0xcc, 0xa3, 0xea, 0x1d, 0x74, 0xb8, 0x42, - 0xbe, 0x02, 0x50, 0xb1, 0x1d, 0x4f, 0x6f, 0xdc, 0x08, 0x35, 0x1f, 0xcf, 0x6f, 0x5b, 0x08, 0xe5, - 0x6d, 0x24, 0x12, 0x5c, 0xbf, 0x42, 0xb3, 0x9a, 0x4f, 0x4c, 0x7c, 0xfd, 0x0a, 0xa0, 0x9a, 0x44, - 0x51, 0xfc, 0x91, 0x34, 0x8c, 0xfa, 0x9d, 0x34, 0x7d, 0x97, 0xd6, 0xdb, 0xf7, 0xf3, 0xdc, 0x14, - 0x95, 0x76, 0xef, 0xb6, 0xd2, 0x2e, 0xfe, 0x91, 0x34, 0x91, 0x4c, 0x36, 0xec, 0xe3, 0x89, 0xe4, - 0xaf, 0x42, 0xc7, 0x8b, 0x5f, 0x91, 0x81, 0x93, 0xbe, 0xd4, 0x67, 0xda, 0x16, 0x9e, 0x7c, 0x4c, - 0xea, 0x8d, 0xc6, 0xfd, 0xbc, 0x1b, 0x1f, 0xf4, 0x05, 0xb1, 0x20, 0x8a, 0xd8, 0x0c, 0xf3, 0xfb, - 0xcf, 0x65, 0x01, 0xae, 0xd9, 0xa6, 0xa1, 0xc9, 0x44, 0xe4, 0x65, 0x18, 0xf2, 0x7f, 0x96, 0x9c, - 0x15, 0x7f, 0x0b, 0x8e, 0xf7, 0x18, 0x41, 0x23, 0xdd, 0x89, 0xa4, 0xba, 0x8c, 0x34, 0x28, 0x7e, - 0x73, 0x06, 0xce, 0xde, 0x36, 0x2d, 0xc3, 0x5e, 0x77, 0xa5, 0x93, 0x15, 0x39, 0x3f, 0x77, 0xb5, - 0x8d, 0x76, 0xba, 0x9c, 0x9f, 0xdb, 0xe5, 0x20, 0xcd, 0xc7, 0x91, 0xe7, 0x60, 0xa8, 0x4a, 0x1d, - 0x53, 0x6f, 0xdc, 0x68, 0x37, 0x97, 0xa8, 0x1f, 0xa8, 0x80, 0x11, 0x1b, 0x2e, 0xc2, 0x6b, 0x16, - 0x22, 0xb4, 0x08, 0x19, 0x79, 0x00, 0x32, 0x37, 0x2b, 0x37, 0xc4, 0xcd, 0x14, 0x77, 0xfe, 0x68, - 0x59, 0x1a, 0x83, 0x91, 0xd7, 0xe0, 0xd4, 0xa4, 0x36, 0x37, 0x65, 0xba, 0x3c, 0x93, 0xa6, 0x69, - 0x5b, 0x15, 0x1b, 0x0b, 0x34, 0x64, 0xc3, 0x2f, 0xac, 0x3b, 0x8d, 0x9a, 0x21, 0x51, 0xd4, 0x5a, - 0x48, 0xa2, 0x25, 0xb7, 0x24, 0x97, 0xa0, 0xff, 0x3a, 0xdd, 0xb8, 0x89, 0xfe, 0xb1, 0xbd, 0x18, - 0x52, 0x82, 0x0e, 0x70, 0x6b, 0x74, 0xa3, 0x86, 0x79, 0x40, 0xb4, 0x00, 0x4d, 0x26, 0x60, 0x6c, - 0xfa, 0xae, 0x47, 0x2d, 0x83, 0x1a, 0x41, 0x13, 0x26, 0xda, 0x5e, 0x3f, 0xef, 0x38, 0xc7, 0xd5, - 0xc2, 0xb6, 0x31, 0x7a, 0xe4, 0x61, 0xad, 0xea, 0x56, 0x5d, 0xe2, 0xd1, 0x17, 0x5e, 0x82, 0x50, - 0x81, 0x8b, 0xf0, 0x50, 0xe8, 0x8b, 0x3f, 0xd2, 0x1f, 0xf4, 0xce, 0x14, 0x75, 0xd7, 0x3c, 0xfb, - 0x6d, 0xe0, 0xc4, 0x7d, 0x60, 0xf1, 0xc0, 0x7b, 0xb4, 0x42, 0x5f, 0x83, 0x53, 0xaa, 0x48, 0x9d, - 0xa0, 0x50, 0xab, 0xd0, 0xac, 0x75, 0x4e, 0x50, 0x33, 0x38, 0x85, 0x7f, 0x55, 0xab, 0x25, 0xb7, - 0x64, 0x23, 0x57, 0x40, 0x24, 0x87, 0x70, 0x1c, 0xb9, 0x3e, 0x03, 0xac, 0x7c, 0x20, 0x13, 0x91, - 0x77, 0x42, 0x6e, 0xca, 0x6e, 0xea, 0xa6, 0xef, 0xd7, 0x84, 0x73, 0x6c, 0xf0, 0x5c, 0xc4, 0x68, - 0x82, 0x82, 0xf1, 0x17, 0x0f, 0xc6, 0x2e, 0x1b, 0x08, 0xf9, 0xfb, 0x0d, 0x98, 0x0d, 0xad, 0xc9, - 0x44, 0xc4, 0x86, 0x61, 0xf1, 0x38, 0x71, 0xb1, 0x0a, 0xb8, 0xb5, 0x7d, 0xce, 0x97, 0x51, 0x67, - 0xb5, 0xba, 0x1c, 0x69, 0xc7, 0x37, 0xb9, 0xf8, 0x76, 0xfe, 0xc7, 0xf0, 0x2b, 0x56, 0x2d, 0xca, - 0x5f, 0x12, 0x02, 0x2e, 0x01, 0x83, 0x71, 0x21, 0xe0, 0x1a, 0x20, 0x13, 0x91, 0x69, 0x18, 0xc7, - 0x32, 0x96, 0xc1, 0x46, 0x97, 0xa9, 0xc4, 0x10, 0x9a, 0xfc, 0x78, 0x5f, 0xc7, 0x2b, 0x5f, 0xb2, - 0x8f, 0xab, 0xd5, 0x05, 0x5a, 0x8b, 0xb7, 0x60, 0xf3, 0xc8, 0x8d, 0xb9, 0x12, 0x5e, 0xf4, 0xf5, - 0xf3, 0x79, 0xc4, 0x6a, 0xe8, 0x1a, 0x83, 0x91, 0xdb, 0x30, 0xc4, 0xe6, 0xb5, 0x60, 0x98, 0x8c, - 0xa0, 0xa6, 0x14, 0x15, 0x29, 0x24, 0x4c, 0x7d, 0xc2, 0x73, 0x22, 0x44, 0x68, 0x11, 0x46, 0xca, - 0x71, 0xf5, 0xe8, 0x81, 0x1f, 0x57, 0x9f, 0x7d, 0x05, 0x48, 0xbc, 0x23, 0x76, 0x75, 0x0d, 0xf9, - 0x37, 0xe0, 0x5c, 0xb7, 0x17, 0x61, 0xbb, 0x2c, 0xdc, 0xca, 0x72, 0xc7, 0x84, 0xfe, 0x30, 0x0f, - 0xf1, 0x1d, 0xaa, 0x89, 0xff, 0xc9, 0x8b, 0x30, 0x2a, 0x82, 0x1a, 0xd8, 0x3c, 0xb5, 0xaa, 0xbb, - 0xab, 0x62, 0x72, 0x47, 0x0f, 0x1b, 0x05, 0xa5, 0x0d, 0x73, 0xc0, 0x75, 0xba, 0x31, 0xab, 0xbb, - 0xab, 0xc5, 0xbf, 0xdd, 0x0b, 0xf9, 0x39, 0xd3, 0x6a, 0xdf, 0x3d, 0x9e, 0xbc, 0x0e, 0x68, 0xf2, - 0x52, 0x66, 0x9a, 0xdc, 0x4e, 0x66, 0x9a, 0x27, 0x60, 0x00, 0x3b, 0x01, 0x25, 0xd6, 0x17, 0x1e, - 0x86, 0x35, 0x18, 0x90, 0xcf, 0x1c, 0x21, 0x01, 0x69, 0xaa, 0xf3, 0x06, 0xf7, 0xfd, 0x7a, 0xc6, - 0x7f, 0xbd, 0x4e, 0xfd, 0x79, 0x30, 0xb3, 0xc6, 0xc0, 0x0e, 0x66, 0x8d, 0x03, 0x18, 0x19, 0xbf, - 0x9c, 0x0d, 0x0f, 0xd9, 0x8e, 0x7a, 0xe4, 0xcb, 0x41, 0x98, 0x9e, 0x1f, 0x86, 0x81, 0xb0, 0xf4, - 0xdc, 0xf6, 0xb9, 0xfc, 0x1f, 0x15, 0x47, 0xbf, 0x67, 0xfc, 0x50, 0x0a, 0xac, 0x3d, 0xa7, 0xa4, - 0xda, 0x0f, 0x39, 0x92, 0xd7, 0xa1, 0x6f, 0x9a, 0xfb, 0xf9, 0xed, 0x20, 0x8f, 0x7f, 0x50, 0x9e, - 0x2f, 0x64, 0x6e, 0xb7, 0x62, 0xd5, 0x81, 0x39, 0x3b, 0x65, 0x3c, 0xf6, 0xed, 0x63, 0x3c, 0xbe, - 0x1f, 0x86, 0x2a, 0xba, 0xe3, 0x99, 0x75, 0xb3, 0xa5, 0x33, 0x3b, 0xb3, 0x1f, 0x4d, 0x35, 0x7e, - 0xf7, 0x29, 0xc1, 0x65, 0x43, 0x5a, 0xa6, 0x2f, 0xfe, 0x51, 0x0a, 0xc6, 0x63, 0x85, 0xa4, 0xc9, - 0x33, 0x00, 0x1c, 0x22, 0x15, 0x55, 0xe3, 0xde, 0xf8, 0x41, 0x71, 0x69, 0xb1, 0x35, 0x0a, 0xc9, - 0xc8, 0x15, 0xe8, 0xe7, 0xbf, 0x44, 0xb9, 0x88, 0x78, 0x93, 0x76, 0xdb, 0x34, 0xb4, 0x80, 0x28, - 0x7c, 0x0a, 0x3a, 0xcb, 0x64, 0x12, 0x9b, 0x78, 0x1b, 0xad, 0xe0, 0x29, 0xe8, 0x28, 0x33, 0x09, - 0xc3, 0xf3, 0x33, 0xa5, 0x5b, 0xa6, 0x3e, 0xe1, 0xd8, 0xeb, 0xae, 0xb8, 0xf4, 0xee, 0xe7, 0x61, - 0xb8, 0xac, 0xdd, 0x1d, 0x53, 0xaf, 0x2d, 0x71, 0x94, 0xec, 0xa8, 0x13, 0x69, 0x53, 0xfc, 0x44, - 0x1a, 0x86, 0x82, 0xaf, 0x2e, 0x19, 0x87, 0x35, 0x84, 0x72, 0xa2, 0xb0, 0x77, 0x66, 0xbb, 0xc2, - 0xde, 0xca, 0xac, 0x2a, 0x2a, 0x79, 0x1f, 0x5c, 0x8a, 0x9a, 0x4f, 0xa5, 0x61, 0x34, 0x78, 0xea, - 0x21, 0x3a, 0x77, 0xdc, 0x43, 0x22, 0xf9, 0x64, 0x0a, 0xf2, 0x13, 0x66, 0xa3, 0x61, 0x5a, 0x2b, - 0x65, 0x6b, 0xd9, 0x76, 0x9a, 0x68, 0xb3, 0x1d, 0xde, 0x1d, 0x60, 0xf1, 0xab, 0x53, 0x30, 0x2e, - 0x5e, 0x68, 0x52, 0x77, 0x8c, 0xc3, 0xbb, 0x60, 0x51, 0xdf, 0xe4, 0xf0, 0xf4, 0xa5, 0xf8, 0x43, - 0x69, 0x80, 0x39, 0xbb, 0xbe, 0x76, 0xc4, 0x33, 0xdc, 0xbc, 0x08, 0x39, 0x9e, 0x23, 0x40, 0x68, - 0xec, 0xb8, 0xc8, 0xe4, 0xc2, 0x3e, 0x8d, 0x23, 0x26, 0xc6, 0xc4, 0xf2, 0x92, 0xe3, 0x69, 0x06, - 0xf2, 0x29, 0x4d, 0x34, 0x61, 0x0f, 0x65, 0x74, 0x62, 0xfd, 0x0b, 0x1e, 0xca, 0x60, 0xd1, 0x87, - 0x6e, 0x6d, 0x16, 0xb2, 0x0d, 0xbb, 0xbe, 0xa6, 0x21, 0x7d, 0xf1, 0xbf, 0xa7, 0xb8, 0xec, 0x8e, - 0x78, 0x9e, 0x2e, 0xff, 0xf3, 0xb3, 0xbb, 0xfc, 0xfc, 0xaf, 0x4d, 0xc1, 0x49, 0x8d, 0xd6, 0xd9, - 0x6e, 0x66, 0x63, 0xd2, 0x36, 0xe8, 0xab, 0xd4, 0xa2, 0xce, 0x61, 0x8d, 0xa8, 0x1f, 0x4d, 0xc1, - 0x98, 0xfc, 0x32, 0x37, 0x5d, 0x7a, 0x38, 0x6b, 0x52, 0x68, 0xde, 0x67, 0x76, 0xe1, 0xc9, 0xf2, - 0x97, 0x7d, 0x90, 0x4f, 0xdc, 0x98, 0x1f, 0x59, 0xeb, 0xb4, 0xe3, 0x69, 0x4b, 0xf6, 0xa0, 0x4e, - 0x5b, 0x7a, 0x77, 0x77, 0xda, 0x92, 0xdb, 0xed, 0x69, 0x4b, 0xdf, 0x4e, 0x4e, 0x5b, 0xb6, 0xdb, - 0x35, 0x75, 0xea, 0xd4, 0x3d, 0xee, 0x9a, 0x22, 0xf6, 0xfa, 0xc0, 0x5b, 0x69, 0xaf, 0xc3, 0xc1, - 0xda, 0xeb, 0x7b, 0x39, 0x24, 0xba, 0xc8, 0x26, 0xc5, 0xba, 0xed, 0x18, 0xd4, 0x10, 0x67, 0x43, - 0x78, 0x6d, 0xec, 0x08, 0x98, 0x16, 0x60, 0xc9, 0xb3, 0x8a, 0x0d, 0x3f, 0x8c, 0x36, 0x3c, 0xb2, - 0x97, 0x6d, 0xf8, 0xa8, 0xe5, 0xae, 0x98, 0x3a, 0x23, 0x7b, 0x37, 0x75, 0x0e, 0x60, 0x63, 0xfa, - 0x27, 0x39, 0x38, 0x9d, 0xb0, 0xc3, 0x3e, 0xb2, 0xc3, 0xff, 0xe0, 0xce, 0x4b, 0xf6, 0x32, 0xea, - 0x23, 0x27, 0x1f, 0xb9, 0xed, 0x4e, 0x3e, 0xd6, 0xd4, 0x31, 0xdc, 0x87, 0x63, 0xf8, 0xe9, 0x2e, - 0x27, 0x1f, 0x07, 0x34, 0x82, 0xfb, 0xdf, 0xca, 0x11, 0x3c, 0xf0, 0x96, 0x8e, 0x60, 0xd8, 0xed, - 0x08, 0x1e, 0xdc, 0xd5, 0x08, 0x1e, 0xda, 0xc9, 0x08, 0x3e, 0x80, 0x71, 0xf7, 0x5f, 0xd3, 0x30, - 0x2e, 0x9d, 0x90, 0x1e, 0x80, 0x19, 0x5c, 0x82, 0x51, 0x89, 0x21, 0x6e, 0xcc, 0xd3, 0x61, 0x20, - 0x4a, 0x9d, 0x3a, 0x9e, 0x1a, 0xc7, 0xa2, 0xd2, 0xb3, 0xc7, 0x97, 0x0d, 0x6a, 0x79, 0xa6, 0xb7, - 0x21, 0x86, 0x5f, 0xf0, 0x78, 0x1f, 0xce, 0x05, 0x69, 0x8a, 0x5f, 0x5a, 0x40, 0x2f, 0xf9, 0x62, - 0x67, 0xf7, 0xe0, 0x8b, 0xbd, 0x06, 0x27, 0xa5, 0x97, 0x29, 0xb5, 0xbd, 0x55, 0xdb, 0x61, 0x6f, - 0xd1, 0x2b, 0x4a, 0x47, 0xfa, 0xbc, 0x12, 0x68, 0xc4, 0xf1, 0x76, 0x88, 0xa9, 0xe9, 0x3e, 0x4a, - 0x4b, 0x64, 0x5a, 0xfc, 0xde, 0x54, 0xf2, 0xd3, 0xc8, 0x05, 0xc8, 0xa2, 0xdc, 0x52, 0xe1, 0x92, - 0xad, 0x88, 0x0c, 0xf1, 0xe4, 0x89, 0x60, 0x71, 0x4f, 0x87, 0x59, 0x3e, 0xf8, 0xa2, 0x2e, 0x67, - 0x55, 0x13, 0xcb, 0xfb, 0x2b, 0x30, 0x24, 0xae, 0x2d, 0xaf, 0xd3, 0x8d, 0xa0, 0x88, 0xf7, 0xb9, - 0xad, 0xcd, 0x42, 0x5e, 0xdc, 0x6b, 0xe2, 0x79, 0x76, 0xc4, 0xcd, 0x3d, 0xd2, 0xa2, 0xf8, 0x3d, - 0x29, 0xb8, 0xa0, 0x51, 0x8b, 0xae, 0xeb, 0x4b, 0x0d, 0xf9, 0x5c, 0x5d, 0x58, 0xbe, 0x6c, 0x3e, - 0x33, 0xdd, 0xa6, 0xee, 0xd5, 0x57, 0xf7, 0xa5, 0x41, 0x33, 0x30, 0x24, 0x4f, 0xd0, 0xbb, 0x98, - 0xbc, 0x23, 0xed, 0x8a, 0x5f, 0xc8, 0x42, 0xdf, 0x84, 0xed, 0x5d, 0xb3, 0x4d, 0x6b, 0x5f, 0xef, - 0x13, 0x9a, 0xb4, 0xe9, 0x5d, 0x9c, 0x58, 0x3f, 0x85, 0x0f, 0xc7, 0xb9, 0x42, 0x8a, 0xca, 0x5c, - 0xb2, 0xbd, 0x58, 0x7e, 0x38, 0x41, 0xc6, 0xba, 0x53, 0x64, 0x1f, 0xc9, 0x86, 0xdd, 0xa9, 0x26, - 0x1f, 0x0f, 0xf2, 0x90, 0x3c, 0x0f, 0x03, 0x98, 0xf2, 0x41, 0x72, 0x57, 0xc0, 0x00, 0x4b, 0x9e, - 0x24, 0x42, 0x79, 0x46, 0x48, 0x4a, 0x3e, 0x14, 0x29, 0xf5, 0xe8, 0x17, 0x9d, 0x55, 0x27, 0xc9, - 0xaa, 0xe7, 0xb4, 0xeb, 0xde, 0x44, 0xb1, 0x53, 0xad, 0xc7, 0x3f, 0xdf, 0x2c, 0xe4, 0x38, 0x4d, - 0xa4, 0xea, 0xe3, 0x33, 0xdc, 0xd3, 0x0d, 0xdf, 0xa9, 0x2f, 0x1c, 0xf5, 0x78, 0x91, 0xa5, 0xbc, - 0x52, 0x40, 0xa8, 0xac, 0x95, 0xfd, 0xfb, 0x58, 0x2b, 0x27, 0x61, 0x78, 0xc2, 0xf6, 0xa4, 0x60, - 0xbe, 0x81, 0x30, 0x71, 0x20, 0x93, 0x7c, 0x72, 0x24, 0x5f, 0xb4, 0x0d, 0xb9, 0x04, 0xbd, 0x58, - 0x45, 0x40, 0x4c, 0xf1, 0x78, 0x9e, 0x88, 0x65, 0x06, 0xe4, 0x1c, 0x22, 0xbc, 0x2a, 0xc6, 0xb7, - 0xf6, 0xc2, 0x90, 0xdf, 0xf2, 0x90, 0xd4, 0xec, 0x49, 0xc8, 0xcd, 0xda, 0x52, 0x51, 0x7e, 0x8c, - 0x15, 0x5c, 0xb5, 0x5d, 0x25, 0x08, 0x52, 0x10, 0xb1, 0x0e, 0xba, 0x61, 0x1b, 0x72, 0xa4, 0x2b, - 0x76, 0x90, 0x65, 0x1b, 0xb1, 0xdc, 0x94, 0x01, 0x21, 0x9b, 0x8f, 0x30, 0x48, 0x58, 0x72, 0x8a, - 0x51, 0x02, 0x83, 0x11, 0x2f, 0x29, 0x70, 0x6e, 0xb7, 0x0a, 0xdc, 0xb7, 0x57, 0x05, 0xee, 0x3f, - 0x58, 0x05, 0xfe, 0x00, 0x0c, 0xe1, 0x93, 0xa6, 0xef, 0xb6, 0x4c, 0x87, 0xba, 0x3b, 0x30, 0x22, - 0x1e, 0x10, 0x46, 0xc4, 0x30, 0x7f, 0x6f, 0xca, 0x1b, 0xa2, 0xed, 0x10, 0x61, 0xa5, 0xa8, 0x39, - 0xec, 0x43, 0xcd, 0x4f, 0x42, 0x2f, 0x2f, 0xd5, 0x32, 0xc8, 0x97, 0x73, 0xfc, 0x11, 0xd6, 0x87, - 0x19, 0x92, 0xea, 0xc3, 0x14, 0x7f, 0x3d, 0x05, 0x7d, 0x37, 0xad, 0x35, 0xcb, 0x5e, 0xdf, 0x9f, - 0x76, 0x3e, 0x03, 0x83, 0x82, 0x8d, 0xb4, 0xa4, 0xa3, 0xa3, 0x4b, 0x9b, 0x83, 0x6b, 0xc8, 0x49, - 0x93, 0xa9, 0xc8, 0x4b, 0x41, 0xa3, 0xc9, 0xa0, 0xf8, 0x0c, 0xbf, 0x5a, 0xf0, 0x1b, 0xd5, 0xa3, - 0x55, 0xec, 0x65, 0x72, 0x72, 0x0e, 0xb2, 0x53, 0xec, 0x55, 0xa5, 0xc4, 0x41, 0x3c, 0xfe, 0x9e, - 0x41, 0x8b, 0x1f, 0xcf, 0xc2, 0x88, 0x72, 0xe9, 0xf0, 0x4e, 0x18, 0x10, 0x87, 0xfe, 0xa6, 0x5f, - 0x56, 0x1f, 0x3d, 0x5d, 0x02, 0xa0, 0xd6, 0xcf, 0xff, 0x2c, 0x1b, 0xe4, 0xfd, 0xd0, 0x67, 0xbb, - 0x68, 0x89, 0xe0, 0xb7, 0x8c, 0x84, 0xc3, 0x6d, 0xa1, 0xca, 0xde, 0x9d, 0x0f, 0x24, 0x41, 0x22, - 0x6b, 0xaf, 0xed, 0xe2, 0xa7, 0x3d, 0x0b, 0x03, 0xba, 0xeb, 0x52, 0xaf, 0xe6, 0xe9, 0x2b, 0x72, - 0xa5, 0xfd, 0x00, 0x28, 0x8f, 0x24, 0x04, 0x2e, 0xea, 0x2b, 0xe4, 0x15, 0x18, 0xae, 0x3b, 0x14, - 0x6d, 0x15, 0xbd, 0x11, 0x66, 0xc9, 0x10, 0x5e, 0x3d, 0x12, 0x42, 0x5e, 0x83, 0x43, 0x44, 0xd9, - 0x20, 0xb7, 0x60, 0x58, 0x7c, 0x0e, 0x0f, 0xe8, 0xc5, 0x41, 0x39, 0x12, 0xae, 0x8e, 0x5c, 0x24, - 0x3c, 0xa4, 0x57, 0xc4, 0x75, 0xcb, 0xe4, 0x32, 0x5f, 0x43, 0x22, 0x25, 0x0b, 0x40, 0xd6, 0xe9, - 0x12, 0xda, 0x2c, 0xec, 0x59, 0xbc, 0x50, 0x34, 0x8e, 0xe3, 0x7e, 0x1e, 0x0c, 0x1d, 0xc7, 0xca, - 0x31, 0xe2, 0xeb, 0x74, 0xa9, 0x14, 0x41, 0x92, 0xdb, 0x70, 0x2a, 0xde, 0x84, 0x7d, 0x32, 0x77, - 0xfb, 0x78, 0x74, 0x6b, 0xb3, 0x50, 0x48, 0x24, 0x90, 0xd8, 0x9e, 0x88, 0xb1, 0x2d, 0x1b, 0xd7, - 0xb2, 0xfd, 0x7d, 0x63, 0xfd, 0xda, 0x08, 0x6b, 0xeb, 0xdb, 0xed, 0xa6, 0x51, 0xfc, 0xb9, 0x14, - 0xb3, 0xcf, 0xd9, 0x07, 0x4d, 0x33, 0x41, 0xec, 0x2b, 0x81, 0xc5, 0x85, 0x20, 0xcf, 0x55, 0xe2, - 0x4c, 0x1c, 0x64, 0xb6, 0xba, 0x0c, 0x39, 0x43, 0xbe, 0x6c, 0x38, 0x1d, 0xed, 0x04, 0xff, 0x39, - 0x9a, 0xa0, 0x22, 0x17, 0x45, 0xd2, 0xa3, 0x6c, 0x67, 0x83, 0x86, 0xa7, 0x39, 0x2a, 0x7e, 0x49, - 0x1a, 0x86, 0xa4, 0xaf, 0xb9, 0xba, 0xaf, 0xcf, 0x79, 0xef, 0xce, 0x5e, 0xd3, 0x77, 0x36, 0xc7, - 0x23, 0x28, 0xff, 0x95, 0xc3, 0x94, 0x5f, 0xbd, 0xbb, 0x48, 0xf9, 0xe5, 0x67, 0x77, 0xca, 0xed, - 0x2e, 0xbb, 0xd3, 0xb5, 0x6c, 0x7f, 0x7a, 0x2c, 0x73, 0x2d, 0xdb, 0x9f, 0x1d, 0xeb, 0xc5, 0x82, - 0x10, 0x58, 0x44, 0x96, 0x1f, 0x69, 0x5a, 0xcb, 0xe6, 0xca, 0x11, 0x0f, 0x29, 0x3f, 0xd8, 0x62, - 0x19, 0x8a, 0x6c, 0x8e, 0x78, 0x7c, 0xf9, 0x5b, 0x2a, 0x9b, 0xa3, 0x1d, 0x6c, 0x7e, 0x80, 0xb2, - 0xf9, 0x8d, 0x14, 0xe4, 0x13, 0x65, 0x53, 0x3a, 0x24, 0xff, 0xe3, 0x03, 0xcb, 0x75, 0x5f, 0xfc, - 0xdd, 0x34, 0x8c, 0x97, 0x2d, 0x8f, 0xae, 0xf0, 0x8d, 0xe8, 0x11, 0x9f, 0x2a, 0xae, 0xc3, 0xa0, - 0xf4, 0x31, 0xa2, 0xcf, 0x83, 0xba, 0x68, 0x12, 0xaa, 0x53, 0x5d, 0x34, 0x89, 0xe4, 0xe0, 0x02, - 0xf4, 0x55, 0x21, 0x1f, 0xf1, 0x39, 0xe7, 0x68, 0x08, 0xf9, 0x88, 0x4f, 0x5e, 0xf7, 0xa8, 0x90, - 0x3f, 0x95, 0x81, 0x13, 0x09, 0x0f, 0x27, 0x17, 0xc2, 0xf4, 0xfa, 0xa9, 0x30, 0x52, 0xcf, 0x4f, - 0xaf, 0x1f, 0x24, 0xd5, 0x27, 0x8b, 0x98, 0x73, 0x6b, 0xa1, 0x3c, 0x35, 0x29, 0xa4, 0x5a, 0x94, - 0x92, 0xba, 0x32, 0x70, 0xd2, 0x97, 0x05, 0x79, 0xb9, 0x6c, 0xd3, 0xa8, 0x2b, 0x79, 0xb9, 0x58, - 0x1b, 0xf2, 0x45, 0x30, 0x50, 0x7a, 0xb3, 0xed, 0x50, 0xe4, 0x9b, 0x89, 0xba, 0xd6, 0x06, 0x88, - 0x24, 0xce, 0x3c, 0xc5, 0x18, 0xa3, 0x50, 0x79, 0x87, 0x0c, 0xc9, 0x02, 0xe4, 0x5e, 0x35, 0xbd, - 0xd9, 0xf6, 0x92, 0xe8, 0x85, 0xa0, 0x46, 0x04, 0x87, 0x26, 0xf1, 0xc5, 0x1d, 0x3c, 0xaf, 0x75, - 0x27, 0xef, 0x81, 0x78, 0x03, 0x32, 0x07, 0xbd, 0xa5, 0xdb, 0x55, 0xad, 0x24, 0x7a, 0xe2, 0x61, - 0x39, 0xaf, 0x6d, 0xa9, 0x23, 0x3b, 0x4c, 0x07, 0xa5, 0xcb, 0x87, 0x29, 0x48, 0x5f, 0xfc, 0x44, - 0x0a, 0xce, 0x76, 0x16, 0x1e, 0x79, 0x0a, 0xfa, 0x34, 0xbb, 0x41, 0x4b, 0xda, 0x0d, 0xd1, 0x33, - 0x28, 0x4d, 0xb6, 0xc9, 0xad, 0xe9, 0x8e, 0xbc, 0x17, 0xf1, 0xc9, 0xc8, 0xfb, 0x60, 0xb0, 0xec, - 0xba, 0x6d, 0xea, 0x54, 0x9f, 0xb9, 0xa9, 0x95, 0xc5, 0x96, 0x15, 0xb7, 0x44, 0x26, 0x82, 0x6b, - 0xee, 0x33, 0x4a, 0x01, 0x0a, 0x99, 0xbe, 0xf8, 0x35, 0x29, 0x38, 0xd7, 0x4d, 0xe8, 0xe4, 0x19, - 0xe8, 0x5f, 0xa4, 0x96, 0x6e, 0x79, 0xe5, 0x29, 0xf1, 0x4a, 0xb8, 0x03, 0xf4, 0x10, 0x16, 0xdd, - 0xc8, 0x04, 0x84, 0xac, 0x11, 0x3f, 0x6b, 0x0e, 0x7c, 0xdc, 0xf8, 0xb9, 0x38, 0xc2, 0x94, 0x46, - 0x3e, 0x61, 0xf1, 0x43, 0xf0, 0x40, 0xc7, 0x3e, 0x22, 0xef, 0x87, 0xa1, 0x05, 0x67, 0x45, 0xb7, - 0xcc, 0x37, 0xf9, 0x10, 0x4b, 0x85, 0xbb, 0x6c, 0x5b, 0x82, 0xcb, 0x3b, 0x3f, 0x99, 0xbe, 0xb8, - 0x04, 0xf9, 0x4e, 0x1d, 0x46, 0x66, 0x60, 0x04, 0x4b, 0x14, 0x96, 0xac, 0xfa, 0xaa, 0xed, 0x84, - 0xb2, 0xc7, 0x84, 0x08, 0x1e, 0xc3, 0xd4, 0x74, 0x44, 0x29, 0x7d, 0xa0, 0xb4, 0x2a, 0xfe, 0x62, - 0x1a, 0x86, 0x2a, 0x8d, 0xf6, 0x8a, 0x79, 0x10, 0x95, 0x63, 0xf7, 0x9a, 0x3b, 0x56, 0xae, 0xe6, - 0x99, 0xd9, 0x63, 0x35, 0xcf, 0x97, 0x20, 0xd7, 0xc2, 0xef, 0x50, 0xaf, 0x17, 0xf8, 0xd7, 0x75, - 0xba, 0x5e, 0xe0, 0x6d, 0xd8, 0xfc, 0x55, 0xdf, 0xc7, 0xfc, 0x15, 0xb6, 0x95, 0x04, 0x7a, 0x00, - 0x85, 0x63, 0x8f, 0x05, 0x2a, 0x0b, 0xf4, 0x00, 0xea, 0xc6, 0x1e, 0x0b, 0xd4, 0x2a, 0xfe, 0xd3, - 0x34, 0x8c, 0x44, 0x1f, 0x49, 0x9e, 0x82, 0x41, 0xfe, 0x18, 0x7e, 0xee, 0x96, 0x92, 0x82, 0x25, - 0x43, 0xb0, 0x06, 0xfc, 0x87, 0x38, 0x40, 0x1c, 0x5d, 0xd5, 0xdd, 0x5a, 0x78, 0x02, 0xc6, 0xdd, - 0x8a, 0xfa, 0xf9, 0xa9, 0xbc, 0x82, 0xd2, 0x46, 0x56, 0x75, 0x77, 0x32, 0xfc, 0x4d, 0xa6, 0x81, - 0x38, 0xb4, 0xed, 0xd2, 0x28, 0x03, 0xee, 0xee, 0xcb, 0x57, 0x8f, 0x18, 0x56, 0x1b, 0xe7, 0x30, - 0x99, 0xcd, 0x87, 0x83, 0xd7, 0x46, 0x65, 0xe8, 0xed, 0x7e, 0xe2, 0xfc, 0xe8, 0xd6, 0x66, 0xe1, - 0x94, 0x44, 0x9f, 0x7c, 0xe4, 0xcc, 0x09, 0xa6, 0x74, 0x4f, 0xe7, 0x87, 0x1e, 0x7e, 0x07, 0x14, - 0xbf, 0xe5, 0x1f, 0xa6, 0xa0, 0x77, 0xc1, 0xa2, 0x0b, 0xcb, 0xe4, 0x69, 0x18, 0x60, 0x1a, 0x33, - 0x67, 0xb3, 0xce, 0x4c, 0x09, 0xbf, 0x3e, 0x49, 0x95, 0x10, 0x31, 0xdb, 0xa3, 0x85, 0x54, 0xe4, - 0x59, 0x80, 0x30, 0x77, 0x86, 0x50, 0x3f, 0x22, 0xb7, 0xe1, 0x98, 0xd9, 0x1e, 0x4d, 0xa2, 0xf3, - 0x5b, 0x89, 0xcc, 0x03, 0x99, 0x78, 0x2b, 0x8e, 0xf1, 0x5b, 0x89, 0x01, 0x32, 0x07, 0x84, 0xfd, - 0xaa, 0xe8, 0xae, 0xbb, 0x6e, 0x3b, 0xc6, 0xe4, 0xaa, 0x6e, 0xad, 0x50, 0x75, 0x7b, 0x1a, 0xa7, - 0x98, 0xed, 0xd1, 0x12, 0xda, 0x91, 0xf7, 0xc2, 0x90, 0x1c, 0x55, 0xa1, 0x3a, 0x23, 0xca, 0xb8, - 0xd9, 0x1e, 0x2d, 0x42, 0x4b, 0xde, 0x0d, 0x83, 0xe2, 0xf7, 0x35, 0x5b, 0x78, 0x3a, 0x49, 0x59, - 0x8b, 0x25, 0xd4, 0x6c, 0x8f, 0x26, 0x53, 0x4a, 0x0f, 0xad, 0x38, 0xa6, 0xe5, 0x09, 0x3f, 0x7a, - 0xf5, 0xa1, 0x88, 0x93, 0x1e, 0x8a, 0xbf, 0xc9, 0xfb, 0x60, 0x38, 0xa8, 0x79, 0x80, 0x71, 0xa2, - 0xfc, 0x26, 0xe2, 0x94, 0xd2, 0x98, 0x23, 0x67, 0x7b, 0xb4, 0x28, 0x35, 0xb9, 0x08, 0x39, 0x8d, - 0xba, 0xe6, 0x9b, 0xbe, 0x9f, 0xc2, 0x88, 0x34, 0xd0, 0xcd, 0x37, 0x99, 0x94, 0x04, 0x9e, 0xf5, - 0x4e, 0xe8, 0x75, 0x21, 0xee, 0x0d, 0x88, 0xf2, 0x94, 0x69, 0xcb, 0x60, 0xbd, 0x23, 0x79, 0xcd, - 0xbc, 0x12, 0x56, 0x82, 0xe0, 0x29, 0xa1, 0xf0, 0xb2, 0x20, 0x92, 0xdb, 0x4d, 0xc6, 0xce, 0xf6, - 0x68, 0x0a, 0xbd, 0x24, 0xd5, 0x29, 0xd3, 0x5d, 0x13, 0x75, 0xc5, 0x54, 0xa9, 0x32, 0x94, 0x24, - 0x55, 0xf6, 0x53, 0x7a, 0xf4, 0x0d, 0xea, 0xad, 0xdb, 0xce, 0x9a, 0xa8, 0x22, 0xa6, 0x3e, 0x5a, - 0x60, 0xa5, 0x47, 0x0b, 0x88, 0xfc, 0xe8, 0x30, 0x76, 0x2d, 0xf6, 0x68, 0xdd, 0xd3, 0xe5, 0x47, - 0xf3, 0xa3, 0x4e, 0xbf, 0x93, 0xe6, 0xa8, 0x7e, 0xc7, 0x0f, 0x4f, 0x53, 0x3b, 0x14, 0x71, 0x52, - 0x87, 0xe2, 0x6f, 0xf6, 0xd0, 0x8a, 0xed, 0x78, 0x33, 0xb6, 0xb3, 0xae, 0x3b, 0x46, 0x7e, 0x2c, - 0xfa, 0x50, 0x09, 0xc5, 0x1e, 0x2a, 0xfd, 0x64, 0x1d, 0xf4, 0xfa, 0xd3, 0x4f, 0xfb, 0xed, 0xc6, - 0xa3, 0x1d, 0x14, 0x62, 0x58, 0x07, 0x85, 0xbf, 0x48, 0x01, 0x32, 0xd5, 0xc9, 0x4a, 0x9e, 0x20, - 0xf9, 0x60, 0xf0, 0x86, 0x93, 0x95, 0xd9, 0x1e, 0x8d, 0x61, 0x48, 0x11, 0xb2, 0xd3, 0x77, 0x69, - 0x3d, 0x7f, 0x02, 0x29, 0x86, 0x7c, 0x0a, 0x06, 0x9b, 0xed, 0xd1, 0x10, 0xc7, 0xa6, 0x88, 0x6a, - 0x7b, 0xc9, 0xc5, 0x82, 0xd6, 0xf9, 0x93, 0xd1, 0x29, 0x22, 0x40, 0xb0, 0x29, 0x22, 0xf8, 0x41, - 0x66, 0x60, 0x8c, 0xdb, 0x95, 0x78, 0x10, 0x84, 0xd3, 0x75, 0xfe, 0x54, 0x74, 0xad, 0x51, 0xf1, - 0xb3, 0x3d, 0x5a, 0xac, 0x0d, 0x13, 0x57, 0xa9, 0xed, 0xad, 0x96, 0x3c, 0x9c, 0xdc, 0xf2, 0xa7, - 0x95, 0x54, 0xe1, 0x21, 0x8a, 0x89, 0x4b, 0xfa, 0x49, 0x16, 0xe0, 0x44, 0xa9, 0x5e, 0xa7, 0xae, - 0x2b, 0x52, 0x27, 0x8b, 0xc9, 0xea, 0x4c, 0x74, 0x67, 0x98, 0x40, 0x32, 0xdb, 0xa3, 0x25, 0xb5, - 0x24, 0x93, 0x30, 0xca, 0x26, 0x14, 0xa9, 0x7a, 0x76, 0x3e, 0x1f, 0x75, 0xda, 0x52, 0xd0, 0xb3, - 0x3d, 0x9a, 0xda, 0x82, 0x75, 0x22, 0xdb, 0x37, 0x88, 0xf6, 0x0f, 0x44, 0x3b, 0x31, 0xc4, 0xb0, - 0x4e, 0x0c, 0x7f, 0xf9, 0xad, 0xc4, 0xcc, 0x79, 0x36, 0xde, 0x2a, 0x9c, 0x39, 0xc3, 0x5f, 0x44, - 0x83, 0x93, 0xd1, 0xc2, 0xdf, 0xe2, 0xa9, 0x0f, 0x46, 0xbd, 0x4c, 0x92, 0x68, 0x66, 0x7b, 0xb4, - 0xc4, 0xb6, 0x71, 0x9e, 0xe2, 0x9d, 0xce, 0x75, 0xe3, 0x19, 0xbc, 0x5d, 0x62, 0x5b, 0xa2, 0x77, - 0x29, 0x50, 0x9e, 0x3f, 0x1f, 0xdd, 0x43, 0x76, 0x24, 0x9c, 0xed, 0xd1, 0xba, 0x94, 0x39, 0xbf, - 0xd9, 0xa1, 0x5a, 0x78, 0xfe, 0x21, 0x64, 0x7f, 0x5e, 0xda, 0xa2, 0xc6, 0x89, 0x66, 0x7b, 0xb4, - 0x0e, 0xb5, 0xc6, 0x6f, 0x76, 0x28, 0x26, 0x9d, 0x2f, 0x74, 0x65, 0x1b, 0xc8, 0xa3, 0x43, 0x29, - 0xea, 0x85, 0xc4, 0x3a, 0xcc, 0xf9, 0x87, 0xa3, 0xaa, 0x9b, 0x40, 0xc2, 0x54, 0x37, 0xa9, 0x82, - 0xf3, 0x42, 0x62, 0xe1, 0xe0, 0xfc, 0x23, 0x5d, 0x18, 0x06, 0xef, 0x98, 0x58, 0x72, 0x78, 0x21, - 0xb1, 0x72, 0x6f, 0xbe, 0x18, 0x65, 0x98, 0x40, 0xc2, 0x18, 0x26, 0xd5, 0xfc, 0x5d, 0x48, 0x2c, - 0xf0, 0x9a, 0x7f, 0xb4, 0x0b, 0xc3, 0xf0, 0x0d, 0x93, 0x4a, 0xc3, 0xbe, 0x3b, 0x52, 0x61, 0x35, - 0xff, 0x58, 0x74, 0xde, 0x90, 0x50, 0x6c, 0xde, 0x90, 0x6b, 0xb1, 0x4e, 0xc6, 0x8a, 0xb9, 0xe5, - 0x1f, 0x8f, 0x0e, 0x73, 0x05, 0xcd, 0x86, 0xb9, 0x5a, 0xfe, 0x6d, 0x32, 0x56, 0x70, 0x2a, 0x7f, - 0xa1, 0x13, 0x13, 0x44, 0x47, 0x99, 0xf0, 0xe4, 0xf8, 0xe5, 0x84, 0x8a, 0x47, 0xf9, 0x77, 0x44, - 0xe3, 0x8d, 0x62, 0x04, 0xb3, 0x3d, 0x5a, 0x42, 0x9d, 0x24, 0x2d, 0x39, 0x8f, 0x6c, 0xfe, 0x62, - 0x74, 0xd8, 0x26, 0xd1, 0xb0, 0x61, 0x9b, 0x98, 0x83, 0x76, 0x2e, 0x29, 0xc6, 0x33, 0x7f, 0x29, - 0x6a, 0x98, 0xc5, 0x29, 0x98, 0x61, 0x96, 0x10, 0x1b, 0xaa, 0x25, 0x27, 0x0f, 0xcd, 0xbf, 0xb3, - 0xeb, 0x1b, 0x22, 0x4d, 0xc2, 0x1b, 0xf2, 0x5c, 0x9a, 0xa1, 0xed, 0x74, 0xb3, 0xd5, 0xb0, 0x75, - 0x23, 0xff, 0xae, 0x44, 0xdb, 0x89, 0x23, 0x25, 0xdb, 0x89, 0x03, 0xd8, 0x2a, 0x2f, 0xc7, 0xde, - 0xe5, 0x9f, 0x88, 0xae, 0xf2, 0x32, 0x8e, 0xad, 0xf2, 0x91, 0x38, 0xbd, 0xc9, 0x58, 0x9c, 0x5a, - 0xfe, 0xc9, 0xa8, 0x02, 0x28, 0x68, 0xa6, 0x00, 0x6a, 0x64, 0xdb, 0x47, 0x3a, 0x47, 0x76, 0xe5, - 0x2f, 0x47, 0xcf, 0xc2, 0x3a, 0xd1, 0xcd, 0xf6, 0x68, 0x9d, 0xa3, 0xc3, 0xca, 0x09, 0x81, 0x5a, - 0xf9, 0x2b, 0x51, 0x05, 0x8b, 0x11, 0x30, 0x05, 0x8b, 0x87, 0x77, 0x95, 0x13, 0x22, 0xad, 0xf2, - 0x4f, 0x75, 0x64, 0x15, 0x7c, 0x73, 0x42, 0x7c, 0xd6, 0xb3, 0x72, 0xa8, 0x54, 0xfe, 0xe9, 0xe8, - 0x62, 0x17, 0x62, 0xd8, 0x62, 0x27, 0x85, 0x54, 0x3d, 0x2b, 0x07, 0x09, 0xe5, 0xaf, 0xc6, 0x5b, - 0x85, 0x4b, 0xa4, 0x14, 0x4c, 0xa4, 0x25, 0xc7, 0xd6, 0xe4, 0x9f, 0x89, 0x6a, 0x5d, 0x12, 0x0d, - 0xd3, 0xba, 0xc4, 0xb8, 0x9c, 0x99, 0x78, 0x88, 0x4c, 0xfe, 0x59, 0x75, 0x97, 0x1d, 0xc5, 0x33, - 0xcb, 0x27, 0x16, 0x56, 0xf3, 0x8a, 0x9a, 0xe4, 0x3c, 0xff, 0x9c, 0x72, 0xaf, 0x1e, 0xc1, 0x32, - 0xfb, 0x56, 0x49, 0x8a, 0xfe, 0x8a, 0x9a, 0x17, 0x3c, 0xff, 0x7c, 0x32, 0x87, 0x40, 0x57, 0xd4, - 0x3c, 0xe2, 0xaf, 0xa8, 0xa9, 0xb4, 0xf3, 0xef, 0x4e, 0xe6, 0x10, 0x48, 0x57, 0x4d, 0xbd, 0xfd, - 0xb4, 0x54, 0xd7, 0x35, 0xff, 0x9e, 0xa8, 0xe9, 0x18, 0x20, 0x98, 0xe9, 0x18, 0x56, 0x7f, 0x7d, - 0x5a, 0xaa, 0x87, 0x9a, 0x7f, 0x21, 0xd6, 0x24, 0x78, 0x59, 0xa9, 0x6a, 0xea, 0xd3, 0x52, 0x1d, - 0xd1, 0xfc, 0x7b, 0x63, 0x4d, 0x82, 0xb7, 0x93, 0xaa, 0x8d, 0x1a, 0xdd, 0x52, 0xbf, 0xe4, 0x5f, - 0x4c, 0xcc, 0x63, 0x91, 0x40, 0x39, 0xdb, 0xa3, 0x75, 0x4b, 0x21, 0xf3, 0x91, 0xce, 0x01, 0x47, - 0xf9, 0x97, 0xa2, 0x43, 0xb8, 0x13, 0x1d, 0x1b, 0xc2, 0x1d, 0x83, 0x96, 0xde, 0xa7, 0x24, 0xe9, - 0xcb, 0xbf, 0x2f, 0x3a, 0xc5, 0x45, 0x90, 0x6c, 0x8a, 0x53, 0x53, 0xfa, 0x45, 0xb2, 0xcf, 0xe5, - 0xdf, 0x1f, 0x9d, 0xe2, 0x64, 0x1c, 0x9b, 0xe2, 0x22, 0x99, 0xea, 0x26, 0x63, 0x49, 0xd1, 0xf2, - 0x2f, 0x47, 0xa7, 0x38, 0x05, 0xcd, 0xa6, 0x38, 0x35, 0x8d, 0xda, 0xfb, 0x94, 0xdc, 0x60, 0xf9, - 0x57, 0x92, 0xdf, 0x1f, 0x91, 0xf2, 0xfb, 0xf3, 0x4c, 0x62, 0x5a, 0x72, 0x92, 0xab, 0x7c, 0x29, - 0x3a, 0x7e, 0x93, 0x68, 0xd8, 0xf8, 0x4d, 0x4c, 0x90, 0xa5, 0x6e, 0x1c, 0x84, 0x56, 0x4d, 0x74, - 0xd9, 0x38, 0x84, 0xa6, 0x48, 0x02, 0x38, 0xb2, 0x47, 0xe6, 0x1b, 0xa1, 0xc9, 0x0e, 0x7b, 0x64, - 0x7f, 0x1b, 0xa4, 0xd0, 0xb3, 0xd9, 0x35, 0xe6, 0x3d, 0x9f, 0x9f, 0x8a, 0xce, 0xae, 0x31, 0x02, - 0x36, 0xbb, 0xc6, 0x7d, 0xee, 0x67, 0x60, 0x4c, 0x68, 0x11, 0x0f, 0x0a, 0x30, 0xad, 0x95, 0xfc, - 0xb4, 0x92, 0xd6, 0x40, 0xc1, 0xb3, 0xd9, 0x49, 0x85, 0xe1, 0x7a, 0xcd, 0x61, 0x93, 0x0d, 0xb3, - 0xb5, 0x64, 0xeb, 0x8e, 0x51, 0xa5, 0x96, 0x91, 0x9f, 0x51, 0xd6, 0xeb, 0x04, 0x1a, 0x5c, 0xaf, - 0x13, 0xe0, 0x98, 0xfb, 0x5a, 0x81, 0x6b, 0xbc, 0xa4, 0x50, 0xfe, 0x55, 0x64, 0x5b, 0xe8, 0xc4, - 0x56, 0x90, 0xcd, 0xf6, 0x68, 0x9d, 0x38, 0x30, 0x5b, 0x7d, 0x7e, 0xa3, 0xfa, 0xda, 0x5c, 0x90, - 0x57, 0xad, 0xe2, 0xd0, 0x96, 0xee, 0xd0, 0xfc, 0x6c, 0xd4, 0x56, 0x4f, 0x24, 0x62, 0xb6, 0x7a, - 0x22, 0x22, 0xce, 0xd6, 0x1f, 0x0b, 0xe5, 0x6e, 0x6c, 0xc3, 0x11, 0x91, 0xdc, 0x9a, 0xcd, 0x4e, - 0x51, 0x04, 0x13, 0xd0, 0x9c, 0x6d, 0xad, 0xe0, 0x49, 0xc5, 0xb5, 0xe8, 0xec, 0xd4, 0x99, 0x92, - 0xcd, 0x4e, 0x9d, 0xb1, 0x4c, 0xd5, 0xa3, 0x58, 0x3e, 0x06, 0xaf, 0x47, 0x55, 0x3d, 0x81, 0x84, - 0xa9, 0x7a, 0x02, 0x38, 0xce, 0x50, 0xa3, 0x2e, 0xf5, 0xf2, 0x73, 0xdd, 0x18, 0x22, 0x49, 0x9c, - 0x21, 0x82, 0xe3, 0x0c, 0x67, 0xa8, 0x57, 0x5f, 0xcd, 0xcf, 0x77, 0x63, 0x88, 0x24, 0x71, 0x86, - 0x08, 0x66, 0x9b, 0xcd, 0x28, 0x78, 0xa2, 0xdd, 0x58, 0xf3, 0xfb, 0xec, 0x46, 0x74, 0xb3, 0xd9, - 0x91, 0x90, 0x6d, 0x36, 0x3b, 0x22, 0xc9, 0xd7, 0xec, 0x38, 0x7e, 0x21, 0xbf, 0x80, 0x0f, 0xbc, - 0x1c, 0xda, 0x05, 0x3b, 0x69, 0x35, 0xdb, 0xa3, 0xed, 0x34, 0x3e, 0xe2, 0x5d, 0x81, 0x57, 0x6e, - 0xbe, 0x82, 0x8f, 0x1a, 0x0d, 0xce, 0x2a, 0x38, 0x78, 0xb6, 0x47, 0x0b, 0xfc, 0x76, 0xdf, 0x0d, - 0x83, 0xf8, 0x51, 0x65, 0xcb, 0xf4, 0xa6, 0x26, 0xf2, 0xaf, 0x45, 0xb7, 0x4c, 0x12, 0x8a, 0x6d, - 0x99, 0xa4, 0x9f, 0x6c, 0x12, 0xc7, 0x9f, 0x7c, 0x8a, 0x99, 0x9a, 0xc8, 0x6b, 0xd1, 0x49, 0x3c, - 0x82, 0x64, 0x93, 0x78, 0x04, 0x10, 0x3c, 0x77, 0xca, 0xb1, 0x5b, 0x53, 0x13, 0xf9, 0x6a, 0xc2, - 0x73, 0x39, 0x2a, 0x78, 0x2e, 0xff, 0x19, 0x3c, 0xb7, 0xba, 0xda, 0xf6, 0xa6, 0xd8, 0x37, 0x2e, - 0x26, 0x3c, 0xd7, 0x47, 0x06, 0xcf, 0xf5, 0x01, 0x6c, 0x2a, 0x44, 0x40, 0xc5, 0xb1, 0xd9, 0xa4, - 0x7d, 0xdd, 0x6c, 0x34, 0xf2, 0x37, 0xa3, 0x53, 0xa1, 0x8a, 0x67, 0x53, 0xa1, 0x0a, 0x63, 0xa6, - 0x27, 0x7f, 0x2b, 0xba, 0xd4, 0x5e, 0xc9, 0xdf, 0x8a, 0x9a, 0x9e, 0x21, 0x86, 0x99, 0x9e, 0xe1, - 0x2f, 0xdc, 0x5d, 0xb0, 0x5f, 0x1a, 0x5d, 0x76, 0xa8, 0xbb, 0x9a, 0xbf, 0xad, 0xec, 0x2e, 0x24, - 0x1c, 0xee, 0x2e, 0xa4, 0xdf, 0x64, 0x05, 0x1e, 0x8c, 0x2c, 0x34, 0xfe, 0xad, 0x4d, 0x95, 0xea, - 0x4e, 0x7d, 0x35, 0xff, 0x3a, 0xb2, 0x7a, 0x34, 0x71, 0xa9, 0x8a, 0x92, 0xce, 0xf6, 0x68, 0xdd, - 0x38, 0xe1, 0xb6, 0xfc, 0xb5, 0x39, 0x5e, 0x81, 0x43, 0xab, 0x4c, 0xfa, 0x9b, 0xd0, 0x0f, 0x28, - 0xdb, 0xf2, 0x38, 0x09, 0x6e, 0xcb, 0xe3, 0x60, 0xd2, 0x82, 0x87, 0x94, 0xad, 0xda, 0xbc, 0xde, - 0x60, 0xfb, 0x12, 0x6a, 0x54, 0xf4, 0xfa, 0x1a, 0xf5, 0xf2, 0x1f, 0x44, 0xde, 0x17, 0x3a, 0x6c, - 0xf8, 0x14, 0xea, 0xd9, 0x1e, 0x6d, 0x1b, 0x7e, 0xa4, 0x08, 0xd9, 0xea, 0xcc, 0x62, 0x25, 0xff, - 0xa1, 0xe8, 0xf9, 0x26, 0x83, 0xcd, 0xf6, 0x68, 0x88, 0x63, 0x56, 0xda, 0xcd, 0xd6, 0x8a, 0xa3, - 0x1b, 0x94, 0x1b, 0x5a, 0x68, 0xbb, 0x09, 0x03, 0xf4, 0x8b, 0xa2, 0x56, 0x5a, 0x27, 0x3a, 0x66, - 0xa5, 0x75, 0xc2, 0x31, 0x45, 0x8d, 0xd4, 0x00, 0xcf, 0x7f, 0x38, 0xaa, 0xa8, 0x11, 0x24, 0x53, - 0xd4, 0x68, 0xc5, 0xf0, 0xd7, 0xe1, 0x74, 0xb0, 0x9f, 0x17, 0xeb, 0x2f, 0xef, 0xb4, 0xfc, 0x47, - 0x90, 0xcf, 0x43, 0xb1, 0xcb, 0x80, 0x08, 0xd5, 0x6c, 0x8f, 0xd6, 0xa1, 0x3d, 0x5b, 0x71, 0xaf, - 0xb7, 0x97, 0xa8, 0x63, 0x51, 0x8f, 0x99, 0x54, 0xf2, 0x29, 0xe1, 0x17, 0x47, 0x57, 0xdc, 0x0e, - 0x64, 0x6c, 0xc5, 0xed, 0x80, 0x4a, 0x64, 0x2e, 0x84, 0xaa, 0x6f, 0xc3, 0x3c, 0x90, 0x69, 0x27, - 0x0e, 0x89, 0xcc, 0x85, 0xa5, 0xb6, 0xb4, 0x0d, 0xf3, 0xc0, 0x5a, 0xeb, 0xc4, 0x81, 0x5c, 0x84, - 0x5c, 0xb5, 0x3a, 0xaf, 0xb5, 0xad, 0x7c, 0x5d, 0x71, 0x47, 0x46, 0xe8, 0x6c, 0x8f, 0x26, 0xf0, - 0xcc, 0x0c, 0x9a, 0x6e, 0xe8, 0xae, 0x67, 0xd6, 0x5d, 0x1c, 0x31, 0xfe, 0x08, 0x31, 0xa2, 0x66, - 0x50, 0x12, 0x0d, 0x33, 0x83, 0x92, 0xe0, 0xcc, 0x5e, 0x9c, 0xd4, 0x5d, 0x57, 0xb7, 0x0c, 0x47, - 0x9f, 0xc0, 0x65, 0x82, 0x2a, 0x31, 0x86, 0x11, 0x2c, 0xb3, 0x17, 0xa3, 0x10, 0x3c, 0x7c, 0xf7, - 0x21, 0xbe, 0x99, 0xb3, 0xac, 0x1c, 0xbe, 0x2b, 0x78, 0x3c, 0x7c, 0x57, 0x60, 0x68, 0x77, 0xfa, - 0x30, 0x8d, 0xae, 0x98, 0x4c, 0x44, 0xf9, 0x15, 0xc5, 0xee, 0x54, 0x09, 0xd0, 0xee, 0x54, 0x81, - 0x91, 0x57, 0xf2, 0x97, 0xdb, 0xd5, 0x0e, 0xaf, 0x14, 0xae, 0xb2, 0xb1, 0x36, 0x6c, 0xfd, 0x0e, - 0x07, 0xc7, 0xd4, 0x86, 0xa5, 0x37, 0xed, 0xa9, 0x09, 0x5f, 0xea, 0x66, 0x74, 0xfd, 0xee, 0x48, - 0xc8, 0xd6, 0xef, 0x8e, 0x48, 0x36, 0xbb, 0xfa, 0x1b, 0xad, 0x55, 0xdd, 0xa1, 0xc6, 0x94, 0xe9, - 0xe0, 0xc9, 0xe2, 0x06, 0xdf, 0x1a, 0xbe, 0x11, 0x9d, 0x5d, 0xbb, 0x90, 0xb2, 0xd9, 0xb5, 0x0b, - 0x9a, 0x19, 0x79, 0xc9, 0x68, 0x8d, 0xea, 0x46, 0x7e, 0x2d, 0x6a, 0xe4, 0x75, 0xa6, 0x64, 0x46, - 0x5e, 0x67, 0x6c, 0xe7, 0xcf, 0xb9, 0xed, 0x98, 0x1e, 0xcd, 0x37, 0x76, 0xf2, 0x39, 0x48, 0xda, - 0xf9, 0x73, 0x10, 0xcd, 0x36, 0x84, 0x6a, 0x87, 0x34, 0xa3, 0x1b, 0xc2, 0x78, 0x37, 0xa8, 0x2d, - 0x98, 0xc5, 0x22, 0x82, 0x29, 0xf3, 0x56, 0xd4, 0x62, 0x11, 0x60, 0x66, 0xb1, 0x84, 0xe1, 0x96, - 0x91, 0xb8, 0xb8, 0xbc, 0x1d, 0x5d, 0x43, 0x65, 0x1c, 0x5b, 0x43, 0x23, 0x31, 0x74, 0xef, 0x8e, - 0x04, 0x72, 0xe4, 0x5b, 0x51, 0xab, 0x43, 0x42, 0x31, 0xab, 0x43, 0x0e, 0xf9, 0x98, 0x84, 0x51, - 0xbc, 0x05, 0xd7, 0xda, 0xc1, 0x3d, 0xce, 0x47, 0xa3, 0x9f, 0xa9, 0xa0, 0xd9, 0x67, 0x2a, 0xa0, - 0x08, 0x13, 0x31, 0x6d, 0x39, 0x1d, 0x98, 0x84, 0xe7, 0x83, 0x0a, 0x88, 0xcc, 0x01, 0xa9, 0x96, - 0xe6, 0xe7, 0xca, 0x46, 0x45, 0xbe, 0x22, 0x73, 0xa3, 0x27, 0xb0, 0x71, 0x8a, 0xd9, 0x1e, 0x2d, - 0xa1, 0x1d, 0x79, 0x03, 0xce, 0x09, 0xa8, 0xc8, 0x04, 0x52, 0x11, 0xe5, 0xce, 0xc5, 0x47, 0x7a, - 0x51, 0x47, 0xc1, 0x6e, 0xb4, 0xb3, 0x3d, 0x5a, 0x57, 0x5e, 0x9d, 0x9f, 0x25, 0xd6, 0x87, 0xf6, - 0x4e, 0x9e, 0x15, 0x2c, 0x12, 0x5d, 0x79, 0x75, 0x7e, 0x96, 0x90, 0xfb, 0x9d, 0x9d, 0x3c, 0x2b, - 0xe8, 0x84, 0xae, 0xbc, 0x88, 0x0b, 0x85, 0x6e, 0xf8, 0x52, 0xa3, 0x91, 0x5f, 0xc7, 0xc7, 0xbd, - 0x63, 0x27, 0x8f, 0x2b, 0xa1, 0xc1, 0xb9, 0x1d, 0x47, 0x36, 0x4b, 0x2f, 0xb4, 0xa8, 0x55, 0x8d, - 0x2c, 0x40, 0x77, 0xa3, 0xb3, 0x74, 0x8c, 0x80, 0xcd, 0xd2, 0x31, 0x20, 0x1b, 0x50, 0x72, 0x3c, - 0x50, 0x7e, 0x23, 0x3a, 0xa0, 0x64, 0x1c, 0x1b, 0x50, 0x91, 0xd8, 0xa1, 0x05, 0x38, 0xb1, 0xb0, - 0xe6, 0xe9, 0xbe, 0x05, 0xe9, 0x8a, 0xae, 0x7c, 0x53, 0xb9, 0x64, 0x8a, 0x93, 0xe0, 0x25, 0x53, - 0x1c, 0xcc, 0xc6, 0x08, 0x03, 0x57, 0x37, 0xac, 0xfa, 0x8c, 0x6e, 0x36, 0xda, 0x0e, 0xcd, 0xff, - 0xb5, 0xe8, 0x18, 0x51, 0xd0, 0x6c, 0x8c, 0x28, 0x20, 0xb6, 0x40, 0x33, 0x50, 0xc9, 0x75, 0xcd, - 0x15, 0x4b, 0xec, 0x2b, 0xdb, 0x0d, 0x2f, 0xff, 0xff, 0x8b, 0x2e, 0xd0, 0x49, 0x34, 0x6c, 0x81, - 0x4e, 0x82, 0xe3, 0xa9, 0x13, 0xeb, 0x05, 0xb6, 0x78, 0xc8, 0x77, 0x95, 0x7f, 0x5d, 0x39, 0x75, - 0x4a, 0xa0, 0xc1, 0x53, 0xa7, 0x04, 0x38, 0x5b, 0x1f, 0xb9, 0x4d, 0x36, 0x67, 0x06, 0x77, 0xd5, - 0xff, 0xff, 0xe8, 0xfa, 0xa8, 0xe2, 0xd9, 0xfa, 0xa8, 0xc2, 0xa2, 0x7c, 0x44, 0x17, 0xfc, 0x8d, - 0x4e, 0x7c, 0x02, 0xf9, 0xc7, 0xda, 0x90, 0x57, 0x65, 0x3e, 0x62, 0xa4, 0x7c, 0x49, 0xaa, 0x13, - 0xa3, 0x60, 0x78, 0xc4, 0x1a, 0x45, 0x19, 0x69, 0xf4, 0x8e, 0x49, 0xd7, 0xf3, 0x5f, 0xda, 0x91, - 0x11, 0x27, 0x88, 0x32, 0xe2, 0x30, 0xf2, 0x01, 0x38, 0x1d, 0xc2, 0xe6, 0x69, 0x73, 0x29, 0x98, - 0x99, 0xbe, 0x2c, 0x15, 0x35, 0x83, 0x93, 0xc9, 0x98, 0x19, 0x9c, 0x8c, 0x49, 0x62, 0x2d, 0x44, - 0xf7, 0xe5, 0xdb, 0xb0, 0x0e, 0x24, 0xd8, 0x81, 0x41, 0x12, 0x6b, 0x21, 0xcd, 0xaf, 0xd8, 0x86, - 0x75, 0x20, 0xd3, 0x0e, 0x0c, 0xc8, 0xc7, 0x52, 0x70, 0x21, 0x19, 0x55, 0x6a, 0x34, 0x66, 0x6c, - 0x27, 0xc4, 0xe5, 0xbf, 0x32, 0x15, 0x3d, 0x68, 0xd8, 0x59, 0xb3, 0xd9, 0x1e, 0x6d, 0x87, 0x0f, - 0x20, 0xef, 0x87, 0xe1, 0x52, 0xdb, 0x30, 0x3d, 0xbc, 0x78, 0x63, 0x86, 0xf3, 0x57, 0xa5, 0x94, - 0x2d, 0x8e, 0x8c, 0xc5, 0x2d, 0x8e, 0x0c, 0x20, 0xd7, 0x60, 0xbc, 0x4a, 0xeb, 0x6d, 0x4c, 0x63, - 0x41, 0x5b, 0xb6, 0xe3, 0x31, 0x1e, 0x5f, 0x9d, 0x8a, 0x4e, 0x62, 0x31, 0x0a, 0x36, 0x89, 0xc5, - 0x80, 0xe4, 0x56, 0xec, 0x56, 0x5e, 0x74, 0xe6, 0xd7, 0xa4, 0xba, 0x5e, 0xcb, 0x07, 0x7d, 0x99, - 0xdc, 0x9c, 0x54, 0x94, 0x5b, 0x74, 0xc1, 0xf5, 0x63, 0xa9, 0x2e, 0xd7, 0xe8, 0xd2, 0x0c, 0x17, - 0x07, 0x33, 0x8e, 0x91, 0xbb, 0x6b, 0xc1, 0xf1, 0x6b, 0x53, 0x5d, 0xae, 0xbd, 0x43, 0x8e, 0x09, - 0x60, 0xf2, 0x1c, 0xf7, 0x14, 0x11, 0x8c, 0xbe, 0x2e, 0x15, 0x77, 0x15, 0x09, 0xda, 0x4b, 0x84, - 0xac, 0xd9, 0x4d, 0x37, 0x50, 0xfa, 0x8f, 0xa7, 0xe2, 0xbe, 0x79, 0x61, 0xb3, 0xf0, 0x17, 0xa1, - 0x70, 0x76, 0xfa, 0xae, 0x47, 0x1d, 0x4b, 0x6f, 0x60, 0x77, 0x56, 0x3d, 0xdb, 0xd1, 0x57, 0xe8, - 0xb4, 0xa5, 0x2f, 0x35, 0x68, 0xfe, 0x13, 0xa9, 0xa8, 0x05, 0xdb, 0x99, 0x94, 0x59, 0xb0, 0x9d, - 0xb1, 0x64, 0x15, 0x1e, 0x4c, 0xc2, 0x4e, 0x99, 0x2e, 0x3e, 0xe7, 0x93, 0xa9, 0xa8, 0x09, 0xdb, - 0x85, 0x96, 0x99, 0xb0, 0x5d, 0xd0, 0xe4, 0x2a, 0x0c, 0x4c, 0xd8, 0xfe, 0xf4, 0xfb, 0xf5, 0x8a, - 0x33, 0x64, 0x80, 0x99, 0xed, 0xd1, 0x42, 0x32, 0xd1, 0x46, 0x0c, 0xea, 0x4f, 0xc5, 0xdb, 0x84, - 0x97, 0x4f, 0xc1, 0x0f, 0xd1, 0x46, 0x88, 0xfb, 0x6f, 0xc6, 0xdb, 0x84, 0x77, 0x5c, 0xc1, 0x0f, - 0x36, 0x93, 0xf0, 0x27, 0xce, 0xcf, 0x94, 0x98, 0xdd, 0x36, 0xb9, 0xaa, 0x37, 0x1a, 0xd4, 0x5a, - 0xa1, 0xf9, 0x4f, 0x2b, 0x33, 0x49, 0x32, 0x19, 0x9b, 0x49, 0x92, 0x31, 0xe4, 0x8b, 0xe0, 0xcc, - 0x2d, 0xbd, 0x61, 0x1a, 0x21, 0xce, 0x2f, 0xd5, 0x9f, 0xff, 0x4c, 0x2a, 0xba, 0x9b, 0xee, 0x40, - 0xc7, 0x76, 0xd3, 0x1d, 0x50, 0x64, 0x1e, 0x08, 0x2e, 0xa3, 0xc1, 0x6c, 0xc1, 0xd6, 0xe7, 0xfc, - 0xdf, 0x4a, 0x45, 0xed, 0xd4, 0x38, 0x09, 0xb3, 0x53, 0xe3, 0x50, 0x52, 0xeb, 0x5c, 0xf3, 0x30, - 0xff, 0x0d, 0xa9, 0xe8, 0x69, 0x4d, 0x27, 0xc2, 0xd9, 0x1e, 0xad, 0x73, 0xe1, 0xc4, 0x57, 0x61, - 0xac, 0x5a, 0x29, 0xcf, 0xcc, 0x4c, 0x57, 0x6f, 0x95, 0xa7, 0x30, 0x54, 0xc3, 0xc8, 0xff, 0x6d, - 0x65, 0xc5, 0x52, 0x09, 0xd8, 0x8a, 0xa5, 0xc2, 0xc8, 0x8b, 0x30, 0xc4, 0xde, 0x9f, 0x0d, 0x18, - 0xfc, 0xe4, 0xcf, 0xa6, 0xa2, 0xe6, 0x94, 0x8c, 0x64, 0xe6, 0x94, 0xfc, 0x9b, 0x54, 0xe1, 0x24, - 0x93, 0x62, 0xc5, 0xa1, 0xcb, 0xd4, 0xa1, 0x56, 0xdd, 0x1f, 0xd3, 0xdf, 0x98, 0x8a, 0x5a, 0x19, - 0x49, 0x44, 0xcc, 0xca, 0x48, 0x82, 0x93, 0x35, 0x38, 0xa7, 0x9e, 0x04, 0xc9, 0x71, 0xbd, 0xf9, - 0xbf, 0x93, 0x52, 0x8c, 0xe1, 0x2e, 0xc4, 0x68, 0x0c, 0x77, 0xc1, 0x13, 0x0b, 0xce, 0x8b, 0x63, - 0x15, 0xe1, 0x70, 0xa9, 0x3e, 0xed, 0x9b, 0xf8, 0xd3, 0x1e, 0x0f, 0x1d, 0x02, 0xbb, 0x50, 0xcf, - 0xf6, 0x68, 0xdd, 0xd9, 0x31, 0x3d, 0x8b, 0x57, 0xf6, 0xcb, 0x7f, 0x73, 0x2a, 0xd9, 0x23, 0x25, - 0xe2, 0xa6, 0x9c, 0x54, 0x12, 0xf0, 0x03, 0x9d, 0xea, 0xd2, 0xe5, 0xff, 0xae, 0x32, 0xde, 0x92, - 0xc9, 0xd8, 0x78, 0xeb, 0x50, 0xd8, 0xee, 0x1a, 0x8c, 0x73, 0xa5, 0xae, 0xe8, 0x38, 0x0c, 0xad, - 0x15, 0x6a, 0xe4, 0xbf, 0x45, 0x59, 0xed, 0x62, 0x14, 0xe8, 0xda, 0xa3, 0x02, 0xd9, 0xd4, 0x5d, - 0x6d, 0xe9, 0x96, 0x85, 0xc7, 0xac, 0xf9, 0xbf, 0xa7, 0x4c, 0xdd, 0x21, 0x0a, 0x1d, 0x77, 0x83, - 0x5f, 0x4c, 0x13, 0xba, 0xd5, 0x74, 0xcd, 0xff, 0x0f, 0x8a, 0x26, 0x74, 0x23, 0x66, 0x9a, 0xd0, - 0xb5, 0x40, 0xec, 0xad, 0x0e, 0x31, 0xf6, 0xf9, 0x6f, 0x55, 0x56, 0xe4, 0x44, 0x2a, 0xb6, 0x22, - 0x27, 0x87, 0xe8, 0xdf, 0xea, 0x10, 0x9f, 0x9e, 0xff, 0xb6, 0xee, 0x7c, 0xc3, 0x95, 0x3e, 0x39, - 0xbc, 0xfd, 0x56, 0x87, 0xd8, 0xee, 0xfc, 0xb7, 0x77, 0xe7, 0x1b, 0x3a, 0xf6, 0x25, 0x87, 0x86, - 0xd7, 0x3a, 0xc7, 0x45, 0xe7, 0xff, 0xbe, 0x3a, 0x75, 0x75, 0x20, 0xc4, 0xa9, 0xab, 0x53, 0x70, - 0xf5, 0x12, 0x3c, 0xc0, 0x35, 0xe4, 0x55, 0x47, 0x6f, 0xad, 0x56, 0xa9, 0xe7, 0x99, 0xd6, 0x8a, - 0xbf, 0x13, 0xfb, 0x8e, 0x94, 0x72, 0x3c, 0xd6, 0x89, 0x12, 0x8f, 0xc7, 0x3a, 0x21, 0x99, 0xf2, - 0xc6, 0x22, 0xa0, 0xf3, 0xff, 0x40, 0x51, 0xde, 0x18, 0x05, 0x53, 0xde, 0x78, 0xe0, 0xf4, 0xb5, - 0x84, 0x40, 0xdf, 0xfc, 0x3f, 0xec, 0xcc, 0x2b, 0x78, 0xbf, 0x84, 0xf8, 0xe0, 0x6b, 0x09, 0xf1, - 0xac, 0xf9, 0xff, 0xb1, 0x33, 0xaf, 0xd0, 0x07, 0x29, 0x1e, 0x06, 0xfb, 0x01, 0x38, 0xcd, 0x67, - 0xf3, 0x19, 0x6a, 0xd0, 0xc8, 0x87, 0x7e, 0xa7, 0x32, 0xf6, 0x93, 0xc9, 0xf0, 0xc8, 0x3d, 0x11, - 0x93, 0xc4, 0x5a, 0xbc, 0xeb, 0x3f, 0xda, 0x86, 0x75, 0xb8, 0x21, 0x48, 0xc6, 0xb0, 0xf5, 0x46, - 0x8e, 0x7e, 0xcb, 0x7f, 0x97, 0xb2, 0xde, 0xc8, 0x48, 0x74, 0xe7, 0x90, 0x43, 0xe5, 0x5e, 0x8c, - 0x46, 0x7a, 0xe5, 0xbf, 0x3b, 0xb1, 0x71, 0xd0, 0x01, 0xd1, 0xb0, 0xb0, 0x17, 0xa3, 0x51, 0x4d, - 0xf9, 0xef, 0x49, 0x6c, 0x1c, 0x7c, 0x40, 0x34, 0x04, 0x8a, 0x6d, 0x91, 0xda, 0x9e, 0xcd, 0x59, - 0x45, 0xa6, 0x87, 0x7f, 0xac, 0x6e, 0x91, 0x12, 0xc9, 0x70, 0x8b, 0x94, 0x88, 0x49, 0x62, 0x2d, - 0x3e, 0xef, 0x7b, 0xb7, 0x61, 0x2d, 0x6d, 0xec, 0x12, 0x31, 0x49, 0xac, 0xc5, 0xc7, 0x7f, 0xdf, - 0x36, 0xac, 0xa5, 0x8d, 0x5d, 0x22, 0x86, 0x99, 0x63, 0x21, 0xe6, 0x16, 0x75, 0xdc, 0x50, 0xfd, - 0xfe, 0x27, 0xc5, 0x1c, 0xeb, 0x40, 0xc7, 0xcc, 0xb1, 0x0e, 0xa8, 0x44, 0xee, 0x42, 0x28, 0xdf, - 0xbf, 0x1d, 0xf7, 0xf0, 0x5e, 0xa6, 0x03, 0x2a, 0x91, 0xbb, 0x90, 0xcb, 0x3f, 0xd9, 0x8e, 0x7b, - 0x78, 0x31, 0xd3, 0x01, 0xc5, 0x8c, 0xa2, 0xaa, 0xa7, 0x7b, 0x66, 0x7d, 0xd6, 0x76, 0x3d, 0x69, - 0x91, 0xff, 0x9f, 0x15, 0xa3, 0x28, 0x89, 0x88, 0x19, 0x45, 0x49, 0xf0, 0x38, 0x53, 0x21, 0x8d, - 0x7f, 0xda, 0x95, 0x69, 0x68, 0x69, 0x25, 0xc1, 0xe3, 0x4c, 0x85, 0x10, 0xfe, 0x97, 0xae, 0x4c, - 0x43, 0x4f, 0xf9, 0x24, 0x38, 0xb3, 0x4c, 0x27, 0x1d, 0x7b, 0xdd, 0xba, 0x46, 0xd7, 0x69, 0x43, - 0x7c, 0xfa, 0x3f, 0x53, 0x2c, 0x53, 0x95, 0x00, 0x6f, 0x51, 0x14, 0x58, 0x94, 0x91, 0xf8, 0xdc, - 0x1f, 0xe8, 0xc8, 0x28, 0x3c, 0x26, 0x52, 0x61, 0x51, 0x46, 0xe2, 0x13, 0x7f, 0xb0, 0x23, 0xa3, - 0xf0, 0x98, 0x48, 0x85, 0x91, 0x12, 0x8c, 0x60, 0xac, 0x84, 0xee, 0xfa, 0x9e, 0x9f, 0x3f, 0x9a, - 0x8a, 0xde, 0x7a, 0x45, 0xd1, 0xb3, 0x3d, 0x9a, 0xd2, 0x40, 0x66, 0x21, 0x3e, 0xe9, 0xc7, 0x3a, - 0xb0, 0x08, 0xfd, 0x1d, 0xa3, 0x10, 0x99, 0x85, 0xf8, 0x98, 0x1f, 0xef, 0xc0, 0x22, 0x74, 0x78, - 0x8c, 0x42, 0xc8, 0x7b, 0x60, 0xb0, 0x3a, 0xb3, 0x58, 0xf1, 0xeb, 0x8e, 0xff, 0xf3, 0x94, 0x12, - 0x55, 0x14, 0xe2, 0x30, 0xaa, 0x28, 0xfc, 0x49, 0xde, 0x0f, 0xc3, 0x93, 0xb6, 0xe5, 0xe9, 0x75, - 0x7f, 0x03, 0xfa, 0x13, 0xca, 0x19, 0x4a, 0x04, 0x3b, 0xdb, 0xa3, 0x45, 0xc9, 0xa5, 0xf6, 0xe2, - 0xdd, 0x7f, 0x32, 0xb9, 0x7d, 0xf0, 0xea, 0x51, 0x72, 0x36, 0xa3, 0xdd, 0xb6, 0x9d, 0xb5, 0x86, - 0xad, 0x1b, 0x7e, 0x9e, 0x53, 0xf1, 0x22, 0x3f, 0xa5, 0xcc, 0x68, 0xc9, 0x64, 0x6c, 0x46, 0x4b, - 0xc6, 0x24, 0xb1, 0x16, 0x5d, 0xf4, 0xb9, 0x6d, 0x58, 0x87, 0xf3, 0x70, 0x32, 0x26, 0x89, 0xb5, - 0xf8, 0xfc, 0x7f, 0xb1, 0x0d, 0xeb, 0x70, 0x1e, 0x4e, 0xc6, 0x30, 0xd3, 0xfa, 0x55, 0xd3, 0xf3, - 0x03, 0xdb, 0xfe, 0xa5, 0x62, 0x5a, 0x87, 0x28, 0x66, 0x5a, 0x87, 0xbf, 0x08, 0x85, 0xb3, 0x41, - 0xa8, 0x64, 0xb8, 0x77, 0x2d, 0x5b, 0x77, 0xd8, 0xfe, 0x38, 0xff, 0xbf, 0x2a, 0xa7, 0x22, 0x9d, - 0x49, 0x67, 0x7b, 0xb4, 0x2e, 0x8c, 0x48, 0x45, 0xf1, 0x53, 0xe4, 0x09, 0x00, 0xf3, 0x3f, 0x9d, - 0xea, 0xe2, 0xa8, 0xc8, 0x69, 0x62, 0x8e, 0x8a, 0x1c, 0x2c, 0xe6, 0xac, 0xa5, 0x06, 0xbd, 0x79, - 0xa3, 0xfc, 0xba, 0x34, 0xbb, 0x7e, 0x3e, 0x3e, 0x67, 0xc5, 0x88, 0xc4, 0x9c, 0x15, 0x83, 0x93, - 0x2f, 0x4f, 0xc1, 0x63, 0xaa, 0x7c, 0x5f, 0x7f, 0xee, 0xa9, 0x17, 0x34, 0x7a, 0xc7, 0xae, 0xcb, - 0x96, 0xd5, 0xcf, 0xf0, 0xa7, 0x3c, 0xd1, 0xa9, 0xbb, 0x92, 0x1a, 0xcd, 0xf6, 0x68, 0x3b, 0x62, - 0xbe, 0x83, 0xb7, 0x10, 0x4a, 0xf3, 0xb3, 0xbb, 0x7a, 0x8b, 0x40, 0x85, 0x76, 0xc4, 0x7c, 0x07, - 0x6f, 0x21, 0x46, 0xc5, 0xcf, 0xed, 0xea, 0x2d, 0x82, 0x31, 0xb2, 0x23, 0xe6, 0xb8, 0xfb, 0xbc, - 0x5d, 0x2d, 0x4f, 0x06, 0xbe, 0x3e, 0x1b, 0x56, 0x3d, 0xff, 0xf3, 0xea, 0xee, 0x53, 0xa5, 0xc0, - 0xdd, 0xa7, 0x0a, 0x64, 0xcb, 0xfd, 0x2c, 0xd5, 0x1b, 0x6c, 0x3b, 0x4a, 0xeb, 0x6b, 0x11, 0xe3, - 0xed, 0x17, 0x94, 0xe5, 0xbe, 0x03, 0x1d, 0x5b, 0xee, 0x3b, 0xa0, 0x12, 0xb9, 0x0b, 0x09, 0xfd, - 0xe2, 0x76, 0xdc, 0x43, 0x53, 0xa5, 0x03, 0x2a, 0x91, 0xbb, 0xd0, 0x82, 0x5f, 0xda, 0x8e, 0x7b, - 0x68, 0xaa, 0x74, 0x40, 0x91, 0xaf, 0x4f, 0xc1, 0xc5, 0xa4, 0xee, 0xe0, 0xb9, 0x3f, 0x82, 0xb2, - 0x81, 0x5c, 0x56, 0xbf, 0xcc, 0x9f, 0xf7, 0x54, 0xb7, 0xfe, 0x4e, 0x6a, 0x38, 0xdb, 0xa3, 0xed, - 0xf8, 0x21, 0x3b, 0x7c, 0x23, 0x21, 0x81, 0x5f, 0xd9, 0xf5, 0x1b, 0x05, 0x22, 0xd9, 0xf1, 0x43, - 0x70, 0xc2, 0x31, 0x57, 0x5c, 0xcf, 0x76, 0x68, 0xc5, 0x6e, 0x98, 0x75, 0x7f, 0xbd, 0xf9, 0x55, - 0x75, 0xc2, 0x49, 0x20, 0xc2, 0x09, 0x27, 0x01, 0x1e, 0x67, 0x2a, 0x34, 0xe6, 0x5f, 0x75, 0x65, - 0x2a, 0x99, 0x73, 0x09, 0xf0, 0x38, 0x53, 0x21, 0xa6, 0x7f, 0xdd, 0x95, 0xa9, 0x64, 0xce, 0x25, - 0xc0, 0x89, 0x05, 0xe7, 0x43, 0x43, 0xb7, 0xb4, 0x42, 0x2d, 0x4f, 0xb3, 0x1b, 0x0d, 0xbb, 0xed, - 0x2d, 0x3a, 0xe6, 0xca, 0x0a, 0x75, 0xf2, 0xbf, 0xa6, 0x1c, 0x90, 0x75, 0xa5, 0x9e, 0xed, 0xd1, - 0xba, 0xb3, 0x23, 0x1e, 0x14, 0x92, 0x09, 0x66, 0x6c, 0xa7, 0x4e, 0xa7, 0x6c, 0x8b, 0xe6, 0x7f, - 0x3d, 0x15, 0xbd, 0x9e, 0xde, 0x86, 0x7e, 0xb6, 0x47, 0xdb, 0x8e, 0x25, 0xf9, 0x28, 0x3c, 0x94, - 0x4c, 0xc2, 0xfe, 0x5b, 0xd2, 0xeb, 0x6b, 0xf9, 0xff, 0x2d, 0x15, 0xf5, 0xf9, 0xeb, 0x4e, 0x3e, - 0xdb, 0xa3, 0x6d, 0xc3, 0x90, 0x4c, 0xc1, 0xe8, 0xfc, 0x64, 0x25, 0x12, 0xd0, 0xf1, 0x1b, 0x29, - 0x25, 0xfc, 0x2a, 0x8a, 0xc7, 0xf0, 0xab, 0x28, 0x88, 0xd9, 0x53, 0x21, 0x68, 0xda, 0x32, 0xf2, - 0xff, 0xbb, 0x62, 0x4f, 0x45, 0xb0, 0xe8, 0x5f, 0x2a, 0x03, 0xd8, 0x3c, 0x1b, 0x02, 0xfc, 0x7b, - 0xf9, 0xdf, 0x54, 0xe6, 0xd9, 0x18, 0x05, 0x9b, 0x67, 0x63, 0x40, 0x66, 0xe5, 0x84, 0xc0, 0x1b, - 0xb6, 0x70, 0xf9, 0x35, 0x6d, 0x2b, 0xff, 0x5b, 0x8a, 0x95, 0x93, 0x4c, 0xc6, 0xac, 0x9c, 0x64, - 0x0c, 0x53, 0xed, 0x09, 0xbb, 0x6d, 0x19, 0xd7, 0xe9, 0x46, 0x4b, 0x37, 0x1d, 0x3f, 0x0e, 0x29, - 0xff, 0xdb, 0x8a, 0x6a, 0x27, 0x11, 0x31, 0xd5, 0x4e, 0x82, 0xc7, 0x98, 0xda, 0x1e, 0x7f, 0xdb, - 0x7f, 0xd3, 0x8d, 0xa9, 0x20, 0x8a, 0x31, 0x15, 0x70, 0xf2, 0xf1, 0x14, 0xbc, 0x43, 0x46, 0x5c, - 0xb3, 0x4d, 0x0b, 0x3d, 0xb0, 0x6f, 0x51, 0x27, 0xf8, 0x9e, 0x19, 0xdd, 0x6c, 0x50, 0x23, 0xbf, - 0xc9, 0x1f, 0x74, 0x25, 0xe9, 0x41, 0x5d, 0xda, 0xcd, 0xf6, 0x68, 0x3b, 0x7d, 0x04, 0x59, 0x82, - 0x07, 0x42, 0x91, 0x32, 0xcb, 0x8c, 0x5a, 0xd5, 0xea, 0x74, 0xd5, 0x73, 0xa8, 0xde, 0xcc, 0x7f, - 0x41, 0x39, 0x6c, 0xeb, 0x48, 0x89, 0xbe, 0xe4, 0x9d, 0x90, 0x64, 0x0d, 0xce, 0x85, 0x48, 0xdf, - 0x2e, 0x5c, 0x5c, 0xac, 0xf8, 0xea, 0xf4, 0x3b, 0xca, 0x31, 0x6d, 0x37, 0xe2, 0xd9, 0x1e, 0xad, - 0x2b, 0x33, 0xbc, 0xf8, 0x98, 0x2c, 0xcf, 0xb3, 0x77, 0x30, 0xad, 0x15, 0xee, 0x17, 0xf5, 0x7f, - 0xa8, 0x17, 0x1f, 0x0a, 0x01, 0x5e, 0x7c, 0x28, 0x30, 0xbc, 0xcd, 0x9d, 0x2c, 0xcf, 0xfb, 0x96, - 0x02, 0xe7, 0xf4, 0xbb, 0xea, 0x6d, 0xae, 0x4a, 0x81, 0xb7, 0xb9, 0x2a, 0x90, 0x59, 0xd3, 0x22, - 0x41, 0x55, 0x45, 0xa3, 0xae, 0xe7, 0x98, 0x18, 0xa9, 0xe3, 0x9f, 0x69, 0xfe, 0x5b, 0xc5, 0x9a, - 0xee, 0x4c, 0xca, 0xac, 0xe9, 0xce, 0x58, 0xf6, 0xed, 0xb7, 0x2c, 0xea, 0x45, 0x2c, 0x98, 0xff, - 0xa0, 0x7c, 0xbb, 0x4a, 0xc0, 0xbe, 0x5d, 0x85, 0x45, 0x19, 0x89, 0xb7, 0xfc, 0x3f, 0x3b, 0x32, - 0x0a, 0xb7, 0xd6, 0x2a, 0x2c, 0xca, 0x48, 0x2c, 0x37, 0x5b, 0x1d, 0x19, 0x85, 0x5b, 0x6b, 0x15, - 0x46, 0x6e, 0xc1, 0x29, 0x7f, 0x45, 0x8e, 0xba, 0xf8, 0xfe, 0x5f, 0xca, 0x69, 0x76, 0x22, 0xd5, - 0x6c, 0x8f, 0x96, 0xdc, 0x3c, 0x81, 0xaf, 0xf8, 0xdc, 0xff, 0xd8, 0x9d, 0x6f, 0x78, 0xfa, 0x9e, - 0x88, 0x48, 0xe0, 0x2b, 0xbe, 0xfe, 0x3f, 0x75, 0xe7, 0x1b, 0x9e, 0xbe, 0x27, 0x22, 0xd8, 0x9c, - 0x54, 0xb6, 0xc4, 0x7d, 0xd8, 0xbc, 0x6d, 0x04, 0x27, 0x28, 0xff, 0x59, 0x99, 0x93, 0x92, 0x88, - 0xd8, 0x9c, 0x94, 0x04, 0x8f, 0x33, 0x15, 0x32, 0xf8, 0xbf, 0xbb, 0x32, 0x0d, 0xad, 0x8d, 0x24, - 0x78, 0x9c, 0xa9, 0x10, 0xc0, 0x1f, 0x74, 0x65, 0x1a, 0x5a, 0x1b, 0x49, 0x70, 0x26, 0xd6, 0x00, - 0x5e, 0xa5, 0x75, 0x87, 0xfa, 0xc7, 0x0c, 0xff, 0x8f, 0x22, 0xd6, 0x44, 0x2a, 0x26, 0xd6, 0x44, - 0x44, 0x02, 0x5f, 0x21, 0x82, 0x3f, 0xec, 0xce, 0x37, 0x54, 0x83, 0x44, 0x44, 0x02, 0x5f, 0x21, - 0x85, 0x3f, 0xea, 0xce, 0x37, 0x54, 0x83, 0x44, 0x44, 0x84, 0x6f, 0xc4, 0xea, 0xfc, 0xe3, 0x4e, - 0x7c, 0x15, 0xb3, 0x33, 0xb9, 0x79, 0x02, 0x5f, 0x21, 0x87, 0x3f, 0xe9, 0xce, 0x37, 0x41, 0x0e, - 0x11, 0xd3, 0x33, 0xce, 0x57, 0xc8, 0xe1, 0xbf, 0x74, 0xe7, 0x9b, 0x20, 0x87, 0x88, 0xf5, 0x89, - 0x2e, 0x37, 0xdc, 0xdc, 0xc1, 0x03, 0x28, 0xf3, 0x4d, 0x6a, 0xe4, 0xff, 0x34, 0xe6, 0x72, 0xa3, - 0x50, 0x70, 0x97, 0x1b, 0x05, 0x88, 0x93, 0x34, 0x75, 0xbc, 0xa0, 0xb6, 0x8c, 0x6f, 0xe7, 0xf3, - 0x99, 0xff, 0xcf, 0xd4, 0x49, 0xba, 0x23, 0x29, 0x4e, 0xd2, 0x1d, 0xb1, 0x78, 0x27, 0x1e, 0xb8, - 0x6d, 0xcf, 0xcd, 0xcd, 0xfb, 0xab, 0xe0, 0x7f, 0x55, 0xef, 0xc4, 0x13, 0x88, 0xf0, 0x4e, 0x3c, - 0x01, 0xce, 0x98, 0x6a, 0xd4, 0x73, 0x4c, 0x7a, 0x47, 0x6f, 0xc8, 0xd3, 0xc2, 0x9f, 0xa7, 0xd4, - 0x20, 0xf0, 0x38, 0x11, 0x0f, 0x02, 0x8f, 0xc3, 0xe3, 0x4c, 0x85, 0x2e, 0xfc, 0xb7, 0xae, 0x4c, - 0xc3, 0x69, 0x21, 0x09, 0x1e, 0x67, 0x2a, 0x14, 0xe1, 0xbf, 0x77, 0x65, 0x1a, 0x4e, 0x0b, 0x49, - 0x70, 0x72, 0x05, 0xfa, 0x31, 0xfd, 0xa4, 0xd6, 0xb6, 0xf2, 0x7f, 0xa1, 0x64, 0xa0, 0xf3, 0x11, - 0xb3, 0x3d, 0x5a, 0x40, 0x44, 0x6a, 0x9d, 0x8b, 0xce, 0xe7, 0xff, 0x52, 0xb9, 0xc4, 0xec, 0x44, - 0x38, 0xdb, 0xa3, 0x75, 0xae, 0x5c, 0xff, 0x81, 0x4e, 0x35, 0xda, 0xf2, 0xff, 0xaf, 0x62, 0xeb, - 0x26, 0x93, 0x31, 0x5b, 0xb7, 0x43, 0x91, 0xb7, 0x1a, 0xe4, 0xa7, 0xad, 0x3b, 0xa6, 0x63, 0x5b, - 0x3c, 0x86, 0xd3, 0x5e, 0x36, 0x03, 0x67, 0xa9, 0x2f, 0x49, 0x47, 0xdf, 0xbd, 0x13, 0x21, 0x7b, - 0xf7, 0x4e, 0x38, 0x36, 0xa8, 0x26, 0xa8, 0xde, 0x74, 0x23, 0x76, 0xc4, 0x97, 0xa6, 0x95, 0x44, - 0x08, 0x2a, 0x05, 0x26, 0x42, 0x50, 0x81, 0x0a, 0x2f, 0xf1, 0x96, 0x5f, 0xd6, 0x99, 0x57, 0x78, - 0x39, 0x1a, 0x03, 0x2a, 0xbc, 0x84, 0xde, 0x7c, 0x79, 0x67, 0x5e, 0x52, 0x82, 0x06, 0x15, 0x88, - 0x67, 0xfe, 0x0d, 0xdd, 0x75, 0xcd, 0x65, 0x33, 0x30, 0x25, 0xbe, 0x22, 0xad, 0xe6, 0x76, 0x8a, - 0x12, 0xf0, 0xdc, 0x4e, 0x51, 0x58, 0x94, 0x91, 0xf8, 0xbe, 0xaf, 0xec, 0xc8, 0x48, 0xba, 0x85, - 0x50, 0x60, 0x51, 0x46, 0xe2, 0xe3, 0xbe, 0xaa, 0x23, 0x23, 0xe9, 0x16, 0x42, 0x81, 0xe1, 0x9c, - 0x58, 0xb7, 0x5b, 0xd4, 0x90, 0xdd, 0x7a, 0xbf, 0x5a, 0x11, 0x53, 0x8c, 0x02, 0xe7, 0x44, 0x15, - 0xa8, 0xf0, 0xf2, 0x5d, 0x10, 0x3b, 0xf3, 0x0a, 0xbb, 0x2f, 0x06, 0x54, 0x78, 0x89, 0x2f, 0xfc, - 0x58, 0x67, 0x5e, 0x61, 0xf7, 0xc5, 0x80, 0xe4, 0x45, 0x18, 0xc2, 0x4d, 0xb3, 0x9f, 0x49, 0xec, - 0x6b, 0x95, 0xf4, 0x91, 0x32, 0x72, 0xb6, 0x47, 0x8b, 0x10, 0xb3, 0xc5, 0x28, 0x9c, 0x44, 0xe5, - 0x8d, 0xc8, 0xd7, 0xa5, 0xa3, 0x8b, 0x51, 0x22, 0x15, 0x5b, 0x8c, 0x12, 0x11, 0x78, 0x48, 0x90, - 0x84, 0x98, 0xb0, 0x8d, 0x0d, 0x9e, 0x3f, 0xe7, 0xe3, 0x69, 0xe5, 0x90, 0xa0, 0x2b, 0x39, 0x1e, - 0x12, 0x74, 0xa5, 0xc0, 0x0b, 0x5c, 0x85, 0x42, 0xf8, 0xbc, 0x7d, 0x22, 0xad, 0x5c, 0xe0, 0x26, - 0x92, 0xe1, 0x05, 0x6e, 0x22, 0x06, 0x0f, 0x5a, 0x12, 0x31, 0xe1, 0xe7, 0x7c, 0x32, 0xad, 0x1c, - 0xb4, 0x74, 0xa7, 0xc7, 0x83, 0x96, 0xee, 0x24, 0x6c, 0x11, 0x0e, 0x49, 0x78, 0xd1, 0xe5, 0x29, - 0x53, 0x6f, 0x4c, 0x51, 0xcb, 0xa4, 0x46, 0xfe, 0xeb, 0xd5, 0x04, 0xd2, 0x1d, 0x49, 0xd9, 0x22, - 0xdc, 0x19, 0x3b, 0xd1, 0x07, 0xbd, 0xb8, 0x1a, 0x5f, 0xcb, 0xf5, 0xff, 0x50, 0x6a, 0xec, 0x87, - 0x53, 0xd7, 0x72, 0xfd, 0x3f, 0x9c, 0x1a, 0xfb, 0x11, 0xf6, 0xff, 0x8f, 0xa4, 0xc6, 0x7e, 0x94, - 0xfd, 0xff, 0xef, 0x52, 0x63, 0xbf, 0xc7, 0xfe, 0xff, 0xbd, 0xd4, 0xd8, 0xbf, 0x67, 0xff, 0xff, - 0xfb, 0xd4, 0xd8, 0xef, 0xb3, 0xff, 0x7f, 0x3f, 0x35, 0xf6, 0x1f, 0x52, 0xda, 0x03, 0xca, 0xe9, - 0x4d, 0xa5, 0xa1, 0x8b, 0x31, 0x93, 0x88, 0xe2, 0x3f, 0x13, 0x51, 0x5c, 0xa3, 0xb5, 0x13, 0xa5, - 0x56, 0x0b, 0x1d, 0x1a, 0xa5, 0xd9, 0x53, 0x01, 0x0a, 0x26, 0x51, 0x60, 0x62, 0x73, 0xdc, 0xe8, - 0x6f, 0x14, 0xbf, 0x35, 0x05, 0x43, 0x7c, 0x0f, 0x2e, 0x2a, 0x60, 0x9d, 0x85, 0x7e, 0x9e, 0x17, - 0xc8, 0x4f, 0xc3, 0xac, 0x05, 0xbf, 0xc9, 0x05, 0x18, 0x99, 0xd3, 0x5d, 0x0f, 0xc5, 0x52, 0xb6, - 0x0c, 0x7a, 0x17, 0x73, 0x62, 0x66, 0x34, 0x05, 0x4a, 0xe6, 0x38, 0x1d, 0x6f, 0x87, 0xd5, 0x23, - 0x33, 0xdb, 0x16, 0x7e, 0xea, 0xff, 0xfc, 0x66, 0xa1, 0x07, 0xeb, 0x3c, 0x29, 0x6d, 0x8b, 0x3f, - 0x97, 0x82, 0x58, 0xc6, 0xa2, 0xbd, 0x67, 0x6f, 0x5f, 0x80, 0x51, 0xa5, 0xe8, 0xa8, 0x48, 0xec, - 0xb9, 0xc3, 0x9a, 0xa4, 0x6a, 0x6b, 0xf2, 0x8e, 0x20, 0xa1, 0xe4, 0x4d, 0x6d, 0x4e, 0x14, 0xf5, - 0xea, 0xdb, 0xda, 0x2c, 0x64, 0xda, 0x4e, 0x43, 0x93, 0x50, 0xa2, 0x10, 0xcb, 0xaf, 0x9f, 0x08, - 0xcb, 0x31, 0x92, 0x0b, 0x22, 0x95, 0xbc, 0x54, 0x9a, 0xb0, 0xed, 0x52, 0x47, 0x2e, 0x05, 0x86, - 0xa9, 0xe3, 0xdf, 0x0f, 0x43, 0xe5, 0x66, 0x8b, 0x3a, 0xae, 0x6d, 0xe9, 0x9e, 0xed, 0x88, 0x54, - 0xd7, 0x98, 0x94, 0xda, 0x94, 0xe0, 0x72, 0x52, 0x6a, 0x99, 0x9e, 0x5c, 0x82, 0x5e, 0x0d, 0x8b, - 0x59, 0x65, 0xb0, 0x10, 0x26, 0xa6, 0x7b, 0xc5, 0x82, 0x56, 0x72, 0xde, 0x70, 0xa4, 0x60, 0xa4, - 0x37, 0x5d, 0x1d, 0x53, 0x8f, 0x06, 0xa4, 0x6d, 0x06, 0x90, 0x49, 0x91, 0x82, 0x3c, 0x01, 0x39, - 0xbc, 0xb3, 0x73, 0xf3, 0xbd, 0x48, 0x8b, 0xf9, 0xc8, 0x1b, 0x08, 0x91, 0xd3, 0x9b, 0x73, 0x1a, - 0x72, 0x1d, 0xc6, 0xc2, 0x38, 0xd4, 0x57, 0x1d, 0xbb, 0xdd, 0x72, 0xf3, 0x39, 0x6c, 0x57, 0xd8, - 0xda, 0x2c, 0x3c, 0xb8, 0x16, 0xe0, 0x6a, 0x2b, 0x88, 0x94, 0x58, 0xc4, 0x1a, 0x92, 0x59, 0x18, - 0x0d, 0x61, 0x4c, 0x44, 0xbc, 0xcc, 0xaa, 0x48, 0xa5, 0x2d, 0xf1, 0x62, 0xe2, 0x94, 0x59, 0xa9, - 0xcd, 0x48, 0x19, 0xfa, 0xfc, 0xea, 0x64, 0xdb, 0xd7, 0x4f, 0x3d, 0x21, 0xaa, 0x93, 0xf5, 0xc9, - 0x75, 0xc9, 0xfc, 0xf6, 0x64, 0x06, 0x46, 0x34, 0xbb, 0xed, 0xd1, 0x45, 0x5b, 0x6c, 0xc8, 0x45, - 0xc1, 0x3c, 0x7c, 0x27, 0x87, 0x61, 0x6a, 0x9e, 0x5d, 0xab, 0x73, 0x9c, 0x9c, 0xde, 0x3b, 0xda, - 0x8a, 0xdc, 0x80, 0xf1, 0x58, 0xc4, 0xae, 0x28, 0x9f, 0x87, 0xb5, 0xa3, 0xa4, 0xcf, 0x8b, 0x33, - 0x8b, 0x37, 0x25, 0x5f, 0x95, 0x82, 0xdc, 0xa2, 0xa3, 0x9b, 0x9e, 0x2b, 0xb2, 0x96, 0x9e, 0xba, - 0xbc, 0xee, 0xe8, 0x2d, 0xa6, 0x1f, 0x97, 0xb1, 0xd2, 0xe9, 0x2d, 0xbd, 0xd1, 0xa6, 0xee, 0xc4, - 0x6d, 0xf6, 0x75, 0xbf, 0xb9, 0x59, 0x78, 0x91, 0xa7, 0xa8, 0xbf, 0x5c, 0xb7, 0x9b, 0x57, 0x56, - 0x1c, 0xfd, 0x8e, 0xc9, 0xcf, 0x25, 0xf5, 0xc6, 0x15, 0x8f, 0x36, 0x30, 0xfc, 0xe0, 0x8a, 0xde, - 0x32, 0xaf, 0x60, 0x4d, 0xfc, 0x2b, 0x01, 0x27, 0xfe, 0x04, 0xa6, 0x02, 0x1e, 0xfe, 0x25, 0xab, - 0x00, 0xc7, 0x91, 0x1b, 0x00, 0xe2, 0x53, 0x4b, 0xad, 0x96, 0x48, 0x81, 0x2a, 0x39, 0xed, 0xfb, - 0x18, 0xae, 0xd8, 0x81, 0xc0, 0xf4, 0x96, 0x54, 0x45, 0x56, 0x93, 0x38, 0x30, 0x2d, 0x58, 0x14, - 0x6f, 0xe4, 0x8b, 0x69, 0x58, 0x4a, 0xa8, 0x2e, 0x50, 0x09, 0x42, 0x52, 0x9b, 0x91, 0x25, 0x18, - 0x15, 0x7c, 0x7d, 0x07, 0x4f, 0x91, 0x26, 0xf5, 0x8c, 0xf2, 0x7a, 0x3e, 0x9a, 0x2b, 0x6d, 0xf0, - 0x8e, 0x86, 0x00, 0xcb, 0xcf, 0x50, 0x5a, 0x90, 0x09, 0x18, 0xf6, 0xff, 0xbe, 0xa1, 0x37, 0xa9, - 0x9b, 0x1f, 0x45, 0x8d, 0xc5, 0x92, 0xa1, 0x7e, 0x7b, 0x2c, 0xd4, 0x27, 0x8b, 0x2e, 0xda, 0x44, - 0xe6, 0xc1, 0xb5, 0x7e, 0x2c, 0x81, 0x87, 0xaa, 0xf3, 0xd1, 0x26, 0x64, 0x12, 0x86, 0x83, 0x0c, - 0x6c, 0x37, 0x6f, 0x96, 0xa7, 0x30, 0xc7, 0xaa, 0x28, 0xeb, 0xd8, 0x5c, 0xd6, 0x6b, 0xa2, 0xc2, - 0x59, 0xbb, 0x1d, 0x2d, 0xeb, 0x18, 0x69, 0x23, 0x25, 0xde, 0xe7, 0x49, 0x57, 0x95, 0xc4, 0xfb, - 0xad, 0x84, 0xc4, 0xfb, 0x15, 0xf2, 0x3e, 0x18, 0x2c, 0xdd, 0xae, 0x8a, 0x82, 0x02, 0x6e, 0xfe, - 0x04, 0xbe, 0x3b, 0x96, 0x10, 0xc0, 0x2a, 0x06, 0xa2, 0xf8, 0x80, 0xfc, 0xea, 0x32, 0x3d, 0x99, - 0x86, 0x91, 0xc8, 0x75, 0xbe, 0x9b, 0x3f, 0x89, 0x1c, 0xf0, 0xcd, 0x75, 0xc4, 0xd4, 0x1c, 0x81, - 0x92, 0x87, 0x57, 0xb4, 0x11, 0xd3, 0x9a, 0x29, 0xd3, 0xd5, 0x1b, 0x0d, 0x7b, 0x5d, 0xa3, 0x58, - 0xbb, 0x00, 0x33, 0xb6, 0xf6, 0x73, 0xad, 0x31, 0x04, 0xaa, 0xe6, 0x70, 0x9c, 0xdc, 0xa3, 0x4a, - 0x33, 0xf2, 0x06, 0x90, 0x12, 0xfb, 0x4d, 0x0d, 0xff, 0x2c, 0xb7, 0x3c, 0xe5, 0xe6, 0x4f, 0x63, - 0xbd, 0x67, 0xa2, 0xa6, 0x1a, 0x2f, 0x4f, 0x4d, 0x5c, 0x10, 0xd3, 0xc7, 0x43, 0x3a, 0x6f, 0x55, - 0xf3, 0xd3, 0x8c, 0xd7, 0x4c, 0x43, 0x7e, 0xe3, 0x04, 0xae, 0x64, 0x1d, 0xce, 0x54, 0x1c, 0x7a, - 0xc7, 0xb4, 0xdb, 0xae, 0xbf, 0x7c, 0xf8, 0xf3, 0xd6, 0x99, 0x6d, 0xe7, 0xad, 0x47, 0xc4, 0x83, - 0x4f, 0xb5, 0x1c, 0x7a, 0xa7, 0xe6, 0xd7, 0xf9, 0x8d, 0x54, 0x57, 0xec, 0xc4, 0x9d, 0x89, 0x0b, - 0xf7, 0xc4, 0x02, 0x6e, 0x52, 0x37, 0x9f, 0x0f, 0xa7, 0x5a, 0x5e, 0x25, 0xc3, 0x0c, 0x70, 0xb2, - 0xb8, 0x94, 0x66, 0x44, 0x03, 0xf2, 0xea, 0xa4, 0x1f, 0xdf, 0x59, 0xaa, 0xd7, 0xed, 0xb6, 0xe5, - 0xb9, 0xf9, 0x07, 0x90, 0x59, 0x91, 0x89, 0x65, 0xa5, 0x1e, 0x54, 0xed, 0xaf, 0xe9, 0x02, 0x2f, - 0x8b, 0x25, 0xde, 0x9a, 0xcc, 0xc1, 0x58, 0xc5, 0x41, 0x6f, 0xf3, 0xeb, 0x74, 0x83, 0x9f, 0xfb, - 0x60, 0xe2, 0x58, 0x31, 0x55, 0xb6, 0x38, 0x0e, 0x4b, 0xf1, 0xb6, 0x10, 0x2b, 0x2f, 0x2b, 0x6a, - 0x4b, 0xb9, 0xca, 0xec, 0x83, 0x3b, 0xab, 0x32, 0x4b, 0x61, 0x4c, 0x44, 0x87, 0xde, 0xf5, 0xa8, - 0xc5, 0x96, 0x7a, 0x57, 0x24, 0x89, 0xcd, 0x2b, 0xd1, 0xa4, 0x01, 0x9e, 0x4f, 0x1d, 0x62, 0x94, - 0xd1, 0x00, 0x2c, 0xbf, 0x98, 0xda, 0x24, 0x5e, 0x8a, 0xf5, 0xfc, 0x1e, 0x4a, 0xb1, 0x3e, 0x0f, - 0x03, 0xd7, 0x6c, 0x93, 0x87, 0x63, 0x62, 0x46, 0x58, 0x51, 0x22, 0xf4, 0x0d, 0xdb, 0xb4, 0x6a, - 0x58, 0x6f, 0x53, 0xae, 0x74, 0x12, 0x90, 0x92, 0x69, 0xe8, 0xc7, 0xed, 0x55, 0xc5, 0xb4, 0x44, - 0xc6, 0xd7, 0xb1, 0xc8, 0x5e, 0xac, 0x62, 0x5a, 0x7c, 0xf4, 0x63, 0xe5, 0xcc, 0x5a, 0x2b, 0x52, - 0x17, 0x31, 0x68, 0x4a, 0x3e, 0x08, 0x79, 0x45, 0xaf, 0xf9, 0xc0, 0x64, 0x63, 0xe6, 0x61, 0x1c, - 0x33, 0xb1, 0xf4, 0xfc, 0x3e, 0xc1, 0x44, 0x96, 0x29, 0xb0, 0xd6, 0xb1, 0x3d, 0xb9, 0x0a, 0x83, - 0xf8, 0xad, 0x3c, 0xe4, 0x09, 0x33, 0xbe, 0xf6, 0xf3, 0x7a, 0xdf, 0x5c, 0x3a, 0x1c, 0xae, 0xc9, - 0x44, 0x64, 0x1e, 0xc6, 0x26, 0x6c, 0x0f, 0x5f, 0x6f, 0x61, 0x99, 0xd7, 0x6d, 0xc4, 0xcc, 0xae, - 0x03, 0x13, 0x8f, 0x6c, 0x6d, 0x16, 0xce, 0xb3, 0x86, 0xfc, 0x83, 0xec, 0xe5, 0x78, 0xb9, 0xc7, - 0x58, 0xd3, 0xe2, 0x57, 0xa4, 0x43, 0x31, 0x91, 0x82, 0x5f, 0xf5, 0x96, 0x5b, 0x76, 0x03, 0x5b, - 0x9b, 0x05, 0x5e, 0x57, 0x54, 0xd4, 0xba, 0x25, 0x8b, 0x30, 0x18, 0xc6, 0xdb, 0xba, 0xf9, 0x34, - 0x7e, 0xff, 0x23, 0xaa, 0x58, 0x2f, 0x4b, 0x34, 0xbc, 0x26, 0x3c, 0xe6, 0xf7, 0xd7, 0x43, 0xa8, - 0x26, 0xb3, 0x21, 0x8f, 0xc0, 0x10, 0xcf, 0x4b, 0x5d, 0x73, 0x42, 0x73, 0x4f, 0x1b, 0xe4, 0x30, - 0xb4, 0xef, 0xce, 0x7e, 0x04, 0xc6, 0x54, 0xa6, 0x09, 0x25, 0xce, 0x9f, 0x95, 0x4b, 0x9c, 0xcb, - 0xbe, 0xda, 0xe2, 0xc5, 0x2c, 0x6a, 0x48, 0x5c, 0xe4, 0x12, 0xe8, 0x2f, 0xc0, 0xe9, 0x64, 0x22, - 0x26, 0x13, 0x6e, 0x84, 0xa6, 0x70, 0xc4, 0xa3, 0x4c, 0xf0, 0x35, 0x85, 0xe9, 0x59, 0xfc, 0xcf, - 0x19, 0xd9, 0x3e, 0x20, 0xe7, 0x20, 0x8b, 0x23, 0x31, 0x15, 0x16, 0x2c, 0xc5, 0x9a, 0xf0, 0x59, - 0x51, 0xde, 0x79, 0xc0, 0xbf, 0xe1, 0xf3, 0x4b, 0xbf, 0x60, 0x51, 0xfe, 0xb0, 0x88, 0xa5, 0x16, - 0x12, 0x90, 0x2b, 0x00, 0x95, 0xf6, 0x52, 0xc3, 0xac, 0x63, 0xd5, 0xff, 0x8c, 0x54, 0x2a, 0x01, - 0xa1, 0xbc, 0xe8, 0xbf, 0x44, 0xc2, 0x14, 0xca, 0x8f, 0xc2, 0x09, 0x8b, 0xfb, 0xa2, 0x42, 0x09, - 0x6b, 0x42, 0xd4, 0xa7, 0x97, 0x88, 0xc8, 0x7b, 0x01, 0xc2, 0xe5, 0x4a, 0xec, 0x04, 0xd0, 0x94, - 0x91, 0x57, 0x37, 0xd9, 0x94, 0x09, 0xa9, 0xd9, 0xc2, 0x2e, 0x4f, 0x97, 0x1b, 0xa2, 0xe6, 0x2f, - 0x2e, 0xec, 0x91, 0x39, 0x56, 0x9e, 0xc0, 0xa2, 0x4d, 0xc8, 0x02, 0x8c, 0xc7, 0x66, 0x48, 0x51, - 0x0a, 0x18, 0x35, 0x3a, 0x61, 0x7a, 0x95, 0x0d, 0xc7, 0x58, 0x5b, 0xf2, 0x28, 0x64, 0x6e, 0x6a, - 0x65, 0x51, 0x62, 0x94, 0x57, 0xa7, 0x8d, 0x14, 0xf8, 0x61, 0x58, 0xf2, 0x02, 0x00, 0xdf, 0x3b, - 0x57, 0x6c, 0xc7, 0x43, 0x8b, 0x77, 0x98, 0x17, 0x6f, 0xf7, 0x10, 0x5a, 0x63, 0x66, 0x96, 0xfc, - 0xd1, 0x21, 0x71, 0xf1, 0xcb, 0xd2, 0x31, 0xb3, 0x8b, 0x09, 0x5e, 0xbc, 0x85, 0xd4, 0xf9, 0x28, - 0x78, 0xff, 0xd5, 0xb9, 0xe0, 0x25, 0x22, 0x72, 0x11, 0xfa, 0x2b, 0x6c, 0xd1, 0xab, 0xdb, 0x0d, - 0xa1, 0x0a, 0x58, 0x67, 0xaa, 0x25, 0x60, 0x5a, 0x80, 0x25, 0x57, 0x79, 0x45, 0x6d, 0x4b, 0xa9, - 0x23, 0xde, 0x16, 0x30, 0xb5, 0xa0, 0x36, 0x83, 0xb1, 0x36, 0x81, 0x51, 0x98, 0x0d, 0xdb, 0x24, - 0x98, 0x7c, 0x01, 0x5d, 0xb8, 0xe1, 0xea, 0xdd, 0x6e, 0xc3, 0x55, 0xfc, 0x89, 0x54, 0x7c, 0x09, - 0x21, 0xcf, 0xc6, 0x6b, 0xef, 0xe2, 0x0c, 0x1b, 0x00, 0xe5, 0xa7, 0x06, 0x55, 0x78, 0x23, 0x55, - 0x74, 0xd3, 0x7b, 0xae, 0xa2, 0x9b, 0xd9, 0x65, 0x15, 0xdd, 0xe2, 0x7f, 0xcb, 0x76, 0x4d, 0xd4, - 0x75, 0x28, 0xd5, 0xd6, 0x5e, 0x80, 0x61, 0xfe, 0xf4, 0x92, 0x1b, 0xdb, 0xfa, 0xf2, 0x3c, 0x44, - 0x35, 0x9d, 0x8f, 0x4a, 0x57, 0x8b, 0x52, 0x92, 0x97, 0x61, 0xc8, 0xff, 0x00, 0xac, 0xce, 0x2c, - 0x55, 0x15, 0x0e, 0x0c, 0x36, 0xa5, 0x8e, 0x71, 0xa4, 0x01, 0x79, 0x0e, 0x06, 0xd0, 0x5c, 0x6f, - 0xe9, 0x75, 0xbf, 0xcc, 0x37, 0xaf, 0x0b, 0xee, 0x03, 0xe5, 0x75, 0x36, 0xa0, 0x24, 0x1f, 0x86, - 0x1c, 0x6e, 0xc7, 0xf8, 0xbe, 0x58, 0x72, 0xea, 0xe8, 0x22, 0x5b, 0xbe, 0x81, 0x13, 0x8b, 0x03, - 0xdf, 0x80, 0x23, 0x20, 0xb2, 0x01, 0x47, 0x08, 0x59, 0x84, 0x13, 0x15, 0x87, 0x1a, 0x98, 0x43, - 0x6f, 0xfa, 0x6e, 0xcb, 0xe1, 0x93, 0xa3, 0x98, 0x20, 0xd0, 0xfe, 0x6a, 0xf9, 0x68, 0x66, 0x19, - 0x0a, 0xbc, 0x5c, 0x3f, 0x38, 0xa1, 0x39, 0x33, 0xca, 0xf9, 0x9b, 0x5c, 0xa7, 0x1b, 0xeb, 0xb6, - 0x63, 0xb0, 0x6d, 0x74, 0x60, 0x94, 0x0b, 0x41, 0xaf, 0x09, 0x94, 0x6c, 0x94, 0x47, 0x1b, 0x9d, - 0x7d, 0x01, 0x06, 0xa5, 0x2f, 0x49, 0x58, 0x91, 0x4e, 0xca, 0x2b, 0xd2, 0x80, 0xbc, 0xe2, 0x7c, - 0x7f, 0xba, 0x43, 0xca, 0xcb, 0x43, 0xd1, 0xbb, 0x97, 0xa1, 0x4f, 0x2c, 0x3b, 0xe2, 0xd0, 0x6b, - 0x87, 0xe7, 0x4a, 0x7e, 0x2b, 0x32, 0xa1, 0x4c, 0x37, 0xb2, 0x25, 0x29, 0xe0, 0x9d, 0x5e, 0x3e, - 0x98, 0x7e, 0x0a, 0xd0, 0xcb, 0xd3, 0x8a, 0xf7, 0x86, 0xe6, 0xc7, 0x47, 0x31, 0x07, 0x01, 0x87, - 0x17, 0xff, 0x38, 0xdd, 0x21, 0x9f, 0xe7, 0xfd, 0x2b, 0x33, 0xb6, 0xf0, 0xf8, 0xc2, 0x28, 0x4f, - 0xa1, 0xe4, 0x86, 0xc5, 0xc2, 0xe3, 0x83, 0x99, 0x51, 0x21, 0x13, 0x91, 0xcb, 0x00, 0x15, 0xdd, - 0xd1, 0x9b, 0xd4, 0x63, 0x7b, 0x71, 0x7e, 0x9a, 0x85, 0x56, 0x48, 0x2b, 0x80, 0x6a, 0x12, 0x45, - 0xf1, 0x3b, 0x33, 0xdd, 0xf2, 0x9d, 0x1e, 0xcb, 0x7e, 0x37, 0xb2, 0xbf, 0x0a, 0x83, 0x81, 0x64, - 0xcb, 0x53, 0x68, 0x2f, 0x89, 0x36, 0x81, 0xf0, 0xb1, 0x8d, 0x44, 0x44, 0x2e, 0xf1, 0x77, 0xad, - 0x9a, 0x6f, 0x52, 0x9c, 0xf7, 0x86, 0x45, 0x45, 0x7b, 0xdd, 0xd3, 0x6b, 0xae, 0xf9, 0x26, 0xd5, - 0x02, 0x74, 0xf1, 0xa7, 0xd2, 0x89, 0x49, 0x63, 0x8f, 0xfb, 0x68, 0x17, 0x7d, 0x94, 0x20, 0x44, - 0x9e, 0xee, 0xf6, 0x58, 0x88, 0xbb, 0x10, 0xe2, 0x1f, 0xa4, 0x13, 0x93, 0x03, 0x1f, 0x0b, 0x71, - 0x37, 0xb3, 0xc5, 0x13, 0x30, 0xa0, 0xd9, 0xeb, 0xee, 0x24, 0xee, 0x89, 0xf8, 0x5c, 0x81, 0x13, - 0xb5, 0x63, 0xaf, 0xbb, 0x35, 0xdc, 0xed, 0x68, 0x21, 0x41, 0xf1, 0xcf, 0xd2, 0x5d, 0xd2, 0x27, - 0x1f, 0x0b, 0xfe, 0xad, 0x5c, 0x22, 0x7f, 0x20, 0x1d, 0x49, 0xcf, 0x7c, 0xff, 0x0a, 0xfb, 0x0a, - 0x40, 0xb5, 0xbe, 0x4a, 0x9b, 0x3a, 0xee, 0x83, 0x7b, 0xc3, 0x23, 0x0b, 0x17, 0xa1, 0x7c, 0x1b, - 0x2c, 0x91, 0x14, 0x7f, 0x28, 0xad, 0xe4, 0xa7, 0x3e, 0x96, 0xdd, 0x8e, 0x65, 0x17, 0x68, 0x9d, - 0x48, 0xb9, 0x7d, 0x2c, 0xb9, 0x9d, 0x4a, 0xee, 0x6b, 0xd2, 0x4a, 0x76, 0xf2, 0xfb, 0x56, 0x76, - 0x6c, 0x00, 0xc6, 0xb3, 0xa6, 0xdf, 0xb7, 0x9a, 0xf4, 0x04, 0x0c, 0x08, 0x39, 0x04, 0x4b, 0x05, - 0x9f, 0xf7, 0x39, 0x10, 0x0f, 0x68, 0x03, 0x82, 0xe2, 0x57, 0xa6, 0x21, 0x9a, 0x35, 0xfe, 0x3e, - 0xd5, 0xa1, 0x1f, 0x48, 0x47, 0xf3, 0xe5, 0xdf, 0xbf, 0xfa, 0x73, 0x19, 0xa0, 0xda, 0x5e, 0xaa, - 0x8b, 0xa8, 0xf4, 0x5e, 0xe9, 0x84, 0x3f, 0x80, 0x6a, 0x12, 0x45, 0xf1, 0x2f, 0xd2, 0x89, 0x49, - 0xfc, 0xef, 0x5f, 0x01, 0x3e, 0x83, 0xa7, 0xe2, 0x75, 0x2b, 0x9c, 0xc8, 0xf1, 0x10, 0x92, 0x8d, - 0x3f, 0xf5, 0x3a, 0x33, 0x20, 0x24, 0xef, 0x49, 0x30, 0xd7, 0xf0, 0x92, 0x30, 0x34, 0xd7, 0xe4, - 0xc3, 0x7c, 0xc9, 0x70, 0xfb, 0xd9, 0xf4, 0x76, 0x35, 0x0f, 0xee, 0xe7, 0x55, 0xb5, 0xaf, 0xa2, - 0x6f, 0x60, 0x6d, 0x3e, 0xd6, 0x13, 0x43, 0x13, 0xa7, 0xb6, 0x36, 0x0b, 0xe3, 0x2d, 0x0e, 0x92, - 0xaf, 0x95, 0x05, 0x55, 0xf1, 0xf7, 0x7a, 0x93, 0x13, 0xee, 0xdf, 0xbf, 0x22, 0x3c, 0x07, 0xd9, - 0x8a, 0xee, 0xad, 0x0a, 0x4d, 0xc6, 0xdb, 0xc0, 0x96, 0xee, 0xad, 0x6a, 0x08, 0x25, 0x97, 0xa0, - 0x5f, 0xd3, 0xd7, 0xf9, 0x99, 0x27, 0xbf, 0x39, 0xc3, 0x83, 0x1d, 0x47, 0x5f, 0xaf, 0xf1, 0x73, - 0xcf, 0x00, 0x4d, 0x8a, 0x90, 0x9b, 0xa7, 0xde, 0xaa, 0x6d, 0x88, 0x93, 0x6f, 0xd8, 0xda, 0x2c, - 0xe4, 0x9a, 0x08, 0xd1, 0x04, 0x86, 0x3d, 0x6c, 0xc2, 0x36, 0x36, 0xf0, 0xe6, 0x6b, 0x88, 0x3f, - 0x6c, 0xc9, 0x36, 0x36, 0x34, 0x84, 0x92, 0x8f, 0xa5, 0xa0, 0x6f, 0x96, 0xea, 0x06, 0x1b, 0x21, - 0x03, 0xdd, 0x1c, 0xaa, 0x5e, 0x3f, 0x18, 0x87, 0xaa, 0xf1, 0x55, 0xfe, 0x30, 0x59, 0x51, 0xc4, - 0xf3, 0xc9, 0xab, 0xd0, 0x3f, 0xa9, 0x7b, 0x74, 0xc5, 0x76, 0x36, 0xd0, 0x45, 0x6c, 0x24, 0x74, - 0x4d, 0x8e, 0xe8, 0x8f, 0x4f, 0xc4, 0x6f, 0xc6, 0xea, 0xe2, 0x97, 0x16, 0x34, 0x66, 0x62, 0xe1, - 0x37, 0x73, 0xe8, 0x23, 0x26, 0xc4, 0xc2, 0xaf, 0xf0, 0x34, 0x81, 0x09, 0x8f, 0x95, 0x87, 0x92, - 0x8f, 0x95, 0xd1, 0x7a, 0x44, 0x37, 0xd2, 0x49, 0xdb, 0xa0, 0xe8, 0x8b, 0x35, 0x2c, 0xac, 0x47, - 0x84, 0xd6, 0xea, 0xb6, 0xc1, 0xac, 0xc7, 0x80, 0xa4, 0xf8, 0x85, 0x5e, 0x48, 0x4c, 0xcf, 0x7d, - 0xac, 0xe4, 0xc7, 0x4a, 0x1e, 0x2a, 0xf9, 0x54, 0x4c, 0xc9, 0xcf, 0xc6, 0x13, 0xbe, 0xdf, 0xa3, - 0x1a, 0xfe, 0xd9, 0x6c, 0xac, 0x5c, 0xc4, 0xfd, 0xbd, 0xbb, 0x0c, 0xa5, 0xd7, 0xbb, 0xad, 0xf4, - 0x82, 0x01, 0x91, 0xdb, 0x76, 0x40, 0xf4, 0xed, 0x74, 0x40, 0xf4, 0x77, 0x1c, 0x10, 0xa1, 0x82, - 0x0c, 0x74, 0x54, 0x90, 0xb2, 0x18, 0x34, 0x20, 0x64, 0xa8, 0xfa, 0xdc, 0x55, 0x3d, 0xa7, 0x5d, - 0xf7, 0xd0, 0x6f, 0x63, 0x84, 0x8d, 0xa6, 0x50, 0x9d, 0xff, 0x7c, 0xb3, 0x90, 0xe3, 0x58, 0x3e, - 0xc2, 0x8a, 0x3f, 0x97, 0xed, 0x52, 0xe3, 0xe5, 0x50, 0x74, 0xe4, 0x19, 0xc8, 0x94, 0x5a, 0x2d, - 0xa1, 0x1f, 0x27, 0xa4, 0x08, 0x8a, 0x0e, 0xad, 0x18, 0x35, 0x79, 0x2f, 0x64, 0x4a, 0xb7, 0xab, - 0x42, 0x25, 0xce, 0x4a, 0x79, 0x75, 0xc4, 0x97, 0x74, 0x6c, 0x7b, 0xbb, 0x4a, 0x5e, 0x0a, 0x4b, - 0x46, 0xae, 0xb6, 0xad, 0x35, 0xb1, 0x51, 0x14, 0x9e, 0xe4, 0xbe, 0x27, 0x4f, 0x9d, 0xa1, 0xd8, - 0x76, 0x51, 0xa1, 0x55, 0xb4, 0x29, 0xb7, 0x73, 0x6d, 0xea, 0xdb, 0x56, 0x9b, 0xfa, 0x77, 0xaa, - 0x4d, 0x03, 0x3b, 0xd0, 0x26, 0xd8, 0x56, 0x9b, 0x06, 0xf7, 0xaf, 0x4d, 0x2d, 0x38, 0x1b, 0xaf, - 0xcb, 0x15, 0x68, 0x84, 0x06, 0x24, 0x8e, 0x15, 0x8e, 0x25, 0x78, 0xf5, 0xdf, 0xe6, 0xd8, 0xda, - 0x3a, 0xa2, 0x6b, 0x2e, 0xc3, 0xcb, 0xae, 0x97, 0xf1, 0xd6, 0xc5, 0xef, 0x4f, 0x77, 0x2e, 0x27, - 0x76, 0x34, 0xa7, 0xb8, 0x2f, 0x4e, 0x94, 0x52, 0x56, 0x49, 0x64, 0xd6, 0x51, 0xca, 0x0a, 0xdb, - 0x24, 0x99, 0xd9, 0x70, 0x36, 0x1e, 0x53, 0x19, 0x7c, 0xf8, 0x6b, 0x40, 0xe2, 0x58, 0xd1, 0x4b, - 0xe8, 0xc1, 0x45, 0x43, 0x6c, 0xad, 0xc5, 0xd1, 0x72, 0x27, 0xc5, 0x1b, 0x63, 0x27, 0x75, 0x8c, - 0xe2, 0x3c, 0xaa, 0x9d, 0x94, 0x20, 0x24, 0xa5, 0x93, 0x3a, 0x0b, 0x59, 0xed, 0xa4, 0x04, 0x99, - 0x7d, 0x5f, 0xba, 0x53, 0x21, 0xba, 0x7d, 0x49, 0xec, 0xf1, 0xb8, 0xc7, 0x22, 0xc6, 0x09, 0xb9, - 0x51, 0x57, 0xc5, 0x19, 0x18, 0x92, 0x85, 0x28, 0xa4, 0xb4, 0x13, 0x01, 0x47, 0xda, 0x91, 0x97, - 0x60, 0xd0, 0x0f, 0x6d, 0x0b, 0x9d, 0x98, 0xd0, 0x1d, 0xd1, 0x9f, 0x58, 0x15, 0x1f, 0x26, 0x99, - 0x9c, 0x3c, 0x01, 0xb9, 0x19, 0x2c, 0xa3, 0x2e, 0xcf, 0xc8, 0xbc, 0xb0, 0xba, 0xec, 0x5a, 0xc4, - 0x69, 0x8a, 0xff, 0xae, 0x3f, 0x21, 0xc8, 0x7e, 0x5f, 0xc2, 0x92, 0xd4, 0x24, 0xbd, 0x27, 0x35, - 0x79, 0x16, 0x72, 0x7c, 0xe6, 0x17, 0x02, 0x1c, 0x91, 0x33, 0x91, 0xb6, 0x5d, 0xa5, 0x99, 0xa0, - 0xdd, 0xa7, 0xd0, 0x9e, 0x87, 0x01, 0x0c, 0x39, 0x97, 0xce, 0xce, 0xf1, 0xec, 0xa4, 0xc9, 0x80, - 0xea, 0x99, 0x4b, 0x48, 0x4a, 0xee, 0x00, 0x09, 0x73, 0x38, 0xb0, 0x79, 0x82, 0x1a, 0x25, 0x7e, - 0x4b, 0xd9, 0xdd, 0xad, 0xff, 0x9d, 0x7e, 0x3c, 0x81, 0xe9, 0xb7, 0xe6, 0x13, 0x37, 0x35, 0x6a, - 0x72, 0x37, 0xa1, 0x7f, 0x7f, 0xc2, 0x13, 0xc8, 0x06, 0x9c, 0x08, 0xa0, 0x33, 0xa6, 0x65, 0xba, - 0xab, 0xf8, 0xe0, 0xbe, 0x6d, 0x1f, 0xfc, 0x2e, 0xf1, 0xe0, 0x42, 0xf8, 0xe0, 0x65, 0xd1, 0x3e, - 0xfe, 0xe4, 0xa4, 0x67, 0x90, 0xeb, 0x30, 0x56, 0x5d, 0xb5, 0x1d, 0x6f, 0x8a, 0xba, 0x75, 0xc7, - 0x6c, 0x61, 0xd2, 0x24, 0xbe, 0xe8, 0xa2, 0x77, 0xbc, 0xcb, 0x70, 0x35, 0x23, 0x44, 0xca, 0xae, - 0xd7, 0x6a, 0x43, 0x26, 0x77, 0xcd, 0x74, 0xd7, 0xe6, 0xe8, 0x1d, 0xda, 0x10, 0x2b, 0x32, 0xca, - 0xdd, 0x31, 0xdd, 0xb5, 0x5a, 0x83, 0x41, 0x65, 0xb9, 0x07, 0xa4, 0xe4, 0x25, 0xc8, 0xf1, 0xe3, - 0x42, 0x61, 0xce, 0x49, 0x95, 0xaa, 0x19, 0xb4, 0x83, 0x8a, 0x89, 0x36, 0xe4, 0xc3, 0x49, 0x61, - 0x5a, 0x83, 0xd1, 0x74, 0x48, 0x31, 0x82, 0x0e, 0x3c, 0x13, 0xa2, 0xb6, 0x64, 0x7b, 0x7b, 0x68, - 0x8f, 0xf6, 0xf6, 0x6b, 0x30, 0x1c, 0x32, 0xae, 0xd8, 0x06, 0x6e, 0x58, 0xa4, 0xe8, 0xe5, 0x08, - 0xb2, 0x03, 0xb7, 0x28, 0x07, 0x32, 0x03, 0x7d, 0x22, 0xdf, 0x80, 0x88, 0x90, 0x7a, 0x28, 0xb9, - 0xb0, 0x7b, 0xa7, 0xf1, 0x29, 0xd0, 0x11, 0x4f, 0xdc, 0xd1, 0x9d, 0x79, 0xe2, 0x16, 0x7f, 0x22, - 0x05, 0x27, 0xd8, 0xdb, 0x28, 0xb2, 0x24, 0x6f, 0x00, 0x30, 0xb0, 0x70, 0x9e, 0x4c, 0xa1, 0xf3, - 0xe4, 0xbb, 0xe4, 0x6f, 0x54, 0x1a, 0x5c, 0x0e, 0xa9, 0xb9, 0xe3, 0xe4, 0x79, 0x3f, 0x3e, 0x66, - 0xad, 0xbd, 0x44, 0x6b, 0x31, 0x0f, 0x4a, 0x89, 0xfb, 0xd9, 0xf7, 0xf1, 0xc8, 0xc3, 0xbd, 0x3a, - 0x2b, 0x7e, 0x6f, 0xba, 0x63, 0x29, 0xd2, 0x43, 0x59, 0x8e, 0x27, 0xa0, 0xdf, 0x77, 0x28, 0x15, - 0x13, 0x65, 0x2c, 0x08, 0xa3, 0xd3, 0xb3, 0x7d, 0x3c, 0xf9, 0x50, 0x62, 0xaf, 0x88, 0x25, 0xf9, - 0xc1, 0x2e, 0xfd, 0xa0, 0x70, 0x4c, 0xe2, 0x92, 0x2c, 0xb0, 0x43, 0xb4, 0x5f, 0xee, 0x79, 0x81, - 0xfd, 0x4a, 0xaa, 0x63, 0xc9, 0xd8, 0xa3, 0x2a, 0xb0, 0xe2, 0xa7, 0x7b, 0xfd, 0x4a, 0xb5, 0xfb, - 0xfa, 0x84, 0x27, 0x60, 0x40, 0x24, 0x2f, 0x8e, 0xc6, 0x8c, 0x88, 0x2b, 0x24, 0xbc, 0x92, 0x0c, - 0x08, 0xd8, 0xf6, 0x52, 0x0a, 0xb8, 0x92, 0x62, 0x46, 0xa4, 0x60, 0x2b, 0x4d, 0x22, 0x61, 0x1b, - 0xc8, 0xe9, 0xbb, 0xa6, 0x87, 0xbb, 0x51, 0xd6, 0x97, 0x19, 0xbe, 0x81, 0xa4, 0x77, 0x4d, 0x8f, - 0xef, 0x45, 0x03, 0x34, 0xdb, 0x1c, 0x0a, 0xeb, 0xa4, 0x37, 0xdc, 0x1c, 0xf2, 0x6d, 0x6b, 0x60, - 0x8b, 0x3c, 0x01, 0x03, 0x22, 0x10, 0x43, 0xb8, 0x37, 0x8a, 0xb7, 0x15, 0xa1, 0x1b, 0xf8, 0xb6, - 0x01, 0x01, 0xe3, 0xa8, 0xd1, 0x95, 0xd0, 0xa1, 0x1b, 0x39, 0x3a, 0x08, 0xd1, 0x04, 0x86, 0x5c, - 0x85, 0x91, 0xaa, 0xa7, 0x5b, 0x86, 0xee, 0x18, 0x0b, 0x6d, 0xaf, 0xd5, 0xf6, 0xe4, 0xc3, 0x10, - 0xd7, 0x33, 0xec, 0xb6, 0xa7, 0x29, 0x14, 0xe4, 0x29, 0x18, 0xf6, 0x21, 0xd3, 0x8e, 0x63, 0x3b, - 0xf2, 0x8e, 0xd7, 0xf5, 0x0c, 0xea, 0x38, 0x5a, 0x94, 0x80, 0xbc, 0x07, 0x86, 0xcb, 0x56, 0x90, - 0x8b, 0x57, 0x9b, 0x13, 0xfb, 0x5f, 0x8c, 0x6e, 0x37, 0x03, 0x44, 0xad, 0xed, 0x34, 0xb4, 0x28, - 0x21, 0x79, 0x0e, 0x86, 0x2a, 0x0d, 0xdd, 0x63, 0x66, 0x26, 0x9a, 0x50, 0x83, 0x61, 0xe0, 0x49, - 0x4b, 0xc0, 0xb9, 0x03, 0x42, 0x84, 0x4c, 0x6e, 0x86, 0x56, 0xdb, 0x50, 0x42, 0x33, 0x66, 0xb6, - 0x69, 0x11, 0x32, 0xf2, 0x7e, 0x18, 0xf5, 0x7f, 0x8b, 0xec, 0xfb, 0x22, 0x7a, 0x18, 0x6d, 0xdd, - 0xa0, 0xe5, 0x1d, 0x8e, 0xd3, 0x54, 0xe2, 0xe2, 0x6f, 0xa5, 0x44, 0xcc, 0x4e, 0xa0, 0x5f, 0x2f, - 0xc2, 0x48, 0xb5, 0xbd, 0x14, 0xd8, 0x25, 0x7e, 0x06, 0x05, 0x11, 0x74, 0x20, 0x61, 0xf8, 0x69, - 0x46, 0x84, 0x94, 0x89, 0xcd, 0xd7, 0x77, 0x0c, 0x72, 0x17, 0x0a, 0x8a, 0x62, 0x0b, 0xc2, 0x0e, - 0x30, 0x34, 0x5e, 0x8b, 0x12, 0x32, 0x45, 0x0d, 0x23, 0x45, 0x65, 0x45, 0x95, 0xc2, 0x4b, 0x35, - 0x89, 0x44, 0xd2, 0x95, 0x6c, 0x27, 0x5d, 0x29, 0xde, 0x14, 0x61, 0x9f, 0xb7, 0xe6, 0x83, 0xcf, - 0x3b, 0x0f, 0xd9, 0x5b, 0xf3, 0xc1, 0x47, 0xe1, 0xd9, 0xe8, 0x9d, 0x26, 0xe3, 0x8d, 0x60, 0xf2, - 0x28, 0xe4, 0x6e, 0xcd, 0x63, 0xbf, 0xf1, 0x37, 0x1f, 0xdc, 0xda, 0x2c, 0xf4, 0xdd, 0x11, 0x3d, - 0x26, 0x50, 0xc5, 0xcf, 0x64, 0xc3, 0x3c, 0x4c, 0xfb, 0x1a, 0xcb, 0x65, 0x45, 0xf8, 0x62, 0x5e, - 0x3a, 0x15, 0xc9, 0xe9, 0xd4, 0xc9, 0xa4, 0x89, 0x76, 0xdb, 0x42, 0xec, 0x53, 0xd5, 0xad, 0xa9, - 0x82, 0x56, 0x53, 0x42, 0xa8, 0x82, 0x52, 0x27, 0x82, 0xde, 0xce, 0x13, 0xc1, 0x8b, 0x30, 0xc2, - 0xdd, 0x0a, 0x79, 0x5a, 0x28, 0xcf, 0xdf, 0x37, 0xa0, 0xca, 0x50, 0x1f, 0x53, 0x43, 0xff, 0x3a, - 0x4d, 0x21, 0x4d, 0x18, 0xcf, 0xb9, 0xdd, 0x8f, 0xe7, 0xbe, 0xed, 0xc6, 0xf3, 0x33, 0xd0, 0x5f, - 0xaa, 0x94, 0x39, 0x71, 0xbf, 0x14, 0x54, 0xd4, 0x32, 0x6b, 0x94, 0x01, 0x65, 0x53, 0xcd, 0x27, - 0x64, 0xfb, 0x47, 0x31, 0xc1, 0x0d, 0x48, 0x27, 0x7a, 0x08, 0x91, 0xf7, 0x8f, 0x9c, 0xa6, 0xb8, - 0x95, 0x8e, 0x57, 0xf2, 0xbe, 0x7f, 0x4f, 0xc9, 0x2f, 0x45, 0xa3, 0x37, 0x50, 0x15, 0xf0, 0x04, - 0x52, 0x0e, 0x1e, 0xe3, 0x07, 0x91, 0x57, 0xa1, 0xff, 0x3a, 0xdd, 0xe0, 0x81, 0x46, 0xb9, 0xd0, - 0x8a, 0x5e, 0x13, 0x30, 0xb9, 0x6b, 0x7c, 0xba, 0xe2, 0x8f, 0xa5, 0xe3, 0x35, 0xca, 0xef, 0x5f, - 0x61, 0x3f, 0x05, 0x7d, 0x28, 0xca, 0xb2, 0xef, 0x63, 0x82, 0x02, 0x44, 0x71, 0x47, 0x43, 0xde, - 0x7c, 0xb2, 0xe2, 0xb7, 0xe5, 0xd4, 0xc2, 0xf5, 0xf7, 0xaf, 0xf4, 0x5e, 0x84, 0xc1, 0x49, 0xdb, - 0x72, 0x31, 0x9b, 0x6f, 0xdd, 0x57, 0x58, 0x8c, 0xfe, 0xac, 0x87, 0x60, 0xf9, 0xb8, 0x44, 0xa2, - 0xde, 0x8b, 0xf2, 0xb2, 0xad, 0x3e, 0x8a, 0x1c, 0x17, 0xfa, 0xbe, 0x70, 0xab, 0xbf, 0xc4, 0x80, - 0xea, 0xe1, 0x4c, 0x48, 0x4a, 0x6e, 0x42, 0xff, 0xe4, 0xaa, 0xd9, 0x30, 0x1c, 0x6a, 0x61, 0x80, - 0x9a, 0xb4, 0x47, 0x8f, 0xf6, 0xe5, 0x65, 0xfc, 0x17, 0x69, 0xf9, 0xeb, 0xd4, 0x45, 0xb3, 0x48, - 0x46, 0x0b, 0x01, 0x3b, 0xfb, 0x0d, 0x69, 0x80, 0xb0, 0x01, 0x79, 0x18, 0xd2, 0xc1, 0xfa, 0x88, - 0x7e, 0xd1, 0x11, 0x0d, 0x4a, 0xa3, 0x8d, 0x28, 0xc6, 0x76, 0x7a, 0xdb, 0xb1, 0x7d, 0x13, 0x72, - 0xfc, 0x8a, 0x15, 0x43, 0x17, 0xa5, 0x1c, 0x5a, 0x1d, 0x5f, 0xf8, 0x32, 0xd2, 0xf3, 0xb9, 0x16, - 0xb7, 0x9c, 0x91, 0xb9, 0x96, 0x33, 0x3b, 0x5b, 0x87, 0x5e, 0xfc, 0x8b, 0x5c, 0x80, 0x2c, 0x4a, - 0x31, 0x85, 0x17, 0x27, 0x68, 0x67, 0x28, 0xf2, 0x43, 0x3c, 0xeb, 0xa6, 0x49, 0xdb, 0xf2, 0x44, - 0x9c, 0x7a, 0xea, 0xe2, 0x90, 0x90, 0x8b, 0x80, 0x45, 0xe4, 0x22, 0x60, 0xc5, 0x9f, 0x4e, 0xc3, - 0x78, 0xf0, 0x9a, 0xcc, 0xa2, 0xc0, 0x23, 0x8d, 0xfb, 0x76, 0x98, 0xbc, 0x00, 0x80, 0xe9, 0xb1, - 0x98, 0x3c, 0xfd, 0x98, 0x60, 0x1c, 0x25, 0xc8, 0x08, 0xd5, 0x36, 0x72, 0xde, 0x10, 0x12, 0x17, - 0x7f, 0x26, 0x15, 0xab, 0xc3, 0xbf, 0x2f, 0x39, 0xca, 0xdb, 0xb1, 0xf4, 0x1e, 0xf7, 0xaf, 0x7e, - 0x5f, 0x64, 0x76, 0xd7, 0x17, 0xd1, 0x6f, 0x39, 0x80, 0x2d, 0xe9, 0x61, 0x7e, 0xcb, 0x17, 0xd2, - 0x40, 0x44, 0xc5, 0xfa, 0x52, 0xdb, 0x5b, 0x2d, 0x79, 0xd8, 0x77, 0x47, 0x53, 0xc5, 0xc3, 0xb3, - 0xf2, 0xec, 0x2e, 0xce, 0xca, 0x3f, 0x02, 0xa3, 0x4a, 0xad, 0x7e, 0x9c, 0xff, 0xa5, 0xdc, 0x85, - 0xc9, 0x15, 0xfd, 0x3b, 0x27, 0x56, 0x8b, 0x90, 0x15, 0xff, 0x32, 0x05, 0xe7, 0x92, 0x39, 0xdc, - 0x23, 0xc3, 0x20, 0x41, 0x00, 0x99, 0xbf, 0x1a, 0x01, 0x1c, 0xc0, 0xf9, 0xd7, 0xd1, 0x16, 0xc0, - 0x3d, 0x32, 0x79, 0xbc, 0xd5, 0x02, 0xf8, 0xb6, 0x14, 0x14, 0xba, 0x09, 0xa0, 0xb4, 0xcf, 0x00, - 0x83, 0x03, 0x90, 0x41, 0xf1, 0xdf, 0xa4, 0xe0, 0xc4, 0xc2, 0x9a, 0xa7, 0xfb, 0x00, 0xf7, 0x00, - 0x94, 0x33, 0xbc, 0x98, 0x49, 0xef, 0xe1, 0x62, 0x66, 0x0e, 0xfa, 0xf8, 0x3b, 0x18, 0xa2, 0x37, - 0x1e, 0x93, 0x2b, 0x51, 0x2b, 0xef, 0xd9, 0xe9, 0xda, 0xc3, 0x67, 0x51, 0xfc, 0xc9, 0x14, 0x8c, - 0xb2, 0x76, 0xd5, 0x0d, 0xab, 0x3e, 0xa3, 0x9b, 0x8d, 0xb6, 0x73, 0x98, 0xdf, 0xb6, 0xa7, 0x6b, - 0xcd, 0xe2, 0xef, 0xa4, 0xe1, 0x24, 0x96, 0x18, 0x0f, 0xd2, 0xf1, 0x88, 0x8a, 0xc5, 0x87, 0xf7, - 0x21, 0x07, 0x71, 0x8e, 0xbe, 0xb7, 0x75, 0x6b, 0x11, 0x46, 0xa2, 0xb2, 0x10, 0xcb, 0xd6, 0x43, - 0x91, 0x12, 0xed, 0x01, 0xb6, 0xc3, 0x5b, 0x28, 0x3c, 0x8a, 0x9f, 0x49, 0xc3, 0x58, 0x58, 0xbc, - 0xee, 0x1e, 0x59, 0xa1, 0xf6, 0x76, 0x09, 0x5e, 0x86, 0xd1, 0xf0, 0x4b, 0x16, 0x4d, 0xaf, 0xe1, - 0x5f, 0x84, 0xe3, 0xd5, 0xac, 0x48, 0xb4, 0xd7, 0x30, 0x5d, 0xaf, 0xe6, 0x31, 0x64, 0x24, 0xe5, - 0x5b, 0xb4, 0x9d, 0x22, 0x95, 0x7b, 0x64, 0xd9, 0xba, 0xc7, 0xa4, 0x72, 0x8f, 0xac, 0x65, 0x87, - 0x2e, 0x95, 0xef, 0x4a, 0xc3, 0xe9, 0x10, 0x36, 0x4f, 0x9b, 0x4b, 0xf7, 0x8c, 0xa5, 0xf7, 0x41, - 0xb9, 0xbf, 0xf8, 0x9b, 0x09, 0x29, 0x3d, 0x1c, 0xcd, 0xd1, 0x13, 0xe2, 0x3b, 0xf0, 0x8c, 0xf1, - 0xd9, 0xdb, 0x24, 0x96, 0x28, 0xac, 0x7b, 0x64, 0x78, 0x1d, 0x0d, 0x61, 0xdd, 0x23, 0xa3, 0xee, - 0xde, 0x13, 0xd6, 0x0f, 0xa7, 0xe1, 0x42, 0xb2, 0xb0, 0x4a, 0x8d, 0xc6, 0x8c, 0xed, 0x84, 0xb8, - 0x63, 0xe1, 0xc5, 0x84, 0xf7, 0xa9, 0xc8, 0xcc, 0xae, 0xd1, 0x3b, 0x26, 0x5d, 0x3f, 0x74, 0x31, - 0xcd, 0x40, 0x8e, 0xbf, 0x49, 0x67, 0xe1, 0x70, 0x7c, 0x27, 0x93, 0x4d, 0x7c, 0xc7, 0xb3, 0x20, - 0xae, 0xa8, 0x77, 0x26, 0x12, 0x4e, 0x5b, 0xfc, 0xb3, 0x14, 0x0c, 0x97, 0xda, 0x86, 0xe9, 0xe1, - 0xa9, 0xe6, 0x7e, 0xaf, 0xfd, 0xf6, 0x7a, 0x46, 0xb2, 0xb7, 0xd5, 0xed, 0x7d, 0xfe, 0x69, 0x6d, - 0x56, 0xa9, 0x1a, 0x1b, 0x7c, 0xcf, 0x14, 0xf5, 0x74, 0xb3, 0xa1, 0xb6, 0x17, 0x59, 0xb6, 0x7e, - 0x2c, 0x0d, 0xe3, 0x31, 0x52, 0x72, 0x21, 0x92, 0xd7, 0x12, 0xcf, 0x5d, 0x15, 0x8f, 0x27, 0x9e, - 0xe1, 0x72, 0x17, 0x47, 0xc5, 0x17, 0x20, 0x3b, 0xa5, 0x6f, 0xf0, 0x6f, 0xeb, 0xe5, 0x2c, 0x0d, - 0x7d, 0x43, 0x3e, 0x52, 0x44, 0x3c, 0x59, 0x82, 0x53, 0xc1, 0x15, 0xe2, 0xa2, 0xd9, 0xa4, 0x65, - 0x6b, 0xde, 0x6c, 0x34, 0x4c, 0x57, 0xb8, 0x2b, 0x3c, 0xb1, 0xb5, 0x59, 0xb8, 0xe8, 0xd9, 0x9e, - 0xde, 0xa8, 0x85, 0x57, 0x8f, 0x9e, 0xd9, 0xa4, 0x35, 0xd3, 0xaa, 0x35, 0x91, 0x52, 0x62, 0x99, - 0xcc, 0x8a, 0x94, 0x81, 0x60, 0x12, 0xa5, 0xba, 0x6e, 0x59, 0xd4, 0x28, 0x5b, 0x13, 0x1b, 0x1e, - 0xe5, 0x6e, 0x0e, 0x19, 0x7e, 0xe6, 0xc9, 0xb3, 0x2d, 0x71, 0x34, 0x63, 0xbc, 0xc4, 0x08, 0xb4, - 0x84, 0x46, 0xc5, 0x1f, 0xc9, 0xc2, 0x78, 0x95, 0xd6, 0xdb, 0x8e, 0xe9, 0x6d, 0x68, 0x18, 0x42, - 0x74, 0xb4, 0xd4, 0xc7, 0xef, 0xe9, 0xec, 0x36, 0x3d, 0x7d, 0x05, 0xfa, 0x7c, 0x0f, 0x04, 0x7e, - 0x83, 0x82, 0xe1, 0xa1, 0xc2, 0xf1, 0x40, 0xbe, 0x82, 0x12, 0x54, 0xa4, 0x01, 0x67, 0x17, 0x59, - 0x37, 0x25, 0x77, 0x66, 0x6e, 0x0f, 0x9d, 0xd9, 0x85, 0x1f, 0xf9, 0x00, 0x9c, 0x41, 0x6c, 0x42, - 0xb7, 0xf6, 0xe1, 0xa3, 0xd0, 0xd6, 0xe3, 0x8f, 0x4a, 0xee, 0xdc, 0x4e, 0xed, 0xc9, 0x07, 0x61, - 0x28, 0x18, 0x20, 0x26, 0x75, 0xc5, 0xd5, 0x4c, 0x97, 0x71, 0xc6, 0xb3, 0x85, 0x33, 0x30, 0x06, - 0x65, 0x44, 0x33, 0x4e, 0x47, 0x78, 0x15, 0x7f, 0x2b, 0x05, 0x67, 0xa7, 0xef, 0xf2, 0x64, 0xbf, - 0x88, 0xa8, 0x7a, 0xb6, 0xa3, 0xaf, 0xd0, 0x69, 0x4b, 0x5f, 0x6a, 0x1c, 0xfe, 0xca, 0xff, 0x3e, - 0xe8, 0x33, 0xf8, 0x47, 0x09, 0x9d, 0x7a, 0x34, 0x70, 0x5e, 0x4f, 0x78, 0x69, 0xf1, 0xfd, 0x9a, - 0xdf, 0xa6, 0xf8, 0xdb, 0x29, 0x78, 0x30, 0x91, 0xd0, 0x74, 0xdf, 0x0e, 0x9f, 0xf7, 0xa9, 0x4c, - 0x87, 0xcf, 0x13, 0x93, 0xe8, 0x25, 0x18, 0x33, 0x2d, 0x8f, 0xae, 0x38, 0xdc, 0x99, 0x28, 0x4c, - 0xe8, 0xaa, 0x8d, 0x4a, 0x70, 0x1c, 0x5d, 0xcf, 0x42, 0xe0, 0xbc, 0xed, 0xf8, 0xee, 0xfc, 0x6e, - 0xad, 0xed, 0x98, 0x7c, 0x5c, 0x6a, 0x27, 0x5d, 0xc5, 0xd7, 0xdf, 0xbd, 0xe9, 0x98, 0xec, 0x01, - 0xba, 0xb7, 0x4a, 0x2d, 0xbd, 0xb6, 0x6e, 0x3b, 0x6b, 0xe8, 0x77, 0xc3, 0x07, 0xa7, 0x36, 0xca, - 0xe1, 0xb7, 0x7d, 0x30, 0x79, 0x14, 0x86, 0x57, 0x1a, 0x6d, 0x1a, 0x14, 0x01, 0xe0, 0x97, 0x99, - 0xda, 0x10, 0x03, 0x06, 0x57, 0x40, 0xe7, 0x01, 0x90, 0x08, 0x6b, 0xc7, 0xf3, 0x9b, 0x4b, 0x6d, - 0x80, 0x41, 0x16, 0x45, 0x77, 0x9d, 0xe5, 0x5a, 0xcd, 0x85, 0x54, 0x6b, 0xd8, 0xd6, 0x4a, 0xcd, - 0xa3, 0x4e, 0x13, 0x5f, 0x14, 0xdd, 0x2e, 0xb4, 0xd3, 0x48, 0x81, 0x77, 0x43, 0xee, 0x9c, 0x6d, - 0xad, 0x2c, 0x52, 0xa7, 0xc9, 0x5e, 0xf5, 0x09, 0x20, 0xe2, 0x55, 0x1d, 0x3c, 0xd5, 0xe1, 0x1f, - 0x87, 0x7e, 0x17, 0x9a, 0xf8, 0x08, 0x7e, 0xdc, 0x83, 0x1f, 0x56, 0x80, 0x41, 0x9e, 0x09, 0x9d, - 0x0b, 0x0d, 0x9d, 0xb3, 0x34, 0xe0, 0x20, 0x94, 0xd7, 0x69, 0x10, 0xbe, 0x40, 0xdc, 0xff, 0x4a, - 0x13, 0xbf, 0x8a, 0x1f, 0xcf, 0x00, 0xc1, 0x43, 0x8f, 0xc0, 0x6a, 0xc0, 0xd2, 0xe8, 0xfb, 0x51, - 0xb4, 0x70, 0x5a, 0x4d, 0xef, 0x6a, 0x5a, 0x1d, 0xb5, 0xda, 0xcd, 0x9a, 0xde, 0x6a, 0xd5, 0x96, - 0xcd, 0x06, 0x26, 0x2a, 0xc0, 0x85, 0x4f, 0x1b, 0xb6, 0xda, 0xcd, 0x52, 0xab, 0x35, 0xc3, 0x81, - 0xe4, 0x9d, 0x30, 0xce, 0xe8, 0xb0, 0x93, 0x02, 0x4a, 0xf4, 0xc7, 0xd1, 0x18, 0x03, 0x74, 0x9e, - 0xf2, 0x69, 0x1f, 0x80, 0x7e, 0xc1, 0x93, 0xaf, 0x55, 0xbd, 0x5a, 0x1f, 0x67, 0xe6, 0xb2, 0x9e, - 0x0b, 0xd8, 0xf0, 0xc9, 0xb5, 0x57, 0x1b, 0xf0, 0xdb, 0x63, 0xc1, 0x1c, 0xab, 0xcd, 0x93, 0x6b, - 0x63, 0xb7, 0xf6, 0x6a, 0xc1, 0x6f, 0x72, 0x01, 0x46, 0x18, 0x97, 0x40, 0x60, 0xbc, 0xc6, 0x48, - 0xaf, 0xa6, 0x40, 0xc9, 0x55, 0x38, 0x19, 0x81, 0x70, 0x1b, 0x94, 0xfb, 0xce, 0xf4, 0x6a, 0x89, - 0xb8, 0xe2, 0x0f, 0x65, 0x60, 0x88, 0x75, 0x07, 0x5b, 0x9f, 0x0e, 0xa9, 0x23, 0xce, 0x40, 0x9f, - 0xed, 0xac, 0xd4, 0xda, 0x4e, 0x43, 0x8c, 0xbd, 0x9c, 0xed, 0xac, 0xdc, 0x74, 0x1a, 0xe4, 0x14, - 0xe4, 0x58, 0xef, 0x98, 0x86, 0x18, 0x62, 0xbd, 0x7a, 0xab, 0x55, 0x36, 0x48, 0x89, 0x77, 0x08, - 0xd6, 0xa7, 0xa8, 0xd5, 0x71, 0x6b, 0xcf, 0xbd, 0x2e, 0x7a, 0xf9, 0x8a, 0x17, 0x43, 0x62, 0x3f, - 0x61, 0xd5, 0x0a, 0x7e, 0x10, 0xa0, 0xb0, 0x30, 0x70, 0x5b, 0x62, 0xf0, 0x3e, 0x51, 0x59, 0x08, - 0x64, 0xc8, 0x82, 0x6f, 0x62, 0x0c, 0x32, 0x05, 0x24, 0xa4, 0x6a, 0xda, 0x86, 0xb9, 0x6c, 0x52, - 0x1e, 0x27, 0xdd, 0xcb, 0x6f, 0xb6, 0xe3, 0x58, 0x6d, 0xcc, 0x67, 0x32, 0x2f, 0x20, 0xe4, 0x45, - 0xae, 0x84, 0x9c, 0x0e, 0xd7, 0x3e, 0xde, 0xb7, 0xdc, 0x4e, 0x53, 0x50, 0xa8, 0x99, 0xd8, 0x1e, - 0x17, 0xc2, 0xe2, 0x0d, 0x18, 0x46, 0x07, 0xf2, 0x2a, 0x6d, 0xd0, 0xba, 0x67, 0x3b, 0xe4, 0x01, - 0xc8, 0x5c, 0xf7, 0x5d, 0xc8, 0x79, 0x54, 0xd0, 0x1a, 0xdd, 0xd0, 0x18, 0x8c, 0x14, 0x03, 0x37, - 0x80, 0x34, 0xde, 0x1b, 0xa3, 0xa7, 0x16, 0xbf, 0xdd, 0xf7, 0xef, 0xf4, 0x8b, 0x7f, 0x73, 0x00, - 0xc6, 0xaa, 0x95, 0xf2, 0xcc, 0xcc, 0x74, 0xf5, 0x56, 0x79, 0x0a, 0x6b, 0xd0, 0x1b, 0x87, 0x62, - 0x27, 0xcd, 0x02, 0x4c, 0xda, 0x96, 0x45, 0xb1, 0xe6, 0x70, 0x50, 0xf5, 0xc9, 0xf7, 0x5b, 0x08, - 0x30, 0x1d, 0x78, 0x48, 0x6d, 0xc9, 0x25, 0xe8, 0xe7, 0x5f, 0x54, 0x9e, 0x12, 0xf6, 0x13, 0xfa, - 0xd0, 0xb9, 0x2d, 0x73, 0x79, 0x19, 0x1d, 0x1a, 0x03, 0x34, 0xb9, 0x00, 0x7d, 0x53, 0x37, 0xaa, - 0xd5, 0xd2, 0x0d, 0xff, 0x6a, 0x1d, 0x23, 0xc0, 0x0d, 0xcb, 0xad, 0xb9, 0xba, 0xe5, 0x6a, 0x3e, - 0x92, 0x3c, 0x0a, 0xb9, 0x72, 0x05, 0xc9, 0x78, 0x5e, 0x13, 0x74, 0x50, 0x34, 0x5b, 0x9c, 0x4a, - 0xa0, 0xf0, 0xb9, 0xb7, 0xca, 0x53, 0x92, 0x7f, 0x09, 0x7f, 0xee, 0x1d, 0xd3, 0xe0, 0x4e, 0xa4, - 0x01, 0x9a, 0x3c, 0x07, 0x43, 0x55, 0xea, 0x98, 0x7a, 0xe3, 0x46, 0x1b, 0xb7, 0x9e, 0x52, 0x9e, - 0x74, 0x17, 0xe1, 0x35, 0x0b, 0x11, 0x5a, 0x84, 0x8c, 0x9c, 0x83, 0xec, 0xac, 0x69, 0xf9, 0x41, - 0xc6, 0x18, 0x85, 0xba, 0x6a, 0x5a, 0x9e, 0x86, 0x50, 0xf2, 0x28, 0x64, 0xae, 0x2d, 0x96, 0x85, - 0xcf, 0x2c, 0xf2, 0x7a, 0xc3, 0x8b, 0xe4, 0x5c, 0xbf, 0xb6, 0x58, 0x26, 0xcf, 0xc1, 0x00, 0x5b, - 0x14, 0xa9, 0x55, 0xa7, 0x6e, 0x7e, 0x10, 0x3f, 0x86, 0xfb, 0xe4, 0xf9, 0x40, 0xd9, 0x09, 0x26, - 0xa0, 0x24, 0xd7, 0x61, 0xcc, 0x2f, 0x19, 0x1c, 0xe4, 0x99, 0x1f, 0x0a, 0x4f, 0xeb, 0xd6, 0x05, - 0x2e, 0x29, 0xd5, 0x7c, 0xac, 0x21, 0x31, 0x20, 0xaf, 0xc2, 0xd8, 0x3e, 0x51, 0xf2, 0xa3, 0xbd, - 0xb8, 0xb5, 0x59, 0x78, 0x2c, 0xc6, 0xb4, 0xe6, 0x08, 0x2a, 0x89, 0x7b, 0x47, 0x4e, 0xe4, 0x43, - 0x00, 0x25, 0xcf, 0x73, 0xcc, 0xa5, 0x36, 0x33, 0x37, 0x47, 0xba, 0xc7, 0xc9, 0x16, 0xb7, 0x36, - 0x0b, 0x27, 0xf5, 0x80, 0x3c, 0x31, 0x5a, 0x56, 0x62, 0x47, 0x5e, 0x86, 0x21, 0xb6, 0xe2, 0xf9, - 0xa3, 0x50, 0xc4, 0xa1, 0xa0, 0x89, 0xc9, 0xd6, 0xc6, 0x9a, 0x2b, 0x10, 0xb2, 0x89, 0x29, 0x37, - 0x20, 0x5f, 0x04, 0x23, 0x91, 0x71, 0xcc, 0xeb, 0xf1, 0x48, 0x5e, 0xa8, 0x11, 0x2c, 0xcf, 0x89, - 0x8d, 0xd1, 0x25, 0x01, 0xeb, 0x48, 0x4e, 0xec, 0x28, 0x2f, 0xf2, 0xa1, 0xb0, 0x40, 0xb4, 0x2f, - 0x17, 0x5e, 0x54, 0x82, 0x17, 0xec, 0x79, 0x7c, 0x6b, 0xb3, 0xf0, 0x48, 0x5c, 0xbc, 0x58, 0x66, - 0x42, 0xde, 0xa6, 0x25, 0xf2, 0x20, 0x35, 0x80, 0xc9, 0x92, 0x5f, 0x76, 0x16, 0x4b, 0xf8, 0x48, - 0xf7, 0x64, 0x21, 0x66, 0x92, 0x3a, 0xa2, 0x86, 0xbe, 0x6f, 0x7a, 0x09, 0xf7, 0x2e, 0xbd, 0x66, - 0x0b, 0x12, 0xd9, 0x71, 0x25, 0x6c, 0x58, 0xfc, 0xc3, 0x34, 0x9c, 0x2c, 0xb5, 0xbd, 0xd5, 0x8a, - 0x43, 0x45, 0xf8, 0xd7, 0x01, 0x9c, 0x4f, 0xee, 0x6d, 0x9d, 0xda, 0xa3, 0x8f, 0x87, 0x32, 0x9b, - 0x65, 0xf7, 0x31, 0x9b, 0x2d, 0xc3, 0x68, 0xc9, 0x68, 0x9a, 0x56, 0x89, 0x17, 0x63, 0x9f, 0x9f, - 0x29, 0xe1, 0xba, 0x27, 0x65, 0x6b, 0x51, 0xd0, 0xe2, 0x7b, 0x78, 0x69, 0x23, 0x86, 0xaa, 0xe9, - 0x1c, 0x57, 0x6b, 0x2e, 0xeb, 0xb5, 0xfa, 0xaa, 0x6e, 0xad, 0xb0, 0xb5, 0x4d, 0x69, 0x55, 0xfc, - 0x4c, 0x1a, 0xce, 0xfb, 0x25, 0x29, 0xa8, 0xc7, 0x94, 0xc3, 0xb4, 0x56, 0x22, 0x15, 0x52, 0xef, - 0x43, 0xe9, 0x17, 0x3f, 0x9d, 0x86, 0x73, 0x6a, 0x40, 0xef, 0x7d, 0x2f, 0x94, 0x3f, 0x48, 0xc3, - 0x38, 0xb7, 0x2a, 0x2b, 0xba, 0xb7, 0x3a, 0xc9, 0x35, 0x6a, 0x5f, 0x92, 0xb8, 0x04, 0xfd, 0x9c, - 0x4d, 0x10, 0x5d, 0x83, 0x4b, 0x27, 0x57, 0x56, 0x5c, 0xb2, 0x7d, 0x34, 0xb9, 0x01, 0x27, 0x4b, - 0xcb, 0xcb, 0xb4, 0xee, 0x85, 0xc5, 0x7c, 0x6e, 0x84, 0x35, 0x39, 0x78, 0xd1, 0x14, 0x81, 0x0f, - 0x8b, 0x64, 0x61, 0x20, 0x41, 0x62, 0x3b, 0xb2, 0x08, 0xa7, 0x55, 0x78, 0x95, 0xef, 0x5f, 0xb3, - 0x52, 0x1d, 0x95, 0x18, 0x47, 0xfe, 0x9f, 0xd6, 0xa1, 0x6d, 0xd2, 0x5b, 0xa2, 0x5d, 0xd0, 0xdb, - 0xed, 0x2d, 0xd1, 0x48, 0x48, 0x6c, 0x57, 0xfc, 0xb1, 0x0c, 0x40, 0xb5, 0xa5, 0x5b, 0x16, 0x26, - 0xa8, 0xbb, 0x7f, 0xdd, 0x21, 0x9f, 0x8d, 0x44, 0x3f, 0xed, 0x74, 0xc8, 0x3c, 0x27, 0x12, 0x4a, - 0x1a, 0x6d, 0xc7, 0xf7, 0x17, 0x0e, 0x12, 0xda, 0x21, 0x50, 0x36, 0x7a, 0x02, 0x4a, 0x52, 0x86, - 0x6c, 0xc9, 0x59, 0x71, 0x45, 0x54, 0x73, 0xf7, 0x1c, 0x1b, 0xba, 0xb3, 0x92, 0x6c, 0x35, 0x20, - 0x8b, 0xe2, 0xd7, 0xa7, 0xe1, 0x01, 0x3e, 0x64, 0x5e, 0x75, 0xf4, 0xd6, 0x6a, 0x95, 0x7a, 0x1e, - 0x1e, 0x17, 0xdc, 0xbf, 0x93, 0xc8, 0x37, 0xa5, 0xe1, 0x34, 0xb7, 0xc3, 0x67, 0xa8, 0x41, 0xf9, - 0xa1, 0xcb, 0xd1, 0x76, 0x52, 0x7d, 0x8b, 0x85, 0x73, 0xb4, 0xbd, 0x5e, 0x0f, 0x50, 0x38, 0xbf, - 0x9c, 0x86, 0xd3, 0x61, 0xa9, 0xe3, 0x48, 0x41, 0xf8, 0xfb, 0x5e, 0x38, 0x7b, 0x9b, 0x20, 0x13, - 0x45, 0x7a, 0x1f, 0xdb, 0xdc, 0xb2, 0x2a, 0xf4, 0xee, 0xd1, 0xb9, 0x31, 0x49, 0xa4, 0xc7, 0x43, - 0x78, 0x5f, 0x5a, 0xfa, 0x2b, 0x69, 0x38, 0x13, 0x8a, 0x54, 0xdc, 0x68, 0x1d, 0x8f, 0xfc, 0x83, - 0x97, 0xe9, 0xf1, 0xd0, 0xdf, 0xd7, 0xd0, 0x4f, 0x94, 0xe9, 0xf1, 0xd8, 0xdf, 0x97, 0x9e, 0x7e, - 0x77, 0x1a, 0xce, 0x87, 0x32, 0x2d, 0xad, 0x50, 0xcb, 0xd3, 0xec, 0x46, 0xc3, 0x6e, 0x7b, 0x8b, - 0x8e, 0xb9, 0xb2, 0x72, 0x48, 0x21, 0x62, 0x07, 0x77, 0x68, 0x7d, 0x1a, 0x72, 0xe2, 0x9a, 0x08, - 0xab, 0xe7, 0x6b, 0xe2, 0xd7, 0x1e, 0xa5, 0xf5, 0x8f, 0xd3, 0x50, 0x48, 0x96, 0xd6, 0x8c, 0xed, - 0xd4, 0xe9, 0x94, 0x6d, 0xd1, 0x63, 0x79, 0x49, 0xf2, 0xfa, 0x9e, 0x34, 0x3c, 0x94, 0x2c, 0x2f, - 0xf6, 0xdf, 0x92, 0x5e, 0x5f, 0x3b, 0x16, 0x97, 0x24, 0xae, 0x5f, 0x4c, 0xc3, 0x49, 0xf6, 0xa7, - 0x59, 0x9f, 0xb5, 0x5d, 0x8f, 0xbd, 0xea, 0x01, 0xac, 0xc2, 0x7b, 0x5b, 0x31, 0x0e, 0x22, 0x18, - 0xc0, 0xef, 0x9e, 0xec, 0xbe, 0xba, 0xa7, 0x77, 0x1f, 0x5b, 0x9a, 0xb8, 0x40, 0x0f, 0x6d, 0x09, - 0x7e, 0xbb, 0x0a, 0xf4, 0x00, 0xd6, 0xdf, 0xfb, 0x59, 0xa0, 0x5f, 0x97, 0x81, 0xb1, 0x49, 0xc7, - 0x5e, 0xb7, 0xae, 0xd1, 0x75, 0xda, 0x38, 0xb4, 0xe1, 0x7e, 0x4f, 0x18, 0x88, 0xce, 0x1e, 0x0d, - 0x44, 0xbf, 0x1d, 0x79, 0x19, 0x46, 0x43, 0x59, 0xca, 0x59, 0xd9, 0xd1, 0x11, 0xa1, 0xce, 0x50, - 0xb5, 0x37, 0x18, 0x4e, 0xa4, 0x0f, 0x56, 0xa9, 0x8b, 0x7f, 0x16, 0xe9, 0x8d, 0xfb, 0xdb, 0x5c, - 0xdf, 0x77, 0x6f, 0xdc, 0x84, 0xd3, 0x93, 0x6d, 0xc7, 0xa1, 0x96, 0x97, 0xdc, 0x29, 0x78, 0x93, - 0x56, 0xe7, 0x14, 0xb5, 0x78, 0xe7, 0x74, 0x68, 0xcc, 0xd8, 0x8a, 0x40, 0x40, 0x95, 0x6d, 0x5f, - 0xc8, 0xb6, 0xcd, 0x29, 0x92, 0xd8, 0x26, 0x37, 0x2e, 0xfe, 0x6c, 0x5a, 0xee, 0xfa, 0x43, 0x9b, - 0xd5, 0xde, 0x16, 0x5d, 0x5f, 0xfc, 0x78, 0x06, 0x46, 0xd8, 0x6b, 0x2d, 0xea, 0xee, 0xda, 0xb1, - 0x09, 0xb3, 0x9f, 0x05, 0x82, 0x7d, 0x85, 0x2f, 0x49, 0x91, 0x7f, 0x36, 0x2f, 0xbf, 0x05, 0x83, - 0x77, 0xfa, 0x0a, 0x1f, 0x5f, 0xfc, 0xde, 0x6c, 0xd8, 0x1d, 0xc7, 0x06, 0xd0, 0x61, 0x77, 0x07, - 0x59, 0x80, 0x93, 0x62, 0x6e, 0xf3, 0x41, 0x3c, 0xcf, 0x58, 0x9f, 0x54, 0xe5, 0x5f, 0x4c, 0x8b, - 0x6d, 0x97, 0x3a, 0x35, 0x4f, 0x77, 0xd7, 0x44, 0xbe, 0xb1, 0xc4, 0x86, 0x8c, 0xa1, 0x98, 0xd5, - 0xa2, 0x0c, 0xfb, 0x43, 0x86, 0xfe, 0x84, 0x18, 0x63, 0x98, 0xd4, 0xb0, 0xf8, 0x03, 0x29, 0x18, - 0x53, 0x3f, 0x87, 0x5c, 0x86, 0x7e, 0xf6, 0x3b, 0xc8, 0x50, 0x23, 0xfc, 0xe7, 0x43, 0x8e, 0xdc, - 0x19, 0xcb, 0xa7, 0x21, 0xcf, 0xc3, 0x00, 0xfa, 0xbd, 0x61, 0x83, 0x74, 0x98, 0x18, 0x28, 0x6c, - 0x60, 0x32, 0x34, 0x6f, 0x16, 0x92, 0x92, 0x17, 0x61, 0xb0, 0x1c, 0x3a, 0x0c, 0x8b, 0x0b, 0x68, - 0x74, 0x71, 0x91, 0x5a, 0x86, 0x04, 0x9a, 0x4c, 0x5d, 0xfc, 0x7c, 0x3a, 0x54, 0xf5, 0x63, 0xd3, - 0x74, 0x5f, 0xa6, 0xe9, 0xf7, 0x67, 0x60, 0x78, 0xd2, 0xb6, 0x3c, 0xbd, 0xee, 0x1d, 0x1f, 0x06, - 0xef, 0xe7, 0x90, 0x8d, 0x14, 0xa0, 0x77, 0xba, 0xa9, 0x9b, 0x0d, 0x61, 0xf8, 0x60, 0x9e, 0x46, - 0xca, 0x00, 0x1a, 0x87, 0x93, 0x57, 0x31, 0x69, 0x17, 0x93, 0x74, 0xe0, 0xe5, 0x38, 0x12, 0x96, - 0xfb, 0x90, 0x50, 0x3c, 0x87, 0x55, 0x9d, 0x03, 0xf8, 0xc8, 0x91, 0x5b, 0xca, 0x7d, 0x76, 0x7c, - 0x30, 0x7a, 0x44, 0xfa, 0xec, 0xb3, 0x19, 0x38, 0xad, 0x7a, 0x04, 0x1e, 0x0f, 0x38, 0xd1, 0x79, - 0x7f, 0x0d, 0x4e, 0xaa, 0xb2, 0x99, 0x62, 0xd2, 0xe8, 0xed, 0xee, 0x3b, 0x72, 0x79, 0x6b, 0xb3, - 0xf0, 0x70, 0xdc, 0x13, 0x93, 0x3d, 0x2c, 0xd1, 0x9b, 0x24, 0xf1, 0x21, 0x89, 0x3d, 0x73, 0x8f, - 0x84, 0x74, 0xdf, 0xe7, 0x3d, 0xf3, 0x4d, 0xe9, 0x78, 0xcf, 0x1c, 0x4f, 0x78, 0x62, 0xe1, 0xfe, - 0xf9, 0x34, 0x3c, 0xa6, 0x0a, 0xe7, 0xf5, 0xe7, 0x9e, 0x7a, 0x41, 0xa3, 0x7e, 0x6a, 0xe7, 0xe3, - 0xe9, 0x45, 0x28, 0x31, 0xe6, 0x5d, 0xd6, 0xdd, 0x20, 0xcc, 0x53, 0xe4, 0x5d, 0x66, 0x10, 0x4d, - 0x60, 0x76, 0x20, 0xce, 0xe3, 0x39, 0x61, 0x17, 0xe2, 0xfc, 0xce, 0x6d, 0xc5, 0x79, 0x3c, 0x90, - 0x7d, 0x77, 0xb5, 0x2c, 0xc0, 0xab, 0xa6, 0x27, 0x12, 0xe0, 0x1f, 0xf1, 0xab, 0x32, 0xc9, 0xcf, - 0x35, 0xbb, 0x27, 0x3f, 0xd7, 0x30, 0xbb, 0x55, 0xef, 0x1e, 0xb2, 0x5b, 0xbd, 0x0c, 0x7d, 0x42, - 0x8e, 0x62, 0xdf, 0x7e, 0x26, 0xfc, 0x0a, 0x04, 0x77, 0x7a, 0xbc, 0x2f, 0xfd, 0xc7, 0xa1, 0xcf, - 0xe5, 0x19, 0xdf, 0xc4, 0xbe, 0x1a, 0x83, 0x95, 0x04, 0x48, 0xf3, 0xff, 0x20, 0xe7, 0x00, 0x2b, - 0xd8, 0xc9, 0xb1, 0x44, 0xbc, 0xa2, 0x1d, 0xfb, 0x97, 0x94, 0xa1, 0x4f, 0x44, 0x0d, 0xe4, 0x01, - 0xe3, 0x52, 0x02, 0xb5, 0x0c, 0xfb, 0x99, 0x07, 0x0f, 0xf0, 0x33, 0x6b, 0x41, 0x2c, 0x47, 0x9c, - 0x0b, 0x50, 0xf1, 0x27, 0x53, 0x30, 0xa6, 0x36, 0x22, 0x4f, 0x40, 0x8e, 0xff, 0x25, 0x76, 0xe8, - 0x98, 0x78, 0x96, 0xb7, 0x90, 0x13, 0xcf, 0x0a, 0xea, 0xe7, 0x60, 0x40, 0xf3, 0x43, 0x41, 0xc4, - 0x0e, 0x1d, 0xfd, 0x77, 0x83, 0xf8, 0x90, 0x48, 0x91, 0x1e, 0x1f, 0x48, 0x1e, 0x85, 0xcc, 0x42, - 0xc3, 0x10, 0x1b, 0x73, 0x0c, 0x88, 0xb2, 0x1b, 0x72, 0x56, 0x5d, 0x86, 0x65, 0x44, 0x37, 0xe8, - 0xba, 0x70, 0xf6, 0x46, 0x22, 0x8b, 0xae, 0xcb, 0x44, 0x37, 0xe8, 0x7a, 0xf1, 0x17, 0x52, 0xbe, - 0xfb, 0xee, 0x9c, 0xe9, 0x7a, 0x65, 0xeb, 0x8e, 0xde, 0x30, 0x83, 0x8e, 0x20, 0xaf, 0xc2, 0x48, - 0x88, 0x94, 0x12, 0x34, 0xc4, 0x12, 0x19, 0x61, 0x08, 0xff, 0xc3, 0x52, 0xd8, 0x4e, 0xb4, 0x19, - 0xb9, 0x20, 0x29, 0xbf, 0x74, 0x6a, 0x21, 0x47, 0xfd, 0x0b, 0x57, 0xec, 0xa1, 0x79, 0xd3, 0x75, - 0x4d, 0x6b, 0x85, 0x07, 0x8f, 0x66, 0x30, 0x8e, 0x0b, 0xcf, 0x4f, 0x9a, 0x1c, 0x5e, 0x73, 0x18, - 0x42, 0x8e, 0x3e, 0x92, 0x1b, 0x14, 0xff, 0x22, 0x05, 0x67, 0x19, 0x27, 0x4c, 0xa9, 0x1a, 0xfb, - 0xb0, 0x7d, 0x0d, 0xe0, 0x66, 0x17, 0x49, 0x89, 0x51, 0xfd, 0x48, 0x3c, 0x8b, 0x88, 0x42, 0xa8, - 0x70, 0xef, 0x22, 0xfb, 0xbd, 0x65, 0xb5, 0xfb, 0xf9, 0x14, 0x5e, 0x0f, 0x2e, 0x35, 0xe8, 0xcd, - 0x1b, 0xe5, 0xd7, 0x0f, 0xe8, 0x02, 0x7b, 0xaf, 0x53, 0xd7, 0x2b, 0x30, 0xe6, 0xe2, 0xbb, 0xd4, - 0xda, 0x96, 0x79, 0x17, 0x4f, 0xbe, 0xc4, 0xc7, 0x9c, 0x96, 0x3e, 0x46, 0x7a, 0x57, 0x6d, 0x84, - 0xd3, 0xdf, 0xb4, 0xcc, 0xbb, 0x98, 0x51, 0xf6, 0xfd, 0x98, 0xcd, 0x5f, 0xa2, 0x20, 0x67, 0xa1, - 0xdf, 0xaf, 0x87, 0x24, 0xaa, 0x0b, 0x05, 0xbf, 0xc9, 0x18, 0x64, 0xda, 0xa6, 0x81, 0xaf, 0xd9, - 0xab, 0xb1, 0x3f, 0x8b, 0xbf, 0x96, 0x81, 0xf1, 0xd2, 0xed, 0x6a, 0x79, 0x32, 0x88, 0x62, 0x10, - 0x51, 0xc1, 0xcd, 0x5d, 0xca, 0xc2, 0xa7, 0x27, 0x93, 0x30, 0xc2, 0xb3, 0x3a, 0x88, 0x92, 0x23, - 0xfc, 0x5c, 0xaa, 0x97, 0x47, 0x53, 0x44, 0x31, 0x92, 0x92, 0x0e, 0x23, 0x46, 0x54, 0x26, 0x71, - 0x49, 0x1d, 0x1e, 0x88, 0x90, 0xd6, 0xf4, 0x20, 0x69, 0x9e, 0x9f, 0xb0, 0xe4, 0x1d, 0x5b, 0x9b, - 0x85, 0x47, 0x3b, 0x12, 0x49, 0xac, 0xcf, 0xc8, 0xac, 0xc3, 0xe4, 0x7b, 0x2e, 0xb9, 0x0e, 0xe3, - 0xbc, 0x3d, 0x1e, 0xda, 0x05, 0x4e, 0x12, 0x8c, 0xb9, 0x94, 0x9c, 0x42, 0x42, 0xca, 0x89, 0xc8, - 0x10, 0xc9, 0x04, 0x2e, 0x22, 0xba, 0x6f, 0xc3, 0x29, 0x4e, 0xdf, 0xa2, 0x0e, 0x8e, 0x44, 0xdb, - 0xaa, 0xb9, 0xd4, 0x13, 0x81, 0xe1, 0x13, 0x8f, 0x6e, 0x6d, 0x16, 0x0a, 0x89, 0x04, 0x12, 0xd3, - 0x13, 0x48, 0x50, 0x09, 0xf0, 0x55, 0xea, 0xc9, 0x7e, 0x1a, 0xb9, 0x5d, 0xa8, 0xf9, 0x37, 0xa7, - 0xe1, 0xcc, 0x2c, 0xd5, 0x1b, 0xde, 0xea, 0xe4, 0x2a, 0xad, 0xaf, 0x1d, 0xbb, 0x4a, 0x47, 0xac, - 0x96, 0x44, 0xe9, 0x1c, 0x9b, 0xc8, 0xdd, 0xa4, 0x73, 0x6c, 0xf1, 0x0a, 0xe9, 0x7c, 0x4f, 0x1a, - 0x2e, 0x26, 0x6d, 0x0e, 0xf0, 0x76, 0xc0, 0x09, 0x42, 0x60, 0x0f, 0x6f, 0x51, 0x39, 0x38, 0x7b, - 0x58, 0xee, 0xb0, 0xec, 0x1e, 0x3d, 0x62, 0x77, 0x26, 0xae, 0x43, 0x2c, 0x35, 0x76, 0x6f, 0x89, - 0xeb, 0x1b, 0xd3, 0x70, 0xb2, 0x6a, 0xae, 0xb8, 0x9e, 0xed, 0xd0, 0x0a, 0xa6, 0x57, 0x39, 0xd6, - 0xa4, 0x8e, 0xa2, 0x39, 0xc4, 0x8a, 0x7e, 0xf7, 0xba, 0x68, 0x8e, 0x07, 0x94, 0x10, 0xcd, 0x7f, - 0xcc, 0xc2, 0xe8, 0xfc, 0x64, 0xc5, 0x2f, 0x34, 0xec, 0xe9, 0xce, 0x11, 0xad, 0xb7, 0x10, 0x9e, - 0x2d, 0x64, 0xf7, 0x70, 0xb6, 0x70, 0x70, 0xee, 0x05, 0xa2, 0xde, 0x7f, 0x6e, 0x57, 0xf5, 0xfe, - 0x1f, 0x83, 0x91, 0x66, 0xbd, 0x55, 0xf3, 0x53, 0x6a, 0x99, 0x86, 0x48, 0x66, 0x35, 0xd4, 0xac, - 0xb7, 0xfc, 0x2a, 0xd6, 0x06, 0x29, 0xc0, 0x60, 0xbd, 0x61, 0x52, 0xcb, 0xab, 0x99, 0xd6, 0xb2, - 0x2d, 0x12, 0x58, 0x01, 0x07, 0x95, 0xad, 0x65, 0x9b, 0x11, 0xb8, 0xf8, 0x3d, 0x9c, 0x80, 0x67, - 0xab, 0x02, 0x0e, 0x42, 0x82, 0x4b, 0x30, 0x86, 0xa7, 0xf9, 0x75, 0xbb, 0xe1, 0xd7, 0xe0, 0x13, - 0xc9, 0xaa, 0x46, 0x7d, 0xb8, 0x9f, 0x0e, 0xef, 0x9d, 0x30, 0x6e, 0x5a, 0x2b, 0x0e, 0xdb, 0xa1, - 0xeb, 0x6d, 0x8f, 0x97, 0x01, 0x12, 0xc9, 0xab, 0x46, 0x05, 0xa2, 0xd4, 0xf6, 0x78, 0x21, 0xa0, - 0x8b, 0x30, 0x46, 0x55, 0x52, 0xcc, 0x81, 0xa2, 0x8d, 0xd0, 0x08, 0x65, 0xf1, 0x73, 0x59, 0x18, - 0x0e, 0xd5, 0x6d, 0xfa, 0x90, 0x8e, 0xc4, 0xee, 0x6f, 0x65, 0x0b, 0x77, 0x4c, 0x7d, 0xbb, 0xb8, - 0x4d, 0xfd, 0x58, 0x0a, 0xfa, 0x56, 0xa9, 0x6e, 0x50, 0x87, 0x27, 0xdc, 0x1a, 0xbc, 0x7a, 0xea, - 0xf2, 0xba, 0xa3, 0xb7, 0x5a, 0xd4, 0x11, 0x29, 0x59, 0x78, 0xde, 0xa4, 0x89, 0xd7, 0x59, 0xf3, - 0xdf, 0xdc, 0x2c, 0xbc, 0xb8, 0x62, 0x7a, 0xab, 0xed, 0xa5, 0xcb, 0x75, 0xbb, 0x79, 0x65, 0xc5, - 0xd1, 0xef, 0x98, 0x1e, 0x1e, 0x0d, 0xeb, 0x8d, 0x2b, 0x1e, 0x6d, 0x60, 0xe2, 0xc9, 0x2b, 0x7a, - 0xcb, 0xbc, 0x82, 0x75, 0x7a, 0xae, 0x04, 0x9c, 0x16, 0x1d, 0xdd, 0xf4, 0xdc, 0xad, 0xcd, 0xc2, - 0xb8, 0x78, 0x98, 0x7c, 0x70, 0x26, 0x40, 0xc5, 0x5f, 0x49, 0xc1, 0xf8, 0xfc, 0x64, 0xe5, 0x5a, - 0x75, 0xe1, 0x86, 0x56, 0x99, 0x9c, 0xa7, 0xae, 0xab, 0xaf, 0x50, 0xf2, 0x38, 0xf4, 0x09, 0x88, - 0x38, 0x65, 0xc2, 0xe3, 0xbd, 0x37, 0x5c, 0xdb, 0x72, 0x5a, 0x75, 0xcd, 0xc7, 0x89, 0x7a, 0x52, - 0xe9, 0x2e, 0xf5, 0xa4, 0x8a, 0x90, 0x6b, 0x52, 0x6f, 0xd5, 0xf6, 0x0f, 0xc8, 0xf0, 0x0c, 0x9c, - 0x43, 0x34, 0xf1, 0x3f, 0x59, 0x80, 0x5c, 0x4b, 0x77, 0xf4, 0xa6, 0xbb, 0xdd, 0x6d, 0xd9, 0x43, - 0x58, 0xf8, 0x12, 0x49, 0x13, 0x6f, 0xc7, 0x04, 0x9b, 0xe2, 0xd7, 0x67, 0xf1, 0x9b, 0x82, 0x94, - 0x1a, 0x1f, 0x6d, 0x53, 0x9e, 0x0d, 0x78, 0xcf, 0x27, 0x0d, 0xcf, 0x43, 0xb6, 0xbd, 0xcb, 0xd1, - 0xd1, 0x16, 0xa3, 0xc3, 0xdd, 0xd3, 0xe8, 0x10, 0xad, 0x7c, 0xad, 0xcc, 0xee, 0x56, 0x2b, 0xdd, - 0x5d, 0xdc, 0xf1, 0x73, 0x5a, 0x72, 0x03, 0xfa, 0x9a, 0xbc, 0xfb, 0xc5, 0x20, 0x08, 0xd2, 0x5c, - 0xc6, 0xf4, 0x63, 0xe2, 0x01, 0x51, 0x90, 0x7a, 0x5c, 0xb4, 0x90, 0x35, 0x4b, 0x80, 0x22, 0x5a, - 0xde, 0x77, 0xc8, 0x5a, 0xfe, 0xb7, 0xb2, 0x70, 0x3a, 0xd4, 0x88, 0x1b, 0xb6, 0xc8, 0x19, 0xc4, - 0x24, 0x7c, 0xff, 0xa8, 0x85, 0xd4, 0xc1, 0xbd, 0x07, 0xd1, 0xc1, 0xa1, 0x9a, 0xe5, 0x76, 0xa1, - 0x66, 0xf7, 0x92, 0x5a, 0xfc, 0x74, 0x06, 0x1e, 0x08, 0xd5, 0x62, 0x0e, 0x0b, 0xff, 0x55, 0xab, - 0xd3, 0x55, 0xcf, 0xa1, 0x7a, 0xf3, 0xc8, 0x6a, 0x86, 0xbe, 0x2b, 0xcd, 0xd0, 0xf7, 0x3c, 0x61, - 0xc8, 0x3d, 0x99, 0x3b, 0xe4, 0x9e, 0xfc, 0xd6, 0x2c, 0x9c, 0x0b, 0x7b, 0x52, 0x9c, 0xdf, 0xcf, - 0x2e, 0x2e, 0x56, 0x8e, 0xfa, 0xec, 0xbf, 0xfb, 0xce, 0xf4, 0xef, 0xdc, 0x7a, 0x13, 0xef, 0xdc, - 0xc2, 0x05, 0x39, 0xd7, 0x71, 0x41, 0x7e, 0x27, 0x0c, 0x38, 0xfa, 0x3a, 0x0f, 0x11, 0x90, 0x93, - 0x0c, 0x06, 0x40, 0xad, 0xdf, 0xd1, 0xd7, 0x79, 0x00, 0xc2, 0x39, 0xc8, 0x2e, 0xd9, 0xc6, 0x06, - 0xda, 0x31, 0x43, 0xfc, 0x69, 0xec, 0xb7, 0x86, 0xff, 0x46, 0x54, 0x64, 0xe0, 0x90, 0x55, 0xe4, - 0xbb, 0xb2, 0x70, 0x72, 0xc2, 0x6e, 0x5b, 0xc6, 0x75, 0xba, 0xd1, 0xd2, 0x4d, 0x47, 0xa3, 0x75, - 0xfb, 0x0e, 0xfb, 0x84, 0xbf, 0x7a, 0x97, 0xd8, 0x83, 0xdb, 0xb7, 0x3e, 0x01, 0x03, 0x8b, 0xf6, - 0x1a, 0xb5, 0xa4, 0x3c, 0xde, 0x58, 0x84, 0xdd, 0x63, 0x40, 0x9e, 0x91, 0x2a, 0x24, 0x20, 0x4f, - 0x41, 0xdf, 0x84, 0xcd, 0x2f, 0x0f, 0xa5, 0x62, 0xb2, 0x4b, 0xb6, 0xb8, 0x34, 0x94, 0x84, 0x26, - 0xc8, 0xc8, 0xf3, 0x30, 0x50, 0x69, 0x2f, 0x35, 0xcc, 0xfa, 0x75, 0xea, 0x47, 0xaa, 0xa0, 0xdb, - 0x72, 0x0b, 0x81, 0xb5, 0x35, 0xba, 0x11, 0xc9, 0x6a, 0xe4, 0x93, 0xf2, 0x6a, 0xe1, 0x5c, 0xbe, - 0x93, 0x76, 0xdb, 0xf2, 0x50, 0x8d, 0x86, 0xfd, 0x6a, 0xe1, 0x1c, 0x51, 0xc3, 0x6b, 0x12, 0x2d, - 0x4a, 0x48, 0x9e, 0x83, 0x21, 0x1f, 0x30, 0x6f, 0x1b, 0x54, 0xce, 0x5a, 0x19, 0x34, 0x6c, 0xda, - 0x06, 0xd5, 0x22, 0x64, 0x64, 0x1e, 0xc6, 0x26, 0x6c, 0x0f, 0x73, 0x07, 0x2e, 0x2c, 0x2f, 0x38, - 0xe6, 0x8a, 0x69, 0x89, 0x5b, 0xe7, 0x47, 0xb6, 0x36, 0x0b, 0xe7, 0xd9, 0x37, 0x62, 0xe6, 0xc1, - 0x9a, 0xbd, 0x5c, 0xb3, 0x11, 0x2d, 0x67, 0x8f, 0x54, 0x9b, 0x32, 0x83, 0x21, 0xaa, 0x2c, 0xb6, - 0x17, 0x98, 0x0b, 0xc7, 0xca, 0xd2, 0x41, 0x59, 0x16, 0x60, 0xbc, 0xe2, 0xd0, 0x3b, 0xa6, 0xdd, - 0x76, 0x55, 0xa5, 0xc1, 0x4e, 0x68, 0x09, 0x64, 0x2d, 0x51, 0x7b, 0xe2, 0x6d, 0xc9, 0x2b, 0x30, - 0x74, 0x83, 0xae, 0x87, 0xbc, 0xfa, 0xc2, 0x64, 0x69, 0x16, 0x5d, 0x4f, 0x66, 0x13, 0x69, 0x91, - 0xa8, 0x16, 0xfd, 0xfb, 0x50, 0x8b, 0x0c, 0xbc, 0x43, 0x56, 0x8b, 0x6b, 0xb6, 0xc9, 0x6b, 0x9d, - 0xdf, 0xa2, 0x4e, 0x60, 0x52, 0xce, 0xe8, 0x66, 0x63, 0x9f, 0xa9, 0xea, 0xde, 0xee, 0x9a, 0x92, - 0xd4, 0x2d, 0xb9, 0xbd, 0x77, 0xcb, 0xef, 0xa6, 0x61, 0xb0, 0x3a, 0x59, 0x9e, 0xf7, 0x17, 0xfb, - 0xed, 0xeb, 0x1c, 0x4f, 0xc0, 0x30, 0x4f, 0xa2, 0x57, 0x32, 0x0c, 0x87, 0xba, 0xae, 0xd8, 0xc4, - 0xa2, 0x6a, 0x89, 0x44, 0x79, 0x3a, 0xc7, 0xc8, 0x37, 0xc7, 0x91, 0x26, 0x6c, 0x6e, 0x64, 0x26, - 0x00, 0x06, 0xf2, 0x8b, 0xed, 0x6d, 0x18, 0xd2, 0xa1, 0x33, 0xa8, 0x3c, 0x37, 0x06, 0xa4, 0xe4, - 0x09, 0xc8, 0xcd, 0xf3, 0x25, 0x38, 0x1b, 0xba, 0xa5, 0xf0, 0xa5, 0x57, 0x76, 0x4b, 0xe1, 0x34, - 0xe4, 0x02, 0x64, 0x2b, 0xe1, 0x72, 0x8e, 0x13, 0x28, 0x5b, 0xc8, 0x65, 0x77, 0x8d, 0x0a, 0x77, - 0xa6, 0xc9, 0x4e, 0xb0, 0x85, 0x38, 0xb7, 0x83, 0x3c, 0x72, 0x6c, 0x6d, 0x4e, 0xce, 0x23, 0xc7, - 0x58, 0x90, 0x93, 0x7e, 0x65, 0x0f, 0x7e, 0x72, 0x26, 0xaa, 0x85, 0x6c, 0xc0, 0x10, 0x97, 0xb1, - 0xdb, 0xb2, 0x2d, 0x97, 0x92, 0x87, 0x00, 0xb8, 0xde, 0x4d, 0xb2, 0x69, 0x1a, 0xab, 0x34, 0x6b, - 0x12, 0x24, 0x78, 0xa1, 0xf4, 0xbe, 0x5f, 0xa8, 0xf8, 0xf7, 0xd2, 0x30, 0xc2, 0x9e, 0x3d, 0x69, - 0x37, 0x9b, 0xb6, 0x35, 0xc5, 0x46, 0xc8, 0x14, 0xf4, 0x89, 0xde, 0x16, 0x8a, 0x1e, 0x98, 0x47, - 0x92, 0x22, 0x70, 0xb7, 0x21, 0x87, 0xff, 0x90, 0xf5, 0xd0, 0x57, 0x14, 0x25, 0xba, 0x26, 0x1b, - 0x46, 0xd7, 0x48, 0x31, 0x35, 0x72, 0x7d, 0x70, 0x89, 0x9a, 0xbc, 0xcc, 0x56, 0xaa, 0x58, 0xda, - 0x45, 0x74, 0x90, 0x89, 0x64, 0x5b, 0x94, 0x27, 0x27, 0xb9, 0x01, 0x99, 0xc5, 0x43, 0x67, 0x94, - 0xa6, 0xe8, 0xb6, 0x93, 0xd1, 0x8f, 0xe0, 0x38, 0x3e, 0x9c, 0x1c, 0xf1, 0x4b, 0xae, 0x67, 0xed, - 0x53, 0x14, 0xbf, 0x29, 0x03, 0x63, 0xac, 0x09, 0xdb, 0xc2, 0x98, 0xd6, 0x0a, 0xe6, 0xe0, 0x3f, - 0x84, 0x09, 0xe8, 0x25, 0xc8, 0xf1, 0x2e, 0x8a, 0xb9, 0x96, 0x44, 0x3a, 0x4f, 0x6d, 0xcd, 0x31, - 0x6c, 0x5c, 0xf0, 0x74, 0xf7, 0xf2, 0xb8, 0xe0, 0x79, 0xf1, 0xe5, 0x71, 0xc1, 0x69, 0xc8, 0x25, - 0xe8, 0xe5, 0x96, 0x45, 0x6f, 0x98, 0xd6, 0x1c, 0x0d, 0x0a, 0xb9, 0xfc, 0x0c, 0x37, 0x29, 0x5e, - 0x40, 0x4d, 0x75, 0xbc, 0xb2, 0x65, 0xd0, 0xbb, 0x22, 0x1b, 0x3b, 0x76, 0xb2, 0xcb, 0xa0, 0x35, - 0x93, 0x81, 0xe5, 0x2c, 0xc1, 0x21, 0x31, 0x9b, 0x27, 0xfc, 0x2e, 0x93, 0xed, 0x18, 0x9c, 0x27, - 0x82, 0x4e, 0x56, 0x1f, 0x1b, 0x6d, 0x52, 0xdc, 0x4a, 0xc3, 0xb8, 0xe8, 0x4f, 0x84, 0x1e, 0xcd, - 0xde, 0x79, 0x01, 0x60, 0x51, 0x98, 0xe0, 0x41, 0xca, 0x73, 0x14, 0xa2, 0x6f, 0x98, 0xd7, 0x22, - 0x93, 0xac, 0x44, 0x8c, 0xe5, 0xc5, 0x45, 0xad, 0x8c, 0xf2, 0x94, 0x5c, 0x84, 0x9f, 0x0a, 0xa8, - 0xd2, 0x34, 0x24, 0x26, 0x57, 0xa0, 0x6f, 0xca, 0x74, 0x5b, 0x0d, 0x3d, 0x12, 0xbc, 0x6e, 0x70, - 0x90, 0x3c, 0xa2, 0x05, 0x55, 0xf1, 0x73, 0x59, 0x38, 0x3b, 0xc9, 0x8f, 0xf1, 0x2b, 0x1a, 0x75, - 0x3d, 0xc7, 0xe4, 0x1e, 0x82, 0xc7, 0x17, 0x78, 0xb1, 0x3a, 0x8a, 0xbb, 0x09, 0x00, 0xba, 0x01, - 0x27, 0x93, 0xa4, 0x2a, 0x32, 0xd5, 0x63, 0xaa, 0x59, 0xff, 0x3e, 0xa5, 0x55, 0x73, 0x24, 0x0a, - 0x2d, 0xb1, 0x1d, 0x5b, 0xd5, 0xd0, 0xba, 0xef, 0x0b, 0x57, 0x35, 0x66, 0xd4, 0xcb, 0xab, 0x1a, - 0x9a, 0xf5, 0x6d, 0x20, 0xd3, 0xd6, 0xb2, 0xed, 0xd4, 0x69, 0x93, 0x5a, 0xde, 0xf4, 0xdd, 0x96, - 0xe9, 0x50, 0xff, 0xd0, 0xfc, 0x6c, 0x6c, 0x49, 0x59, 0x34, 0x9b, 0xd4, 0xf5, 0xf4, 0x66, 0x6b, - 0xe2, 0x92, 0x38, 0xbf, 0x3a, 0x4f, 0xc3, 0xd6, 0x35, 0xca, 0x9b, 0x87, 0x0f, 0xf9, 0xd4, 0x17, - 0x0a, 0x29, 0x2d, 0xe1, 0x01, 0xc5, 0xaf, 0x4a, 0xc3, 0xd8, 0x2d, 0x8b, 0x7a, 0xaa, 0x33, 0xd3, - 0x9e, 0x8f, 0x10, 0xc2, 0x13, 0x96, 0xf4, 0x2e, 0x4e, 0x58, 0xfc, 0x83, 0x87, 0xcc, 0x2e, 0x0f, - 0x1e, 0x66, 0x01, 0xea, 0xfb, 0x70, 0x41, 0x09, 0xdb, 0x2a, 0x82, 0x08, 0x07, 0xd1, 0x7d, 0x2e, - 0x88, 0xf0, 0xce, 0xfb, 0xbe, 0x13, 0xc4, 0xdf, 0xcf, 0x84, 0x69, 0xff, 0x45, 0x2a, 0xf7, 0x63, - 0x67, 0xbf, 0x7d, 0x05, 0x57, 0x6a, 0xd0, 0x57, 0xd1, 0x37, 0x98, 0x3c, 0xb7, 0x33, 0xde, 0x0b, - 0x6c, 0xf1, 0x6b, 0x71, 0xda, 0x44, 0x73, 0xd9, 0x67, 0x94, 0xd4, 0x4b, 0xc7, 0xf9, 0x16, 0xf7, - 0x93, 0x6f, 0xf1, 0x2d, 0xe9, 0xa5, 0x5f, 0x4a, 0xc7, 0x7a, 0xe9, 0xd8, 0xf9, 0x71, 0x5f, 0x19, - 0x1c, 0xbf, 0x3d, 0x03, 0x27, 0xcb, 0x96, 0x88, 0xcd, 0x60, 0x16, 0x44, 0xe3, 0x78, 0x72, 0xba, - 0xe7, 0x26, 0xa7, 0x78, 0x27, 0x1d, 0x3b, 0x44, 0xdf, 0x73, 0x9d, 0xf4, 0x8b, 0x69, 0xb5, 0x93, - 0x8e, 0xa7, 0xa6, 0x7d, 0x4d, 0x4d, 0x6c, 0xb2, 0x0f, 0x04, 0x5a, 0xa5, 0x75, 0x87, 0x1e, 0x67, - 0x12, 0x39, 0x70, 0x89, 0x1e, 0x4f, 0x24, 0x07, 0x2d, 0xd1, 0xe3, 0x51, 0xbf, 0x2f, 0x89, 0x32, - 0x43, 0x3c, 0x90, 0xe8, 0x81, 0xb9, 0xd9, 0x1f, 0x2f, 0x76, 0x07, 0xbf, 0x5d, 0x52, 0x7a, 0xe9, - 0x78, 0x26, 0xb9, 0xe7, 0x7a, 0x29, 0x32, 0x3b, 0x1d, 0x58, 0xf0, 0xc1, 0xfd, 0xbe, 0x5d, 0xd2, - 0xa8, 0xe7, 0x98, 0xf4, 0x8e, 0xde, 0x38, 0xde, 0x2e, 0xdd, 0xbb, 0xdb, 0xa5, 0x68, 0x27, 0x1d, - 0xcf, 0x4d, 0xf7, 0xe4, 0x76, 0x29, 0xda, 0x49, 0xc7, 0x53, 0xd3, 0xbe, 0xa6, 0xa6, 0xff, 0x94, - 0x86, 0xb3, 0x93, 0xd4, 0xf1, 0x4a, 0x6d, 0x6f, 0xd5, 0x76, 0x4c, 0x6f, 0xc3, 0x0f, 0xe0, 0x0c, - 0xae, 0x4f, 0xff, 0xca, 0xfd, 0x39, 0xe5, 0x64, 0xc0, 0x99, 0x3d, 0xe6, 0x81, 0x0e, 0x8f, 0xfd, - 0xb3, 0xbb, 0x38, 0xf6, 0xd7, 0x61, 0x50, 0xaa, 0x68, 0x2b, 0xe4, 0xf8, 0x78, 0xd0, 0x1b, 0x49, - 0x62, 0x0a, 0xde, 0xa4, 0x5b, 0x3d, 0xdc, 0xba, 0x1e, 0xd4, 0xc3, 0xfd, 0x44, 0x1a, 0xce, 0x77, - 0x65, 0x44, 0x2e, 0x43, 0x5f, 0x5d, 0xe7, 0x11, 0x49, 0x29, 0x29, 0x91, 0xb8, 0xae, 0x3a, 0x39, - 0xe4, 0xea, 0x3a, 0xba, 0x37, 0xbc, 0x0f, 0x86, 0xea, 0xfc, 0x50, 0x93, 0x97, 0xf2, 0x4f, 0x87, - 0x65, 0x29, 0x65, 0xb8, 0xec, 0x5e, 0x21, 0xe0, 0xe8, 0x23, 0xf4, 0xd7, 0xe1, 0x54, 0x3d, 0xac, - 0xee, 0x1b, 0xbc, 0x3e, 0x4f, 0x44, 0x32, 0x78, 0xf5, 0x51, 0xf9, 0xeb, 0x05, 0x51, 0xec, 0xdb, - 0x31, 0x17, 0x42, 0x22, 0x17, 0xe9, 0xa9, 0x27, 0xeb, 0x71, 0x0e, 0x6e, 0xf1, 0x4f, 0x52, 0xf0, - 0x60, 0x17, 0xd6, 0xe4, 0x03, 0x30, 0x28, 0xb5, 0x13, 0x3a, 0x78, 0xce, 0x7f, 0xa9, 0xd7, 0xff, - 0x3f, 0xf6, 0xae, 0xa7, 0xb7, 0x71, 0x22, 0x8a, 0xd7, 0x49, 0x93, 0xa6, 0x2f, 0xed, 0x76, 0x3a, - 0x6d, 0x69, 0xbb, 0xbb, 0x4d, 0xb7, 0x61, 0x97, 0x3f, 0x11, 0xd2, 0xa2, 0x45, 0x20, 0x21, 0x71, - 0x60, 0x62, 0x8f, 0x9b, 0xa1, 0xfe, 0x13, 0x3c, 0x4e, 0x4b, 0xb9, 0x58, 0x6e, 0xea, 0xdd, 0x18, - 0xb2, 0x4d, 0x14, 0x87, 0x2e, 0x1c, 0x91, 0x90, 0xf8, 0x0a, 0x88, 0x03, 0x17, 0xbe, 0x03, 0x67, - 0x8e, 0x7b, 0xe4, 0x13, 0x20, 0xe8, 0x91, 0x6f, 0xc0, 0x05, 0xa1, 0x19, 0x3b, 0x89, 0x1d, 0x35, - 0xa0, 0xc2, 0x65, 0x57, 0xed, 0x6d, 0xe6, 0xf7, 0x7e, 0x33, 0xf3, 0x3c, 0xf3, 0x9c, 0xd8, 0x9e, - 0xf7, 0xde, 0xbc, 0xfb, 0xf6, 0xfb, 0x33, 0x56, 0x62, 0xd2, 0x28, 0x73, 0xe5, 0x13, 0x18, 0xef, - 0x43, 0xa1, 0xdd, 0xf1, 0xc3, 0x33, 0x79, 0xa2, 0xfa, 0xbf, 0x75, 0x1a, 0x3b, 0x3e, 0x08, 0x7a, - 0xda, 0xf1, 0x41, 0x02, 0xf8, 0x11, 0x94, 0x4e, 0xc3, 0xc8, 0x3f, 0xe9, 0x06, 0x71, 0xfc, 0x4d, - 0x29, 0x76, 0x25, 0x19, 0x61, 0x69, 0x57, 0x92, 0x11, 0x56, 0xfd, 0x4b, 0x81, 0xf5, 0xcb, 0x06, - 0xc2, 0x6f, 0x41, 0x51, 0xa6, 0xa8, 0x1d, 0xa4, 0xb3, 0xe9, 0xc4, 0x48, 0x7a, 0xf1, 0x63, 0x04, - 0x3f, 0x84, 0x85, 0xe8, 0x8b, 0x93, 0xcf, 0x82, 0xf6, 0x30, 0x59, 0x77, 0x69, 0x2c, 0x09, 0x94, - 0xde, 0xb8, 0x4f, 0x20, 0xfc, 0x21, 0x64, 0x4f, 0x25, 0x4f, 0x3c, 0xaa, 0xa4, 0x3b, 0x4d, 0x46, - 0x90, 0x76, 0xa7, 0x89, 0xd2, 0x07, 0x97, 0x53, 0x58, 0x99, 0x78, 0x04, 0x7a, 0x1d, 0x3f, 0xea, - 0x24, 0x6e, 0x0a, 0x32, 0x09, 0xfa, 0x94, 0x28, 0xed, 0xae, 0xd1, 0x1f, 0xb9, 0x0b, 0x36, 0xfc, - 0xa8, 0x53, 0xfd, 0x36, 0x07, 0xab, 0xf5, 0xc0, 0x7f, 0x1a, 0x5d, 0xfb, 0xcd, 0xdf, 0xa9, 0x99, - 0xb8, 0xc6, 0xbb, 0xbf, 0x53, 0x33, 0x71, 0x8d, 0xb7, 0x7f, 0x7f, 0xc8, 0x03, 0x52, 0xbb, 0x7e, - 0x14, 0x85, 0x8f, 0xc3, 0x9b, 0x9d, 0xdf, 0x17, 0xf0, 0x41, 0x34, 0xbb, 0x40, 0x37, 0x6f, 0x0a, - 0x2f, 0xdc, 0x02, 0xc9, 0xb3, 0x35, 0xc6, 0x0b, 0x74, 0xf3, 0x96, 0xf0, 0xbf, 0xde, 0x12, 0x7e, - 0xce, 0xc1, 0x92, 0xf4, 0xaa, 0xd6, 0x7b, 0x83, 0x67, 0xfe, 0xe0, 0x65, 0x4f, 0x0b, 0x39, 0x99, - 0x84, 0xf9, 0xab, 0x39, 0x76, 0xfe, 0xf7, 0x5c, 0x90, 0xd5, 0x6f, 0x0a, 0x70, 0x9b, 0xf4, 0x47, - 0xb9, 0x11, 0x5c, 0x7f, 0xf0, 0x24, 0x18, 0x6a, 0xa1, 0xdf, 0xd5, 0x82, 0xb3, 0x30, 0xb8, 0x96, - 0x49, 0x05, 0x92, 0xe8, 0xda, 0xc2, 0x95, 0xa2, 0x6b, 0x1f, 0x02, 0xc4, 0x73, 0xd7, 0xe8, 0x45, - 0xc3, 0xc4, 0x59, 0x75, 0xe5, 0x8f, 0x5f, 0x77, 0xcb, 0x43, 0x89, 0x7a, 0x9d, 0x5e, 0x34, 0x74, - 0x52, 0x94, 0x49, 0x83, 0x66, 0x6f, 0x30, 0x4c, 0x1c, 0x21, 0xd3, 0x0d, 0xfa, 0xbd, 0xc1, 0xb8, - 0x81, 0xa0, 0xe0, 0x0f, 0xa0, 0x2c, 0xee, 0xd7, 0xee, 0x79, 0x70, 0xca, 0x9a, 0xd1, 0x56, 0x69, - 0xe2, 0x7a, 0x39, 0x48, 0x60, 0x2f, 0xcc, 0x64, 0x8e, 0x4b, 0xd3, 0x71, 0x15, 0x8a, 0xf1, 0x27, - 0xd3, 0x24, 0x2c, 0x4a, 0x06, 0xfe, 0xf5, 0x25, 0xe2, 0x24, 0x12, 0xfc, 0x1e, 0x2c, 0xd6, 0xbb, - 0xbd, 0xf6, 0xe7, 0xa2, 0x45, 0x9c, 0xcc, 0x22, 0x8e, 0x68, 0x38, 0x89, 0x41, 0x2f, 0xec, 0xa7, - 0x23, 0x1a, 0xc6, 0x54, 0x5c, 0x87, 0xe5, 0xa4, 0xd2, 0x1c, 0x04, 0x8f, 0xc3, 0x2f, 0xe3, 0xe4, - 0x16, 0xb1, 0x97, 0xf4, 0xa8, 0x6d, 0x5f, 0x4a, 0xd2, 0x8f, 0xdd, 0x99, 0x26, 0xb5, 0x76, 0x7c, - 0x96, 0xc8, 0x41, 0x78, 0x76, 0x8a, 0xb7, 0x61, 0xa3, 0xc5, 0xa9, 0xe3, 0x1d, 0x30, 0x4b, 0xf3, - 0x5a, 0x16, 0x6f, 0x52, 0x95, 0xe9, 0x8c, 0x6a, 0x68, 0x0e, 0xaf, 0xc1, 0xca, 0x44, 0xd4, 0x68, - 0x99, 0xc4, 0x42, 0x0a, 0x5e, 0x85, 0xe5, 0x09, 0x58, 0xb7, 0x5d, 0x94, 0xc3, 0xeb, 0x80, 0x26, - 0x10, 0x3f, 0xe6, 0x2e, 0x35, 0x51, 0xbe, 0xf6, 0xbd, 0x02, 0x20, 0x46, 0x89, 0xe3, 0x46, 0xf0, - 0x1d, 0xd8, 0x94, 0x24, 0xdb, 0x61, 0xfb, 0xcc, 0x9a, 0x1a, 0x69, 0x03, 0x56, 0xd3, 0x42, 0xc3, - 0x56, 0x89, 0x81, 0x94, 0xb1, 0x02, 0x09, 0xcc, 0xb9, 0x9d, 0x1a, 0x2d, 0x01, 0xed, 0x03, 0x97, - 0xa0, 0xfc, 0x34, 0xca, 0x55, 0x66, 0xa2, 0x79, 0xbc, 0x09, 0x6b, 0x69, 0x94, 0x5a, 0xae, 0x43, - 0x98, 0x86, 0x0a, 0xb5, 0xd7, 0xa1, 0x2c, 0xbf, 0x6d, 0x24, 0xf9, 0x48, 0x97, 0xa0, 0x64, 0xd7, - 0x39, 0x75, 0x0e, 0xa5, 0x36, 0x00, 0x45, 0x8d, 0x5a, 0x42, 0x33, 0xa5, 0x36, 0x80, 0xb2, 0xa9, - 0x13, 0xbd, 0xdb, 0x7b, 0x26, 0xdf, 0xb3, 0x77, 0x60, 0xdb, 0xd4, 0x89, 0xa7, 0x1b, 0xf6, 0x91, - 0xe7, 0x1e, 0x37, 0xe9, 0xd4, 0x75, 0xbc, 0x09, 0x0f, 0xb2, 0xe2, 0x26, 0x75, 0x3c, 0x4e, 0x39, - 0x67, 0xb6, 0xe5, 0xa9, 0xd4, 0x71, 0x99, 0xce, 0x54, 0xe2, 0x52, 0xa4, 0x88, 0x79, 0xcf, 0x52, - 0x99, 0xe5, 0xd5, 0x89, 0xa5, 0xa1, 0x5c, 0xed, 0x4f, 0x05, 0x80, 0xeb, 0x6e, 0x33, 0x51, 0xae, - 0x0c, 0x0b, 0xcc, 0x3a, 0x24, 0x06, 0x13, 0x23, 0x94, 0x60, 0xde, 0x6e, 0x52, 0xb1, 0x10, 0x8b, - 0x50, 0x50, 0x0d, 0x9b, 0x53, 0x94, 0x13, 0xa0, 0x43, 0x89, 0x86, 0xf2, 0x02, 0x3c, 0x72, 0x98, - 0x4b, 0xd1, 0xbc, 0x28, 0x1a, 0xdc, 0x25, 0x2e, 0x2a, 0x88, 0xa2, 0x2e, 0x8b, 0x45, 0xd1, 0x19, - 0xa7, 0xae, 0xac, 0x2c, 0x88, 0xcb, 0xd6, 0x47, 0xb5, 0x92, 0x10, 0x89, 0xae, 0x35, 0xe6, 0xa0, - 0x45, 0x51, 0x11, 0x5d, 0x8a, 0x0a, 0x88, 0x09, 0x71, 0xa8, 0x69, 0x1f, 0x52, 0x54, 0x16, 0x7d, - 0x99, 0x07, 0x02, 0x5e, 0x12, 0x45, 0xc7, 0x14, 0xc5, 0x65, 0xd1, 0x93, 0x43, 0x89, 0xd1, 0x24, - 0x6e, 0x03, 0xdd, 0x12, 0xfa, 0xc8, 0x3e, 0x57, 0xe2, 0x96, 0x16, 0x31, 0x29, 0x42, 0x09, 0x47, - 0x33, 0x98, 0x75, 0x80, 0x56, 0xa5, 0x22, 0xc7, 0xa6, 0xac, 0x60, 0xd1, 0x40, 0x96, 0xd6, 0x6a, - 0x5f, 0x2b, 0x50, 0xb4, 0xb9, 0x9c, 0xeb, 0x4d, 0x58, 0xb3, 0xf9, 0x65, 0xb3, 0xbc, 0x0a, 0xcb, - 0x23, 0x81, 0xc1, 0xac, 0xd6, 0x27, 0xb1, 0x55, 0x8e, 0x20, 0x93, 0xa8, 0x36, 0x47, 0x39, 0x61, - 0x3c, 0x23, 0xe8, 0x88, 0x59, 0x9a, 0x7d, 0xc4, 0x51, 0x1e, 0xaf, 0x40, 0x79, 0x04, 0x32, 0x9b, - 0xa3, 0x79, 0x8c, 0xe1, 0xd6, 0x18, 0x68, 0x12, 0xcd, 0xe6, 0xa8, 0x50, 0x3b, 0x87, 0x25, 0x2d, - 0x38, 0x0f, 0xdb, 0x41, 0x62, 0xba, 0x3b, 0xb0, 0xad, 0xd1, 0x43, 0xa6, 0xd2, 0x99, 0xc6, 0x9b, - 0x15, 0x93, 0x26, 0x43, 0x0a, 0x7e, 0x05, 0x70, 0x16, 0xfe, 0x88, 0x98, 0x3a, 0xca, 0xe1, 0x2d, - 0x58, 0xcf, 0xe2, 0xcc, 0x72, 0x5b, 0x16, 0x45, 0xf9, 0xda, 0x8f, 0x0a, 0x6c, 0xd0, 0xae, 0x1f, - 0x0d, 0xc3, 0x76, 0x14, 0xf8, 0x83, 0x76, 0x47, 0xf5, 0x87, 0xc1, 0x93, 0xde, 0xe0, 0x2b, 0x5c, - 0x85, 0x0a, 0x35, 0x08, 0x77, 0x99, 0xca, 0x29, 0x71, 0xd4, 0x86, 0x27, 0x8c, 0x68, 0xdf, 0x76, - 0x8e, 0xbd, 0x7d, 0x6a, 0x51, 0x87, 0x18, 0x68, 0x0e, 0xbf, 0x0a, 0xbb, 0x33, 0x38, 0x9c, 0xaa, - 0x2d, 0x87, 0xb9, 0xc7, 0x48, 0xc1, 0x7b, 0xb0, 0x33, 0x93, 0x24, 0xea, 0x28, 0x87, 0x2b, 0x70, - 0x7b, 0x16, 0xe5, 0x63, 0x03, 0xe5, 0x6b, 0xdf, 0x29, 0x80, 0xed, 0x7e, 0x70, 0xc6, 0xb3, 0x2a, - 0xde, 0x83, 0xbb, 0xc2, 0x7a, 0xbc, 0xd9, 0x0a, 0xee, 0xc1, 0xce, 0xa5, 0x8c, 0x94, 0x7a, 0xbb, - 0x70, 0x67, 0x06, 0x25, 0x51, 0xee, 0x2e, 0x6c, 0x5d, 0x4e, 0x90, 0xaa, 0xfd, 0xa4, 0xc0, 0x06, - 0x39, 0x7d, 0x1a, 0x9e, 0xc5, 0x77, 0x4e, 0x64, 0xea, 0x24, 0xf9, 0xdb, 0x7e, 0x03, 0xee, 0x13, - 0xcd, 0x14, 0x6b, 0xa3, 0xba, 0xcc, 0xb6, 0xb8, 0x27, 0xee, 0x3d, 0x61, 0xa3, 0x2d, 0x3e, 0xb5, - 0x9a, 0xaf, 0x41, 0xf5, 0x1f, 0x98, 0x6a, 0x83, 0x58, 0xfb, 0xe2, 0x87, 0x01, 0xdf, 0x87, 0x7b, - 0x33, 0x79, 0xd4, 0x22, 0x75, 0x83, 0x6a, 0x28, 0x87, 0x1f, 0xc0, 0xde, 0x4c, 0x96, 0xc6, 0x78, - 0x4c, 0xcb, 0xd7, 0xfc, 0xcc, 0x91, 0x1e, 0xe2, 0x2a, 0x55, 0xdb, 0x72, 0x89, 0xea, 0x5e, 0x66, - 0xfe, 0xdb, 0xb0, 0x91, 0x91, 0xd6, 0x5b, 0x9c, 0x59, 0x94, 0xf3, 0xf8, 0x47, 0x25, 0x23, 0x1a, - 0x4f, 0x6d, 0xae, 0xae, 0x3d, 0xff, 0xbd, 0x32, 0xf7, 0xfc, 0xa2, 0xa2, 0xfc, 0x72, 0x51, 0x51, - 0x7e, 0xbb, 0xa8, 0x28, 0x9f, 0x3e, 0xba, 0x4a, 0xfc, 0x77, 0xfc, 0xc7, 0x7c, 0x52, 0x94, 0xcf, - 0xe2, 0xef, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xa3, 0x96, 0x0b, 0x83, 0xb9, 0x3c, 0x02, 0x00, + 0x32, 0x8b, 0x99, 0x59, 0x8d, 0xc6, 0xf8, 0xf3, 0x67, 0xd3, 0x3a, 0x29, 0x91, 0x14, 0x4d, 0x5a, + 0xa6, 0x2c, 0x59, 0x5e, 0x49, 0x96, 0xbd, 0xb2, 0x24, 0x4b, 0x2b, 0xaf, 0xd7, 0xba, 0x65, 0x49, + 0xb4, 0x57, 0xd4, 0x7d, 0xd8, 0xd2, 0x6a, 0x25, 0x19, 0xd4, 0xca, 0x96, 0xe5, 0xc5, 0xda, 0x5e, + 0xad, 0x0e, 0x4b, 0x96, 0x2d, 0xed, 0xfe, 0xe2, 0x45, 0x64, 0x66, 0x64, 0x64, 0x56, 0xe1, 0x1c, + 0xa1, 0x31, 0x8d, 0x7f, 0xba, 0x51, 0xef, 0xbd, 0x78, 0x99, 0xf9, 0xe2, 0x45, 0xc4, 0x8b, 0x88, + 0x77, 0xc0, 0x25, 0x8f, 0x36, 0x68, 0xcb, 0x76, 0xbc, 0x2b, 0x0d, 0xba, 0xa2, 0xd7, 0x37, 0xae, + 0x78, 0x1b, 0x2d, 0xea, 0x5e, 0xa1, 0x77, 0xa8, 0xe5, 0xf9, 0xff, 0x5d, 0x6e, 0x39, 0xb6, 0x67, + 0x93, 0x1c, 0xff, 0x75, 0xf6, 0xe4, 0x8a, 0xbd, 0x62, 0x23, 0xe8, 0x0a, 0xfb, 0x8b, 0x63, 0xcf, + 0x9e, 0x5b, 0xb1, 0xed, 0x95, 0x06, 0xbd, 0x82, 0xbf, 0x96, 0xda, 0xcb, 0x57, 0x5c, 0xcf, 0x69, + 0xd7, 0x3d, 0x81, 0x2d, 0xa8, 0x58, 0xcf, 0x6c, 0x52, 0xd7, 0xd3, 0x9b, 0x2d, 0x41, 0xf0, 0x90, + 0x4a, 0xb0, 0xee, 0xe8, 0xad, 0x16, 0x75, 0xc4, 0xc3, 0xcf, 0xbe, 0x23, 0x78, 0x4f, 0xbd, 0x5e, + 0xa7, 0xae, 0xdb, 0x30, 0x5d, 0xef, 0xca, 0x9d, 0xa7, 0xa5, 0x5f, 0x82, 0xf0, 0x91, 0xe4, 0x0f, + 0xc2, 0x7f, 0x05, 0xc9, 0x93, 0xc9, 0x24, 0xfe, 0x13, 0x95, 0x47, 0x17, 0x3f, 0x93, 0x86, 0xfe, + 0x79, 0xea, 0xe9, 0x86, 0xee, 0xe9, 0xe4, 0x1c, 0xf4, 0x96, 0x2d, 0x83, 0xde, 0xcd, 0xa7, 0x1e, + 0x4e, 0x5d, 0xcc, 0x4c, 0xe4, 0xb6, 0x36, 0x0b, 0x69, 0x6a, 0x6a, 0x1c, 0x48, 0xce, 0x43, 0x76, + 0x71, 0xa3, 0x45, 0xf3, 0xe9, 0x87, 0x53, 0x17, 0x07, 0x26, 0x06, 0xb6, 0x36, 0x0b, 0xbd, 0x28, + 0x34, 0x0d, 0xc1, 0xe4, 0x11, 0x48, 0x97, 0xa7, 0xf2, 0x19, 0x44, 0x8e, 0x6f, 0x6d, 0x16, 0x86, + 0xdb, 0xa6, 0xf1, 0x84, 0xdd, 0x34, 0x3d, 0xda, 0x6c, 0x79, 0x1b, 0x5a, 0xba, 0x3c, 0x45, 0x2e, + 0x40, 0x76, 0xd2, 0x36, 0x68, 0x3e, 0x8b, 0x44, 0x64, 0x6b, 0xb3, 0x30, 0x52, 0xb7, 0x0d, 0x2a, + 0x51, 0x21, 0x9e, 0xbc, 0x02, 0xd9, 0x45, 0xb3, 0x49, 0xf3, 0xbd, 0x0f, 0xa7, 0x2e, 0x0e, 0x5e, + 0x3d, 0x7b, 0x99, 0x8b, 0xef, 0xb2, 0x2f, 0xbe, 0xcb, 0x8b, 0xbe, 0x7c, 0x27, 0xc6, 0xbe, 0xb0, + 0x59, 0xe8, 0xd9, 0xda, 0x2c, 0x64, 0x99, 0xc8, 0x3f, 0xfd, 0xc5, 0x42, 0x4a, 0xc3, 0x96, 0xe4, + 0x25, 0x18, 0x9c, 0x6c, 0xb4, 0x5d, 0x8f, 0x3a, 0x37, 0xf4, 0x26, 0xcd, 0xe7, 0xf0, 0x81, 0x67, + 0xb7, 0x36, 0x0b, 0xa7, 0xeb, 0x1c, 0x5c, 0xb3, 0xf4, 0xa6, 0xfc, 0x60, 0x99, 0xbc, 0xf8, 0x03, + 0x29, 0x18, 0xad, 0x52, 0xd7, 0x35, 0x6d, 0x2b, 0x90, 0xcd, 0xe3, 0x30, 0x20, 0x40, 0xe5, 0x29, + 0x94, 0xcf, 0xc0, 0x44, 0xdf, 0xd6, 0x66, 0x21, 0xe3, 0x9a, 0x86, 0x16, 0x62, 0xc8, 0x53, 0xd0, + 0x77, 0xdb, 0xf4, 0x56, 0xe7, 0x67, 0x4a, 0x42, 0x4e, 0xa7, 0xb7, 0x36, 0x0b, 0x64, 0xdd, 0xf4, + 0x56, 0x6b, 0xcd, 0x65, 0x5d, 0x7a, 0xa0, 0x4f, 0x46, 0xe6, 0x60, 0xac, 0xe2, 0x98, 0x77, 0x74, + 0x8f, 0x5e, 0xa7, 0x1b, 0x15, 0xbb, 0x61, 0xd6, 0x37, 0x84, 0x14, 0x1f, 0xde, 0xda, 0x2c, 0x9c, + 0x6b, 0x71, 0x5c, 0x6d, 0x8d, 0x6e, 0xd4, 0x5a, 0x88, 0x95, 0x98, 0xc4, 0x5a, 0x16, 0x7f, 0x11, + 0x60, 0xe8, 0xa6, 0x4b, 0x9d, 0xe0, 0xbd, 0x2f, 0x40, 0x96, 0xfd, 0x16, 0xaf, 0x8c, 0x32, 0x6f, + 0xbb, 0xd4, 0x91, 0x65, 0xce, 0xf0, 0xe4, 0x12, 0xf4, 0xce, 0xd9, 0x2b, 0xa6, 0x25, 0x5e, 0xfb, + 0xc4, 0xd6, 0x66, 0x61, 0xb4, 0xc1, 0x00, 0x12, 0x25, 0xa7, 0x20, 0xef, 0x87, 0xa1, 0x72, 0x93, + 0xe9, 0x90, 0x6d, 0xe9, 0x9e, 0xed, 0x88, 0xb7, 0x45, 0xe9, 0x9a, 0x12, 0x5c, 0x6a, 0x18, 0xa1, + 0x27, 0xef, 0x05, 0x28, 0xdd, 0xae, 0x6a, 0x76, 0x83, 0x96, 0xb4, 0x1b, 0x42, 0x19, 0xb0, 0xb5, + 0xbe, 0xee, 0xd6, 0x1c, 0xbb, 0x41, 0x6b, 0xba, 0x23, 0x3f, 0x56, 0xa2, 0x26, 0xd3, 0x30, 0x52, + 0xc2, 0x51, 0xa1, 0xd1, 0x8f, 0xb6, 0xa9, 0xeb, 0xb9, 0xf9, 0xde, 0x87, 0x33, 0x17, 0x07, 0x26, + 0xce, 0x6f, 0x6d, 0x16, 0x1e, 0xe0, 0xe3, 0xa5, 0xe6, 0x08, 0x94, 0xc4, 0x42, 0x69, 0x44, 0x26, + 0x60, 0xb8, 0xf4, 0x66, 0xdb, 0xa1, 0x65, 0x83, 0x5a, 0x9e, 0xe9, 0x6d, 0x08, 0x0d, 0x39, 0xb7, + 0xb5, 0x59, 0xc8, 0xeb, 0x0c, 0x51, 0x33, 0x05, 0x46, 0x62, 0x12, 0x6d, 0x42, 0x16, 0x60, 0xfc, + 0xd5, 0xc9, 0x4a, 0x95, 0x3a, 0x77, 0xcc, 0x3a, 0x2d, 0xd5, 0xeb, 0x76, 0xdb, 0xf2, 0xf2, 0x7d, + 0xc8, 0xe7, 0x91, 0xad, 0xcd, 0xc2, 0xf9, 0x95, 0x7a, 0xab, 0xe6, 0x72, 0x6c, 0x4d, 0xe7, 0x68, + 0x89, 0x59, 0xbc, 0x2d, 0xf9, 0x20, 0x0c, 0x2f, 0x3a, 0x4c, 0x0b, 0x8d, 0x29, 0xca, 0xe0, 0xf9, + 0x7e, 0xd4, 0xff, 0xd3, 0x97, 0xc5, 0x4c, 0xc5, 0xa1, 0x7e, 0xcf, 0xf2, 0x97, 0xf5, 0x78, 0x83, + 0x9a, 0x81, 0x38, 0xf9, 0x65, 0x23, 0xac, 0x08, 0x85, 0x3c, 0xfb, 0x78, 0xd3, 0xa1, 0x46, 0x4c, + 0xdb, 0x06, 0xf0, 0x9d, 0x2f, 0x6d, 0x6d, 0x16, 0x1e, 0x77, 0x04, 0x4d, 0xad, 0xab, 0xda, 0x75, + 0x64, 0x45, 0xa6, 0xa1, 0x9f, 0x69, 0xd3, 0x75, 0xd3, 0x32, 0xf2, 0xf0, 0x70, 0xea, 0xe2, 0xc8, + 0xd5, 0x31, 0xff, 0xed, 0x7d, 0xf8, 0xc4, 0x99, 0xad, 0xcd, 0xc2, 0x09, 0xa6, 0x83, 0xb5, 0x35, + 0xd3, 0x92, 0xa7, 0x88, 0xa0, 0x29, 0x1b, 0x45, 0x13, 0xb6, 0x87, 0x43, 0x77, 0x30, 0x1c, 0x45, + 0x4b, 0xb6, 0xa7, 0x0e, 0x5b, 0x9f, 0x8c, 0x4c, 0xc2, 0xf0, 0x84, 0xed, 0x95, 0x2d, 0xd7, 0xd3, + 0xad, 0x3a, 0x2d, 0x4f, 0xe5, 0x87, 0xb0, 0x1d, 0xaa, 0x05, 0x6b, 0x67, 0x0a, 0x4c, 0x2d, 0x32, + 0x29, 0x45, 0xdb, 0x90, 0x79, 0x00, 0xf6, 0x0a, 0x0b, 0x8e, 0xc9, 0x06, 0xc2, 0x30, 0xbe, 0x3f, + 0x91, 0xdf, 0x9f, 0x63, 0x26, 0x1e, 0xd8, 0xda, 0x2c, 0x9c, 0xc2, 0x2f, 0xb0, 0x11, 0x20, 0xeb, + 0x6a, 0x48, 0x46, 0x9e, 0x87, 0x01, 0xf6, 0x8b, 0xa9, 0xae, 0x9b, 0x1f, 0x41, 0x35, 0xcd, 0x6f, + 0x6d, 0x16, 0x4e, 0x62, 0x4b, 0xa6, 0xe7, 0xb2, 0x86, 0x86, 0xa4, 0xe4, 0xb3, 0x29, 0xfe, 0x1e, + 0x8b, 0x8e, 0x6e, 0x7a, 0x6e, 0x7e, 0x14, 0xb5, 0xe0, 0xd4, 0xe5, 0x60, 0xe6, 0x9e, 0xd3, 0x97, + 0x68, 0xe3, 0x96, 0xde, 0x68, 0x53, 0x77, 0xe2, 0x4b, 0xd8, 0x04, 0xf8, 0x1b, 0x9b, 0x85, 0x17, + 0x57, 0x4c, 0x6f, 0xb5, 0xbd, 0x74, 0xb9, 0x6e, 0x37, 0xaf, 0xac, 0x38, 0xfa, 0x1d, 0xd3, 0xd3, + 0x3d, 0xd3, 0xb6, 0xf4, 0xc6, 0x95, 0x70, 0x6d, 0x69, 0x99, 0xca, 0x62, 0x70, 0x99, 0x3f, 0x21, + 0xf8, 0x1a, 0x0f, 0x7f, 0xaa, 0x5f, 0xc3, 0x89, 0x48, 0x19, 0x46, 0xd9, 0x2f, 0x79, 0x5a, 0x1d, + 0x43, 0x19, 0x17, 0xb6, 0x36, 0x0b, 0x0f, 0x62, 0xfb, 0x0e, 0x73, 0xab, 0xda, 0x8e, 0x3c, 0x09, + 0x03, 0x6e, 0xdd, 0x6e, 0xd1, 0x5a, 0xcb, 0xb4, 0xf2, 0xe3, 0xf8, 0x79, 0x81, 0x9a, 0x54, 0x19, + 0xa2, 0x62, 0x5a, 0x5a, 0xbf, 0x2b, 0xfe, 0x22, 0x4f, 0x42, 0x6e, 0x82, 0xea, 0xcd, 0xf2, 0x54, + 0x9e, 0xe0, 0x03, 0x4f, 0x6d, 0x6d, 0x16, 0xc6, 0x97, 0xa8, 0xde, 0x8c, 0x76, 0xa6, 0x20, 0x22, + 0xf3, 0x30, 0x36, 0x61, 0x7b, 0xc8, 0x67, 0x61, 0x59, 0xf4, 0xe5, 0x89, 0x70, 0x58, 0x32, 0x6d, + 0xe0, 0x4f, 0xb7, 0x97, 0xe3, 0xfd, 0x17, 0x6b, 0x5a, 0xfc, 0x93, 0x2c, 0x8c, 0xb0, 0x81, 0x2a, + 0xcd, 0xa9, 0x25, 0xb6, 0x3c, 0x30, 0x08, 0xfb, 0x1a, 0xb7, 0xa5, 0xd7, 0xa9, 0x98, 0x5e, 0x51, + 0xb5, 0x2d, 0x1f, 0x28, 0x8b, 0x40, 0xa1, 0x27, 0x97, 0xa0, 0x9f, 0x83, 0xca, 0x53, 0x62, 0xc6, + 0x1d, 0xde, 0xda, 0x2c, 0x0c, 0xb8, 0x08, 0xab, 0x99, 0x86, 0x16, 0xa0, 0xd9, 0x94, 0xc7, 0xff, + 0x9e, 0xb5, 0x5d, 0x8f, 0x31, 0x17, 0x13, 0x2e, 0xea, 0xb6, 0x68, 0xb0, 0x2a, 0x50, 0xf2, 0x94, + 0x17, 0x6d, 0x44, 0x5e, 0x00, 0xe0, 0x90, 0x92, 0x61, 0x38, 0x62, 0xd6, 0x45, 0x45, 0x16, 0x2c, + 0x74, 0xc3, 0x90, 0xa7, 0x6c, 0x89, 0x98, 0x34, 0x61, 0x88, 0xff, 0x42, 0xcd, 0xe3, 0x53, 0xee, + 0xe0, 0xd5, 0x8b, 0x41, 0x97, 0x45, 0xa4, 0x73, 0x59, 0x26, 0x9d, 0xb6, 0x3c, 0x67, 0x63, 0xa2, + 0x20, 0x56, 0xe9, 0x33, 0xe2, 0x51, 0x0d, 0xc4, 0xc9, 0xeb, 0x83, 0xdc, 0x86, 0x2d, 0xde, 0x33, + 0xb6, 0xb3, 0xae, 0x3b, 0x06, 0x35, 0x26, 0x36, 0xe4, 0xc5, 0x7b, 0xd9, 0x07, 0xd7, 0x96, 0xe4, + 0xf9, 0x48, 0x26, 0x67, 0x33, 0x01, 0xe7, 0x56, 0x6d, 0x2f, 0xe1, 0x3c, 0xd4, 0x17, 0x93, 0x96, + 0xdb, 0x5e, 0x52, 0xe7, 0x9e, 0x68, 0x1b, 0xb6, 0x3e, 0x70, 0xc0, 0x2d, 0xea, 0xb0, 0x95, 0x1d, + 0xa7, 0x62, 0xb1, 0x3e, 0x08, 0x26, 0x77, 0x38, 0x26, 0xce, 0x43, 0x34, 0x39, 0xfb, 0x32, 0x8c, + 0xc7, 0x44, 0x41, 0xc6, 0x20, 0xb3, 0x46, 0x37, 0xb8, 0xba, 0x68, 0xec, 0x4f, 0x72, 0x12, 0x7a, + 0xef, 0xb0, 0xb1, 0xcc, 0xd5, 0x40, 0xe3, 0x3f, 0xde, 0x9b, 0x7e, 0x4f, 0x8a, 0x99, 0x21, 0x64, + 0xd2, 0xb6, 0x2c, 0x5a, 0xf7, 0x64, 0x4b, 0xe4, 0x79, 0x18, 0x98, 0xb3, 0xeb, 0x7a, 0x03, 0xfb, + 0x91, 0xeb, 0x1d, 0x4e, 0x2b, 0xac, 0x03, 0x2f, 0x37, 0x18, 0x46, 0x9e, 0x56, 0x02, 0x52, 0xa6, + 0x00, 0x1a, 0x6d, 0xda, 0x1e, 0xc5, 0x86, 0xe9, 0x50, 0x01, 0xb0, 0xa1, 0x83, 0x28, 0x59, 0x01, + 0x42, 0x62, 0x72, 0x05, 0xfa, 0x2b, 0xcc, 0xf8, 0xaa, 0xdb, 0x0d, 0xa1, 0x7c, 0x68, 0x1f, 0xa0, + 0x41, 0x26, 0x4f, 0xe0, 0x3e, 0x51, 0x71, 0x16, 0x46, 0x26, 0x1b, 0x26, 0xb5, 0x3c, 0xf9, 0xad, + 0xd9, 0x34, 0x50, 0x5a, 0xa1, 0x96, 0x27, 0xbf, 0x35, 0x4e, 0x1c, 0x3a, 0x83, 0xaa, 0x93, 0x21, + 0x92, 0x16, 0x7f, 0x21, 0x03, 0x0f, 0x5c, 0x6f, 0x2f, 0x51, 0xc7, 0xa2, 0x1e, 0x75, 0xc5, 0x2c, + 0x12, 0x70, 0xbd, 0x01, 0xe3, 0x31, 0xa4, 0xe0, 0x8e, 0xd6, 0xd3, 0x5a, 0x80, 0xf4, 0x27, 0x27, + 0x79, 0x09, 0x8e, 0x35, 0x25, 0xb3, 0x30, 0x1a, 0x02, 0xd9, 0x4b, 0xb8, 0xf9, 0x34, 0x4e, 0xdc, + 0x0f, 0x6d, 0x6d, 0x16, 0xce, 0x4a, 0xdc, 0xd8, 0x6b, 0xcb, 0x1a, 0xac, 0x36, 0x23, 0xd7, 0x61, + 0x2c, 0x04, 0xbd, 0xea, 0xd8, 0xed, 0x96, 0x9b, 0xcf, 0x20, 0x2b, 0x9c, 0x2f, 0x25, 0x56, 0x2b, + 0x88, 0x94, 0xe7, 0x20, 0xb5, 0x21, 0xf9, 0xf2, 0x94, 0xcc, 0x4d, 0x8c, 0xc2, 0x2c, 0x8e, 0xc2, + 0x77, 0xfb, 0xa3, 0xb0, 0xa3, 0x90, 0x2e, 0xab, 0x2d, 0xc5, 0xa0, 0x54, 0x5e, 0x23, 0x36, 0x28, + 0x63, 0x4f, 0x3c, 0x3b, 0x09, 0xa7, 0x12, 0x79, 0xed, 0x4a, 0xab, 0x7f, 0x37, 0x23, 0x73, 0xa9, + 0xd8, 0x46, 0xd0, 0x99, 0x0b, 0x72, 0x67, 0x56, 0x6c, 0x03, 0xd7, 0x98, 0x54, 0x38, 0x73, 0x4b, + 0x2f, 0xdb, 0xb2, 0x0d, 0x75, 0x95, 0x89, 0xb7, 0x25, 0x1f, 0x81, 0xd3, 0x31, 0x20, 0x9f, 0xae, + 0xb9, 0xf6, 0x5f, 0xd8, 0xda, 0x2c, 0x14, 0x13, 0xb8, 0xaa, 0xb3, 0x77, 0x07, 0x2e, 0x44, 0x87, + 0x33, 0x92, 0xd4, 0x6d, 0xcb, 0xd3, 0x4d, 0x4b, 0x2c, 0x8d, 0x7c, 0x94, 0xbc, 0x63, 0x6b, 0xb3, + 0xf0, 0xa8, 0xac, 0x83, 0x3e, 0x8d, 0xfa, 0xf2, 0x9d, 0xf8, 0x10, 0x03, 0xf2, 0x09, 0xa8, 0x72, + 0x53, 0x5f, 0xf1, 0xb7, 0x51, 0x17, 0xb7, 0x36, 0x0b, 0x8f, 0x25, 0x3e, 0xc3, 0x64, 0x54, 0xb2, + 0xd9, 0xd6, 0x89, 0x13, 0xd1, 0x80, 0x84, 0xb8, 0x1b, 0xb6, 0x41, 0xf1, 0x1b, 0x7a, 0x91, 0x7f, + 0x71, 0x6b, 0xb3, 0xf0, 0x90, 0xc4, 0xdf, 0xb2, 0x0d, 0xaa, 0xbe, 0x7e, 0x42, 0xeb, 0xe2, 0x6f, + 0x67, 0xe0, 0xf4, 0x6d, 0xd3, 0x32, 0xec, 0x75, 0x77, 0x8a, 0xba, 0x6b, 0x9e, 0xdd, 0x0a, 0x3a, + 0xfa, 0x43, 0x70, 0x2a, 0x8a, 0x11, 0x86, 0xb0, 0xe8, 0xec, 0xc7, 0xb7, 0x36, 0x0b, 0x8f, 0xac, + 0x73, 0x82, 0x9a, 0xc1, 0x29, 0x7c, 0x4b, 0x5a, 0x7a, 0x68, 0x32, 0x0f, 0xb6, 0x7a, 0x08, 0x88, + 0x34, 0xcf, 0xe1, 0xea, 0xe1, 0xb3, 0x52, 0x56, 0x3a, 0x99, 0x9c, 0x3c, 0x0b, 0xb9, 0x29, 0xbb, + 0xa9, 0x9b, 0x96, 0xe8, 0x41, 0x9c, 0xf1, 0x83, 0x77, 0x41, 0x8c, 0x6c, 0x72, 0x70, 0x5a, 0xf6, + 0x4c, 0xf1, 0x32, 0xb8, 0xd7, 0x92, 0xb6, 0x34, 0x7e, 0x53, 0x65, 0xcf, 0x25, 0x93, 0x93, 0x36, + 0x0c, 0x8b, 0x57, 0x88, 0xac, 0xaf, 0x4f, 0xfb, 0x23, 0x3b, 0x59, 0x8a, 0x97, 0x23, 0x6d, 0xf8, + 0x98, 0xc6, 0xb7, 0xf5, 0x3f, 0x33, 0x36, 0xa0, 0xa3, 0x4f, 0x39, 0xfb, 0x0a, 0x90, 0x38, 0x8b, + 0xdd, 0x2d, 0x50, 0x19, 0x78, 0xa8, 0x5a, 0x9a, 0x9f, 0x2b, 0x1b, 0xfe, 0x56, 0xa6, 0xe2, 0xd8, + 0x77, 0x4c, 0x43, 0x9a, 0xa0, 0x97, 0xe0, 0x8c, 0x82, 0x9a, 0xc6, 0xdd, 0x53, 0xb0, 0x89, 0x46, + 0xf5, 0xf5, 0xb7, 0x49, 0x2d, 0x41, 0x53, 0xe3, 0x5b, 0xac, 0xa8, 0x7d, 0xd7, 0x89, 0x11, 0x1b, + 0x86, 0x0a, 0xaa, 0xba, 0x6a, 0x3b, 0x5e, 0xbd, 0xed, 0x89, 0xde, 0xc7, 0x61, 0x18, 0x7b, 0x86, + 0x2b, 0x88, 0xba, 0x3c, 0xc2, 0xe7, 0x43, 0x3e, 0x9e, 0x82, 0xb1, 0x92, 0xe7, 0x39, 0xe6, 0x52, + 0xdb, 0xa3, 0xf3, 0x7a, 0xab, 0x65, 0x5a, 0x2b, 0x38, 0x9d, 0x0f, 0x5e, 0x7d, 0x29, 0x30, 0x83, + 0xba, 0x4a, 0xe2, 0xb2, 0xda, 0x5c, 0x9a, 0x85, 0x75, 0x1f, 0x55, 0x6b, 0x72, 0x9c, 0x3c, 0x0b, + 0xab, 0xed, 0xd8, 0x2c, 0x9c, 0xc8, 0x6b, 0x57, 0x5d, 0xf7, 0x99, 0x0c, 0x9c, 0x5b, 0x58, 0xf3, + 0x74, 0x8d, 0xba, 0x76, 0xdb, 0xa9, 0x53, 0xf7, 0x66, 0xcb, 0xd0, 0x3d, 0x1a, 0x4e, 0xc6, 0x05, + 0xe8, 0x2d, 0x19, 0x06, 0x35, 0x90, 0x5d, 0x2f, 0x3f, 0xee, 0xd1, 0x19, 0x40, 0xe3, 0x70, 0xf2, + 0x38, 0xf4, 0x89, 0x36, 0xc8, 0xbd, 0x77, 0x62, 0x70, 0x6b, 0xb3, 0xd0, 0xd7, 0xe6, 0x20, 0xcd, + 0xc7, 0x31, 0xb2, 0x29, 0xda, 0xa0, 0x8c, 0x2c, 0x13, 0x92, 0x19, 0x1c, 0xa4, 0xf9, 0x38, 0xf2, + 0x1a, 0x8c, 0x20, 0xdb, 0xe0, 0x7d, 0xc4, 0xf2, 0x76, 0xd2, 0x97, 0xae, 0xfc, 0xb2, 0xdc, 0xfa, + 0xc0, 0xb7, 0xa9, 0x39, 0x7e, 0x03, 0x4d, 0x61, 0x40, 0x6e, 0xc3, 0x98, 0x78, 0x89, 0x90, 0x69, + 0x6f, 0x17, 0xa6, 0xb8, 0xad, 0x10, 0xef, 0x2f, 0xb1, 0x8d, 0x31, 0x61, 0x8c, 0xc5, 0x6b, 0x87, + 0x8c, 0x73, 0xdb, 0x31, 0x16, 0x5f, 0x2c, 0x33, 0x56, 0x99, 0x14, 0x3f, 0x00, 0x43, 0x72, 0x43, + 0x72, 0x1a, 0x8f, 0xd4, 0xf8, 0x38, 0xc1, 0xc3, 0x38, 0xd3, 0xc0, 0x73, 0xb4, 0xa7, 0x71, 0x8a, + 0xab, 0x3b, 0x66, 0x8b, 0x19, 0x86, 0x42, 0xc9, 0x47, 0xb7, 0x36, 0x0b, 0x83, 0x46, 0x08, 0xd6, + 0x64, 0x9a, 0xe2, 0x7f, 0x49, 0xc1, 0x69, 0xc6, 0xbb, 0xe4, 0xba, 0xe6, 0x8a, 0xd5, 0x94, 0x2d, + 0xb3, 0x27, 0x20, 0x57, 0xc5, 0xe7, 0x89, 0x27, 0x9d, 0xdc, 0xda, 0x2c, 0x8c, 0xf1, 0x37, 0x90, + 0xa7, 0x3a, 0x4e, 0x13, 0x9c, 0x27, 0xa5, 0xb7, 0x39, 0x4f, 0x62, 0xbb, 0x16, 0x4f, 0x77, 0x3c, + 0xd3, 0x5a, 0xa9, 0x7a, 0xba, 0xd7, 0x76, 0x23, 0xbb, 0x16, 0x81, 0xa9, 0xb9, 0x88, 0x8a, 0xec, + 0x5a, 0x22, 0x8d, 0xc8, 0xcb, 0x30, 0x34, 0x6d, 0x19, 0x21, 0x13, 0x3e, 0xb5, 0x3e, 0xc8, 0x36, + 0x13, 0x14, 0xe1, 0x71, 0x16, 0x91, 0x06, 0xc5, 0x6f, 0x4f, 0x43, 0x9e, 0x1f, 0xfe, 0xcc, 0x99, + 0xae, 0x37, 0x4f, 0x9b, 0x4b, 0xd2, 0xec, 0x34, 0xe3, 0x9f, 0x26, 0x31, 0x9c, 0x64, 0x6e, 0xa0, + 0xb5, 0x27, 0x4e, 0x93, 0x1a, 0xa6, 0x1b, 0x3b, 0x76, 0x50, 0x5a, 0x91, 0x32, 0xf4, 0x71, 0xce, + 0xdc, 0x5c, 0x1c, 0xbc, 0x9a, 0xf7, 0x15, 0x41, 0x7d, 0x34, 0x57, 0x86, 0x26, 0x27, 0x96, 0x0f, + 0x32, 0x44, 0x7b, 0xb6, 0xcd, 0x0e, 0xdb, 0x2c, 0x9a, 0x5e, 0xc3, 0x5f, 0xe7, 0xf9, 0x4c, 0x21, + 0xbd, 0x93, 0xc7, 0x90, 0xb2, 0x09, 0xaa, 0xb4, 0x23, 0xef, 0x84, 0x71, 0x99, 0x1e, 0x37, 0xbd, + 0x7c, 0x51, 0xd7, 0x46, 0xf5, 0x80, 0x16, 0x77, 0xbb, 0xc5, 0xcf, 0x67, 0x60, 0x4c, 0x7d, 0x57, + 0x72, 0x1b, 0xfa, 0xaf, 0xd9, 0xa6, 0x45, 0x8d, 0x05, 0x0b, 0x05, 0xd3, 0xfd, 0x2c, 0xd6, 0xdf, + 0xe5, 0x9d, 0x78, 0x03, 0xdb, 0xd4, 0xe4, 0xbd, 0x11, 0x1e, 0xcd, 0x06, 0xcc, 0xc8, 0x07, 0x61, + 0x80, 0xed, 0x2e, 0xee, 0x20, 0xe7, 0xf4, 0xb6, 0x9c, 0x1f, 0x16, 0x9c, 0x4f, 0x3a, 0xbc, 0x51, + 0x9c, 0x75, 0xc8, 0x8e, 0xa9, 0xb3, 0x46, 0x75, 0xd7, 0xf6, 0x57, 0x70, 0x54, 0x67, 0x07, 0x21, + 0xb2, 0x3a, 0x73, 0x1a, 0xb6, 0x29, 0xe2, 0x1f, 0x8b, 0xbd, 0x2f, 0xed, 0x8a, 0x79, 0x17, 0xa9, + 0x1d, 0x2f, 0x11, 0x13, 0x0b, 0x46, 0x45, 0x3f, 0xae, 0x9a, 0x2d, 0xdc, 0x4f, 0xa2, 0x70, 0x47, + 0xae, 0x5e, 0xb8, 0xec, 0x9f, 0xb9, 0x5c, 0x96, 0x4e, 0xf0, 0xef, 0x3c, 0x7d, 0x79, 0x3e, 0x20, + 0xc7, 0x83, 0x30, 0x1c, 0x0a, 0x0a, 0x0b, 0x59, 0xc9, 0x9a, 0x11, 0x72, 0xf2, 0x08, 0x0c, 0x89, + 0x97, 0xe2, 0x3d, 0x89, 0xfb, 0x62, 0x6d, 0x90, 0xc3, 0x78, 0x2f, 0x7e, 0x65, 0x1a, 0x9e, 0x0c, + 0x7b, 0x51, 0xa3, 0x77, 0x4c, 0xba, 0x1e, 0x3e, 0x54, 0x9c, 0xda, 0xb1, 0xc1, 0xef, 0x4e, 0xae, + 0xea, 0xd6, 0x0a, 0x35, 0xc8, 0x25, 0xe8, 0xe5, 0xe7, 0x53, 0x29, 0xdc, 0x9b, 0xe0, 0xc4, 0xaa, + 0x1e, 0x4d, 0x71, 0x0a, 0x62, 0x43, 0x4e, 0x9c, 0x48, 0x71, 0x1d, 0x2f, 0xc5, 0x75, 0x7c, 0x07, + 0x4f, 0x14, 0x67, 0x4e, 0x7c, 0xf5, 0xc3, 0xbe, 0x89, 0x9d, 0x3a, 0x89, 0xc7, 0x9c, 0x7d, 0x01, + 0x06, 0x25, 0xe2, 0x5d, 0x2d, 0x6f, 0x3f, 0xd4, 0x2b, 0x8f, 0x7a, 0xff, 0xb5, 0xc4, 0xa8, 0xbf, + 0xc2, 0x46, 0xab, 0xeb, 0x32, 0x13, 0x3a, 0x15, 0x1e, 0x28, 0x35, 0x39, 0x28, 0x3a, 0x26, 0x11, + 0x44, 0x9e, 0x81, 0x7e, 0xce, 0x22, 0x38, 0xac, 0xc1, 0x83, 0x1e, 0x07, 0x61, 0x51, 0x23, 0x25, + 0x20, 0x24, 0xdf, 0x99, 0x82, 0xf3, 0x5d, 0x25, 0x81, 0xfa, 0x39, 0x78, 0xf5, 0xb9, 0x3d, 0x89, + 0x71, 0xe2, 0xc9, 0xad, 0xcd, 0xc2, 0x25, 0x49, 0x79, 0x1c, 0x89, 0xa6, 0x56, 0xe7, 0x44, 0xd2, + 0x7b, 0x75, 0x7f, 0x15, 0xb6, 0x53, 0xe2, 0x0f, 0x9d, 0xc1, 0xc3, 0x73, 0xab, 0xbe, 0xe1, 0xbf, + 0x64, 0x36, 0xdc, 0x29, 0x89, 0xef, 0x5d, 0xf6, 0x49, 0x12, 0x1e, 0xd3, 0x81, 0x0b, 0xa9, 0xc3, + 0x19, 0x8e, 0x99, 0xd2, 0x37, 0x16, 0x96, 0xe7, 0x6d, 0xcb, 0x5b, 0xf5, 0x1f, 0xd0, 0x2b, 0x9f, + 0x3e, 0xe3, 0x03, 0x0c, 0x7d, 0xa3, 0x66, 0x2f, 0xd7, 0x9a, 0x8c, 0x2a, 0xe1, 0x19, 0x9d, 0x38, + 0xb1, 0x25, 0x47, 0x4c, 0x03, 0xfe, 0x64, 0x9c, 0x0b, 0xef, 0x06, 0xfc, 0xa9, 0x23, 0x3e, 0xf5, + 0x2a, 0x8d, 0x92, 0x66, 0xe0, 0xbe, 0x3d, 0xce, 0xc0, 0xcf, 0xc2, 0x69, 0x1c, 0xab, 0x6c, 0xd9, + 0x8f, 0x3c, 0x3e, 0xdf, 0xcf, 0xde, 0x4c, 0x3b, 0xc9, 0xb1, 0xd1, 0x17, 0x28, 0x96, 0x61, 0x68, + 0xce, 0xae, 0xaf, 0x05, 0xfa, 0xfa, 0x02, 0xe4, 0x16, 0x75, 0x67, 0x85, 0x7a, 0xd8, 0x19, 0x83, + 0x57, 0xc7, 0x2f, 0xf3, 0x0b, 0x3f, 0x46, 0xc4, 0x11, 0x13, 0x23, 0x62, 0x86, 0xcc, 0x79, 0xf8, + 0x5b, 0x13, 0x0d, 0x8a, 0x3f, 0x96, 0x83, 0x21, 0x71, 0x39, 0x85, 0x0b, 0x2b, 0x79, 0x6f, 0x78, + 0xdd, 0x27, 0xa6, 0xf4, 0xe0, 0xe4, 0x35, 0xb8, 0x58, 0x18, 0x62, 0xcc, 0x7e, 0x69, 0xb3, 0x90, + 0xda, 0xda, 0x2c, 0xf4, 0x68, 0xfd, 0xd2, 0x11, 0x4e, 0xb8, 0xf4, 0x4b, 0xb6, 0x8e, 0x7c, 0xdd, + 0xa4, 0xb4, 0xe5, 0xa6, 0xc0, 0xcb, 0xd0, 0x27, 0xde, 0x41, 0xa8, 0xfc, 0x99, 0xf0, 0xe4, 0x30, + 0x72, 0xc9, 0xa6, 0xb4, 0xf6, 0x5b, 0x91, 0x97, 0x20, 0xc7, 0x4f, 0xd2, 0x84, 0x00, 0x4e, 0x27, + 0x9f, 0x3c, 0x2a, 0xcd, 0x45, 0x1b, 0x32, 0x0b, 0x10, 0x9e, 0xa2, 0x05, 0x77, 0x8a, 0x82, 0x43, + 0xfc, 0x7c, 0x4d, 0xe1, 0x22, 0xb5, 0x25, 0xcf, 0xc3, 0xd0, 0x22, 0x75, 0x9a, 0xa6, 0xa5, 0x37, + 0xaa, 0xe6, 0x9b, 0xfe, 0xb5, 0x22, 0xda, 0x40, 0xae, 0xf9, 0xa6, 0xac, 0x09, 0x11, 0x3a, 0xf2, + 0xe1, 0xa4, 0x53, 0xaa, 0x3e, 0x7c, 0x91, 0x47, 0xb6, 0x3d, 0xbe, 0x51, 0xde, 0x27, 0xe1, 0xd0, + 0xea, 0x35, 0x18, 0x8e, 0x1c, 0x50, 0x88, 0x7b, 0xa3, 0xf3, 0x71, 0xd6, 0xd2, 0x69, 0x8b, 0xc2, + 0x36, 0xca, 0x81, 0x0d, 0xa5, 0xb2, 0x65, 0x7a, 0xa6, 0xde, 0x98, 0xb4, 0x9b, 0x4d, 0xdd, 0x32, + 0xf2, 0x03, 0xe1, 0x50, 0x32, 0x39, 0xa6, 0x56, 0xe7, 0x28, 0x79, 0x28, 0x45, 0x1b, 0x91, 0xeb, + 0x30, 0x26, 0xfa, 0x50, 0xa3, 0x75, 0xdb, 0x61, 0x66, 0x19, 0x5e, 0x0b, 0x89, 0xb1, 0xe4, 0x72, + 0x5c, 0xcd, 0xf1, 0x91, 0xf2, 0xbe, 0x47, 0x6d, 0xc8, 0xa6, 0xed, 0xb2, 0x75, 0xc7, 0x64, 0x3b, + 0x89, 0x21, 0x7c, 0x19, 0x9c, 0xb6, 0x4d, 0x0e, 0x92, 0xa7, 0x6d, 0x41, 0x25, 0x59, 0x02, 0xc3, + 0xdb, 0x5b, 0x02, 0xd7, 0xb2, 0xfd, 0x83, 0x63, 0x43, 0xea, 0x45, 0x61, 0xf1, 0x1f, 0x65, 0x60, + 0x50, 0xbc, 0x09, 0xb3, 0x5e, 0x8e, 0xc7, 0xcf, 0x7e, 0xc6, 0x4f, 0xe2, 0x38, 0xc8, 0x1d, 0xd4, + 0x38, 0x28, 0x7e, 0x22, 0x1d, 0x4c, 0x76, 0x15, 0xc7, 0xb4, 0xf6, 0x37, 0xd9, 0x5d, 0x00, 0x98, + 0x5c, 0x6d, 0x5b, 0x6b, 0xdc, 0x21, 0x22, 0x1d, 0x3a, 0x44, 0xd4, 0x4d, 0x4d, 0xc2, 0x90, 0xf3, + 0x90, 0x9d, 0x62, 0xfc, 0x59, 0xcf, 0x0c, 0x4d, 0x0c, 0x7c, 0x81, 0x73, 0x4a, 0x3d, 0xa9, 0x21, + 0x98, 0x6d, 0xa3, 0x27, 0x36, 0x3c, 0xca, 0x37, 0x2e, 0x19, 0xbe, 0x8d, 0x5e, 0x62, 0x00, 0x8d, + 0xc3, 0xc9, 0xb3, 0x30, 0x3e, 0x45, 0x1b, 0xfa, 0xc6, 0xbc, 0xd9, 0x68, 0x98, 0x2e, 0xad, 0xdb, + 0x96, 0xe1, 0xa2, 0x90, 0xc5, 0xe3, 0x9a, 0xae, 0x16, 0x27, 0x20, 0x45, 0xc8, 0x2d, 0x2c, 0x2f, + 0xbb, 0xd4, 0x43, 0xf1, 0x65, 0x26, 0x80, 0xcd, 0xfd, 0x36, 0x42, 0x34, 0x81, 0x29, 0xfe, 0x5e, + 0x8a, 0xed, 0x53, 0xf1, 0x80, 0x27, 0x1c, 0x44, 0xfb, 0x11, 0xc9, 0xa5, 0xd0, 0x6e, 0x4a, 0xe3, + 0xd7, 0x8e, 0x8a, 0xaf, 0xed, 0x13, 0xb6, 0x53, 0x68, 0x31, 0x25, 0x7e, 0x55, 0x66, 0xbb, 0xaf, + 0x7a, 0x1e, 0x06, 0x17, 0xa7, 0x2a, 0x13, 0xfe, 0x43, 0xb2, 0xf8, 0x90, 0x93, 0xe2, 0x21, 0x43, + 0x9e, 0xd1, 0x5a, 0xaa, 0xf9, 0x4f, 0x92, 0x09, 0x8b, 0xdf, 0x9e, 0x81, 0x33, 0xe2, 0x4b, 0x27, + 0x1b, 0x66, 0x6b, 0xc9, 0xd6, 0x1d, 0x43, 0xa3, 0x75, 0x6a, 0xde, 0xa1, 0x47, 0x73, 0xc0, 0x46, + 0x87, 0x5c, 0x76, 0x1f, 0x43, 0xee, 0x6a, 0xf4, 0x34, 0x94, 0x1b, 0x5b, 0x63, 0x4c, 0x9c, 0xf2, + 0x69, 0x68, 0xf4, 0x0c, 0xb4, 0x08, 0xb9, 0x39, 0x6a, 0xad, 0x78, 0xab, 0xa8, 0x5c, 0xbd, 0x5c, + 0xb9, 0x1a, 0x08, 0xd1, 0x04, 0x46, 0xe2, 0x8b, 0x1b, 0xa7, 0xbe, 0x38, 0x5f, 0xb6, 0x73, 0xd2, + 0x64, 0xa2, 0xe2, 0xb7, 0x66, 0xe0, 0xa4, 0xda, 0x4d, 0x55, 0x6a, 0x19, 0xc7, 0x7d, 0x74, 0xef, + 0xf4, 0xd1, 0x77, 0x64, 0xe1, 0x41, 0xff, 0x40, 0x7d, 0x55, 0x77, 0xa8, 0x31, 0x65, 0x3a, 0xb4, + 0xee, 0xd9, 0xce, 0xc6, 0x11, 0xb6, 0x1f, 0x0f, 0xae, 0xab, 0x9e, 0x85, 0x9c, 0x38, 0x88, 0xe2, + 0xeb, 0xe0, 0x48, 0xf0, 0x26, 0x08, 0x8d, 0xad, 0xa0, 0xfc, 0x10, 0x4b, 0xe9, 0xe0, 0xdc, 0x4e, + 0x3a, 0xf8, 0x3d, 0x30, 0x1c, 0x88, 0x5e, 0xea, 0x3e, 0x34, 0x36, 0x0d, 0x1f, 0xc1, 0x3b, 0x30, + 0x4a, 0x88, 0x4f, 0xf3, 0x01, 0xe5, 0x29, 0x34, 0x06, 0x87, 0xc5, 0xd3, 0x82, 0x76, 0xa6, 0xa1, + 0xc9, 0x44, 0xaa, 0xaa, 0x0c, 0xec, 0x44, 0x55, 0x3e, 0xd3, 0x0b, 0x67, 0x93, 0x55, 0x45, 0xa3, + 0xba, 0x71, 0xac, 0x29, 0x6f, 0x4f, 0x4d, 0x79, 0x04, 0xb2, 0x15, 0xdd, 0x5b, 0x15, 0x2a, 0x82, + 0x4e, 0x2b, 0xcb, 0x66, 0x83, 0xd6, 0x5a, 0xba, 0xb7, 0xaa, 0x21, 0x4a, 0x9a, 0x9b, 0x00, 0x39, + 0x26, 0xcd, 0x4d, 0xa1, 0x01, 0x33, 0xf8, 0x70, 0xea, 0x62, 0x36, 0xc9, 0x80, 0x51, 0x95, 0x72, + 0x68, 0x27, 0x4a, 0xf9, 0xd9, 0xde, 0x4e, 0xf3, 0xd7, 0x6d, 0xc7, 0xf4, 0xe8, 0xb1, 0x56, 0x1e, + 0x6b, 0xe5, 0x21, 0x68, 0xe5, 0xaf, 0xa4, 0x61, 0x38, 0xd8, 0xcf, 0xbe, 0x41, 0xeb, 0x87, 0xb3, + 0x8e, 0x86, 0xdb, 0xc0, 0xcc, 0xbe, 0xb7, 0x81, 0xfb, 0x51, 0xc2, 0x62, 0xb0, 0x2f, 0xe7, 0xa6, + 0x0e, 0x4a, 0x99, 0xef, 0xcb, 0x83, 0x73, 0xf9, 0x47, 0xa0, 0x6f, 0x5e, 0xbf, 0x6b, 0x36, 0xdb, + 0x4d, 0xb1, 0xc3, 0x41, 0x67, 0xeb, 0xa6, 0x7e, 0x57, 0xf3, 0xe1, 0xc5, 0x7f, 0x9d, 0x82, 0x11, + 0x21, 0x54, 0xc1, 0x7c, 0x5f, 0x52, 0x0d, 0xa5, 0x93, 0xde, 0xb7, 0x74, 0x32, 0x7b, 0x97, 0x4e, + 0xf1, 0xef, 0x66, 0x20, 0x3f, 0x63, 0x36, 0xe8, 0xa2, 0xa3, 0x5b, 0xee, 0x32, 0x75, 0xc4, 0x51, + 0xc4, 0x34, 0x63, 0xb5, 0xaf, 0x0f, 0x94, 0xa6, 0xa1, 0xf4, 0x9e, 0xa6, 0xa1, 0x77, 0xc1, 0x80, + 0x78, 0x99, 0xc0, 0xd1, 0x1f, 0x47, 0x9a, 0xe3, 0x03, 0xb5, 0x10, 0xcf, 0x88, 0x4b, 0xad, 0x96, + 0x63, 0xdf, 0xa1, 0x0e, 0xbf, 0xcb, 0x15, 0xc4, 0xba, 0x0f, 0xd4, 0x42, 0xbc, 0xc4, 0x99, 0xfa, + 0xf6, 0xaf, 0xcc, 0x99, 0x3a, 0x5a, 0x88, 0x27, 0x17, 0xa1, 0x7f, 0xce, 0xae, 0xa3, 0x7b, 0xab, + 0x98, 0x8a, 0x86, 0xb6, 0x36, 0x0b, 0xfd, 0x0d, 0x01, 0xd3, 0x02, 0x2c, 0xa3, 0x9c, 0xb2, 0xd7, + 0xad, 0x86, 0xad, 0x73, 0x2f, 0xc0, 0x7e, 0x4e, 0x69, 0x08, 0x98, 0x16, 0x60, 0x19, 0x25, 0x93, + 0x39, 0x7a, 0x57, 0xf6, 0x87, 0x3c, 0x97, 0x05, 0x4c, 0x0b, 0xb0, 0xc5, 0xef, 0xcd, 0x32, 0xed, + 0x75, 0xcd, 0x37, 0xef, 0xfb, 0xb5, 0x24, 0x1c, 0x30, 0xbd, 0x7b, 0x18, 0x30, 0xf7, 0xcd, 0x59, + 0x6a, 0xf1, 0x4f, 0xfa, 0x00, 0x84, 0xf4, 0xa7, 0x8f, 0x37, 0xbb, 0xfb, 0xd3, 0x9a, 0x29, 0x18, + 0x9f, 0xb6, 0x56, 0x75, 0xab, 0x4e, 0x8d, 0xf0, 0x44, 0x39, 0x87, 0x43, 0x1b, 0x43, 0x04, 0xa8, + 0x40, 0x86, 0x47, 0xca, 0x5a, 0xbc, 0x01, 0x79, 0x1a, 0x06, 0xcb, 0x96, 0x47, 0x1d, 0xbd, 0xee, + 0x99, 0x77, 0xa8, 0x98, 0x1a, 0xd0, 0x7f, 0xc2, 0x0c, 0xc1, 0x9a, 0x4c, 0x43, 0x9e, 0x85, 0xa1, + 0x8a, 0xee, 0x78, 0x66, 0xdd, 0x6c, 0xe9, 0x96, 0x27, 0xee, 0x6f, 0xf8, 0xb2, 0xdf, 0x92, 0xe0, + 0x5a, 0x84, 0x8a, 0x7c, 0x18, 0x06, 0x70, 0xdb, 0x8c, 0xd1, 0x4c, 0x03, 0xdb, 0xde, 0x73, 0x3f, + 0x1a, 0xfa, 0x49, 0xf3, 0x83, 0x71, 0xf4, 0x93, 0x50, 0xaf, 0xba, 0x03, 0x8e, 0xe4, 0x75, 0xe8, + 0x9b, 0xb6, 0x0c, 0x64, 0x0e, 0xdb, 0x32, 0x2f, 0x0a, 0xe6, 0xa7, 0x43, 0xe6, 0x76, 0x4b, 0xe1, + 0xed, 0xb3, 0x4b, 0x1e, 0x65, 0x83, 0x6f, 0xdd, 0x28, 0x1b, 0x7a, 0x0b, 0x6e, 0x2c, 0x86, 0x0f, + 0xea, 0xc6, 0x62, 0x64, 0x8f, 0x37, 0x16, 0xc5, 0x1f, 0x48, 0xc1, 0xe0, 0x44, 0x65, 0x26, 0x18, + 0xbe, 0x0f, 0x40, 0xa6, 0x22, 0x1c, 0x7a, 0xb2, 0xdc, 0xa0, 0x69, 0x99, 0x86, 0xc6, 0x60, 0xe4, + 0x12, 0xf4, 0x4f, 0xa2, 0x23, 0xb0, 0xb8, 0x62, 0xce, 0xf2, 0x05, 0xb0, 0x8e, 0x30, 0x8c, 0x07, + 0xf0, 0xd1, 0xe4, 0x71, 0xe8, 0xab, 0x38, 0xf6, 0x8a, 0xa3, 0x37, 0xc5, 0x22, 0x8c, 0x1e, 0x55, + 0x2d, 0x0e, 0xd2, 0x7c, 0x1c, 0x79, 0x09, 0x46, 0x4a, 0x6d, 0xc3, 0xf4, 0xc2, 0xa8, 0xb5, 0x2c, + 0xda, 0xbd, 0x78, 0x0b, 0xa2, 0x33, 0x4c, 0xcd, 0xff, 0x1a, 0xd3, 0xd0, 0x14, 0xda, 0xe2, 0x67, + 0x53, 0xfe, 0x4e, 0x81, 0x3d, 0xaf, 0xda, 0xc6, 0x5b, 0x11, 0x7c, 0xf3, 0x7e, 0xfe, 0x3c, 0x97, + 0x83, 0x34, 0x1f, 0x47, 0x2e, 0x41, 0xef, 0xb4, 0xe3, 0xd8, 0x8e, 0x1c, 0x40, 0x46, 0x19, 0x40, + 0xf6, 0x24, 0x40, 0x0a, 0xf2, 0x6e, 0x18, 0xe4, 0x53, 0x16, 0x3f, 0xe7, 0xcd, 0x74, 0xbb, 0x84, + 0x97, 0x29, 0x8b, 0x9f, 0xcf, 0x48, 0x26, 0x1f, 0xef, 0xb0, 0xfb, 0xf0, 0x42, 0xe6, 0x19, 0xc8, + 0x4c, 0x54, 0x66, 0xc4, 0xfc, 0x79, 0xc2, 0x6f, 0x2a, 0x29, 0x9a, 0xd2, 0x8e, 0x51, 0x93, 0x73, + 0x90, 0xad, 0x30, 0xe5, 0xcb, 0xa1, 0x72, 0xf5, 0x6f, 0x6d, 0x16, 0xb2, 0x2d, 0xa6, 0x7d, 0x08, + 0x45, 0x2c, 0xdb, 0x3f, 0xf1, 0x4d, 0x1a, 0xc7, 0x86, 0x5b, 0xa7, 0x73, 0x90, 0x2d, 0x39, 0x2b, + 0x77, 0xc4, 0xa4, 0x87, 0x58, 0xdd, 0x59, 0xb9, 0xa3, 0x21, 0x94, 0x5c, 0x01, 0xd0, 0xa8, 0xd7, + 0x76, 0x2c, 0x8c, 0xed, 0x1c, 0xc0, 0xe3, 0x48, 0x9c, 0x4c, 0x1d, 0x84, 0xd6, 0xea, 0xb6, 0x41, + 0x35, 0x89, 0xa4, 0xf8, 0x0f, 0xc2, 0x3b, 0xb5, 0x29, 0xd3, 0x5d, 0x3b, 0xee, 0xc2, 0x5d, 0x74, + 0xa1, 0x2e, 0x4e, 0x7c, 0xe3, 0x9d, 0x54, 0x80, 0xde, 0x99, 0x86, 0xbe, 0xe2, 0x62, 0x1f, 0x0a, + 0x87, 0xcd, 0x65, 0x06, 0xd0, 0x38, 0x5c, 0xe9, 0xa7, 0xfe, 0xed, 0xfb, 0xe9, 0x73, 0xbd, 0xc1, + 0x68, 0xbb, 0x41, 0xbd, 0x75, 0xdb, 0x39, 0xee, 0xaa, 0x9d, 0x76, 0xd5, 0x05, 0xe8, 0xab, 0x3a, + 0x75, 0xe9, 0xb4, 0x04, 0xb7, 0x13, 0xae, 0x53, 0xe7, 0x27, 0x25, 0x3e, 0x92, 0xd1, 0x4d, 0xb9, + 0x1e, 0xd2, 0xf5, 0x85, 0x74, 0x86, 0xeb, 0x09, 0x3a, 0x81, 0x14, 0x74, 0x15, 0xdb, 0xf1, 0x44, + 0xc7, 0x05, 0x74, 0x2d, 0xdb, 0xf1, 0x34, 0x1f, 0x49, 0xde, 0x05, 0xb0, 0x38, 0x59, 0xf1, 0x83, + 0x96, 0x06, 0x42, 0x87, 0x5b, 0x11, 0xad, 0xa4, 0x49, 0x68, 0xb2, 0x08, 0x03, 0x0b, 0x2d, 0xea, + 0xf0, 0x9d, 0x14, 0x8f, 0xd6, 0x7c, 0x87, 0x22, 0x5a, 0xd1, 0xef, 0x97, 0xc5, 0xff, 0x01, 0x39, + 0x5f, 0x9d, 0x6c, 0xff, 0xa7, 0x16, 0x32, 0x22, 0xef, 0x86, 0x5c, 0x89, 0x9b, 0x89, 0x83, 0xc8, + 0x32, 0x10, 0x19, 0xee, 0x60, 0x39, 0x8a, 0x6f, 0xf9, 0x75, 0xfc, 0x5b, 0x13, 0xe4, 0xc5, 0x4b, + 0x30, 0xa6, 0x3e, 0x86, 0x0c, 0x42, 0xdf, 0xe4, 0xc2, 0x8d, 0x1b, 0xd3, 0x93, 0x8b, 0x63, 0x3d, + 0xa4, 0x1f, 0xb2, 0xd5, 0xe9, 0x1b, 0x53, 0x63, 0xa9, 0xe2, 0x77, 0x4b, 0x33, 0x08, 0x53, 0xad, + 0xe3, 0x5b, 0xf9, 0x7d, 0x5d, 0x3f, 0x8d, 0xe1, 0x55, 0x34, 0x1e, 0x38, 0x34, 0x4d, 0xcf, 0xa3, + 0x86, 0x58, 0x25, 0xf0, 0xaa, 0xd6, 0xbb, 0xab, 0xc5, 0xf0, 0xe4, 0x09, 0x18, 0x46, 0x98, 0xb8, + 0x65, 0xe5, 0xdb, 0x6b, 0xd1, 0xc0, 0xb9, 0xab, 0x45, 0x91, 0xc5, 0x9f, 0x0d, 0x2f, 0xe6, 0xe7, + 0xa8, 0x7e, 0x54, 0x2f, 0x65, 0xef, 0x91, 0xfe, 0x2a, 0xfe, 0x50, 0x2f, 0x0f, 0xa5, 0xe3, 0xc1, + 0xf8, 0x87, 0x21, 0xca, 0xf0, 0x14, 0x39, 0xb3, 0x8b, 0x53, 0xe4, 0x27, 0x20, 0x37, 0x4f, 0xbd, + 0x55, 0xdb, 0xf7, 0x29, 0x44, 0x43, 0xb4, 0x89, 0x10, 0xd9, 0x1d, 0x87, 0xd3, 0x90, 0x35, 0x20, + 0x7e, 0xa4, 0x7d, 0x10, 0xed, 0xe0, 0x9f, 0x5a, 0x9f, 0x89, 0x6d, 0x73, 0xaa, 0x98, 0x8f, 0x03, + 0x63, 0x95, 0x4e, 0x06, 0xd1, 0x14, 0x92, 0x93, 0xdf, 0x9f, 0x6d, 0x16, 0x72, 0x9c, 0x46, 0x4b, + 0x60, 0x4b, 0x5e, 0x83, 0x81, 0xf9, 0x99, 0x92, 0x88, 0xba, 0xe7, 0x0e, 0x29, 0x0f, 0x04, 0x52, + 0xf4, 0x11, 0x81, 0x48, 0x30, 0x6e, 0xb1, 0xb9, 0xac, 0xc7, 0x83, 0xee, 0x43, 0x2e, 0x4c, 0x5b, + 0x78, 0x04, 0xa4, 0x38, 0x9c, 0x08, 0xb4, 0x25, 0x1a, 0x17, 0xa9, 0xca, 0x8a, 0x63, 0x15, 0x6d, + 0xe9, 0xdf, 0xc7, 0xe8, 0x5e, 0x80, 0xf1, 0x52, 0xab, 0xd5, 0x30, 0xa9, 0x81, 0xfa, 0xa2, 0xb5, + 0x1b, 0xd4, 0x15, 0xce, 0x5c, 0x18, 0x54, 0xa7, 0x73, 0x64, 0x0d, 0x73, 0x3d, 0xd4, 0x9c, 0x76, + 0xd4, 0xf5, 0x37, 0xde, 0x16, 0x53, 0x6b, 0x70, 0xf6, 0xb6, 0x53, 0x9e, 0x12, 0xee, 0x5c, 0x3c, + 0xb5, 0x86, 0x0f, 0x8e, 0xba, 0xc4, 0xca, 0xe4, 0xc5, 0xdf, 0x4e, 0xc3, 0xe9, 0x49, 0x87, 0xea, + 0x1e, 0x9d, 0x9f, 0x29, 0x95, 0xda, 0xe8, 0xbc, 0xd9, 0x68, 0x50, 0x6b, 0xe5, 0x70, 0x26, 0x85, + 0x17, 0x61, 0x24, 0x78, 0x01, 0x74, 0xa1, 0x96, 0xc3, 0x5b, 0xeb, 0x3e, 0x86, 0x3b, 0x5c, 0x6b, + 0x0a, 0x29, 0xb9, 0x0e, 0x27, 0x02, 0x48, 0xa9, 0xd1, 0xb0, 0xd7, 0x35, 0xda, 0x76, 0xb9, 0xdb, + 0x4a, 0x3f, 0x77, 0x22, 0x0f, 0x39, 0xe8, 0x0c, 0x5f, 0x73, 0x18, 0x81, 0x96, 0xd4, 0x8a, 0xcc, + 0x42, 0xff, 0x4c, 0xc3, 0x5e, 0xc7, 0x0c, 0x2b, 0xbd, 0xd1, 0x85, 0x73, 0x7e, 0xa6, 0xe4, 0xa3, + 0xb8, 0xe3, 0xf1, 0x32, 0xe3, 0xe5, 0x6d, 0xb4, 0x64, 0xd5, 0x0b, 0x5a, 0x17, 0xb7, 0x32, 0x70, + 0xe6, 0x96, 0xde, 0x30, 0x8d, 0x50, 0xc8, 0x1a, 0x75, 0x5b, 0xb6, 0xe5, 0xd2, 0x23, 0x34, 0x5b, + 0x44, 0x86, 0x64, 0xf6, 0x40, 0x86, 0x64, 0xbc, 0xb3, 0x7b, 0xf7, 0xdd, 0xd9, 0xb9, 0x7d, 0x77, + 0x76, 0xdf, 0xbe, 0x3a, 0xfb, 0x73, 0x69, 0x18, 0xf3, 0xe3, 0x85, 0xe4, 0x9c, 0x2f, 0x52, 0x30, + 0x0b, 0x9e, 0xa9, 0x2a, 0x71, 0x0c, 0x88, 0x27, 0x55, 0xe8, 0x9b, 0xbe, 0xdb, 0x32, 0x1d, 0xea, + 0xee, 0x20, 0x08, 0xe3, 0xbc, 0x38, 0x3f, 0x1a, 0xa7, 0xbc, 0x49, 0xec, 0xe8, 0x88, 0x83, 0x31, + 0xd0, 0x9b, 0x47, 0x4c, 0x4d, 0xf8, 0x89, 0x6c, 0x78, 0xa0, 0xb7, 0x88, 0xac, 0x8a, 0x44, 0xee, + 0x87, 0xa4, 0xe4, 0x51, 0xc8, 0x2c, 0x2e, 0xce, 0x89, 0xb5, 0x01, 0x13, 0x08, 0x79, 0x9e, 0x1c, + 0xc9, 0xce, 0xb0, 0xe4, 0x12, 0xf4, 0xba, 0x6a, 0xcf, 0x21, 0x40, 0x3e, 0x64, 0x40, 0x40, 0xf1, + 0xb7, 0xd2, 0x3c, 0x8b, 0x06, 0x9f, 0x6d, 0x0e, 0x45, 0xf3, 0x27, 0xa0, 0xdf, 0xef, 0x1b, 0xa1, + 0xfb, 0x41, 0x5c, 0x90, 0xda, 0x67, 0xea, 0xb3, 0x83, 0x18, 0xb0, 0x82, 0x1f, 0xa0, 0xc1, 0xef, + 0x50, 0x70, 0x5b, 0x87, 0x01, 0x1a, 0x7e, 0x58, 0xc6, 0xbb, 0x60, 0x20, 0x98, 0x60, 0xe5, 0xbb, + 0x93, 0x60, 0x36, 0xd6, 0x42, 0xbc, 0xb2, 0xae, 0xe4, 0xf6, 0x61, 0x85, 0xf8, 0xe2, 0xe5, 0x1d, + 0x78, 0x2c, 0xde, 0x03, 0x16, 0xef, 0xa7, 0x84, 0x78, 0x79, 0x8c, 0xe0, 0x91, 0x15, 0xef, 0x81, + 0xdd, 0x1b, 0x14, 0x7f, 0x25, 0x05, 0x84, 0xbd, 0x56, 0x45, 0x77, 0xdd, 0x75, 0xdb, 0x31, 0x78, + 0xd0, 0xc7, 0xa1, 0x08, 0xe6, 0xe0, 0xee, 0x7a, 0xbf, 0x06, 0xe0, 0x44, 0xc4, 0xe1, 0xfc, 0x88, + 0x4f, 0x56, 0x97, 0xa2, 0xa3, 0xa9, 0x5b, 0x34, 0xd9, 0x63, 0xf2, 0x65, 0x72, 0x6f, 0x24, 0xc4, + 0x55, 0xba, 0x45, 0x7e, 0x12, 0x86, 0xc4, 0x0f, 0x66, 0x16, 0xf8, 0xb7, 0x84, 0x38, 0x4a, 0x5d, + 0x06, 0xd0, 0x22, 0x68, 0xf2, 0x1c, 0x0c, 0xb0, 0x01, 0xb3, 0x82, 0x69, 0xc9, 0xfa, 0xc2, 0x48, + 0x2d, 0xc3, 0x07, 0xca, 0x4b, 0x4f, 0x40, 0x29, 0x05, 0x0a, 0xf4, 0xef, 0x20, 0x64, 0xf0, 0x23, + 0x30, 0x58, 0xb2, 0x2c, 0x9b, 0x27, 0x58, 0x72, 0xc5, 0xb5, 0x4e, 0xc7, 0x2d, 0xc9, 0xa3, 0x98, + 0x61, 0x25, 0xa4, 0x4f, 0xdc, 0x93, 0xc8, 0x0c, 0xc9, 0x55, 0x3f, 0xda, 0x8c, 0x3a, 0xc2, 0xba, + 0xc6, 0xab, 0x2d, 0x47, 0xc0, 0xe2, 0xc1, 0x66, 0xd8, 0x79, 0xc3, 0x15, 0xc7, 0x6e, 0xd9, 0x2e, + 0x35, 0xb8, 0xa0, 0x06, 0xc3, 0xec, 0x05, 0x2d, 0x81, 0xc0, 0x48, 0xd9, 0x48, 0x8a, 0xb0, 0x48, + 0x13, 0xb2, 0x0c, 0x27, 0xfd, 0x4b, 0xf6, 0x20, 0x26, 0xb9, 0x3c, 0xe5, 0x62, 0xb0, 0xc5, 0x60, + 0x98, 0x07, 0x2b, 0x44, 0x4d, 0x3c, 0xe4, 0x5f, 0x29, 0xf9, 0x41, 0xcd, 0x35, 0xd3, 0x90, 0xbb, + 0x3a, 0x91, 0x1f, 0xf9, 0x52, 0x18, 0x9c, 0xd7, 0xef, 0x4e, 0xb5, 0xc5, 0xc1, 0xd3, 0xf0, 0xce, + 0x6f, 0xae, 0x9a, 0xfa, 0xdd, 0x9a, 0x21, 0xda, 0x29, 0xe6, 0x87, 0xcc, 0x92, 0xd4, 0xe0, 0x74, + 0xc5, 0xb1, 0x9b, 0xb6, 0x47, 0x0d, 0x25, 0xbc, 0x77, 0x34, 0xcc, 0x07, 0xd0, 0x12, 0x14, 0xb5, + 0x2e, 0x71, 0xbe, 0x1d, 0xd8, 0x90, 0x26, 0x8c, 0x96, 0x5c, 0xb7, 0xdd, 0xa4, 0xe1, 0xed, 0xde, + 0xd8, 0xb6, 0x9f, 0xf1, 0x0e, 0xe1, 0xc8, 0xfe, 0xa0, 0x8e, 0x4d, 0xf9, 0xe5, 0x5e, 0xcd, 0x33, + 0xe5, 0x27, 0xe2, 0xb7, 0xa8, 0xbc, 0x59, 0xef, 0xfa, 0x02, 0xc4, 0xe4, 0x23, 0xf9, 0x71, 0x1c, + 0x5e, 0xd8, 0xbb, 0x81, 0xe8, 0x31, 0x71, 0x89, 0xdc, 0xbb, 0x91, 0x26, 0xe4, 0x23, 0x70, 0x36, + 0xd6, 0x1b, 0xfc, 0xab, 0x58, 0x1f, 0x93, 0x68, 0xd4, 0xb2, 0x4a, 0x30, 0x91, 0x65, 0x5d, 0xa0, + 0x75, 0xe1, 0x40, 0x1e, 0x81, 0x21, 0xb7, 0xbd, 0xc4, 0x8f, 0x87, 0x6a, 0x4b, 0x1b, 0x3c, 0xe3, + 0x96, 0x36, 0x18, 0xc0, 0x26, 0x36, 0xae, 0x65, 0xfb, 0x47, 0xc6, 0x46, 0xb5, 0x33, 0x71, 0x99, + 0x62, 0xb0, 0x5c, 0xf1, 0x9b, 0xd3, 0xca, 0x64, 0xc8, 0x2d, 0xca, 0x7d, 0x4d, 0x86, 0xf2, 0xa4, + 0x96, 0xde, 0xe3, 0xa4, 0xf6, 0x58, 0xdc, 0xed, 0x25, 0x61, 0xa6, 0xfa, 0x52, 0x18, 0xf1, 0x5b, + 0xe0, 0x7b, 0x6f, 0x04, 0xab, 0x5d, 0x67, 0x8d, 0x38, 0x27, 0x34, 0x62, 0x0c, 0x4d, 0xea, 0x0d, + 0x45, 0x0d, 0x14, 0x7e, 0xc5, 0x1f, 0x4d, 0x01, 0x84, 0xe3, 0x88, 0x3c, 0x19, 0x4d, 0x71, 0x99, + 0x0a, 0xaf, 0x02, 0x45, 0xa6, 0xa3, 0x48, 0x4e, 0x4b, 0x72, 0x0e, 0xb2, 0x98, 0x0d, 0x2b, 0x1d, + 0x5e, 0x1e, 0xac, 0x99, 0x96, 0xa1, 0x21, 0x94, 0x61, 0xa5, 0xb4, 0x35, 0x88, 0x45, 0xbf, 0x17, + 0xbe, 0x4f, 0x98, 0x82, 0xd1, 0x6a, 0x7b, 0x49, 0xd6, 0x27, 0x39, 0xc5, 0x89, 0xdb, 0x5e, 0x0a, + 0xb2, 0x1a, 0x44, 0x52, 0x9e, 0x45, 0x9b, 0x14, 0xbf, 0x5a, 0xda, 0xaa, 0xf8, 0x7a, 0x43, 0x2e, + 0x42, 0xda, 0x34, 0x44, 0xb7, 0x26, 0x4d, 0x31, 0x5c, 0xf1, 0xd2, 0xa6, 0x41, 0xe6, 0xe1, 0x44, + 0xdb, 0x5a, 0xb3, 0xec, 0x75, 0xab, 0x56, 0xb7, 0x2d, 0xd7, 0x73, 0x74, 0xd3, 0xf2, 0xdc, 0xe0, + 0xe2, 0xdb, 0x5f, 0xe6, 0x38, 0xc9, 0x64, 0x48, 0x31, 0xdb, 0xa3, 0x91, 0x76, 0x0c, 0x4a, 0x5e, + 0x81, 0x41, 0x7d, 0x1d, 0xb3, 0xe5, 0xb8, 0x76, 0x83, 0x8a, 0xbb, 0xed, 0xe0, 0x02, 0xba, 0x74, + 0xbb, 0x3a, 0xc9, 0x31, 0x51, 0x4e, 0xa0, 0xaf, 0xbb, 0x02, 0x41, 0xde, 0x09, 0x19, 0xd7, 0x5d, + 0x15, 0x57, 0xd7, 0xe1, 0x61, 0x60, 0x75, 0x36, 0xda, 0x84, 0x11, 0x4d, 0x0c, 0xc3, 0xa0, 0xf4, + 0xd2, 0xc5, 0x93, 0x40, 0xe2, 0x2f, 0x5a, 0x5c, 0x83, 0x53, 0x89, 0xcf, 0x25, 0x17, 0x60, 0xd4, + 0x4f, 0x8b, 0xc9, 0xde, 0xb8, 0x2d, 0x32, 0x68, 0x0d, 0x6b, 0xc3, 0x0c, 0x5c, 0x72, 0x2c, 0x77, + 0x12, 0x13, 0x48, 0xbe, 0x13, 0xc6, 0x43, 0xba, 0x16, 0x5f, 0x30, 0x78, 0xfe, 0x2a, 0x6d, 0xd4, + 0xa7, 0xac, 0x70, 0x70, 0xf1, 0x83, 0x30, 0x12, 0x7d, 0x55, 0x36, 0x82, 0xf1, 0x10, 0x28, 0xfa, + 0x88, 0x41, 0x0e, 0xe3, 0x0f, 0x78, 0x1c, 0x46, 0x04, 0x49, 0x94, 0xfb, 0x30, 0x87, 0xfa, 0xbc, + 0xbf, 0x37, 0xa5, 0x8c, 0xe4, 0x43, 0xb4, 0x62, 0x77, 0x34, 0x7a, 0x8b, 0xbf, 0x9c, 0x81, 0xc1, + 0x8a, 0xed, 0x78, 0x22, 0x91, 0xdc, 0xd1, 0x36, 0x2b, 0xa5, 0x13, 0x97, 0xec, 0x2e, 0x4e, 0x5c, + 0xce, 0x41, 0x56, 0x8a, 0x3f, 0xe1, 0xb7, 0xbc, 0x86, 0xe1, 0x68, 0x08, 0x7d, 0x8b, 0x63, 0xf7, + 0xe2, 0x1e, 0x21, 0x7d, 0xfb, 0xf6, 0xbb, 0xfa, 0xb1, 0x34, 0xc0, 0xeb, 0x4f, 0x3f, 0x7d, 0x3f, + 0x77, 0xe9, 0xbe, 0x9c, 0xad, 0x8a, 0xdf, 0x94, 0x82, 0x51, 0xe1, 0x66, 0x21, 0xa5, 0x41, 0xee, + 0xf3, 0xfd, 0x6b, 0xe4, 0x15, 0x87, 0x83, 0x34, 0x1f, 0xc7, 0x6c, 0xd8, 0xe9, 0xbb, 0xa6, 0x87, + 0x37, 0xcd, 0x52, 0x1e, 0x64, 0x2a, 0x60, 0xb2, 0x0d, 0xeb, 0xd3, 0x91, 0x27, 0x7d, 0x07, 0x92, + 0x4c, 0x68, 0xb8, 0xb3, 0x06, 0xd3, 0x89, 0x4e, 0x24, 0xc5, 0x7f, 0x96, 0x85, 0xec, 0xf4, 0x5d, + 0x5a, 0x3f, 0xe2, 0x1d, 0x2b, 0x5d, 0x4b, 0x65, 0xf7, 0x79, 0x2d, 0xb5, 0x17, 0x87, 0xba, 0x97, + 0xc3, 0xfe, 0xcc, 0x45, 0x1f, 0xaf, 0xf4, 0xbc, 0xfa, 0x78, 0xbf, 0xa7, 0x8f, 0x9e, 0x3f, 0xe6, + 0xbf, 0xcc, 0x40, 0xa6, 0x3a, 0x59, 0x39, 0xd6, 0x9b, 0x43, 0xd5, 0x9b, 0xee, 0x1e, 0x47, 0xc5, + 0xc0, 0x89, 0xa0, 0x3f, 0x0c, 0x11, 0x50, 0xfc, 0x05, 0xfe, 0x28, 0x03, 0x23, 0xd5, 0x99, 0xc5, + 0x8a, 0x74, 0x8f, 0x77, 0x9d, 0xbb, 0x71, 0xa3, 0x43, 0x31, 0xef, 0xd2, 0x73, 0x31, 0xf3, 0xfb, + 0x66, 0xd9, 0xf2, 0x9e, 0x7f, 0x16, 0xd3, 0x27, 0xe3, 0x31, 0x33, 0x0f, 0x14, 0x71, 0xcd, 0x37, + 0xe9, 0xb7, 0x60, 0x92, 0x22, 0x9f, 0x01, 0x79, 0x11, 0x32, 0x37, 0x85, 0x37, 0x5e, 0x27, 0x3e, + 0xcf, 0x5c, 0xe5, 0x7c, 0xd8, 0x24, 0x98, 0x69, 0x9b, 0x06, 0x72, 0x60, 0xad, 0x58, 0xe3, 0x57, + 0x85, 0xc1, 0xb1, 0xa3, 0xc6, 0x2b, 0x7e, 0xe3, 0x57, 0xcb, 0x53, 0xa4, 0x0a, 0x83, 0x15, 0xea, + 0x34, 0x4d, 0xec, 0x28, 0x7f, 0xc6, 0xef, 0xce, 0xe4, 0xc4, 0xd6, 0x66, 0x61, 0xb0, 0x15, 0x36, + 0x42, 0x66, 0x32, 0x17, 0xf2, 0x01, 0x00, 0x6e, 0x93, 0xed, 0x30, 0xb5, 0xfe, 0x79, 0x3c, 0xb8, + 0xe0, 0x7b, 0xe3, 0x84, 0x4d, 0xaa, 0xc4, 0x8c, 0xac, 0xc1, 0xd8, 0xbc, 0x6d, 0x98, 0xcb, 0x26, + 0xf7, 0xbb, 0xc7, 0x07, 0xe4, 0xb6, 0xf7, 0x76, 0x65, 0x7b, 0xe1, 0xa6, 0xd4, 0x2e, 0xe9, 0x31, + 0x31, 0xc6, 0xc5, 0x7f, 0xde, 0x0b, 0x59, 0xd6, 0xed, 0xc7, 0xe3, 0x77, 0x3f, 0xe3, 0xb7, 0x04, + 0x63, 0xb7, 0x6d, 0x67, 0xcd, 0xb4, 0x56, 0x82, 0x30, 0x2a, 0x71, 0xb8, 0x86, 0x7e, 0x98, 0xeb, + 0x1c, 0x57, 0x0b, 0x22, 0xae, 0xb4, 0x18, 0xf9, 0x36, 0x23, 0xf8, 0x05, 0x00, 0x9e, 0x83, 0x06, + 0x69, 0xfa, 0xc3, 0xc4, 0x5a, 0x3c, 0x43, 0x0d, 0x46, 0x66, 0xc9, 0x89, 0xb5, 0x42, 0x62, 0x72, + 0xc9, 0xf7, 0x64, 0x1b, 0x40, 0x87, 0x55, 0x3c, 0x45, 0x44, 0x4f, 0x36, 0xd9, 0x08, 0xe0, 0x3e, + 0x6d, 0x15, 0x00, 0xc9, 0x3b, 0x00, 0x14, 0x41, 0x44, 0x26, 0x07, 0x91, 0x24, 0x39, 0xc1, 0x39, + 0x40, 0x93, 0x78, 0x90, 0xe7, 0x15, 0xf7, 0x25, 0x12, 0xe1, 0xd6, 0xd1, 0x7b, 0x29, 0x74, 0x7f, + 0x1d, 0xda, 0xce, 0xfd, 0xb5, 0xf8, 0x5d, 0x19, 0x18, 0x64, 0xdc, 0xaa, 0xed, 0x66, 0x53, 0x77, + 0x36, 0x8e, 0x15, 0x79, 0x3f, 0x8a, 0x5c, 0x83, 0x71, 0x39, 0x5a, 0x8a, 0x19, 0xbe, 0x7e, 0x36, + 0xc6, 0xe0, 0xa8, 0x47, 0x25, 0x10, 0x17, 0xab, 0x6c, 0xde, 0xf7, 0x04, 0x18, 0x0f, 0x47, 0x5d, + 0x2d, 0xce, 0xab, 0xf8, 0xf5, 0x29, 0x18, 0x53, 0xa1, 0x81, 0xee, 0xa7, 0x12, 0x75, 0xff, 0x09, + 0x18, 0x10, 0x0e, 0x50, 0xba, 0x21, 0xbc, 0xb9, 0x47, 0xb6, 0x36, 0x0b, 0x80, 0x89, 0x3f, 0x6a, + 0x0e, 0xd5, 0x0d, 0x2d, 0x24, 0x20, 0xcf, 0xc1, 0x10, 0xfe, 0xb8, 0xed, 0x98, 0x9e, 0x47, 0x79, + 0x67, 0x64, 0xf9, 0x0d, 0x28, 0x6f, 0xb0, 0xce, 0x11, 0x5a, 0x84, 0xac, 0xf8, 0x33, 0x69, 0x18, + 0xa8, 0xb6, 0x97, 0xdc, 0x0d, 0xd7, 0xa3, 0xcd, 0x23, 0xae, 0x43, 0xfe, 0xf1, 0x53, 0x36, 0xf1, + 0xf8, 0xe9, 0x51, 0x7f, 0x68, 0x49, 0xd7, 0x6f, 0xc1, 0xc6, 0xc0, 0xf7, 0x29, 0x0f, 0xb5, 0x28, + 0xb7, 0x87, 0xad, 0xce, 0x3f, 0x49, 0xc3, 0x18, 0x77, 0xbd, 0x99, 0x32, 0xdd, 0xfa, 0x01, 0x44, + 0x13, 0x1e, 0xbe, 0x4c, 0xf7, 0xe7, 0xae, 0xb6, 0x83, 0x18, 0xcd, 0xe2, 0x57, 0x64, 0x60, 0xb0, + 0xd4, 0xf6, 0x56, 0x4b, 0x1e, 0xce, 0x6f, 0xc7, 0x3b, 0xec, 0x3d, 0x84, 0x33, 0x0d, 0xe9, 0xad, + 0x56, 0xad, 0xe9, 0x4b, 0x2d, 0x17, 0x75, 0x32, 0x2e, 0xb5, 0x5a, 0x1d, 0x18, 0x0c, 0xea, 0x21, + 0xaa, 0xf8, 0xd3, 0x29, 0x5e, 0x9a, 0x63, 0xd1, 0x5e, 0xa3, 0xd6, 0x01, 0xdc, 0x20, 0x1e, 0xc4, + 0xa1, 0xb9, 0xdf, 0x9f, 0x99, 0xdd, 0xf5, 0x27, 0xde, 0x7b, 0x6b, 0x76, 0x83, 0x1e, 0xed, 0xcf, + 0x38, 0xc0, 0x7b, 0x6f, 0x5f, 0x20, 0x07, 0xe0, 0x67, 0xf1, 0xf6, 0x12, 0xc8, 0x01, 0x9c, 0x29, + 0xbf, 0x3d, 0x04, 0xf2, 0xf9, 0x14, 0x0c, 0x4c, 0xd8, 0xde, 0x11, 0x1f, 0xf8, 0xe2, 0x2b, 0x8e, + 0xb6, 0x9a, 0xfb, 0x5f, 0x71, 0xb4, 0x75, 0xb3, 0xf8, 0x0d, 0x69, 0x38, 0x29, 0xca, 0x87, 0x89, + 0x73, 0xb8, 0xe3, 0xe9, 0x58, 0x0c, 0xb6, 0xb8, 0x68, 0x8e, 0xe7, 0x21, 0x21, 0x9a, 0xcf, 0xa4, + 0xe0, 0x04, 0xfa, 0xca, 0x1a, 0x68, 0x88, 0x04, 0x5f, 0x77, 0x12, 0xb8, 0x47, 0x1d, 0x4f, 0x31, + 0xad, 0xf1, 0x1f, 0xa4, 0x00, 0x83, 0x6f, 0xd8, 0xa6, 0x55, 0xe3, 0x01, 0x00, 0x22, 0x83, 0x33, + 0x30, 0x90, 0x08, 0x00, 0x38, 0x0f, 0xd0, 0x76, 0xf5, 0x15, 0x5a, 0x6b, 0xda, 0x86, 0xb8, 0x98, + 0xd6, 0x06, 0x10, 0x32, 0x6f, 0x1b, 0x94, 0x3c, 0x0e, 0x23, 0x3a, 0xe6, 0xb2, 0xa7, 0x86, 0xc8, + 0x87, 0x8d, 0x9b, 0x07, 0x6d, 0xd8, 0x87, 0xf2, 0x8c, 0xd8, 0xbf, 0x98, 0x86, 0x71, 0xe9, 0xa5, + 0x42, 0x3d, 0x6e, 0xee, 0xb2, 0xb3, 0x9a, 0x52, 0x67, 0x39, 0x7b, 0xec, 0x2c, 0x47, 0xea, 0xac, + 0xf6, 0x2e, 0x3b, 0xab, 0x1d, 0x75, 0xa5, 0xde, 0x8d, 0x8d, 0x3a, 0x07, 0x43, 0x22, 0x0b, 0xb1, + 0xc7, 0x64, 0x20, 0x2c, 0xd5, 0x07, 0x23, 0x15, 0xd7, 0xa2, 0x7d, 0xa6, 0x5a, 0x9b, 0x6e, 0x48, + 0xa2, 0x4a, 0x34, 0x9c, 0xb0, 0x8f, 0x25, 0xba, 0x47, 0x89, 0x7e, 0x2c, 0x2a, 0xd1, 0x70, 0x42, + 0xb9, 0x7f, 0x24, 0x5a, 0xfc, 0x8e, 0x0c, 0x9c, 0xc4, 0x92, 0x29, 0xae, 0x69, 0x5b, 0x6f, 0x83, + 0x8d, 0x0c, 0xa9, 0x47, 0x5d, 0x21, 0xe7, 0x13, 0x5c, 0x21, 0xff, 0x6c, 0xb3, 0xf0, 0xfc, 0x2e, + 0xca, 0x36, 0x5e, 0xae, 0xe2, 0x51, 0x0f, 0xe3, 0xea, 0x3b, 0x51, 0xba, 0x00, 0xd7, 0x82, 0x19, + 0x53, 0xec, 0xd4, 0xab, 0x5b, 0x9b, 0x85, 0x53, 0xd2, 0xd4, 0xba, 0xf7, 0xe7, 0x85, 0xac, 0x35, + 0xe9, 0x31, 0xc5, 0x7f, 0x95, 0x82, 0x07, 0xa2, 0x4b, 0xe0, 0xdb, 0x61, 0xe3, 0xf9, 0x77, 0xd2, + 0x70, 0xea, 0x55, 0x14, 0x4e, 0xe0, 0xce, 0x7d, 0x6c, 0xf4, 0x88, 0x95, 0x3d, 0x41, 0x36, 0xc7, + 0xdb, 0xd1, 0xce, 0xb2, 0x39, 0xb6, 0x08, 0x85, 0x6c, 0x7e, 0x3e, 0x05, 0x27, 0x16, 0xca, 0x53, + 0x93, 0x6f, 0x93, 0x11, 0x15, 0xff, 0x9e, 0x23, 0xbe, 0x5b, 0x8d, 0x7d, 0xcf, 0x11, 0xdf, 0xb7, + 0x7e, 0x26, 0x0d, 0x27, 0xaa, 0xa5, 0xf9, 0xb9, 0xb7, 0xcb, 0x0c, 0x3e, 0x29, 0xc7, 0x1e, 0xf9, + 0xa7, 0xf8, 0xc2, 0x16, 0x90, 0x3f, 0xf3, 0xd6, 0xd5, 0xce, 0x31, 0x49, 0x71, 0xa1, 0x1c, 0xf1, + 0xa9, 0xfb, 0x40, 0x84, 0xc2, 0x34, 0x3f, 0x42, 0x7d, 0xc4, 0x35, 0xff, 0x5f, 0xe4, 0x60, 0xf0, + 0x7a, 0x7b, 0x89, 0x0a, 0x8f, 0xd6, 0xfb, 0xfa, 0xea, 0xea, 0x2a, 0x0c, 0x0a, 0x31, 0xe0, 0x15, + 0xad, 0x94, 0x4e, 0x5d, 0xa4, 0x93, 0xe4, 0x59, 0x61, 0x65, 0x22, 0x72, 0x0e, 0xb2, 0xb7, 0xa8, + 0xb3, 0x24, 0xa7, 0xd6, 0xb9, 0x43, 0x9d, 0x25, 0x0d, 0xa1, 0x64, 0x4e, 0x72, 0x5c, 0xaf, 0x94, + 0xb1, 0x26, 0xaf, 0xf0, 0x7a, 0xc0, 0x22, 0xc3, 0x41, 0x08, 0x86, 0xde, 0x32, 0x79, 0x35, 0x5f, + 0x39, 0x2b, 0x98, 0xda, 0x92, 0xdc, 0x80, 0xf1, 0x48, 0x68, 0x06, 0x16, 0xa4, 0xed, 0x4f, 0x60, + 0x97, 0x54, 0x8a, 0x36, 0xde, 0x94, 0xbc, 0x0c, 0x43, 0x3e, 0x10, 0x3d, 0xfc, 0x07, 0xc2, 0x12, + 0x79, 0x01, 0x2b, 0xa5, 0xb4, 0x58, 0xa4, 0x81, 0xcc, 0x00, 0x6f, 0x61, 0x21, 0x81, 0x81, 0x12, + 0x18, 0x13, 0x69, 0x40, 0x9e, 0x43, 0x06, 0x18, 0x61, 0x8e, 0x1e, 0x9f, 0x83, 0x98, 0x77, 0x06, + 0x6f, 0xb0, 0x1d, 0x01, 0xe7, 0xd9, 0x85, 0x22, 0x64, 0x64, 0x01, 0x20, 0xf4, 0xcc, 0x13, 0x7e, + 0xf4, 0xbb, 0xf6, 0x19, 0x94, 0x58, 0xc8, 0xae, 0x08, 0xc3, 0x7b, 0x71, 0x45, 0x28, 0x7e, 0x21, + 0x03, 0x83, 0xd2, 0xa5, 0x1b, 0x79, 0x12, 0x72, 0xa5, 0x56, 0xeb, 0xa6, 0x56, 0x96, 0x0b, 0x85, + 0xe9, 0xad, 0x56, 0xad, 0xed, 0x98, 0x72, 0x64, 0x18, 0x27, 0x22, 0x93, 0x30, 0x5c, 0x6a, 0xb5, + 0x2a, 0xed, 0xa5, 0x86, 0x59, 0x97, 0x8a, 0xcf, 0x62, 0x0a, 0x3a, 0xd6, 0xaa, 0x85, 0x18, 0xb5, + 0xfe, 0x6c, 0xb4, 0x0d, 0xf9, 0x08, 0x26, 0x4e, 0x15, 0x95, 0x60, 0x79, 0x89, 0xd1, 0x62, 0xc2, + 0x85, 0xe0, 0xe5, 0x80, 0x48, 0x94, 0x7e, 0xf5, 0x6b, 0xe4, 0xb1, 0x07, 0xc5, 0x4a, 0xbf, 0x86, + 0x2c, 0xc9, 0x53, 0xd0, 0x57, 0x6a, 0xb5, 0xa4, 0xeb, 0x76, 0xf4, 0xcc, 0x65, 0xad, 0x94, 0x3e, + 0xf6, 0xc9, 0xc4, 0x67, 0x09, 0x07, 0x1d, 0xdb, 0xf1, 0x70, 0x48, 0x0d, 0x87, 0x9f, 0xe5, 0x7b, + 0xf4, 0xd8, 0x72, 0xae, 0x42, 0x2d, 0xda, 0x86, 0x3d, 0xd6, 0x2f, 0xc8, 0x9e, 0x0b, 0x1f, 0x9b, + 0x50, 0x89, 0xdd, 0x27, 0x3b, 0xfb, 0x12, 0x8c, 0x44, 0xbf, 0x71, 0x57, 0x15, 0xe0, 0xfe, 0x34, + 0x85, 0x72, 0x3c, 0xe2, 0x01, 0x95, 0xcf, 0x40, 0xa6, 0xd4, 0x6a, 0x89, 0x69, 0x70, 0x07, 0xf7, + 0xc2, 0x8c, 0xda, 0xff, 0xf4, 0x23, 0x1e, 0x99, 0xbd, 0xa7, 0x4f, 0xff, 0x3c, 0xff, 0xf4, 0xa3, + 0x1d, 0x35, 0x5d, 0xfc, 0xc6, 0x2c, 0x8c, 0x96, 0x5a, 0xad, 0xe3, 0xc2, 0x6d, 0x07, 0x95, 0xe2, + 0xea, 0x69, 0x00, 0x69, 0x56, 0xee, 0x0b, 0x52, 0x4c, 0x0c, 0x4a, 0x33, 0x72, 0x3e, 0xa5, 0x49, + 0x44, 0xbe, 0xfa, 0xf5, 0xef, 0x46, 0xfd, 0x42, 0x27, 0xc0, 0x81, 0xdd, 0xe6, 0xc0, 0x84, 0x1d, + 0xe7, 0xc0, 0xfc, 0x58, 0x06, 0xa7, 0xe3, 0xa3, 0x9e, 0x51, 0xf8, 0x5e, 0x51, 0x0d, 0xd1, 0xcf, + 0xb9, 0x5d, 0x4d, 0x33, 0x1f, 0x8b, 0x0c, 0x50, 0xac, 0x06, 0x76, 0xdc, 0x0b, 0xbd, 0xfb, 0xb2, + 0xf8, 0x47, 0x64, 0x61, 0x8a, 0x3c, 0xa5, 0x7e, 0x2d, 0x6a, 0x91, 0xa6, 0xb6, 0xce, 0x50, 0x98, + 0xac, 0x36, 0x4a, 0xeb, 0xf7, 0x61, 0xdf, 0xae, 0xc6, 0xea, 0xfb, 0x13, 0x33, 0x3a, 0x63, 0x44, + 0xab, 0x9c, 0xd1, 0x59, 0x36, 0x7a, 0x65, 0xfa, 0xe2, 0x66, 0x1a, 0x33, 0x6b, 0x05, 0x49, 0x7f, + 0xf7, 0xbf, 0xf1, 0xba, 0x02, 0xc0, 0x6f, 0x43, 0x82, 0xb0, 0xa9, 0x61, 0x9e, 0xa0, 0x93, 0x97, + 0xb8, 0x16, 0x09, 0x3a, 0x43, 0x92, 0xc0, 0x0b, 0x35, 0x93, 0xe8, 0x85, 0x7a, 0x09, 0xfa, 0x35, + 0x7d, 0xfd, 0xb5, 0x36, 0x15, 0xb1, 0xc8, 0x7e, 0x4e, 0x7d, 0x7d, 0xbd, 0xf6, 0x51, 0x06, 0xd4, + 0x02, 0x34, 0x29, 0x06, 0xa9, 0xd9, 0x24, 0x6f, 0x3f, 0x7e, 0x7d, 0x10, 0x24, 0x64, 0xdb, 0xcb, + 0x40, 0x21, 0xef, 0x85, 0x4c, 0xe9, 0x76, 0x55, 0xf4, 0xcc, 0x59, 0x29, 0xba, 0x56, 0xc8, 0xab, + 0x63, 0xdb, 0xdb, 0xd5, 0xe2, 0xf7, 0x66, 0xe1, 0x64, 0x28, 0xe0, 0xb9, 0xb9, 0x79, 0x49, 0xc6, + 0x7b, 0xbe, 0x19, 0x7b, 0x06, 0x32, 0x7a, 0xab, 0x25, 0x06, 0xda, 0x4e, 0xbe, 0x42, 0x6f, 0xb5, + 0xc8, 0xb3, 0x90, 0x73, 0x77, 0x91, 0xc1, 0xca, 0x0d, 0xe2, 0x29, 0x59, 0x6f, 0xc8, 0x4e, 0xad, + 0xbc, 0x77, 0x5a, 0x22, 0xc2, 0xa5, 0xd9, 0x51, 0xe4, 0xfc, 0x7f, 0x72, 0x11, 0xfa, 0x5b, 0x8e, + 0x7d, 0xc7, 0x34, 0x68, 0x24, 0x8d, 0xa8, 0x0f, 0xd3, 0x82, 0xbf, 0xd8, 0xc2, 0xd3, 0xb4, 0x0d, + 0xda, 0x10, 0x6b, 0x1b, 0x2e, 0x3c, 0x08, 0x90, 0x17, 0x1e, 0x04, 0x90, 0x97, 0x60, 0xd4, 0x2f, + 0xab, 0x60, 0xd4, 0x78, 0xa3, 0xfe, 0xb0, 0x91, 0x82, 0xd2, 0x46, 0x02, 0xc0, 0x3c, 0xb6, 0xbe, + 0x0e, 0xe3, 0xa6, 0xd5, 0x6a, 0x7b, 0xfc, 0xe2, 0x53, 0x44, 0x0a, 0x0f, 0x60, 0x85, 0x0e, 0x4c, + 0x90, 0x1d, 0x43, 0xca, 0x11, 0xe1, 0x88, 0xe4, 0x97, 0x45, 0x18, 0x4e, 0x7c, 0x03, 0x88, 0xdd, + 0xf6, 0x54, 0x6e, 0x80, 0xdc, 0x70, 0x2b, 0x1c, 0xc7, 0xca, 0x3b, 0x6b, 0x8e, 0x0d, 0xf9, 0x15, + 0xaf, 0x03, 0xcc, 0x2e, 0x2e, 0x56, 0x66, 0xa9, 0xce, 0x64, 0x72, 0x0e, 0xd0, 0x89, 0x58, 0xf6, + 0xd1, 0xe6, 0x4e, 0xc5, 0xec, 0x5f, 0x52, 0x88, 0xec, 0x1c, 0x78, 0x4a, 0x11, 0x04, 0x88, 0x4d, + 0x44, 0xf1, 0xeb, 0xb3, 0x70, 0x2a, 0x54, 0x3f, 0xc6, 0xf7, 0x20, 0xf4, 0xcf, 0xbf, 0x55, 0x4d, + 0xef, 0xf2, 0x56, 0xf5, 0x65, 0xe8, 0x73, 0xf7, 0x34, 0xd3, 0x8b, 0x56, 0xbe, 0xe2, 0x67, 0x77, + 0xa5, 0xf8, 0x4f, 0x02, 0xf8, 0x67, 0x26, 0xa6, 0xaf, 0xa8, 0xe8, 0xc9, 0x1e, 0x42, 0xb5, 0xa0, + 0xfc, 0x87, 0x21, 0xe9, 0x74, 0xae, 0xa3, 0x4e, 0x3f, 0x00, 0x99, 0xb6, 0xe3, 0xeb, 0x29, 0xe6, + 0x40, 0x6f, 0x3b, 0x0d, 0x8d, 0xfd, 0x43, 0x9e, 0x81, 0xa1, 0x55, 0xcf, 0x6b, 0xd5, 0x44, 0xae, + 0x5a, 0xa1, 0x96, 0x78, 0x72, 0x23, 0xc3, 0xb5, 0x41, 0xf6, 0xcb, 0x4f, 0x62, 0xfb, 0x02, 0xf4, + 0xad, 0x62, 0x7f, 0xf3, 0xac, 0x86, 0x52, 0x16, 0x81, 0x50, 0x15, 0x78, 0x90, 0xaa, 0x20, 0xd3, + 0xfc, 0x3f, 0xc8, 0xb3, 0x10, 0x4e, 0x86, 0xc2, 0x00, 0xe3, 0x75, 0xbd, 0x7d, 0xa0, 0x1c, 0xa6, + 0xea, 0x88, 0xb9, 0xb2, 0xf8, 0x93, 0x69, 0x78, 0x28, 0x51, 0x2f, 0x26, 0x6c, 0x63, 0x23, 0x30, + 0x05, 0xf6, 0xac, 0x20, 0x52, 0x47, 0xa7, 0xf7, 0xd4, 0xd1, 0xd1, 0x3e, 0xcb, 0x6c, 0xd7, 0x67, + 0x4f, 0xc1, 0xa0, 0x58, 0x57, 0xb1, 0xd0, 0x29, 0x2f, 0x53, 0x8a, 0xab, 0x8e, 0x04, 0xd6, 0xa0, + 0x1e, 0x56, 0x3c, 0x7d, 0x0c, 0xfa, 0x4c, 0xb7, 0xd6, 0xd0, 0x5d, 0x7e, 0x10, 0x20, 0xf2, 0xc1, + 0x0b, 0x90, 0x96, 0x33, 0xdd, 0x39, 0xdd, 0xc5, 0x08, 0x89, 0xc0, 0xa5, 0x79, 0x88, 0x8f, 0x3e, + 0xf6, 0x5b, 0xc3, 0x7f, 0x8b, 0x7f, 0x90, 0x85, 0xd3, 0xaa, 0x10, 0xc3, 0x14, 0x85, 0x87, 0x27, + 0x3c, 0x31, 0x4a, 0x32, 0xfb, 0x18, 0x25, 0xd9, 0x1d, 0x48, 0x5c, 0x5a, 0xd0, 0xc5, 0x61, 0x4a, + 0x7c, 0x9d, 0x77, 0xc3, 0x75, 0x3e, 0x6c, 0xe1, 0xd1, 0xbb, 0x9e, 0x18, 0x5c, 0x72, 0x0b, 0x06, + 0xf6, 0x5b, 0x2c, 0xd2, 0xbb, 0x5e, 0x6c, 0x28, 0xf5, 0xed, 0x72, 0x28, 0xf5, 0xef, 0x72, 0x28, + 0x5d, 0x85, 0x21, 0x97, 0x5a, 0x06, 0x46, 0xf0, 0xd5, 0x9a, 0xae, 0x58, 0x11, 0xf0, 0x79, 0x32, + 0x5c, 0x03, 0xf6, 0x6b, 0xd1, 0x6c, 0xd2, 0x79, 0x6c, 0xb3, 0xae, 0x9b, 0x5e, 0xd0, 0x06, 0xc2, + 0x36, 0x32, 0x5c, 0x03, 0xf6, 0x4b, 0xb4, 0x79, 0x91, 0x2d, 0x5e, 0x98, 0x55, 0x38, 0x68, 0x36, + 0x88, 0xcd, 0xc4, 0xe2, 0x15, 0x41, 0x69, 0xc3, 0x02, 0xc0, 0x1b, 0x17, 0x3f, 0x9f, 0x86, 0x42, + 0xb2, 0xd2, 0x1d, 0x0f, 0xdd, 0x9d, 0x0f, 0xdd, 0x8f, 0xa5, 0x81, 0xc4, 0x0d, 0x38, 0xf2, 0x3c, + 0x0c, 0x20, 0x74, 0x85, 0x7d, 0x7e, 0x2a, 0x4c, 0xfb, 0xa8, 0xaf, 0xbb, 0x35, 0x07, 0xa1, 0x91, + 0xe3, 0x48, 0x9f, 0x94, 0xbc, 0x00, 0x50, 0xba, 0x5d, 0x65, 0x1b, 0x0a, 0xb3, 0xee, 0x2f, 0xc6, + 0x18, 0x27, 0xc8, 0x1a, 0xba, 0x1c, 0x1c, 0x09, 0xd5, 0x0b, 0x88, 0xf1, 0x24, 0xf3, 0x76, 0x75, + 0xd6, 0x76, 0x3d, 0x21, 0x2b, 0x7e, 0x92, 0xb9, 0xee, 0xd6, 0x56, 0x6d, 0xd7, 0x8b, 0x9c, 0x64, + 0x72, 0x32, 0x32, 0x03, 0x23, 0xa5, 0xdb, 0x55, 0x9e, 0x5e, 0xc9, 0xd0, 0xec, 0x86, 0x7f, 0x04, + 0xfa, 0xd0, 0xd6, 0x66, 0xe1, 0x2c, 0x6b, 0xc8, 0x53, 0x33, 0x19, 0x35, 0xc7, 0x8e, 0x14, 0xf6, + 0x57, 0x5a, 0x15, 0x3f, 0xd9, 0x0f, 0x63, 0x53, 0xba, 0xa7, 0x2f, 0xe9, 0x2e, 0x95, 0xee, 0x7f, + 0x46, 0x7d, 0x98, 0xff, 0x39, 0x92, 0x1c, 0x8c, 0xa5, 0x84, 0xaf, 0x51, 0x1b, 0x90, 0x17, 0x43, + 0xbe, 0x15, 0xc7, 0xf6, 0xec, 0xba, 0xdd, 0x10, 0x32, 0xc1, 0x7e, 0x35, 0x96, 0x6a, 0x2d, 0x01, + 0xd6, 0x62, 0x84, 0xe4, 0x09, 0x18, 0xf4, 0x61, 0x37, 0xb5, 0xb2, 0x90, 0x09, 0xae, 0xc1, 0xc6, + 0x52, 0xad, 0xed, 0x98, 0x9a, 0x8c, 0x26, 0x2f, 0xc0, 0x90, 0xff, 0x53, 0x3a, 0x0c, 0xc6, 0x13, + 0x08, 0x63, 0x29, 0x76, 0xde, 0x2f, 0x93, 0xca, 0x4d, 0x71, 0xdb, 0xda, 0x1b, 0x69, 0xca, 0xec, + 0x95, 0xa4, 0xa6, 0xb8, 0x63, 0xfd, 0x28, 0x8c, 0xf8, 0xbf, 0xc5, 0x11, 0x37, 0x8f, 0xd5, 0x7b, + 0xc2, 0x1f, 0x2a, 0xaa, 0x58, 0x2f, 0x47, 0xc9, 0xf9, 0x61, 0xf7, 0x83, 0xe2, 0xb0, 0xfb, 0x84, + 0xb1, 0x14, 0x3f, 0xeb, 0x56, 0x1e, 0x40, 0xca, 0x30, 0xee, 0x43, 0x42, 0x0d, 0xed, 0x0b, 0xef, + 0x38, 0x8c, 0xa5, 0x5a, 0xa2, 0x92, 0xc6, 0x5b, 0x91, 0x06, 0x9c, 0x8b, 0x00, 0x0d, 0x77, 0xd5, + 0x5c, 0xf6, 0xc4, 0x05, 0x85, 0xa8, 0x55, 0x38, 0x30, 0x71, 0x71, 0x6b, 0xb3, 0xf0, 0x58, 0xc0, + 0x95, 0xd3, 0xd4, 0x44, 0x52, 0xa6, 0x68, 0x6a, 0xe4, 0xae, 0xdc, 0x48, 0x15, 0x4e, 0xfa, 0xf8, + 0x57, 0x27, 0x2b, 0x15, 0xc7, 0x7e, 0x83, 0xd6, 0xbd, 0xf2, 0x94, 0x38, 0x5c, 0x42, 0x73, 0xdb, + 0x58, 0xaa, 0xad, 0xd4, 0x5b, 0x4c, 0x29, 0x18, 0x2e, 0xca, 0x3c, 0xb1, 0x31, 0xb9, 0x05, 0xa7, + 0x24, 0x78, 0xd9, 0x72, 0x3d, 0xdd, 0xaa, 0xd3, 0x20, 0x91, 0x33, 0x9a, 0xdd, 0x82, 0xab, 0x29, + 0x90, 0x51, 0xb6, 0xc9, 0xcd, 0xc9, 0x4b, 0x30, 0xec, 0x23, 0xb8, 0xdf, 0xdb, 0x20, 0xee, 0xa7, + 0x71, 0x48, 0x1a, 0x4b, 0x35, 0x35, 0x0b, 0x60, 0x94, 0x58, 0xd6, 0x28, 0x4c, 0x8a, 0x3b, 0x14, + 0xd1, 0x28, 0x25, 0xfb, 0x6d, 0x84, 0x94, 0xbc, 0x1c, 0x6a, 0xd4, 0x82, 0x63, 0xae, 0x98, 0x7e, + 0xb5, 0xff, 0x33, 0x42, 0x3f, 0x6c, 0x04, 0x26, 0xe9, 0x07, 0x27, 0x3f, 0x5b, 0x82, 0x13, 0x09, + 0x3a, 0xb6, 0xab, 0xcb, 0x86, 0x4f, 0xa4, 0xc3, 0x97, 0x38, 0xe2, 0x37, 0x0e, 0x13, 0xd0, 0xef, + 0x7f, 0x89, 0xd8, 0x2c, 0xe4, 0x3b, 0x0d, 0x4d, 0x95, 0x87, 0x8f, 0x8f, 0x88, 0xe3, 0x88, 0xdf, + 0x42, 0x1c, 0x84, 0x38, 0xbe, 0x90, 0x0a, 0xc5, 0x71, 0xc4, 0x6f, 0x26, 0xbe, 0xb3, 0x37, 0x9c, + 0x93, 0x8e, 0xaf, 0x27, 0x0e, 0xea, 0xf4, 0x33, 0x74, 0x99, 0xce, 0xed, 0xc2, 0x09, 0x5d, 0x56, + 0xcd, 0xbe, 0xbd, 0xa9, 0x26, 0x79, 0x09, 0x06, 0x2b, 0xb6, 0xeb, 0xad, 0x38, 0xd4, 0xad, 0x04, + 0xb5, 0x76, 0xf9, 0x19, 0xa8, 0x00, 0xd7, 0x5a, 0xd1, 0x64, 0xfe, 0x12, 0xb9, 0x54, 0xe3, 0x60, + 0x60, 0x0f, 0x35, 0x0e, 0x6a, 0x00, 0x93, 0xa5, 0x85, 0x3b, 0xd4, 0x71, 0x4c, 0xc3, 0x2f, 0x60, + 0xf7, 0x58, 0xc0, 0x21, 0xc0, 0x4c, 0x52, 0xc7, 0xe3, 0x19, 0x40, 0xe8, 0x14, 0xf5, 0x74, 0xb3, + 0xe1, 0x8a, 0x74, 0xeb, 0x7a, 0xcd, 0x16, 0x24, 0xb2, 0x7d, 0x18, 0x36, 0x2c, 0xfe, 0x5a, 0x26, + 0xa6, 0xac, 0xfc, 0xb8, 0xf3, 0x48, 0x2a, 0xeb, 0x01, 0xcc, 0x44, 0xe4, 0x6a, 0xb8, 0x48, 0xf3, + 0x73, 0xe1, 0x5e, 0xa9, 0xe8, 0xd0, 0x92, 0x38, 0x16, 0x8e, 0x92, 0x90, 0x0f, 0xc1, 0x99, 0x08, + 0xa0, 0xa2, 0x3b, 0x7a, 0x93, 0x7a, 0x6c, 0x97, 0x98, 0x0b, 0xcb, 0x48, 0xf8, 0xad, 0x6b, 0xad, + 0x00, 0x2d, 0xf5, 0x4a, 0x27, 0x0e, 0x92, 0xe6, 0xf7, 0xed, 0x22, 0x58, 0xe0, 0x73, 0x99, 0xd0, + 0x0e, 0x8b, 0x96, 0x83, 0xd3, 0xa8, 0xdb, 0x6e, 0x78, 0xf7, 0x6f, 0x07, 0xef, 0xad, 0xbe, 0xf7, + 0x2c, 0x8c, 0x96, 0x96, 0x97, 0x69, 0xdd, 0xf3, 0x8b, 0x64, 0xba, 0xa2, 0x00, 0x10, 0xdf, 0x17, + 0x09, 0x94, 0x28, 0x7a, 0x28, 0xf7, 0xab, 0xda, 0xac, 0xf8, 0xeb, 0x59, 0xc8, 0x07, 0xfb, 0x92, + 0x20, 0xf9, 0xd0, 0x21, 0xda, 0x00, 0xf7, 0x44, 0xaf, 0x98, 0x30, 0x1e, 0x0a, 0x43, 0x64, 0x7d, + 0xc9, 0xf7, 0xe2, 0xbe, 0xa7, 0xa0, 0x32, 0x0b, 0x09, 0xf9, 0x56, 0xe7, 0xac, 0xd8, 0xea, 0x90, + 0x30, 0xb9, 0x53, 0xcd, 0xe5, 0x2c, 0xb4, 0x38, 0x57, 0xf2, 0xa9, 0x14, 0x9c, 0xf4, 0x3b, 0x65, + 0x61, 0x89, 0xd9, 0xfc, 0x78, 0x34, 0xee, 0x6f, 0xb3, 0xde, 0xdb, 0xf9, 0x71, 0xbc, 0x93, 0x2e, + 0x27, 0x35, 0xe6, 0x6f, 0x12, 0x64, 0x7b, 0x0e, 0x14, 0xc2, 0x46, 0x1a, 0x7e, 0x30, 0xef, 0x6a, + 0x89, 0xcf, 0x3d, 0xfb, 0x2a, 0x3c, 0xd0, 0x91, 0xe5, 0x76, 0x36, 0x76, 0xaf, 0x6c, 0x63, 0xff, + 0x6a, 0x2a, 0x9c, 0x88, 0x14, 0x21, 0x91, 0xcb, 0x00, 0x21, 0x48, 0xec, 0xba, 0xf1, 0xe0, 0x24, + 0x14, 0x9a, 0x26, 0x51, 0x90, 0x05, 0xc8, 0x09, 0xb1, 0xa4, 0x51, 0x2c, 0xef, 0xda, 0xa6, 0x17, + 0x2e, 0xcb, 0x72, 0xc0, 0x1d, 0xb5, 0xf8, 0x66, 0xc1, 0xe6, 0xec, 0x0b, 0x30, 0xb8, 0xd7, 0xef, + 0xfa, 0x54, 0x06, 0x88, 0xbc, 0x45, 0x3e, 0xc4, 0xfd, 0xc3, 0x11, 0x9e, 0xc2, 0x2e, 0x42, 0x3f, + 0xfb, 0x04, 0xbc, 0xf0, 0x91, 0xae, 0xca, 0xda, 0x02, 0xa6, 0x05, 0xd8, 0xb0, 0xe2, 0x43, 0x5f, + 0x72, 0xc5, 0x87, 0xe2, 0xd7, 0x67, 0xe0, 0xb4, 0xdc, 0x21, 0x53, 0x14, 0x6b, 0x00, 0x1f, 0x77, + 0xca, 0x5b, 0xd8, 0x29, 0x45, 0xc8, 0xf1, 0x9d, 0x91, 0x28, 0xc6, 0xcc, 0x4f, 0xad, 0x10, 0xa2, + 0x09, 0x4c, 0xf1, 0xdf, 0xa6, 0x61, 0x38, 0xb0, 0x3e, 0x75, 0xc7, 0xbd, 0x8f, 0xbb, 0xe3, 0x3d, + 0x30, 0x8c, 0x39, 0xfb, 0x9b, 0xd4, 0xe2, 0x79, 0xed, 0x7b, 0xa5, 0xea, 0xe9, 0x3e, 0x02, 0xcf, + 0xf0, 0xb4, 0x28, 0x21, 0xd3, 0x7e, 0x6e, 0xf9, 0x49, 0x95, 0x14, 0xb8, 0xd9, 0xc7, 0xe1, 0xc5, + 0x6f, 0xcb, 0xc0, 0x90, 0x2f, 0xe5, 0x09, 0xf3, 0xa8, 0x7a, 0x17, 0x1d, 0xae, 0x90, 0xaf, 0x00, + 0x54, 0x6c, 0xc7, 0xd3, 0x1b, 0x37, 0x42, 0xcd, 0xc7, 0xf3, 0xdb, 0x16, 0x42, 0x79, 0x1b, 0x89, + 0x04, 0xd7, 0xaf, 0xd0, 0xac, 0xe6, 0x13, 0x13, 0x5f, 0xbf, 0x02, 0xa8, 0x26, 0x51, 0x14, 0x7f, + 0x38, 0x0d, 0xa3, 0x7e, 0x27, 0x4d, 0xdf, 0xa5, 0xf5, 0xf6, 0xfd, 0x3c, 0x37, 0x45, 0xa5, 0xdd, + 0xbb, 0xad, 0xb4, 0x8b, 0x7f, 0x28, 0x4d, 0x24, 0x93, 0x0d, 0xfb, 0x78, 0x22, 0xf9, 0xcb, 0xd0, + 0xf1, 0xe2, 0x97, 0x67, 0xe0, 0xa4, 0x2f, 0xf5, 0x99, 0xb6, 0x85, 0x27, 0x1f, 0x93, 0x7a, 0xa3, + 0x71, 0x3f, 0xef, 0xc6, 0x07, 0x7d, 0x41, 0x2c, 0x88, 0x22, 0x38, 0xc3, 0xfc, 0xfe, 0x73, 0x59, + 0x80, 0x6b, 0xb6, 0x69, 0x68, 0x32, 0x11, 0x79, 0x19, 0x86, 0xfc, 0x9f, 0x25, 0x67, 0xc5, 0xdf, + 0x82, 0xe3, 0x3d, 0x46, 0xd0, 0x48, 0x77, 0x22, 0xa9, 0x32, 0x23, 0x0d, 0x8a, 0xdf, 0x94, 0x81, + 0xb3, 0xb7, 0x4d, 0xcb, 0xb0, 0xd7, 0x5d, 0xe9, 0x64, 0x45, 0xce, 0xef, 0x5d, 0x6d, 0xa3, 0x9d, + 0x2e, 0xe7, 0xf7, 0x76, 0x39, 0x48, 0xf3, 0x71, 0xe4, 0x39, 0x18, 0xaa, 0x52, 0xc7, 0xd4, 0x1b, + 0x37, 0xda, 0xcd, 0x25, 0xea, 0x07, 0x3a, 0x60, 0xc4, 0x87, 0x8b, 0xf0, 0x9a, 0x85, 0x08, 0x2d, + 0x42, 0x46, 0x1e, 0x80, 0xcc, 0xcd, 0xca, 0x0d, 0x71, 0x33, 0xc5, 0x9d, 0x3f, 0x5a, 0x96, 0xc6, + 0x60, 0xe4, 0x35, 0x38, 0x35, 0xa9, 0xcd, 0x4d, 0x99, 0x2e, 0xcf, 0xc4, 0x69, 0xda, 0x56, 0xc5, + 0xc6, 0x02, 0x0f, 0xd9, 0xf0, 0x0b, 0xeb, 0x4e, 0xa3, 0x66, 0x48, 0x14, 0xb5, 0x16, 0x92, 0x68, + 0xc9, 0x2d, 0xc9, 0x25, 0xe8, 0xbf, 0x4e, 0x37, 0x6e, 0xa2, 0x7f, 0x6d, 0x2f, 0x86, 0xa4, 0xa0, + 0x03, 0xdc, 0x1a, 0xdd, 0xa8, 0x61, 0x1e, 0x11, 0x2d, 0x40, 0x93, 0x09, 0x18, 0x9b, 0xbe, 0xeb, + 0x51, 0xcb, 0xa0, 0x46, 0xd0, 0x84, 0x89, 0xb6, 0xd7, 0xcf, 0x5b, 0xce, 0x71, 0xb5, 0xb0, 0x6d, + 0x8c, 0x1e, 0x79, 0x58, 0xab, 0xba, 0x55, 0x97, 0x78, 0xf4, 0x85, 0x97, 0x20, 0x54, 0xe0, 0x22, + 0x3c, 0x14, 0xfa, 0xe2, 0x0f, 0xf7, 0x07, 0xbd, 0x33, 0x45, 0xdd, 0x35, 0xcf, 0x7e, 0x1b, 0x38, + 0x81, 0x1f, 0x58, 0x3c, 0xf1, 0x1e, 0xad, 0xd0, 0xd7, 0xe0, 0x94, 0x2a, 0x52, 0x27, 0x28, 0xf4, + 0x2a, 0x34, 0x6b, 0x9d, 0x13, 0xd4, 0x0c, 0x4e, 0xe1, 0x5f, 0xd5, 0x6a, 0xc9, 0x2d, 0xd9, 0xc8, + 0x15, 0x10, 0xc9, 0xa1, 0x1c, 0x47, 0xae, 0xcf, 0x00, 0x2b, 0x27, 0xc8, 0x44, 0xe4, 0x9d, 0x90, + 0x9b, 0xb2, 0x9b, 0xba, 0xe9, 0xfb, 0x35, 0xe1, 0x1c, 0x1b, 0x3c, 0x17, 0x31, 0x9a, 0xa0, 0x60, + 0xfc, 0xc5, 0x83, 0xb1, 0xcb, 0x06, 0x42, 0xfe, 0x7e, 0x03, 0x66, 0x43, 0x6b, 0x32, 0x11, 0xb1, + 0x61, 0x58, 0x3c, 0x4e, 0x5c, 0xac, 0x02, 0x6e, 0x6d, 0x9f, 0xf3, 0x65, 0xd4, 0x59, 0xad, 0x2e, + 0x47, 0xda, 0xf1, 0x4d, 0x2e, 0xbe, 0x9d, 0xff, 0x31, 0xfc, 0x8a, 0x55, 0x8b, 0xf2, 0x97, 0x84, + 0x80, 0x4b, 0xc0, 0x60, 0x5c, 0x08, 0xb8, 0x06, 0xc8, 0x44, 0x64, 0x1a, 0xc6, 0xb1, 0x0c, 0x66, + 0xb0, 0xd1, 0x65, 0x2a, 0x31, 0x84, 0x26, 0x3f, 0xde, 0xd7, 0xf1, 0xca, 0x99, 0xec, 0xe3, 0x6a, + 0x75, 0x81, 0xd6, 0xe2, 0x2d, 0xd8, 0x3c, 0x72, 0x63, 0xae, 0x84, 0x17, 0x7d, 0xfd, 0x7c, 0x1e, + 0xb1, 0x1a, 0xba, 0xc6, 0x60, 0xe4, 0x36, 0x0c, 0xb1, 0x79, 0x2d, 0x18, 0x26, 0x23, 0xa8, 0x29, + 0x45, 0x45, 0x0a, 0x09, 0x53, 0x9f, 0xf0, 0x9c, 0x08, 0x11, 0x5a, 0x84, 0x91, 0x72, 0x5c, 0x3d, + 0x7a, 0xe0, 0xc7, 0xd5, 0x67, 0x5f, 0x01, 0x12, 0xef, 0x88, 0x5d, 0x5d, 0x43, 0xfe, 0x35, 0x38, + 0xd7, 0xed, 0x45, 0xd8, 0x2e, 0x0b, 0xb7, 0xb2, 0xdc, 0x31, 0xa1, 0x3f, 0xcc, 0x63, 0x7c, 0x87, + 0x6a, 0xe2, 0x7f, 0xf2, 0x22, 0x8c, 0x8a, 0xa0, 0x08, 0x36, 0x4f, 0xad, 0xea, 0xee, 0xaa, 0x98, + 0xdc, 0xd1, 0xc3, 0x46, 0x41, 0x69, 0xc3, 0x1c, 0x70, 0x9d, 0x6e, 0xcc, 0xea, 0xee, 0x6a, 0xf1, + 0x6f, 0xf7, 0x42, 0x7e, 0xce, 0xb4, 0xda, 0x77, 0x8f, 0x27, 0xaf, 0x03, 0x9a, 0xbc, 0x94, 0x99, + 0x26, 0xb7, 0x93, 0x99, 0xe6, 0x09, 0x18, 0xc0, 0x4e, 0x40, 0x89, 0xf5, 0x85, 0x87, 0x61, 0x0d, + 0x06, 0xe4, 0x33, 0x47, 0x48, 0x40, 0x9a, 0xea, 0xbc, 0xc1, 0x7d, 0xbf, 0x9e, 0xf1, 0x5f, 0xaf, + 0x53, 0x7f, 0x1e, 0xcc, 0xac, 0x31, 0xb0, 0x83, 0x59, 0xe3, 0x00, 0x46, 0xc6, 0x2f, 0x65, 0xc3, + 0x43, 0xb6, 0xa3, 0x1e, 0x39, 0x73, 0x10, 0xa6, 0xe7, 0x87, 0x61, 0x20, 0x2c, 0x5d, 0xb7, 0x7d, + 0x2d, 0x80, 0x47, 0xc5, 0xd1, 0xef, 0x19, 0x3f, 0x14, 0x03, 0x6b, 0xd7, 0x29, 0xa9, 0xfa, 0x43, + 0x8e, 0xe4, 0x75, 0xe8, 0x9b, 0xe6, 0x7e, 0x7e, 0x3b, 0xa8, 0x03, 0x10, 0x94, 0xf7, 0x0b, 0x99, + 0xdb, 0xad, 0x58, 0x75, 0x61, 0xce, 0x4e, 0x19, 0x8f, 0x7d, 0xfb, 0x18, 0x8f, 0xfb, 0x8d, 0xff, + 0xf8, 0xc3, 0x14, 0x8c, 0xc7, 0x0a, 0x51, 0x93, 0x67, 0x00, 0x38, 0x44, 0x2a, 0xca, 0xc6, 0xbd, + 0xf1, 0x83, 0xe2, 0xd4, 0x62, 0x6b, 0x14, 0x92, 0x91, 0x2b, 0xd0, 0xcf, 0x7f, 0x89, 0x72, 0x13, + 0xf1, 0x26, 0xed, 0xb6, 0x69, 0x68, 0x01, 0x51, 0xf8, 0x14, 0x74, 0x96, 0xc9, 0x24, 0x36, 0xf1, + 0x36, 0x5a, 0xc1, 0x53, 0xd0, 0x51, 0x66, 0x12, 0x86, 0xe7, 0x67, 0x4a, 0xb7, 0x4c, 0x7d, 0xc2, + 0xb1, 0xd7, 0x5d, 0x71, 0xe9, 0xdd, 0xcf, 0xc3, 0x78, 0x59, 0xbb, 0x3b, 0xa6, 0x5e, 0x5b, 0xe2, + 0x28, 0xd9, 0x51, 0x27, 0xd2, 0xa6, 0xf8, 0xc9, 0x34, 0x0c, 0x05, 0x5f, 0x5d, 0x32, 0x0e, 0x6b, + 0x08, 0xe5, 0x44, 0x61, 0xf0, 0xcc, 0x76, 0x85, 0xc1, 0x95, 0x59, 0x55, 0x54, 0x02, 0x3f, 0xb8, + 0x14, 0x37, 0x9f, 0x4e, 0xc3, 0x68, 0xf0, 0xd4, 0x43, 0x74, 0xee, 0xb8, 0x87, 0x44, 0xf2, 0xa9, + 0x14, 0xe4, 0x27, 0xcc, 0x46, 0xc3, 0xb4, 0x56, 0xca, 0xd6, 0xb2, 0xed, 0x34, 0xd1, 0x66, 0x3b, + 0xbc, 0x3b, 0xc0, 0xe2, 0x57, 0xa5, 0x60, 0x5c, 0xbc, 0xd0, 0xa4, 0xee, 0x18, 0x87, 0x77, 0xc1, + 0xa2, 0xbe, 0xc9, 0xe1, 0xe9, 0x4b, 0xf1, 0x07, 0xd3, 0x00, 0x73, 0x76, 0x7d, 0xed, 0x88, 0x67, + 0xc8, 0x79, 0x11, 0x72, 0x3c, 0xc7, 0x80, 0xd0, 0xd8, 0x71, 0x91, 0x09, 0x86, 0x7d, 0x1a, 0x47, + 0x4c, 0x8c, 0x89, 0xe5, 0x25, 0xc7, 0xd3, 0x14, 0xe4, 0x53, 0x9a, 0x68, 0xc2, 0x1e, 0xca, 0xe8, + 0xc4, 0xfa, 0x17, 0x3c, 0x94, 0xc1, 0xa2, 0x0f, 0xdd, 0xda, 0x2c, 0x64, 0x1b, 0x76, 0x7d, 0x4d, + 0x43, 0xfa, 0xe2, 0x7f, 0x4f, 0x71, 0xd9, 0x1d, 0xf1, 0x3c, 0x5f, 0xfe, 0xe7, 0x67, 0x77, 0xf9, + 0xf9, 0x5f, 0x93, 0x82, 0x93, 0x1a, 0xad, 0xb3, 0xdd, 0xcc, 0xc6, 0xa4, 0x6d, 0xd0, 0x57, 0xa9, + 0x45, 0x9d, 0xc3, 0x1a, 0x51, 0x3f, 0x92, 0x82, 0x31, 0xf9, 0x65, 0x6e, 0xba, 0xf4, 0x70, 0xd6, + 0xa4, 0xd0, 0xbc, 0xcf, 0xec, 0xc2, 0x93, 0xe5, 0x2f, 0xfa, 0x20, 0x9f, 0xb8, 0x31, 0x3f, 0xb2, + 0xd6, 0x69, 0xc7, 0xd3, 0x96, 0xec, 0x41, 0x9d, 0xb6, 0xf4, 0xee, 0xee, 0xb4, 0x25, 0xb7, 0xdb, + 0xd3, 0x96, 0xbe, 0x9d, 0x9c, 0xb6, 0x6c, 0xb7, 0x6b, 0xea, 0xd4, 0xa9, 0x7b, 0xdc, 0x35, 0x45, + 0xec, 0xf5, 0x81, 0xb7, 0xd2, 0x5e, 0x87, 0x83, 0xb5, 0xd7, 0xf7, 0x72, 0x48, 0x74, 0x91, 0x4d, + 0x8a, 0x75, 0xdb, 0x31, 0xa8, 0x21, 0xce, 0x86, 0xf0, 0xda, 0xd8, 0x11, 0x30, 0x2d, 0xc0, 0x92, + 0x67, 0x15, 0x1b, 0x7e, 0x18, 0x6d, 0x78, 0x64, 0x2f, 0xdb, 0xf0, 0x51, 0xcb, 0x5d, 0x31, 0x75, + 0x46, 0xf6, 0x6e, 0xea, 0x1c, 0xc0, 0xc6, 0xf4, 0x8f, 0x73, 0x70, 0x3a, 0x61, 0x87, 0x7d, 0x64, + 0x87, 0xff, 0xc1, 0x9d, 0x97, 0xec, 0x65, 0xd4, 0x47, 0x4e, 0x3e, 0x72, 0xdb, 0x9d, 0x7c, 0xac, + 0xa9, 0x63, 0xb8, 0x0f, 0xc7, 0xf0, 0xd3, 0x5d, 0x4e, 0x3e, 0x0e, 0x68, 0x04, 0xf7, 0xbf, 0x95, + 0x23, 0x78, 0xe0, 0x2d, 0x1d, 0xc1, 0xb0, 0xdb, 0x11, 0x3c, 0xb8, 0xab, 0x11, 0x3c, 0xb4, 0x93, + 0x11, 0x7c, 0x00, 0xe3, 0xee, 0xbf, 0xa6, 0x61, 0x5c, 0x3a, 0x21, 0x3d, 0x00, 0x33, 0xb8, 0x04, + 0xa3, 0x12, 0x43, 0xdc, 0x98, 0xa7, 0xc3, 0x40, 0x94, 0x3a, 0x75, 0x3c, 0x35, 0x8e, 0x45, 0xa5, + 0x67, 0x8f, 0x2f, 0x1b, 0xd4, 0xf2, 0x4c, 0x6f, 0x43, 0x0c, 0xbf, 0xe0, 0xf1, 0x3e, 0x9c, 0x0b, + 0xd2, 0x14, 0xbf, 0xb4, 0x80, 0x5e, 0xf2, 0xc5, 0xce, 0xee, 0xc1, 0x17, 0x7b, 0x0d, 0x4e, 0x4a, + 0x2f, 0x53, 0x6a, 0x7b, 0xab, 0xb6, 0xc3, 0xde, 0xa2, 0x57, 0x94, 0x9e, 0xf4, 0x79, 0x25, 0xd0, + 0x88, 0xe3, 0xed, 0x10, 0x53, 0xd3, 0x7d, 0x94, 0x96, 0xc8, 0xb4, 0xf8, 0x3d, 0xa9, 0xe4, 0xa7, + 0x91, 0x0b, 0x90, 0x45, 0xb9, 0xa5, 0xc2, 0x25, 0x5b, 0x11, 0x19, 0xe2, 0xc9, 0x13, 0xc1, 0xe2, + 0x9e, 0x0e, 0xb3, 0x84, 0xf0, 0x45, 0x5d, 0xce, 0xca, 0x26, 0x96, 0xf7, 0x57, 0x60, 0x48, 0x5c, + 0x5b, 0x5e, 0xa7, 0x1b, 0x41, 0x11, 0xf0, 0x73, 0x5b, 0x9b, 0x85, 0xbc, 0xb8, 0xd7, 0xc4, 0xf3, + 0xec, 0x88, 0x9b, 0x7b, 0xa4, 0x45, 0xf1, 0xbb, 0x53, 0x70, 0x41, 0xa3, 0x16, 0x5d, 0xd7, 0x97, + 0x1a, 0xf2, 0xb9, 0xba, 0xb0, 0x7c, 0xd9, 0x7c, 0x66, 0xba, 0x4d, 0xdd, 0xab, 0xaf, 0xee, 0x4b, + 0x83, 0x66, 0x60, 0x48, 0x9e, 0xa0, 0x77, 0x31, 0x79, 0x47, 0xda, 0x15, 0xbf, 0x98, 0x85, 0xbe, + 0x09, 0xdb, 0xbb, 0x66, 0x9b, 0xd6, 0xbe, 0xde, 0x27, 0x34, 0x69, 0xd3, 0xbb, 0x38, 0xb1, 0x7e, + 0x0a, 0x1f, 0x8e, 0x73, 0x85, 0x14, 0x95, 0xb9, 0x64, 0x7b, 0xb1, 0xfc, 0x72, 0x82, 0x8c, 0x75, + 0xa7, 0xc8, 0x3e, 0x92, 0x0d, 0xbb, 0x53, 0x4d, 0x5e, 0x1e, 0xe4, 0x21, 0x79, 0x1e, 0x06, 0x30, + 0xe5, 0x83, 0xe4, 0xae, 0x80, 0x01, 0x96, 0x3c, 0x49, 0x84, 0xf2, 0x8c, 0x90, 0x94, 0x7c, 0x28, + 0x52, 0x2a, 0xd2, 0x2f, 0x5a, 0xab, 0x4e, 0x92, 0x55, 0xcf, 0x69, 0xd7, 0xbd, 0x89, 0x62, 0xa7, + 0x5a, 0x91, 0x7f, 0xb6, 0x59, 0xc8, 0x71, 0x9a, 0x48, 0xd5, 0xc8, 0x67, 0xb8, 0xa7, 0x1b, 0xbe, + 0x53, 0x5f, 0x38, 0xea, 0xf1, 0x22, 0x4b, 0x79, 0xa5, 0x80, 0x50, 0x59, 0x2b, 0xfb, 0xf7, 0xb1, + 0x56, 0x4e, 0xc2, 0xf0, 0x84, 0xed, 0x49, 0xc1, 0x7c, 0x03, 0x61, 0xe2, 0x41, 0x26, 0xf9, 0xe4, + 0x48, 0xbe, 0x68, 0x1b, 0x72, 0x09, 0x7a, 0xb1, 0x0a, 0x81, 0x98, 0xe2, 0xf1, 0x3c, 0x11, 0xcb, + 0x14, 0xc8, 0x39, 0x44, 0x78, 0x55, 0x8d, 0x6f, 0xe9, 0x85, 0x21, 0xbf, 0xe5, 0x21, 0xa9, 0xd9, + 0x93, 0x90, 0x9b, 0xb5, 0xa5, 0xa2, 0xfe, 0x18, 0x2b, 0xb8, 0x6a, 0xbb, 0x4a, 0x10, 0xa4, 0x20, + 0x62, 0x1d, 0x74, 0xc3, 0x36, 0xe4, 0x48, 0x57, 0xec, 0x20, 0xcb, 0x36, 0x62, 0xb9, 0x2d, 0x03, + 0x42, 0x36, 0x1f, 0x61, 0x90, 0xb0, 0xe4, 0x14, 0xa3, 0x04, 0x06, 0x23, 0x5e, 0x52, 0xe0, 0xdc, + 0x6e, 0x15, 0xb8, 0x6f, 0xaf, 0x0a, 0xdc, 0x7f, 0xb0, 0x0a, 0xfc, 0x01, 0x18, 0xc2, 0x27, 0x4d, + 0xdf, 0x6d, 0x99, 0x0e, 0x75, 0x77, 0x60, 0x44, 0x3c, 0x20, 0x8c, 0x88, 0x61, 0xfe, 0xde, 0x94, + 0x37, 0x44, 0xdb, 0x21, 0xc2, 0x4a, 0x51, 0x73, 0xd8, 0x87, 0x9a, 0x9f, 0x84, 0x5e, 0x5e, 0xea, + 0x65, 0x90, 0x2f, 0xe7, 0xf8, 0x23, 0xac, 0x2f, 0x33, 0x24, 0xd5, 0x97, 0x29, 0xfe, 0x5a, 0x0a, + 0xfa, 0x6e, 0x5a, 0x6b, 0x96, 0xbd, 0xbe, 0x3f, 0xed, 0x7c, 0x06, 0x06, 0x05, 0x1b, 0x69, 0x49, + 0x47, 0x47, 0x97, 0x36, 0x07, 0xd7, 0x90, 0x93, 0x26, 0x53, 0x91, 0x97, 0x82, 0x46, 0x93, 0x41, + 0xf1, 0x1a, 0x7e, 0xb5, 0xe0, 0x37, 0xaa, 0x47, 0xab, 0xe0, 0xcb, 0xe4, 0xe4, 0x1c, 0x64, 0xa7, + 0xd8, 0xab, 0x4a, 0x89, 0x83, 0x78, 0xfc, 0x3d, 0x83, 0x16, 0x3f, 0x91, 0x85, 0x11, 0xe5, 0xd2, + 0xe1, 0x9d, 0x30, 0x20, 0x0e, 0xfd, 0x4d, 0xbf, 0x2c, 0x3f, 0x7a, 0xba, 0x04, 0x40, 0xad, 0x9f, + 0xff, 0x59, 0x36, 0xc8, 0xfb, 0xa1, 0xcf, 0x76, 0xd1, 0x12, 0xc1, 0x6f, 0x19, 0x09, 0x87, 0xdb, + 0x42, 0x95, 0xbd, 0x3b, 0x1f, 0x48, 0x82, 0x44, 0xd6, 0x5e, 0xdb, 0xc5, 0x4f, 0x7b, 0x16, 0x06, + 0x74, 0xd7, 0xa5, 0x5e, 0xcd, 0xd3, 0x57, 0xe4, 0x4a, 0xfd, 0x01, 0x50, 0x1e, 0x49, 0x08, 0x5c, + 0xd4, 0x57, 0xc8, 0x2b, 0x30, 0x5c, 0x77, 0x28, 0xda, 0x2a, 0x7a, 0x23, 0xcc, 0x92, 0x21, 0xbc, + 0x7a, 0x24, 0x84, 0xbc, 0x06, 0x87, 0x88, 0xb2, 0x41, 0x6e, 0xc1, 0xb0, 0xf8, 0x1c, 0x1e, 0xd0, + 0x8b, 0x83, 0x72, 0x24, 0x5c, 0x1d, 0xb9, 0x48, 0x78, 0x48, 0xaf, 0x88, 0xeb, 0x96, 0xc9, 0x65, + 0xbe, 0x86, 0x44, 0x4a, 0x16, 0x80, 0xac, 0xd3, 0x25, 0xb4, 0x59, 0xd8, 0xb3, 0x78, 0xa1, 0x69, + 0x1c, 0xc7, 0xfd, 0x3c, 0x18, 0x3a, 0x8e, 0x95, 0x63, 0xc4, 0xd7, 0xe9, 0x52, 0x29, 0x82, 0x24, + 0xb7, 0xe1, 0x54, 0xbc, 0x09, 0xfb, 0x64, 0xee, 0xf6, 0xf1, 0xe8, 0xd6, 0x66, 0xa1, 0x90, 0x48, + 0x20, 0xb1, 0x3d, 0x11, 0x63, 0x5b, 0x36, 0xae, 0x65, 0xfb, 0xfb, 0xc6, 0xfa, 0xb5, 0x11, 0xd6, + 0xd6, 0xb7, 0xdb, 0x4d, 0xa3, 0xf8, 0xb3, 0x29, 0x66, 0x9f, 0xb3, 0x0f, 0x9a, 0x66, 0x82, 0xd8, + 0x57, 0x02, 0x8b, 0x0b, 0x41, 0x9e, 0xab, 0xc4, 0x99, 0x38, 0xc8, 0x6c, 0x75, 0x19, 0x72, 0x86, + 0x7c, 0xd9, 0x70, 0x3a, 0xda, 0x09, 0xfe, 0x73, 0x34, 0x41, 0x45, 0x2e, 0x8a, 0xa4, 0x47, 0xd9, + 0xce, 0x06, 0x0d, 0x4f, 0x73, 0x54, 0xfc, 0xeb, 0x69, 0x18, 0x92, 0xbe, 0xe6, 0xea, 0xbe, 0x3e, + 0xe7, 0xbd, 0x3b, 0x7b, 0x4d, 0xdf, 0xd9, 0x1c, 0x8f, 0xa0, 0xfc, 0x57, 0x0e, 0x53, 0x7e, 0xf5, + 0xee, 0x22, 0xe5, 0x97, 0x9f, 0xdd, 0x29, 0xb7, 0xbb, 0xec, 0x4e, 0xd7, 0xb2, 0xfd, 0xe9, 0xb1, + 0xcc, 0xb5, 0x6c, 0x7f, 0x76, 0xac, 0x17, 0x0b, 0x4a, 0x60, 0x11, 0x5a, 0x7e, 0xa4, 0x69, 0x2d, + 0x9b, 0x2b, 0x47, 0x3c, 0xa4, 0xfc, 0x60, 0x8b, 0x6d, 0x28, 0xb2, 0x39, 0xe2, 0xf1, 0xe5, 0x6f, + 0xa9, 0x6c, 0x8e, 0x76, 0xb0, 0xf9, 0x01, 0xca, 0xe6, 0xd7, 0x53, 0x90, 0x4f, 0x94, 0x4d, 0xe9, + 0x90, 0xfc, 0x8f, 0x0f, 0x2c, 0x57, 0x7e, 0xf1, 0x77, 0xd2, 0x30, 0x5e, 0xb6, 0x3c, 0xba, 0xc2, + 0x37, 0xa2, 0x47, 0x7c, 0xaa, 0xb8, 0x0e, 0x83, 0xd2, 0xc7, 0x88, 0x3e, 0x0f, 0xea, 0xaa, 0x49, + 0xa8, 0x4e, 0x75, 0xd5, 0x24, 0x92, 0x83, 0x0b, 0xd0, 0x57, 0x85, 0x7c, 0xc4, 0xe7, 0x9c, 0xa3, + 0x21, 0xe4, 0x23, 0x3e, 0x79, 0xdd, 0xa3, 0x42, 0xfe, 0x74, 0x06, 0x4e, 0x24, 0x3c, 0x9c, 0x5c, + 0x08, 0xd3, 0xf3, 0xa7, 0xc2, 0x48, 0x3d, 0x3f, 0x3d, 0x7f, 0x90, 0x94, 0x9f, 0x2c, 0x62, 0xce, + 0xad, 0x85, 0xf2, 0xd4, 0xa4, 0x90, 0x6a, 0x51, 0x4a, 0xea, 0xca, 0xc0, 0x49, 0x5f, 0x16, 0xe4, + 0xe5, 0xb2, 0x4d, 0xa3, 0xae, 0xe4, 0xe5, 0x62, 0x6d, 0xc8, 0x97, 0xc0, 0x40, 0xe9, 0xcd, 0xb6, + 0x43, 0x91, 0x6f, 0x26, 0xea, 0x5a, 0x1b, 0x20, 0x92, 0x38, 0xf3, 0x14, 0x63, 0x8c, 0x42, 0xe5, + 0x1d, 0x32, 0x24, 0x0b, 0x90, 0x7b, 0xd5, 0xf4, 0x66, 0xdb, 0x4b, 0xa2, 0x17, 0x82, 0x1a, 0x13, + 0x1c, 0x9a, 0xc4, 0x17, 0x77, 0xf0, 0xbc, 0x56, 0x9e, 0xbc, 0x07, 0xe2, 0x0d, 0xc8, 0x1c, 0xf4, + 0x96, 0x6e, 0x57, 0xb5, 0x92, 0xe8, 0x89, 0x87, 0xe5, 0xbc, 0xb6, 0xa5, 0x8e, 0xec, 0x30, 0x1d, + 0x94, 0x2e, 0x1f, 0xa6, 0x20, 0x7d, 0xf1, 0x93, 0x29, 0x38, 0xdb, 0x59, 0x78, 0xe4, 0x29, 0xe8, + 0xd3, 0xec, 0x06, 0x2d, 0x69, 0x37, 0x44, 0xcf, 0xa0, 0x34, 0xd9, 0x26, 0xb7, 0xa6, 0x3b, 0xf2, + 0x5e, 0xc4, 0x27, 0x23, 0xef, 0x83, 0xc1, 0xb2, 0xeb, 0xb6, 0xa9, 0x53, 0x7d, 0xe6, 0xa6, 0x56, + 0x16, 0x5b, 0x56, 0xdc, 0x12, 0x99, 0x08, 0xae, 0xb9, 0xcf, 0x28, 0x05, 0x2c, 0x64, 0xfa, 0xe2, + 0x57, 0xa7, 0xe0, 0x5c, 0x37, 0xa1, 0x93, 0x67, 0xa0, 0x7f, 0x91, 0x5a, 0xba, 0xe5, 0x95, 0xa7, + 0xc4, 0x2b, 0xe1, 0x0e, 0xd0, 0x43, 0x58, 0x74, 0x23, 0x13, 0x10, 0xb2, 0x46, 0xfc, 0xac, 0x39, + 0xf0, 0x71, 0xe3, 0xe7, 0xe2, 0x08, 0x53, 0x1a, 0xf9, 0x84, 0xc5, 0x0f, 0xc1, 0x03, 0x1d, 0xfb, + 0x88, 0xbc, 0x1f, 0x86, 0x16, 0x9c, 0x15, 0xdd, 0x32, 0xdf, 0xe4, 0x43, 0x2c, 0x15, 0xee, 0xb2, + 0x6d, 0x09, 0x2e, 0xef, 0xfc, 0x64, 0xfa, 0xe2, 0x12, 0xe4, 0x3b, 0x75, 0x18, 0x99, 0x81, 0x11, + 0x2c, 0x71, 0x58, 0xb2, 0xea, 0xab, 0xb6, 0x13, 0xca, 0x1e, 0x13, 0x22, 0x78, 0x0c, 0x53, 0xd3, + 0x11, 0xa5, 0xf4, 0x81, 0xd2, 0xaa, 0xf8, 0x0b, 0x69, 0x18, 0xaa, 0x34, 0xda, 0x2b, 0xe6, 0x41, + 0x54, 0x9e, 0xdd, 0x6b, 0xee, 0x58, 0xb9, 0x1a, 0x68, 0x66, 0x8f, 0xd5, 0x40, 0x5f, 0x82, 0x5c, + 0x0b, 0xbf, 0x43, 0xbd, 0x5e, 0xe0, 0x5f, 0xd7, 0xe9, 0x7a, 0x81, 0xb7, 0x61, 0xf3, 0x57, 0x7d, + 0x1f, 0xf3, 0x57, 0xd8, 0x56, 0x12, 0xe8, 0x01, 0x14, 0x9e, 0x3d, 0x16, 0xa8, 0x2c, 0xd0, 0x03, + 0xa8, 0x3b, 0x7b, 0x2c, 0x50, 0xab, 0xf8, 0x4f, 0xd3, 0x30, 0x12, 0x7d, 0x24, 0x79, 0x0a, 0x06, + 0xf9, 0x63, 0xf8, 0xb9, 0x5b, 0x4a, 0x0a, 0x96, 0x0c, 0xc1, 0x1a, 0xf0, 0x1f, 0xe2, 0x00, 0x71, + 0x74, 0x55, 0x77, 0x6b, 0xe1, 0x09, 0x18, 0x77, 0x2b, 0xea, 0xe7, 0xa7, 0xf2, 0x0a, 0x4a, 0x1b, + 0x59, 0xd5, 0xdd, 0xc9, 0xf0, 0x37, 0x99, 0x06, 0xe2, 0xd0, 0xb6, 0x4b, 0xa3, 0x0c, 0xb8, 0xbb, + 0x2f, 0x5f, 0x3d, 0x62, 0x58, 0x6d, 0x9c, 0xc3, 0x64, 0x36, 0x1f, 0x0e, 0x5e, 0x1b, 0x95, 0xa1, + 0xb7, 0xfb, 0x89, 0xf3, 0xa3, 0x5b, 0x9b, 0x85, 0x53, 0x12, 0x7d, 0xf2, 0x91, 0x33, 0x27, 0x98, + 0xd2, 0x3d, 0x9d, 0x1f, 0x7a, 0xf8, 0x1d, 0x50, 0xfc, 0xe6, 0x7f, 0x98, 0x82, 0xde, 0x05, 0x8b, + 0x2e, 0x2c, 0x93, 0xa7, 0x61, 0x80, 0x69, 0xcc, 0x9c, 0xcd, 0x3a, 0x33, 0x25, 0xfc, 0xfa, 0x24, + 0x55, 0x42, 0xc4, 0x6c, 0x8f, 0x16, 0x52, 0x91, 0x67, 0x01, 0xc2, 0xdc, 0x19, 0x42, 0xfd, 0x88, + 0xdc, 0x86, 0x63, 0x66, 0x7b, 0x34, 0x89, 0xce, 0x6f, 0x25, 0x32, 0x0f, 0x64, 0xe2, 0xad, 0x38, + 0xc6, 0x6f, 0x25, 0x06, 0xc8, 0x1c, 0x10, 0xf6, 0xab, 0xa2, 0xbb, 0xee, 0xba, 0xed, 0x18, 0x93, + 0xab, 0xba, 0xb5, 0x42, 0xd5, 0xed, 0x69, 0x9c, 0x62, 0xb6, 0x47, 0x4b, 0x68, 0x47, 0xde, 0x0b, + 0x43, 0x72, 0x54, 0x85, 0xea, 0x8c, 0x28, 0xe3, 0x66, 0x7b, 0xb4, 0x08, 0x2d, 0x79, 0x37, 0x0c, + 0x8a, 0xdf, 0xd7, 0x6c, 0xe1, 0xe9, 0x24, 0x65, 0x2d, 0x96, 0x50, 0xb3, 0x3d, 0x9a, 0x4c, 0x29, + 0x3d, 0xb4, 0xe2, 0x98, 0x96, 0x27, 0xfc, 0xe8, 0xd5, 0x87, 0x22, 0x4e, 0x7a, 0x28, 0xfe, 0x26, + 0xef, 0x83, 0xe1, 0xa0, 0xe6, 0x01, 0xc6, 0x89, 0xf2, 0x9b, 0x88, 0x53, 0x4a, 0x63, 0x8e, 0x9c, + 0xed, 0xd1, 0xa2, 0xd4, 0xe4, 0x22, 0xe4, 0x34, 0xea, 0x9a, 0x6f, 0xfa, 0x7e, 0x0a, 0x23, 0xd2, + 0x40, 0x37, 0xdf, 0x64, 0x52, 0x12, 0x78, 0xd6, 0x3b, 0xa1, 0xd7, 0x85, 0xb8, 0x37, 0x20, 0xca, + 0x53, 0xa6, 0x2d, 0x83, 0xf5, 0x8e, 0xe4, 0x35, 0xf3, 0x4a, 0x58, 0x49, 0x82, 0xa7, 0x84, 0xc2, + 0xcb, 0x82, 0x48, 0x6e, 0x37, 0x19, 0x3b, 0xdb, 0xa3, 0x29, 0xf4, 0x92, 0x54, 0xa7, 0x4c, 0x77, + 0x4d, 0xd4, 0x25, 0x53, 0xa5, 0xca, 0x50, 0x92, 0x54, 0xd9, 0x4f, 0xe9, 0xd1, 0x37, 0xa8, 0xb7, + 0x6e, 0x3b, 0x6b, 0xa2, 0x0a, 0x99, 0xfa, 0x68, 0x81, 0x95, 0x1e, 0x2d, 0x20, 0xf2, 0xa3, 0xc3, + 0xd8, 0xb5, 0xd8, 0xa3, 0x75, 0x4f, 0x97, 0x1f, 0xcd, 0x8f, 0x3a, 0xfd, 0x4e, 0x9a, 0xa3, 0xfa, + 0x1d, 0x3f, 0x3c, 0x4d, 0xed, 0x50, 0xc4, 0x49, 0x1d, 0x8a, 0xbf, 0xd9, 0x43, 0x2b, 0xb6, 0xe3, + 0xcd, 0xd8, 0xce, 0xba, 0xee, 0x18, 0xf9, 0xb1, 0xe8, 0x43, 0x25, 0x14, 0x7b, 0xa8, 0xf4, 0x93, + 0x75, 0xd0, 0xeb, 0x4f, 0x3f, 0xed, 0xb7, 0x1b, 0x8f, 0x76, 0x50, 0x88, 0x61, 0x1d, 0x14, 0xfe, + 0x22, 0x05, 0xc8, 0x54, 0x27, 0x2b, 0x79, 0x82, 0xe4, 0x83, 0xc1, 0x1b, 0x4e, 0x56, 0x66, 0x7b, + 0x34, 0x86, 0x21, 0x45, 0xc8, 0x4e, 0xdf, 0xa5, 0xf5, 0xfc, 0x09, 0xa4, 0x18, 0xf2, 0x29, 0x18, + 0x6c, 0xb6, 0x47, 0x43, 0x1c, 0x9b, 0x22, 0xaa, 0xed, 0x25, 0x17, 0x0b, 0x62, 0xe7, 0x4f, 0x46, + 0xa7, 0x88, 0x00, 0xc1, 0xa6, 0x88, 0xe0, 0x07, 0x99, 0x81, 0x31, 0x6e, 0x57, 0xe2, 0x41, 0x10, + 0x4e, 0xd7, 0xf9, 0x53, 0xd1, 0xb5, 0x46, 0xc5, 0xcf, 0xf6, 0x68, 0xb1, 0x36, 0x4c, 0x5c, 0xa5, + 0xb6, 0xb7, 0x5a, 0xf2, 0x70, 0x72, 0xcb, 0x9f, 0x56, 0x52, 0x85, 0x87, 0x28, 0x26, 0x2e, 0xe9, + 0x27, 0x59, 0x80, 0x13, 0xa5, 0x7a, 0x9d, 0xba, 0xae, 0x48, 0x9d, 0x2c, 0x26, 0xab, 0x33, 0xd1, + 0x9d, 0x61, 0x02, 0xc9, 0x6c, 0x8f, 0x96, 0xd4, 0x92, 0x4c, 0xc2, 0x28, 0x9b, 0x50, 0xa4, 0xea, + 0xdb, 0xf9, 0x7c, 0xd4, 0x69, 0x4b, 0x41, 0xcf, 0xf6, 0x68, 0x6a, 0x0b, 0xd6, 0x89, 0x6c, 0xdf, + 0x20, 0xda, 0x3f, 0x10, 0xed, 0xc4, 0x10, 0xc3, 0x3a, 0x31, 0xfc, 0xe5, 0xb7, 0x12, 0x33, 0xe7, + 0xd9, 0x78, 0xab, 0x70, 0xe6, 0x0c, 0x7f, 0x11, 0x0d, 0x4e, 0x46, 0x0b, 0x87, 0x8b, 0xa7, 0x3e, + 0x18, 0xf5, 0x32, 0x49, 0xa2, 0x99, 0xed, 0xd1, 0x12, 0xdb, 0xc6, 0x79, 0x8a, 0x77, 0x3a, 0xd7, + 0x8d, 0x67, 0xf0, 0x76, 0x89, 0x6d, 0x89, 0xde, 0xa5, 0xc0, 0x79, 0xfe, 0x7c, 0x74, 0x0f, 0xd9, + 0x91, 0x70, 0xb6, 0x47, 0xeb, 0x52, 0x26, 0xfd, 0x66, 0x87, 0x6a, 0xe3, 0xf9, 0x87, 0x90, 0xfd, + 0x79, 0x69, 0x8b, 0x1a, 0x27, 0x9a, 0xed, 0xd1, 0x3a, 0xd4, 0x2a, 0xbf, 0xd9, 0xa1, 0x18, 0x75, + 0xbe, 0xd0, 0x95, 0x6d, 0x20, 0x8f, 0x0e, 0xa5, 0xac, 0x17, 0x12, 0xeb, 0x38, 0xe7, 0x1f, 0x8e, + 0xaa, 0x6e, 0x02, 0x09, 0x53, 0xdd, 0xa4, 0x0a, 0xd0, 0x0b, 0x89, 0x85, 0x87, 0xf3, 0x8f, 0x74, + 0x61, 0x18, 0xbc, 0x63, 0x62, 0xc9, 0xe2, 0x85, 0xc4, 0xca, 0xbf, 0xf9, 0x62, 0x94, 0x61, 0x02, + 0x09, 0x63, 0x98, 0x54, 0x33, 0x78, 0x21, 0xb1, 0x40, 0x6c, 0xfe, 0xd1, 0x2e, 0x0c, 0xc3, 0x37, + 0x4c, 0x2a, 0x2d, 0xfb, 0xee, 0x48, 0x85, 0xd6, 0xfc, 0x63, 0xd1, 0x79, 0x43, 0x42, 0xb1, 0x79, + 0x43, 0xae, 0xe5, 0x3a, 0x19, 0x2b, 0x06, 0x97, 0x7f, 0x3c, 0x3a, 0xcc, 0x15, 0x34, 0x1b, 0xe6, + 0x6a, 0xf9, 0xb8, 0xc9, 0x58, 0xc1, 0xaa, 0xfc, 0x85, 0x4e, 0x4c, 0x10, 0x1d, 0x65, 0xc2, 0x93, + 0xe3, 0x97, 0x13, 0x2a, 0x1e, 0xe5, 0xdf, 0x11, 0x8d, 0x37, 0x8a, 0x11, 0xcc, 0xf6, 0x68, 0x09, + 0x75, 0x92, 0xb4, 0xe4, 0x3c, 0xb2, 0xf9, 0x8b, 0xd1, 0x61, 0x9b, 0x44, 0xc3, 0x86, 0x6d, 0x62, + 0x0e, 0xda, 0xb9, 0xa4, 0x18, 0xcf, 0xfc, 0xa5, 0xa8, 0x61, 0x16, 0xa7, 0x60, 0x86, 0x59, 0x42, + 0x6c, 0xa8, 0x96, 0x9c, 0x3c, 0x34, 0xff, 0xce, 0xae, 0x6f, 0x88, 0x34, 0x09, 0x6f, 0xc8, 0x73, + 0x69, 0x86, 0xb6, 0xd3, 0xcd, 0x56, 0xc3, 0xd6, 0x8d, 0xfc, 0xbb, 0x12, 0x6d, 0x27, 0x8e, 0x94, + 0x6c, 0x27, 0x0e, 0x60, 0xab, 0xbc, 0x1c, 0x7b, 0x97, 0x7f, 0x22, 0xba, 0xca, 0xcb, 0x38, 0xb6, + 0xca, 0x47, 0xe2, 0xf4, 0x26, 0x63, 0x71, 0x6a, 0xf9, 0x27, 0xa3, 0x0a, 0xa0, 0xa0, 0x99, 0x02, + 0xa8, 0x91, 0x6d, 0x1f, 0xe9, 0x1c, 0xd9, 0x95, 0xbf, 0x1c, 0x3d, 0x0b, 0xeb, 0x44, 0x37, 0xdb, + 0xa3, 0x75, 0x8e, 0x0e, 0x2b, 0x27, 0x04, 0x6a, 0xe5, 0xaf, 0x44, 0x15, 0x2c, 0x46, 0xc0, 0x14, + 0x2c, 0x1e, 0xde, 0x55, 0x4e, 0x88, 0xb4, 0xca, 0x3f, 0xd5, 0x91, 0x55, 0xf0, 0xcd, 0x09, 0xf1, + 0x59, 0xcf, 0xca, 0xa1, 0x52, 0xf9, 0xa7, 0xa3, 0x8b, 0x5d, 0x88, 0x61, 0x8b, 0x9d, 0x14, 0x52, + 0xf5, 0xac, 0x1c, 0x24, 0x94, 0xbf, 0x1a, 0x6f, 0x15, 0x2e, 0x91, 0x52, 0x30, 0x91, 0x96, 0x1c, + 0x5b, 0x93, 0x7f, 0x26, 0xaa, 0x75, 0x49, 0x34, 0x4c, 0xeb, 0x12, 0xe3, 0x72, 0x66, 0xe2, 0x21, + 0x32, 0xf9, 0x67, 0xd5, 0x5d, 0x76, 0x14, 0xcf, 0x2c, 0x9f, 0x58, 0x58, 0xcd, 0x2b, 0x6a, 0x92, + 0xf3, 0xfc, 0x73, 0xca, 0xbd, 0x7a, 0x04, 0xcb, 0xec, 0x5b, 0x25, 0x29, 0xfa, 0x2b, 0x6a, 0x5e, + 0xf0, 0xfc, 0xf3, 0xc9, 0x1c, 0x02, 0x5d, 0x51, 0xf3, 0x88, 0xbf, 0xa2, 0xa6, 0xd2, 0xce, 0xbf, + 0x3b, 0x99, 0x43, 0x20, 0x5d, 0x35, 0xf5, 0xf6, 0xd3, 0x52, 0x5d, 0xd8, 0xfc, 0x7b, 0xa2, 0xa6, + 0x63, 0x80, 0x60, 0xa6, 0x63, 0x58, 0x3d, 0xf6, 0x69, 0xa9, 0x9e, 0x6a, 0xfe, 0x85, 0x58, 0x93, + 0xe0, 0x65, 0xa5, 0xaa, 0xab, 0x4f, 0x4b, 0x75, 0x48, 0xf3, 0xef, 0x8d, 0x35, 0x09, 0xde, 0x4e, + 0xaa, 0x56, 0x6a, 0x74, 0x4b, 0xfd, 0x92, 0x7f, 0x31, 0x31, 0x8f, 0x45, 0x02, 0xe5, 0x6c, 0x8f, + 0xd6, 0x2d, 0x85, 0xcc, 0x47, 0x3a, 0x07, 0x1c, 0xe5, 0x5f, 0x8a, 0x0e, 0xe1, 0x4e, 0x74, 0x6c, + 0x08, 0x77, 0x0c, 0x5a, 0x7a, 0x9f, 0x92, 0xa4, 0x2f, 0xff, 0xbe, 0xe8, 0x14, 0x17, 0x41, 0xb2, + 0x29, 0x4e, 0x4d, 0xe9, 0x17, 0xc9, 0x3e, 0x97, 0x7f, 0x7f, 0x74, 0x8a, 0x93, 0x71, 0x6c, 0x8a, + 0x8b, 0x64, 0xaa, 0x9b, 0x8c, 0x25, 0x45, 0xcb, 0xbf, 0x1c, 0x9d, 0xe2, 0x14, 0x34, 0x9b, 0xe2, + 0xd4, 0x34, 0x6a, 0xef, 0x53, 0x72, 0x83, 0xe5, 0x5f, 0x49, 0x7e, 0x7f, 0x44, 0xca, 0xef, 0xcf, + 0x33, 0x89, 0x69, 0xc9, 0x49, 0xae, 0xf2, 0xa5, 0xe8, 0xf8, 0x4d, 0xa2, 0x61, 0xe3, 0x37, 0x31, + 0x41, 0x96, 0xba, 0x71, 0x10, 0x5a, 0x35, 0xd1, 0x65, 0xe3, 0x10, 0x9a, 0x22, 0x09, 0xe0, 0xc8, + 0x1e, 0x99, 0x6f, 0x84, 0x26, 0x3b, 0xec, 0x91, 0xfd, 0x6d, 0x90, 0x42, 0xcf, 0x66, 0xd7, 0x98, + 0xf7, 0x7c, 0x7e, 0x2a, 0x3a, 0xbb, 0xc6, 0x08, 0xd8, 0xec, 0x1a, 0xf7, 0xb9, 0x9f, 0x81, 0x31, + 0xa1, 0x45, 0x3c, 0x28, 0xc0, 0xb4, 0x56, 0xf2, 0xd3, 0x4a, 0x5a, 0x03, 0x05, 0xcf, 0x66, 0x27, + 0x15, 0x86, 0xeb, 0x35, 0x87, 0x4d, 0x36, 0xcc, 0xd6, 0x92, 0xad, 0x3b, 0x46, 0x95, 0x5a, 0x46, + 0x7e, 0x46, 0x59, 0xaf, 0x13, 0x68, 0x70, 0xbd, 0x4e, 0x80, 0x63, 0xee, 0x6b, 0x05, 0xae, 0xf1, + 0x92, 0x42, 0xf9, 0x57, 0x91, 0x6d, 0xa1, 0x13, 0x5b, 0x41, 0x36, 0xdb, 0xa3, 0x75, 0xe2, 0xc0, + 0x6c, 0xf5, 0xf9, 0x8d, 0xea, 0x6b, 0x73, 0x41, 0x5e, 0xb5, 0x8a, 0x43, 0x5b, 0xba, 0x43, 0xf3, + 0xb3, 0x51, 0x5b, 0x3d, 0x91, 0x88, 0xd9, 0xea, 0x89, 0x88, 0x38, 0x5b, 0x7f, 0x2c, 0x94, 0xbb, + 0xb1, 0x0d, 0x47, 0x44, 0x72, 0x6b, 0x36, 0x3b, 0x45, 0x11, 0x4c, 0x40, 0x73, 0xb6, 0xb5, 0x82, + 0x27, 0x15, 0xd7, 0xa2, 0xb3, 0x53, 0x67, 0x4a, 0x36, 0x3b, 0x75, 0xc6, 0x32, 0x55, 0x8f, 0x62, + 0xf9, 0x18, 0xbc, 0x1e, 0x55, 0xf5, 0x04, 0x12, 0xa6, 0xea, 0x09, 0xe0, 0x38, 0x43, 0x8d, 0xba, + 0xd4, 0xcb, 0xcf, 0x75, 0x63, 0x88, 0x24, 0x71, 0x86, 0x08, 0x8e, 0x33, 0x9c, 0xa1, 0x5e, 0x7d, + 0x35, 0x3f, 0xdf, 0x8d, 0x21, 0x92, 0xc4, 0x19, 0x22, 0x98, 0x6d, 0x36, 0xa3, 0xe0, 0x89, 0x76, + 0x63, 0xcd, 0xef, 0xb3, 0x1b, 0xd1, 0xcd, 0x66, 0x47, 0x42, 0xb6, 0xd9, 0xec, 0x88, 0x24, 0x5f, + 0xbd, 0xe3, 0xf8, 0x85, 0xfc, 0x02, 0x3e, 0xf0, 0x72, 0x68, 0x17, 0xec, 0xa4, 0xd5, 0x6c, 0x8f, + 0xb6, 0xd3, 0xf8, 0x88, 0x77, 0x05, 0x5e, 0xb9, 0xf9, 0x0a, 0x3e, 0x6a, 0x34, 0x38, 0xab, 0xe0, + 0xe0, 0xd9, 0x1e, 0x2d, 0xf0, 0xdb, 0x7d, 0x37, 0x0c, 0xe2, 0x47, 0x95, 0x2d, 0xd3, 0x9b, 0x9a, + 0xc8, 0xbf, 0x16, 0xdd, 0x32, 0x49, 0x28, 0xb6, 0x65, 0x92, 0x7e, 0xb2, 0x49, 0x1c, 0x7f, 0xf2, + 0x29, 0x66, 0x6a, 0x22, 0xaf, 0x45, 0x27, 0xf1, 0x08, 0x92, 0x4d, 0xe2, 0x11, 0x40, 0xf0, 0xdc, + 0x29, 0xc7, 0x6e, 0x4d, 0x4d, 0xe4, 0xab, 0x09, 0xcf, 0xe5, 0xa8, 0xe0, 0xb9, 0xfc, 0x67, 0xf0, + 0xdc, 0xea, 0x6a, 0xdb, 0x9b, 0x62, 0xdf, 0xb8, 0x98, 0xf0, 0x5c, 0x1f, 0x19, 0x3c, 0xd7, 0x07, + 0xb0, 0xa9, 0x10, 0x01, 0x15, 0xc7, 0x66, 0x93, 0xf6, 0x75, 0xb3, 0xd1, 0xc8, 0xdf, 0x8c, 0x4e, + 0x85, 0x2a, 0x9e, 0x4d, 0x85, 0x2a, 0x8c, 0x99, 0x9e, 0xfc, 0xad, 0xe8, 0x52, 0x7b, 0x25, 0x7f, + 0x2b, 0x6a, 0x7a, 0x86, 0x18, 0x66, 0x7a, 0x86, 0xbf, 0x70, 0x77, 0xc1, 0x7e, 0x69, 0x74, 0xd9, + 0xa1, 0xee, 0x6a, 0xfe, 0xb6, 0xb2, 0xbb, 0x90, 0x70, 0xb8, 0xbb, 0x90, 0x7e, 0x93, 0x15, 0x78, + 0x30, 0xb2, 0xd0, 0xf8, 0xb7, 0x36, 0x55, 0xaa, 0x3b, 0xf5, 0xd5, 0xfc, 0xeb, 0xc8, 0xea, 0xd1, + 0xc4, 0xa5, 0x2a, 0x4a, 0x3a, 0xdb, 0xa3, 0x75, 0xe3, 0x84, 0xdb, 0xf2, 0xd7, 0xe6, 0x78, 0x05, + 0x0e, 0xad, 0x32, 0xe9, 0x6f, 0x42, 0x3f, 0xa0, 0x6c, 0xcb, 0xe3, 0x24, 0xb8, 0x2d, 0x8f, 0x83, + 0x49, 0x0b, 0x1e, 0x52, 0xb6, 0x6a, 0xf3, 0x7a, 0x83, 0xed, 0x4b, 0xa8, 0x51, 0xd1, 0xeb, 0x6b, + 0xd4, 0xcb, 0x7f, 0x10, 0x79, 0x5f, 0xe8, 0xb0, 0xe1, 0x53, 0xa8, 0x67, 0x7b, 0xb4, 0x6d, 0xf8, + 0x91, 0x22, 0x64, 0xab, 0x33, 0x8b, 0x95, 0xfc, 0x87, 0xa2, 0xe7, 0x9b, 0x0c, 0x36, 0xdb, 0xa3, + 0x21, 0x8e, 0x59, 0x69, 0x37, 0x5b, 0x2b, 0x8e, 0x6e, 0x50, 0x6e, 0x68, 0xa1, 0xed, 0x26, 0x0c, + 0xd0, 0x2f, 0x89, 0x5a, 0x69, 0x9d, 0xe8, 0x98, 0x95, 0xd6, 0x09, 0xc7, 0x14, 0x35, 0x52, 0x43, + 0x3c, 0xff, 0xe1, 0xa8, 0xa2, 0x46, 0x90, 0x4c, 0x51, 0xa3, 0x15, 0xc7, 0x5f, 0x87, 0xd3, 0xc1, + 0x7e, 0x5e, 0xac, 0xbf, 0xbc, 0xd3, 0xf2, 0x1f, 0x41, 0x3e, 0x0f, 0xc5, 0x2e, 0x03, 0x22, 0x54, + 0xb3, 0x3d, 0x5a, 0x87, 0xf6, 0x6c, 0xc5, 0xbd, 0xde, 0x5e, 0xa2, 0x8e, 0x45, 0x3d, 0x66, 0x52, + 0xc9, 0xa7, 0x84, 0x5f, 0x1a, 0x5d, 0x71, 0x3b, 0x90, 0xb1, 0x15, 0xb7, 0x03, 0x2a, 0x91, 0xb9, + 0x10, 0xaa, 0xbe, 0x0d, 0xf3, 0x40, 0xa6, 0x9d, 0x38, 0x24, 0x32, 0x17, 0x96, 0xda, 0xd2, 0x36, + 0xcc, 0x03, 0x6b, 0xad, 0x13, 0x07, 0x72, 0x11, 0x72, 0xd5, 0xea, 0xbc, 0xd6, 0xb6, 0xf2, 0x75, + 0xc5, 0x1d, 0x19, 0xa1, 0xb3, 0x3d, 0x9a, 0xc0, 0x33, 0x33, 0x68, 0xba, 0xa1, 0xbb, 0x9e, 0x59, + 0x77, 0x71, 0xc4, 0xf8, 0x23, 0xc4, 0x88, 0x9a, 0x41, 0x49, 0x34, 0xcc, 0x0c, 0x4a, 0x82, 0x33, + 0x7b, 0x71, 0x52, 0x77, 0x5d, 0xdd, 0x32, 0x1c, 0x7d, 0x02, 0x97, 0x09, 0xaa, 0xc4, 0x18, 0x46, + 0xb0, 0xcc, 0x5e, 0x8c, 0x42, 0xf0, 0xf0, 0xdd, 0x87, 0xf8, 0x66, 0xce, 0xb2, 0x72, 0xf8, 0xae, + 0xe0, 0xf1, 0xf0, 0x5d, 0x81, 0xa1, 0xdd, 0xe9, 0xc3, 0x34, 0xba, 0x62, 0x32, 0x11, 0xe5, 0x57, + 0x14, 0xbb, 0x53, 0x25, 0x40, 0xbb, 0x53, 0x05, 0x46, 0x5e, 0xc9, 0x5f, 0x6e, 0x57, 0x3b, 0xbc, + 0x52, 0xb8, 0xca, 0xc6, 0xda, 0xb0, 0xf5, 0x3b, 0x1c, 0x1c, 0x53, 0x1b, 0x96, 0xde, 0xb4, 0xa7, + 0x26, 0x7c, 0xa9, 0x9b, 0xd1, 0xf5, 0xbb, 0x23, 0x21, 0x5b, 0xbf, 0x3b, 0x22, 0xd9, 0xec, 0xea, + 0x6f, 0xb4, 0x56, 0x75, 0x87, 0x1a, 0x53, 0xa6, 0x83, 0x27, 0x8b, 0x1b, 0x7c, 0x6b, 0xf8, 0x46, + 0x74, 0x76, 0xed, 0x42, 0xca, 0x66, 0xd7, 0x2e, 0x68, 0x66, 0xe4, 0x25, 0xa3, 0x35, 0xaa, 0x1b, + 0xf9, 0xb5, 0xa8, 0x91, 0xd7, 0x99, 0x92, 0x19, 0x79, 0x9d, 0xb1, 0x9d, 0x3f, 0xe7, 0xb6, 0x63, + 0x7a, 0x34, 0xdf, 0xd8, 0xc9, 0xe7, 0x20, 0x69, 0xe7, 0xcf, 0x41, 0x34, 0xdb, 0x10, 0xaa, 0x1d, + 0xd2, 0x8c, 0x6e, 0x08, 0xe3, 0xdd, 0xa0, 0xb6, 0x60, 0x16, 0x8b, 0x08, 0xa6, 0xcc, 0x5b, 0x51, + 0x8b, 0x45, 0x80, 0x99, 0xc5, 0x12, 0x86, 0x5b, 0x46, 0xe2, 0xe2, 0xf2, 0x76, 0x74, 0x0d, 0x95, + 0x71, 0x6c, 0x0d, 0x8d, 0xc4, 0xd0, 0xbd, 0x3b, 0x12, 0xc8, 0x91, 0x6f, 0x45, 0xad, 0x0e, 0x09, + 0xc5, 0xac, 0x0e, 0x39, 0xe4, 0x63, 0x12, 0x46, 0xf1, 0x16, 0x5c, 0x6b, 0x07, 0xf7, 0x38, 0x1f, + 0x8d, 0x7e, 0xa6, 0x82, 0x66, 0x9f, 0xa9, 0x80, 0x22, 0x4c, 0xc4, 0xb4, 0xe5, 0x74, 0x60, 0x12, + 0x9e, 0x0f, 0x2a, 0x20, 0x32, 0x07, 0xa4, 0x5a, 0x9a, 0x9f, 0x2b, 0x1b, 0x15, 0xf9, 0x8a, 0xcc, + 0x8d, 0x9e, 0xc0, 0xc6, 0x29, 0x66, 0x7b, 0xb4, 0x84, 0x76, 0xe4, 0x0d, 0x38, 0x27, 0xa0, 0x22, + 0x13, 0x48, 0x45, 0x94, 0x3b, 0x17, 0x1f, 0xe9, 0x45, 0x1d, 0x05, 0xbb, 0xd1, 0xce, 0xf6, 0x68, + 0x5d, 0x79, 0x75, 0x7e, 0x96, 0x58, 0x1f, 0xda, 0x3b, 0x79, 0x56, 0xb0, 0x48, 0x74, 0xe5, 0xd5, + 0xf9, 0x59, 0x42, 0xee, 0x77, 0x76, 0xf2, 0xac, 0xa0, 0x13, 0xba, 0xf2, 0x22, 0x2e, 0x14, 0xba, + 0xe1, 0x4b, 0x8d, 0x46, 0x7e, 0x1d, 0x1f, 0xf7, 0x8e, 0x9d, 0x3c, 0xae, 0x84, 0x06, 0xe7, 0x76, + 0x1c, 0xd9, 0x2c, 0xbd, 0xd0, 0xa2, 0x56, 0x35, 0xb2, 0x00, 0xdd, 0x8d, 0xce, 0xd2, 0x31, 0x02, + 0x36, 0x4b, 0xc7, 0x80, 0x6c, 0x40, 0xc9, 0xf1, 0x40, 0xf9, 0x8d, 0xe8, 0x80, 0x92, 0x71, 0x6c, + 0x40, 0x45, 0x62, 0x87, 0x16, 0xe0, 0xc4, 0xc2, 0x9a, 0xa7, 0xfb, 0x16, 0xa4, 0x2b, 0xba, 0xf2, + 0x4d, 0xe5, 0x92, 0x29, 0x4e, 0x82, 0x97, 0x4c, 0x71, 0x30, 0x1b, 0x23, 0x0c, 0x5c, 0xdd, 0xb0, + 0xea, 0x33, 0xba, 0xd9, 0x68, 0x3b, 0x34, 0xff, 0x57, 0xa2, 0x63, 0x44, 0x41, 0xb3, 0x31, 0xa2, + 0x80, 0xd8, 0x02, 0xcd, 0x40, 0x25, 0xd7, 0x35, 0x57, 0x2c, 0xb1, 0xaf, 0x6c, 0x37, 0xbc, 0xfc, + 0xff, 0x2f, 0xba, 0x40, 0x27, 0xd1, 0xb0, 0x05, 0x3a, 0x09, 0x8e, 0xa7, 0x4e, 0xac, 0x17, 0xd8, + 0xe2, 0x21, 0xdf, 0x55, 0xfe, 0x55, 0xe5, 0xd4, 0x29, 0x81, 0x06, 0x4f, 0x9d, 0x12, 0xe0, 0x6c, + 0x7d, 0xe4, 0x36, 0xd9, 0x9c, 0x19, 0xdc, 0x55, 0xff, 0xff, 0xa3, 0xeb, 0xa3, 0x8a, 0x67, 0xeb, + 0xa3, 0x0a, 0x8b, 0xf2, 0x11, 0x5d, 0xf0, 0xd7, 0x3a, 0xf1, 0x09, 0xe4, 0x1f, 0x6b, 0x43, 0x5e, + 0x95, 0xf9, 0x88, 0x91, 0xf2, 0xd7, 0x53, 0x9d, 0x18, 0x05, 0xc3, 0x23, 0xd6, 0x28, 0xca, 0x48, + 0xa3, 0x77, 0x4c, 0xba, 0x9e, 0xff, 0x58, 0x47, 0x46, 0x9c, 0x20, 0xca, 0x88, 0xc3, 0xc8, 0x07, + 0xe0, 0x74, 0x08, 0x9b, 0xa7, 0xcd, 0xa5, 0x60, 0x66, 0xfa, 0x1b, 0xa9, 0xa8, 0x19, 0x9c, 0x4c, + 0xc6, 0xcc, 0xe0, 0x64, 0x4c, 0x12, 0x6b, 0x21, 0xba, 0x2f, 0xdb, 0x86, 0x75, 0x20, 0xc1, 0x0e, + 0x0c, 0x92, 0x58, 0x0b, 0x69, 0x7e, 0xf9, 0x36, 0xac, 0x03, 0x99, 0x76, 0x60, 0x40, 0x3e, 0x9e, + 0x82, 0x0b, 0xc9, 0xa8, 0x52, 0xa3, 0x31, 0x63, 0x3b, 0x21, 0x2e, 0xff, 0x15, 0xa9, 0xe8, 0x41, + 0xc3, 0xce, 0x9a, 0xcd, 0xf6, 0x68, 0x3b, 0x7c, 0x00, 0x79, 0x3f, 0x0c, 0x97, 0xda, 0x86, 0xe9, + 0xe1, 0xc5, 0x1b, 0x33, 0x9c, 0xbf, 0x32, 0xa5, 0x6c, 0x71, 0x64, 0x2c, 0x6e, 0x71, 0x64, 0x00, + 0xb9, 0x06, 0xe3, 0x55, 0x5a, 0x6f, 0x63, 0x1a, 0x0b, 0xda, 0xb2, 0x1d, 0x8f, 0xf1, 0xf8, 0xaa, + 0x54, 0x74, 0x12, 0x8b, 0x51, 0xb0, 0x49, 0x2c, 0x06, 0x24, 0xb7, 0x62, 0xb7, 0xf2, 0xa2, 0x33, + 0xbf, 0x3a, 0xd5, 0xf5, 0x5a, 0x3e, 0xe8, 0xcb, 0xe4, 0xe6, 0xa4, 0xa2, 0xdc, 0xa2, 0x0b, 0xae, + 0x1f, 0x4f, 0x75, 0xb9, 0x46, 0x97, 0x66, 0xb8, 0x38, 0x98, 0x71, 0x8c, 0xdc, 0x5d, 0x0b, 0x8e, + 0x5f, 0x93, 0xea, 0x72, 0xed, 0x1d, 0x72, 0x4c, 0x00, 0x93, 0xe7, 0xb8, 0xa7, 0x88, 0x60, 0xf4, + 0xb5, 0xa9, 0xb8, 0xab, 0x48, 0xd0, 0x5e, 0x22, 0x64, 0xcd, 0x6e, 0xba, 0x81, 0xd2, 0x7f, 0x22, + 0x15, 0xf7, 0xcd, 0x0b, 0x9b, 0x85, 0xbf, 0x08, 0x85, 0xb3, 0xd3, 0x77, 0x3d, 0xea, 0x58, 0x7a, + 0x03, 0xbb, 0xb3, 0xea, 0xd9, 0x8e, 0xbe, 0x42, 0xa7, 0x2d, 0x7d, 0xa9, 0x41, 0xf3, 0x9f, 0x4c, + 0x45, 0x2d, 0xd8, 0xce, 0xa4, 0xcc, 0x82, 0xed, 0x8c, 0x25, 0xab, 0xf0, 0x60, 0x12, 0x76, 0xca, + 0x74, 0xf1, 0x39, 0x9f, 0x4a, 0x45, 0x4d, 0xd8, 0x2e, 0xb4, 0xcc, 0x84, 0xed, 0x82, 0x26, 0x57, + 0x61, 0x60, 0xc2, 0xf6, 0xa7, 0xdf, 0xaf, 0x53, 0x9c, 0x21, 0x03, 0xcc, 0x6c, 0x8f, 0x16, 0x92, + 0x89, 0x36, 0x62, 0x50, 0x7f, 0x3a, 0xde, 0x26, 0xbc, 0x7c, 0x0a, 0x7e, 0x88, 0x36, 0x42, 0xdc, + 0x7f, 0x33, 0xde, 0x26, 0xbc, 0xe3, 0x0a, 0x7e, 0xb0, 0x99, 0x84, 0x3f, 0x71, 0x7e, 0xa6, 0xc4, + 0xec, 0xb6, 0xc9, 0x55, 0xbd, 0xd1, 0xa0, 0xd6, 0x0a, 0xcd, 0x7f, 0x46, 0x99, 0x49, 0x92, 0xc9, + 0xd8, 0x4c, 0x92, 0x8c, 0x21, 0x5f, 0x02, 0x67, 0x6e, 0xe9, 0x0d, 0xd3, 0x08, 0x71, 0x7e, 0xa9, + 0xfe, 0xfc, 0x67, 0x53, 0xd1, 0xdd, 0x74, 0x07, 0x3a, 0xb6, 0x9b, 0xee, 0x80, 0x22, 0xf3, 0x40, + 0x70, 0x19, 0x0d, 0x66, 0x0b, 0xb6, 0x3e, 0xe7, 0xff, 0x56, 0x2a, 0x6a, 0xa7, 0xc6, 0x49, 0x98, + 0x9d, 0x1a, 0x87, 0x92, 0x5a, 0xe7, 0x9a, 0x87, 0xf9, 0xaf, 0x4f, 0x45, 0x4f, 0x6b, 0x3a, 0x11, + 0xce, 0xf6, 0x68, 0x9d, 0x0b, 0x27, 0xbe, 0x0a, 0x63, 0xd5, 0x4a, 0x79, 0x66, 0x66, 0xba, 0x7a, + 0xab, 0x3c, 0x85, 0xa1, 0x1a, 0x46, 0xfe, 0x6f, 0x2b, 0x2b, 0x96, 0x4a, 0xc0, 0x56, 0x2c, 0x15, + 0x46, 0x5e, 0x84, 0x21, 0xf6, 0xfe, 0x6c, 0xc0, 0xe0, 0x27, 0x7f, 0x2e, 0x15, 0x35, 0xa7, 0x64, + 0x24, 0x33, 0xa7, 0xe4, 0xdf, 0xa4, 0x0a, 0x27, 0x99, 0x14, 0x2b, 0x0e, 0x5d, 0xa6, 0x0e, 0xb5, + 0xea, 0xfe, 0x98, 0xfe, 0x86, 0x54, 0xd4, 0xca, 0x48, 0x22, 0x62, 0x56, 0x46, 0x12, 0x9c, 0xac, + 0xc1, 0x39, 0xf5, 0x24, 0x48, 0x8e, 0xeb, 0xcd, 0xff, 0x9d, 0x94, 0x62, 0x0c, 0x77, 0x21, 0x46, + 0x63, 0xb8, 0x0b, 0x9e, 0x58, 0x70, 0x5e, 0x1c, 0xab, 0x08, 0x87, 0x4b, 0xf5, 0x69, 0xdf, 0xc8, + 0x9f, 0xf6, 0x78, 0xe8, 0x10, 0xd8, 0x85, 0x7a, 0xb6, 0x47, 0xeb, 0xce, 0x8e, 0xe9, 0x59, 0xbc, + 0xb2, 0x5f, 0xfe, 0x9b, 0x52, 0xc9, 0x1e, 0x29, 0x11, 0x37, 0xe5, 0xa4, 0x92, 0x80, 0x1f, 0xe8, + 0x54, 0x97, 0x2e, 0xff, 0x77, 0x95, 0xf1, 0x96, 0x4c, 0xc6, 0xc6, 0x5b, 0x87, 0xc2, 0x76, 0xd7, + 0x60, 0x9c, 0x2b, 0x75, 0x45, 0xc7, 0x61, 0x68, 0xad, 0x50, 0x23, 0xff, 0xcd, 0xca, 0x6a, 0x17, + 0xa3, 0x40, 0xd7, 0x1e, 0x15, 0xc8, 0xa6, 0xee, 0x6a, 0x4b, 0xb7, 0x2c, 0x3c, 0x66, 0xcd, 0xff, + 0x3d, 0x65, 0xea, 0x0e, 0x51, 0xe8, 0xb8, 0x1b, 0xfc, 0x62, 0x9a, 0xd0, 0xad, 0xa6, 0x6b, 0xfe, + 0x7f, 0x50, 0x34, 0xa1, 0x1b, 0x31, 0xd3, 0x84, 0xae, 0x05, 0x62, 0x6f, 0x75, 0x88, 0xb1, 0xcf, + 0x7f, 0x8b, 0xb2, 0x22, 0x27, 0x52, 0xb1, 0x15, 0x39, 0x39, 0x44, 0xff, 0x56, 0x87, 0xf8, 0xf4, + 0xfc, 0xb7, 0x76, 0xe7, 0x1b, 0xae, 0xf4, 0xc9, 0xe1, 0xed, 0xb7, 0x3a, 0xc4, 0x76, 0xe7, 0xbf, + 0xad, 0x3b, 0xdf, 0xd0, 0xb1, 0x2f, 0x39, 0x34, 0xbc, 0xd6, 0x39, 0x2e, 0x3a, 0xff, 0xf7, 0xd5, + 0xa9, 0xab, 0x03, 0x21, 0x4e, 0x5d, 0x9d, 0x82, 0xab, 0x97, 0xe0, 0x01, 0xae, 0x21, 0xaf, 0x3a, + 0x7a, 0x6b, 0xb5, 0x4a, 0x3d, 0xcf, 0xb4, 0x56, 0xfc, 0x9d, 0xd8, 0xb7, 0xa7, 0x94, 0xe3, 0xb1, + 0x4e, 0x94, 0x78, 0x3c, 0xd6, 0x09, 0xc9, 0x94, 0x37, 0x16, 0x01, 0x9d, 0xff, 0x07, 0x8a, 0xf2, + 0xc6, 0x28, 0x98, 0xf2, 0xc6, 0x03, 0xa7, 0xaf, 0x25, 0x04, 0xfa, 0xe6, 0xff, 0x61, 0x67, 0x5e, + 0xc1, 0xfb, 0x25, 0xc4, 0x07, 0x5f, 0x4b, 0x88, 0x67, 0xcd, 0xff, 0x8f, 0x9d, 0x79, 0x85, 0x3e, + 0x48, 0xf1, 0x30, 0xd8, 0x0f, 0xc0, 0x69, 0x3e, 0x9b, 0xcf, 0x50, 0x83, 0x46, 0x3e, 0xf4, 0x3b, + 0x94, 0xb1, 0x9f, 0x4c, 0x86, 0x47, 0xee, 0x89, 0x98, 0x24, 0xd6, 0xe2, 0x5d, 0xff, 0xd1, 0x36, + 0xac, 0xc3, 0x0d, 0x41, 0x32, 0x86, 0xad, 0x37, 0x72, 0xf4, 0x5b, 0xfe, 0x3b, 0x95, 0xf5, 0x46, + 0x46, 0xa2, 0x3b, 0x87, 0x1c, 0x2a, 0xf7, 0x62, 0x34, 0xd2, 0x2b, 0xff, 0x5d, 0x89, 0x8d, 0x83, + 0x0e, 0x88, 0x86, 0x85, 0xbd, 0x18, 0x8d, 0x6a, 0xca, 0x7f, 0x77, 0x62, 0xe3, 0xe0, 0x03, 0xa2, + 0x21, 0x50, 0x6c, 0x8b, 0xd4, 0xf6, 0x6c, 0xce, 0x2a, 0x32, 0x3d, 0xfc, 0x63, 0x75, 0x8b, 0x94, + 0x48, 0x86, 0x5b, 0xa4, 0x44, 0x4c, 0x12, 0x6b, 0xf1, 0x79, 0xdf, 0xb3, 0x0d, 0x6b, 0x69, 0x63, + 0x97, 0x88, 0x49, 0x62, 0x2d, 0x3e, 0xfe, 0x7b, 0xb7, 0x61, 0x2d, 0x6d, 0xec, 0x12, 0x31, 0xcc, + 0x1c, 0x0b, 0x31, 0xb7, 0xa8, 0xe3, 0x86, 0xea, 0xf7, 0x3f, 0x29, 0xe6, 0x58, 0x07, 0x3a, 0x66, + 0x8e, 0x75, 0x40, 0x25, 0x72, 0x17, 0x42, 0xf9, 0xbe, 0xed, 0xb8, 0x87, 0xf7, 0x32, 0x1d, 0x50, + 0x89, 0xdc, 0x85, 0x5c, 0xfe, 0xc9, 0x76, 0xdc, 0xc3, 0x8b, 0x99, 0x0e, 0x28, 0x66, 0x14, 0x55, + 0x3d, 0xdd, 0x33, 0xeb, 0xb3, 0xb6, 0xeb, 0x49, 0x8b, 0xfc, 0xff, 0xac, 0x18, 0x45, 0x49, 0x44, + 0xcc, 0x28, 0x4a, 0x82, 0xc7, 0x99, 0x0a, 0x69, 0xfc, 0xd3, 0xae, 0x4c, 0x43, 0x4b, 0x2b, 0x09, + 0x1e, 0x67, 0x2a, 0x84, 0xf0, 0xbf, 0x74, 0x65, 0x1a, 0x7a, 0xca, 0x27, 0xc1, 0x99, 0x65, 0x3a, + 0xe9, 0xd8, 0xeb, 0xd6, 0x35, 0xba, 0x4e, 0x1b, 0xe2, 0xd3, 0xff, 0x99, 0x62, 0x99, 0xaa, 0x04, + 0x78, 0x8b, 0xa2, 0xc0, 0xa2, 0x8c, 0xc4, 0xe7, 0x7e, 0x7f, 0x47, 0x46, 0xe1, 0x31, 0x91, 0x0a, + 0x8b, 0x32, 0x12, 0x9f, 0xf8, 0x03, 0x1d, 0x19, 0x85, 0xc7, 0x44, 0x2a, 0x8c, 0x94, 0x60, 0x04, + 0x63, 0x25, 0x74, 0xd7, 0xf7, 0xfc, 0xfc, 0x91, 0x54, 0xf4, 0xd6, 0x2b, 0x8a, 0x9e, 0xed, 0xd1, + 0x94, 0x06, 0x32, 0x0b, 0xf1, 0x49, 0x3f, 0xda, 0x81, 0x45, 0xe8, 0xef, 0x18, 0x85, 0xc8, 0x2c, + 0xc4, 0xc7, 0xfc, 0x58, 0x07, 0x16, 0xa1, 0xc3, 0x63, 0x14, 0x42, 0xde, 0x03, 0x83, 0xd5, 0x99, + 0xc5, 0x8a, 0x5f, 0x77, 0xfc, 0x9f, 0xa7, 0x94, 0xa8, 0xa2, 0x10, 0x87, 0x51, 0x45, 0xe1, 0x4f, + 0xf2, 0x7e, 0x18, 0x9e, 0xb4, 0x2d, 0x4f, 0xaf, 0xfb, 0x1b, 0xd0, 0x1f, 0x57, 0xce, 0x50, 0x22, + 0xd8, 0xd9, 0x1e, 0x2d, 0x4a, 0x2e, 0xb5, 0x17, 0xef, 0xfe, 0x13, 0xc9, 0xed, 0x83, 0x57, 0x8f, + 0x92, 0xb3, 0x19, 0xed, 0xb6, 0xed, 0xac, 0x35, 0x6c, 0xdd, 0xf0, 0xf3, 0x9c, 0x8a, 0x17, 0xf9, + 0x49, 0x65, 0x46, 0x4b, 0x26, 0x63, 0x33, 0x5a, 0x32, 0x26, 0x89, 0xb5, 0xe8, 0xa2, 0xcf, 0x6f, + 0xc3, 0x3a, 0x9c, 0x87, 0x93, 0x31, 0x49, 0xac, 0xc5, 0xe7, 0xff, 0x8b, 0x6d, 0x58, 0x87, 0xf3, + 0x70, 0x32, 0x86, 0x99, 0xd6, 0xaf, 0x9a, 0x9e, 0x1f, 0xd8, 0xf6, 0x2f, 0x15, 0xd3, 0x3a, 0x44, + 0x31, 0xd3, 0x3a, 0xfc, 0x45, 0x28, 0x9c, 0x0d, 0x42, 0x25, 0xc3, 0xbd, 0x6b, 0xd9, 0xba, 0xc3, + 0xf6, 0xc7, 0xf9, 0xff, 0x55, 0x39, 0x15, 0xe9, 0x4c, 0x3a, 0xdb, 0xa3, 0x75, 0x61, 0x44, 0x2a, + 0x8a, 0x9f, 0x22, 0x4f, 0x00, 0x98, 0xff, 0xa9, 0x54, 0x17, 0x47, 0x45, 0x4e, 0x13, 0x73, 0x54, + 0xe4, 0x60, 0x31, 0x67, 0x2d, 0x35, 0xe8, 0xcd, 0x1b, 0xe5, 0xd7, 0xa5, 0xd9, 0xf5, 0x0b, 0xf1, + 0x39, 0x2b, 0x46, 0x24, 0xe6, 0xac, 0x18, 0x9c, 0x7c, 0x59, 0x0a, 0x1e, 0x53, 0xe5, 0xfb, 0xfa, + 0x73, 0x4f, 0xbd, 0xa0, 0xd1, 0x3b, 0x76, 0x5d, 0xb6, 0xac, 0x7e, 0x9a, 0x3f, 0xe5, 0x89, 0x4e, + 0xdd, 0x95, 0xd4, 0x68, 0xb6, 0x47, 0xdb, 0x11, 0xf3, 0x1d, 0xbc, 0x85, 0x50, 0x9a, 0x9f, 0xd9, + 0xd5, 0x5b, 0x04, 0x2a, 0xb4, 0x23, 0xe6, 0x3b, 0x78, 0x0b, 0x31, 0x2a, 0x7e, 0x76, 0x57, 0x6f, + 0x11, 0x8c, 0x91, 0x1d, 0x31, 0xc7, 0xdd, 0xe7, 0xed, 0x6a, 0x79, 0x32, 0xf0, 0xf5, 0xd9, 0xb0, + 0xea, 0xf9, 0x9f, 0x53, 0x77, 0x9f, 0x2a, 0x05, 0xee, 0x3e, 0x55, 0x20, 0x5b, 0xee, 0x67, 0xa9, + 0xde, 0x60, 0xdb, 0x51, 0x5a, 0x5f, 0x8b, 0x18, 0x6f, 0x3f, 0xaf, 0x2c, 0xf7, 0x1d, 0xe8, 0xd8, + 0x72, 0xdf, 0x01, 0x95, 0xc8, 0x5d, 0x48, 0xe8, 0x17, 0xb6, 0xe3, 0x1e, 0x9a, 0x2a, 0x1d, 0x50, + 0x89, 0xdc, 0x85, 0x16, 0xfc, 0xe2, 0x76, 0xdc, 0x43, 0x53, 0xa5, 0x03, 0x8a, 0x7c, 0x5d, 0x0a, + 0x2e, 0x26, 0x75, 0x07, 0xcf, 0xfd, 0x11, 0x94, 0x0d, 0xe4, 0xb2, 0xfa, 0x25, 0xfe, 0xbc, 0xa7, + 0xba, 0xf5, 0x77, 0x52, 0xc3, 0xd9, 0x1e, 0x6d, 0xc7, 0x0f, 0xd9, 0xe1, 0x1b, 0x09, 0x09, 0xfc, + 0xf2, 0xae, 0xdf, 0x28, 0x10, 0xc9, 0x8e, 0x1f, 0x82, 0x13, 0x8e, 0xb9, 0xe2, 0x7a, 0xb6, 0x43, + 0x2b, 0x76, 0xc3, 0xac, 0xfb, 0xeb, 0xcd, 0xaf, 0xa8, 0x13, 0x4e, 0x02, 0x11, 0x4e, 0x38, 0x09, + 0xf0, 0x38, 0x53, 0xa1, 0x31, 0xff, 0xaa, 0x2b, 0x53, 0xc9, 0x9c, 0x4b, 0x80, 0xc7, 0x99, 0x0a, + 0x31, 0xfd, 0xeb, 0xae, 0x4c, 0x25, 0x73, 0x2e, 0x01, 0x4e, 0x2c, 0x38, 0x1f, 0x1a, 0xba, 0xa5, + 0x15, 0x6a, 0x79, 0x9a, 0xdd, 0x68, 0xd8, 0x6d, 0x6f, 0xd1, 0x31, 0x57, 0x56, 0xa8, 0x93, 0xff, + 0x55, 0xe5, 0x80, 0xac, 0x2b, 0xf5, 0x6c, 0x8f, 0xd6, 0x9d, 0x1d, 0xf1, 0xa0, 0x90, 0x4c, 0x30, + 0x63, 0x3b, 0x75, 0x3a, 0x65, 0x5b, 0x34, 0xff, 0x6b, 0xa9, 0xe8, 0xf5, 0xf4, 0x36, 0xf4, 0xb3, + 0x3d, 0xda, 0x76, 0x2c, 0xc9, 0x47, 0xe1, 0xa1, 0x64, 0x12, 0xf6, 0xdf, 0x92, 0x5e, 0x5f, 0xcb, + 0xff, 0x6f, 0xa9, 0xa8, 0xcf, 0x5f, 0x77, 0xf2, 0xd9, 0x1e, 0x6d, 0x1b, 0x86, 0x64, 0x0a, 0x46, + 0xe7, 0x27, 0x2b, 0x91, 0x80, 0x8e, 0x5f, 0x4f, 0x29, 0xe1, 0x57, 0x51, 0x3c, 0x86, 0x5f, 0x45, + 0x41, 0xcc, 0x9e, 0x0a, 0x41, 0xd3, 0x96, 0x91, 0xff, 0xdf, 0x15, 0x7b, 0x2a, 0x82, 0x45, 0xff, + 0x52, 0x19, 0xc0, 0xe6, 0xd9, 0x10, 0xe0, 0xdf, 0xcb, 0xff, 0x86, 0x32, 0xcf, 0xc6, 0x28, 0xd8, + 0x3c, 0x1b, 0x03, 0x32, 0x2b, 0x27, 0x04, 0xde, 0xb0, 0x85, 0xcb, 0xaf, 0x69, 0x5b, 0xf9, 0xdf, + 0x54, 0xac, 0x9c, 0x64, 0x32, 0x66, 0xe5, 0x24, 0x63, 0x98, 0x6a, 0x4f, 0xd8, 0x6d, 0xcb, 0xb8, + 0x4e, 0x37, 0x5a, 0xba, 0xe9, 0xf8, 0x71, 0x48, 0xf9, 0xdf, 0x52, 0x54, 0x3b, 0x89, 0x88, 0xa9, + 0x76, 0x12, 0x3c, 0xc6, 0xd4, 0xf6, 0xf8, 0xdb, 0xfe, 0x9b, 0x6e, 0x4c, 0x05, 0x51, 0x8c, 0xa9, + 0x80, 0x93, 0x4f, 0xa4, 0xe0, 0x1d, 0x32, 0xe2, 0x9a, 0x6d, 0x5a, 0xe8, 0x81, 0x7d, 0x8b, 0x3a, + 0xc1, 0xf7, 0xcc, 0xe8, 0x66, 0x83, 0x1a, 0xf9, 0x4d, 0xfe, 0xa0, 0x2b, 0x49, 0x0f, 0xea, 0xd2, + 0x6e, 0xb6, 0x47, 0xdb, 0xe9, 0x23, 0xc8, 0x12, 0x3c, 0x10, 0x8a, 0x94, 0x59, 0x66, 0xd4, 0xaa, + 0x56, 0xa7, 0xab, 0x9e, 0x43, 0xf5, 0x66, 0xfe, 0x8b, 0xca, 0x61, 0x5b, 0x47, 0x4a, 0xf4, 0x25, + 0xef, 0x84, 0x24, 0x6b, 0x70, 0x2e, 0x44, 0xfa, 0x76, 0xe1, 0xe2, 0x62, 0xc5, 0x57, 0xa7, 0xdf, + 0x56, 0x8e, 0x69, 0xbb, 0x11, 0xcf, 0xf6, 0x68, 0x5d, 0x99, 0xe1, 0xc5, 0xc7, 0x64, 0x79, 0x9e, + 0xbd, 0x83, 0x69, 0xad, 0x70, 0xbf, 0xa8, 0xff, 0x43, 0xbd, 0xf8, 0x50, 0x08, 0xf0, 0xe2, 0x43, + 0x81, 0xe1, 0x6d, 0xee, 0x64, 0x79, 0xde, 0xb7, 0x14, 0x38, 0xa7, 0xdf, 0x51, 0x6f, 0x73, 0x55, + 0x0a, 0xbc, 0xcd, 0x55, 0x81, 0xcc, 0x9a, 0x16, 0x09, 0xaa, 0x2a, 0x1a, 0x75, 0x3d, 0xc7, 0xc4, + 0x48, 0x1d, 0xff, 0x4c, 0xf3, 0xdf, 0x2a, 0xd6, 0x74, 0x67, 0x52, 0x66, 0x4d, 0x77, 0xc6, 0xb2, + 0x6f, 0xbf, 0x65, 0x51, 0x2f, 0x62, 0xc1, 0xfc, 0x07, 0xe5, 0xdb, 0x55, 0x02, 0xf6, 0xed, 0x2a, + 0x2c, 0xca, 0x48, 0xbc, 0xe5, 0xff, 0xd9, 0x91, 0x51, 0xb8, 0xb5, 0x56, 0x61, 0x51, 0x46, 0x62, + 0xb9, 0xd9, 0xea, 0xc8, 0x28, 0xdc, 0x5a, 0xab, 0x30, 0x72, 0x0b, 0x4e, 0xf9, 0x2b, 0x72, 0xd4, + 0xc5, 0xf7, 0xff, 0x52, 0x4e, 0xb3, 0x13, 0xa9, 0x66, 0x7b, 0xb4, 0xe4, 0xe6, 0x09, 0x7c, 0xc5, + 0xe7, 0xfe, 0xc7, 0xee, 0x7c, 0xc3, 0xd3, 0xf7, 0x44, 0x44, 0x02, 0x5f, 0xf1, 0xf5, 0xff, 0xa9, + 0x3b, 0xdf, 0xf0, 0xf4, 0x3d, 0x11, 0xc1, 0xe6, 0xa4, 0xb2, 0x25, 0xee, 0xc3, 0xe6, 0x6d, 0x23, + 0x38, 0x41, 0xf9, 0xcf, 0xca, 0x9c, 0x94, 0x44, 0xc4, 0xe6, 0xa4, 0x24, 0x78, 0x9c, 0xa9, 0x90, + 0xc1, 0xff, 0xdd, 0x95, 0x69, 0x68, 0x6d, 0x24, 0xc1, 0xe3, 0x4c, 0x85, 0x00, 0x7e, 0xbf, 0x2b, + 0xd3, 0xd0, 0xda, 0x48, 0x82, 0x33, 0xb1, 0x06, 0xf0, 0x2a, 0xad, 0x3b, 0xd4, 0x3f, 0x66, 0xf8, + 0x7f, 0x14, 0xb1, 0x26, 0x52, 0x31, 0xb1, 0x26, 0x22, 0x12, 0xf8, 0x0a, 0x11, 0xfc, 0x41, 0x77, + 0xbe, 0xa1, 0x1a, 0x24, 0x22, 0x12, 0xf8, 0x0a, 0x29, 0xfc, 0x61, 0x77, 0xbe, 0xa1, 0x1a, 0x24, + 0x22, 0x22, 0x7c, 0x23, 0x56, 0xe7, 0x1f, 0x75, 0xe2, 0xab, 0x98, 0x9d, 0xc9, 0xcd, 0x13, 0xf8, + 0x0a, 0x39, 0xfc, 0x71, 0x77, 0xbe, 0x09, 0x72, 0x88, 0x98, 0x9e, 0x71, 0xbe, 0x42, 0x0e, 0xff, + 0xa5, 0x3b, 0xdf, 0x04, 0x39, 0x44, 0xac, 0x4f, 0x74, 0xb9, 0xe1, 0xe6, 0x0e, 0x1e, 0x40, 0x99, + 0x6f, 0x52, 0x23, 0xff, 0x27, 0x31, 0x97, 0x1b, 0x85, 0x82, 0xbb, 0xdc, 0x28, 0x40, 0x9c, 0xa4, + 0xa9, 0xe3, 0x05, 0xb5, 0x65, 0x7c, 0x3b, 0x9f, 0xcf, 0xfc, 0x7f, 0xaa, 0x4e, 0xd2, 0x1d, 0x49, + 0x71, 0x92, 0xee, 0x88, 0xc5, 0x3b, 0xf1, 0xc0, 0x6d, 0x7b, 0x6e, 0x6e, 0xde, 0x5f, 0x05, 0xff, + 0xab, 0x7a, 0x27, 0x9e, 0x40, 0x84, 0x77, 0xe2, 0x09, 0x70, 0xc6, 0x54, 0xa3, 0x9e, 0x63, 0xd2, + 0x3b, 0x7a, 0x43, 0x9e, 0x16, 0xfe, 0x2c, 0xa5, 0x06, 0x81, 0xc7, 0x89, 0x78, 0x10, 0x78, 0x1c, + 0x1e, 0x67, 0x2a, 0x74, 0xe1, 0xbf, 0x75, 0x65, 0x1a, 0x4e, 0x0b, 0x49, 0xf0, 0x38, 0x53, 0xa1, + 0x08, 0xff, 0xbd, 0x2b, 0xd3, 0x70, 0x5a, 0x48, 0x82, 0x93, 0x2b, 0xd0, 0x8f, 0xe9, 0x27, 0xb5, + 0xb6, 0x95, 0xff, 0x73, 0x25, 0x03, 0x9d, 0x8f, 0x98, 0xed, 0xd1, 0x02, 0x22, 0x52, 0xeb, 0x5c, + 0x74, 0x3e, 0xff, 0x17, 0xca, 0x25, 0x66, 0x27, 0xc2, 0xd9, 0x1e, 0xad, 0x73, 0xe5, 0xfa, 0x0f, + 0x74, 0xaa, 0xd1, 0x96, 0xff, 0x7f, 0x15, 0x5b, 0x37, 0x99, 0x8c, 0xd9, 0xba, 0x1d, 0x8a, 0xbc, + 0xd5, 0x20, 0x3f, 0x6d, 0xdd, 0x31, 0x1d, 0xdb, 0xe2, 0x31, 0x9c, 0xf6, 0xb2, 0x19, 0x38, 0x4b, + 0xfd, 0xf5, 0x74, 0xf4, 0xdd, 0x3b, 0x11, 0xb2, 0x77, 0xef, 0x84, 0x63, 0x83, 0x6a, 0x82, 0xea, + 0x4d, 0x37, 0x62, 0x47, 0x7c, 0x2c, 0xad, 0x24, 0x42, 0x50, 0x29, 0x30, 0x11, 0x82, 0x0a, 0x54, + 0x78, 0x89, 0xb7, 0xfc, 0x1b, 0x9d, 0x79, 0x85, 0x97, 0xa3, 0x31, 0xa0, 0xc2, 0x4b, 0xe8, 0xcd, + 0x97, 0x75, 0xe6, 0x25, 0x25, 0x68, 0x50, 0x81, 0x78, 0xe6, 0xdf, 0xd0, 0x5d, 0xd7, 0x5c, 0x36, + 0x03, 0x53, 0xe2, 0xcb, 0xd3, 0x6a, 0x6e, 0xa7, 0x28, 0x01, 0xcf, 0xed, 0x14, 0x85, 0x45, 0x19, + 0x89, 0xef, 0xfb, 0x8a, 0x8e, 0x8c, 0xa4, 0x5b, 0x08, 0x05, 0x16, 0x65, 0x24, 0x3e, 0xee, 0x2b, + 0x3b, 0x32, 0x92, 0x6e, 0x21, 0x14, 0x18, 0xce, 0x89, 0x75, 0xbb, 0x45, 0x0d, 0xd9, 0xad, 0xf7, + 0xab, 0x14, 0x31, 0xc5, 0x28, 0x70, 0x4e, 0x54, 0x81, 0x0a, 0x2f, 0xdf, 0x05, 0xb1, 0x33, 0xaf, + 0xb0, 0xfb, 0x62, 0x40, 0x85, 0x97, 0xf8, 0xc2, 0x8f, 0x77, 0xe6, 0x15, 0x76, 0x5f, 0x0c, 0x48, + 0x5e, 0x84, 0x21, 0xdc, 0x34, 0xfb, 0x99, 0xc4, 0xbe, 0x46, 0x49, 0x1f, 0x29, 0x23, 0x67, 0x7b, + 0xb4, 0x08, 0x31, 0x5b, 0x8c, 0xc2, 0x49, 0x54, 0xde, 0x88, 0x7c, 0x6d, 0x3a, 0xba, 0x18, 0x25, + 0x52, 0xb1, 0xc5, 0x28, 0x11, 0x81, 0x87, 0x04, 0x49, 0x88, 0x09, 0xdb, 0xd8, 0xe0, 0xf9, 0x73, + 0x3e, 0x91, 0x56, 0x0e, 0x09, 0xba, 0x92, 0xe3, 0x21, 0x41, 0x57, 0x0a, 0xbc, 0xc0, 0x55, 0x28, + 0x84, 0xcf, 0xdb, 0x27, 0xd3, 0xca, 0x05, 0x6e, 0x22, 0x19, 0x5e, 0xe0, 0x26, 0x62, 0xf0, 0xa0, + 0x25, 0x11, 0x13, 0x7e, 0xce, 0xa7, 0xd2, 0xca, 0x41, 0x4b, 0x77, 0x7a, 0x3c, 0x68, 0xe9, 0x4e, + 0xc2, 0x16, 0xe1, 0x90, 0x84, 0x17, 0x5d, 0x9e, 0x32, 0xf5, 0xc6, 0x14, 0xb5, 0x4c, 0x6a, 0xe4, + 0xbf, 0x4e, 0x4d, 0x20, 0xdd, 0x91, 0x94, 0x2d, 0xc2, 0x9d, 0xb1, 0x13, 0x7d, 0xd0, 0x8b, 0xab, + 0xf1, 0xb5, 0x5c, 0xff, 0x0f, 0xa6, 0xc6, 0x7e, 0x28, 0x75, 0x2d, 0xd7, 0xff, 0x43, 0xa9, 0xb1, + 0x1f, 0x66, 0xff, 0xff, 0x70, 0x6a, 0xec, 0x47, 0xd8, 0xff, 0xff, 0x2e, 0x35, 0xf6, 0xbb, 0xec, + 0xff, 0xdf, 0x4d, 0x8d, 0xfd, 0x7b, 0xf6, 0xff, 0xbf, 0x4f, 0x8d, 0xfd, 0x1e, 0xfb, 0xff, 0xf7, + 0x52, 0x63, 0xff, 0x21, 0xa5, 0x3d, 0xa0, 0x9c, 0xde, 0x54, 0x1a, 0xba, 0x18, 0x33, 0x89, 0x28, + 0xfe, 0x33, 0x11, 0xc5, 0x35, 0x5a, 0x3b, 0x51, 0x6a, 0xb5, 0xd0, 0xa1, 0x51, 0x9a, 0x3d, 0x15, + 0xa0, 0x60, 0x12, 0x05, 0x26, 0x36, 0xc7, 0x8d, 0xfe, 0x46, 0xf1, 0x5b, 0x52, 0x30, 0xc4, 0xf7, + 0xe0, 0xa2, 0x02, 0xd6, 0x59, 0xe8, 0xe7, 0x79, 0x81, 0xfc, 0x34, 0xcc, 0x5a, 0xf0, 0x9b, 0x5c, + 0x80, 0x91, 0x39, 0xdd, 0xf5, 0x50, 0x2c, 0x65, 0xcb, 0xa0, 0x77, 0x31, 0x27, 0x66, 0x46, 0x53, + 0xa0, 0x64, 0x8e, 0xd3, 0xf1, 0x76, 0x58, 0x3d, 0x32, 0xb3, 0x6d, 0xe1, 0xa7, 0xfe, 0x2f, 0x6c, + 0x16, 0x7a, 0xb0, 0xce, 0x93, 0xd2, 0xb6, 0xf8, 0xb3, 0x29, 0x88, 0x65, 0x2c, 0xda, 0x7b, 0xf6, + 0xf6, 0x05, 0x18, 0x55, 0x8a, 0x8e, 0x8a, 0xc4, 0x9e, 0x3b, 0xac, 0x49, 0xaa, 0xb6, 0x26, 0xef, + 0x08, 0x12, 0x4a, 0xde, 0xd4, 0xe6, 0x44, 0x51, 0xaf, 0xbe, 0xad, 0xcd, 0x42, 0xa6, 0xed, 0x34, + 0x34, 0x09, 0x25, 0x0a, 0xb1, 0xfc, 0xda, 0x89, 0xb0, 0x1c, 0x23, 0xb9, 0x20, 0x52, 0xc9, 0x4b, + 0xa5, 0x09, 0xdb, 0x2e, 0x75, 0xe4, 0x52, 0x60, 0x98, 0x3a, 0xfe, 0xfd, 0x30, 0x54, 0x6e, 0xb6, + 0xa8, 0xe3, 0xda, 0x96, 0xee, 0xd9, 0x8e, 0x48, 0x75, 0x8d, 0x49, 0xa9, 0x4d, 0x09, 0x2e, 0x27, + 0xa5, 0x96, 0xe9, 0xc9, 0x25, 0xe8, 0xd5, 0xb0, 0x98, 0x55, 0x06, 0x0b, 0x61, 0x62, 0xba, 0x57, + 0x2c, 0x68, 0x25, 0xe7, 0x0d, 0x47, 0x0a, 0x46, 0x7a, 0xd3, 0xd5, 0x31, 0xf5, 0x68, 0x40, 0xda, + 0x66, 0x00, 0x99, 0x14, 0x29, 0xc8, 0x13, 0x90, 0xc3, 0x3b, 0x3b, 0x37, 0xdf, 0x8b, 0xb4, 0x98, + 0x8f, 0xbc, 0x81, 0x10, 0x39, 0xbd, 0x39, 0xa7, 0x21, 0xd7, 0x61, 0x2c, 0x8c, 0x43, 0x7d, 0xd5, + 0xb1, 0xdb, 0x2d, 0x37, 0x9f, 0xc3, 0x76, 0x85, 0xad, 0xcd, 0xc2, 0x83, 0x6b, 0x01, 0xae, 0xb6, + 0x82, 0x48, 0x89, 0x45, 0xac, 0x21, 0x99, 0x85, 0xd1, 0x10, 0xc6, 0x44, 0xc4, 0xcb, 0xac, 0x8a, + 0x54, 0xda, 0x12, 0x2f, 0x26, 0x4e, 0x99, 0x95, 0xda, 0x8c, 0x94, 0xa1, 0xcf, 0xaf, 0x4e, 0xb6, + 0x7d, 0xfd, 0xd4, 0x13, 0xa2, 0x3a, 0x59, 0x9f, 0x5c, 0x97, 0xcc, 0x6f, 0x4f, 0x66, 0x60, 0x44, + 0xb3, 0xdb, 0x1e, 0x5d, 0xb4, 0xc5, 0x86, 0x5c, 0x14, 0xcc, 0xc3, 0x77, 0x72, 0x18, 0xa6, 0xe6, + 0xd9, 0xb5, 0x3a, 0xc7, 0xc9, 0xe9, 0xbd, 0xa3, 0xad, 0xc8, 0x0d, 0x18, 0x8f, 0x45, 0xec, 0x8a, + 0xf2, 0x79, 0x58, 0x3b, 0x4a, 0xfa, 0xbc, 0x38, 0xb3, 0x78, 0x53, 0xf2, 0x95, 0x29, 0xc8, 0x2d, + 0x3a, 0xba, 0xe9, 0xb9, 0x22, 0x6b, 0xe9, 0xa9, 0xcb, 0xeb, 0x8e, 0xde, 0x62, 0xfa, 0x71, 0x19, + 0x2b, 0x9d, 0xde, 0xd2, 0x1b, 0x6d, 0xea, 0x4e, 0xdc, 0x66, 0x5f, 0xf7, 0x1b, 0x9b, 0x85, 0x17, + 0x79, 0x8a, 0xfa, 0xcb, 0x75, 0xbb, 0x79, 0x65, 0xc5, 0xd1, 0xef, 0x98, 0xfc, 0x5c, 0x52, 0x6f, + 0x5c, 0xf1, 0x68, 0x03, 0xc3, 0x0f, 0xae, 0xe8, 0x2d, 0xf3, 0x0a, 0xd6, 0xc4, 0xbf, 0x12, 0x70, + 0xe2, 0x4f, 0x60, 0x2a, 0xe0, 0xe1, 0x5f, 0xb2, 0x0a, 0x70, 0x1c, 0xb9, 0x01, 0x20, 0x3e, 0xb5, + 0xd4, 0x6a, 0x89, 0x14, 0xa8, 0x92, 0xd3, 0xbe, 0x8f, 0xe1, 0x8a, 0x1d, 0x08, 0x4c, 0x6f, 0x49, + 0x55, 0x64, 0x35, 0x89, 0x03, 0xd3, 0x82, 0x45, 0xf1, 0x46, 0xbe, 0x98, 0x86, 0xa5, 0x84, 0xea, + 0x02, 0x95, 0x20, 0x24, 0xb5, 0x19, 0x59, 0x82, 0x51, 0xc1, 0xd7, 0x77, 0xf0, 0x14, 0x69, 0x52, + 0xcf, 0x28, 0xaf, 0xe7, 0xa3, 0xb9, 0xd2, 0x06, 0xef, 0x68, 0x08, 0xb0, 0xfc, 0x0c, 0xa5, 0x05, + 0x99, 0x80, 0x61, 0xff, 0xef, 0x1b, 0x7a, 0x93, 0xba, 0xf9, 0x51, 0xd4, 0x58, 0x2c, 0x19, 0xea, + 0xb7, 0xc7, 0x42, 0x7d, 0xb2, 0xe8, 0xa2, 0x4d, 0x64, 0x1e, 0x5c, 0xeb, 0xc7, 0x12, 0x78, 0xa8, + 0x3a, 0x1f, 0x6d, 0x42, 0x26, 0x61, 0x38, 0xc8, 0xc0, 0x76, 0xf3, 0x66, 0x79, 0x0a, 0x73, 0xac, + 0x8a, 0xb2, 0x8e, 0xcd, 0x65, 0xbd, 0x26, 0x2a, 0x9c, 0xb5, 0xdb, 0xd1, 0xb2, 0x8e, 0x91, 0x36, + 0x52, 0xe2, 0x7d, 0x9e, 0x74, 0x55, 0x49, 0xbc, 0xdf, 0x4a, 0x48, 0xbc, 0x5f, 0x21, 0xef, 0x83, + 0xc1, 0xd2, 0xed, 0xaa, 0x28, 0x28, 0xe0, 0xe6, 0x4f, 0xe0, 0xbb, 0x63, 0x09, 0x01, 0xac, 0x62, + 0x20, 0x8a, 0x0f, 0xc8, 0xaf, 0x2e, 0xd3, 0x93, 0x69, 0x18, 0x89, 0x5c, 0xe7, 0xbb, 0xf9, 0x93, + 0xc8, 0x01, 0xdf, 0x5c, 0x47, 0x4c, 0xcd, 0x11, 0x28, 0x79, 0x78, 0x45, 0x1b, 0x31, 0xad, 0x99, + 0x32, 0x5d, 0xbd, 0xd1, 0xb0, 0xd7, 0x35, 0x8a, 0xb5, 0x0b, 0x30, 0x63, 0x6b, 0x3f, 0xd7, 0x1a, + 0x43, 0xa0, 0x6a, 0x0e, 0xc7, 0xc9, 0x3d, 0xaa, 0x34, 0x23, 0x6f, 0x00, 0x29, 0xb1, 0xdf, 0xd4, + 0xf0, 0xcf, 0x72, 0xcb, 0x53, 0x6e, 0xfe, 0x34, 0xd6, 0x7b, 0x26, 0x6a, 0xaa, 0xf1, 0xf2, 0xd4, + 0xc4, 0x05, 0x31, 0x7d, 0x3c, 0xa4, 0xf3, 0x56, 0x35, 0x3f, 0xcd, 0x78, 0xcd, 0x34, 0xe4, 0x37, + 0x4e, 0xe0, 0x4a, 0xd6, 0xe1, 0x4c, 0xc5, 0xa1, 0x77, 0x4c, 0xbb, 0xed, 0xfa, 0xcb, 0x87, 0x3f, + 0x6f, 0x9d, 0xd9, 0x76, 0xde, 0x7a, 0x44, 0x3c, 0xf8, 0x54, 0xcb, 0xa1, 0x77, 0x6a, 0x7e, 0x9d, + 0xdf, 0x48, 0x75, 0xc5, 0x4e, 0xdc, 0x99, 0xb8, 0x70, 0x4f, 0x2c, 0xe0, 0x26, 0x75, 0xf3, 0xf9, + 0x70, 0xaa, 0xe5, 0x55, 0x32, 0xcc, 0x00, 0x27, 0x8b, 0x4b, 0x69, 0x46, 0x34, 0x20, 0xaf, 0x4e, + 0xfa, 0xf1, 0x9d, 0xa5, 0x7a, 0xdd, 0x6e, 0x5b, 0x9e, 0x9b, 0x7f, 0x00, 0x99, 0x15, 0x99, 0x58, + 0x56, 0xea, 0x41, 0xd5, 0xfe, 0x9a, 0x2e, 0xf0, 0xb2, 0x58, 0xe2, 0xad, 0xc9, 0x1c, 0x8c, 0x55, + 0x1c, 0xf4, 0x36, 0xbf, 0x4e, 0x37, 0xf8, 0xb9, 0x0f, 0x26, 0x8e, 0x15, 0x53, 0x65, 0x8b, 0xe3, + 0xb0, 0x14, 0x6f, 0x0b, 0xb1, 0xf2, 0xb2, 0xa2, 0xb6, 0x94, 0xab, 0xcc, 0x3e, 0xb8, 0xb3, 0x2a, + 0xb3, 0x14, 0xc6, 0x44, 0x74, 0xe8, 0x5d, 0x8f, 0x5a, 0x6c, 0xa9, 0x77, 0x45, 0x92, 0xd8, 0xbc, + 0x12, 0x4d, 0x1a, 0xe0, 0xf9, 0xd4, 0x21, 0x46, 0x19, 0x0d, 0xc0, 0xf2, 0x8b, 0xa9, 0x4d, 0xe2, + 0xa5, 0x58, 0xcf, 0xef, 0xa1, 0x14, 0xeb, 0xf3, 0x30, 0x70, 0xcd, 0x36, 0x79, 0x38, 0x26, 0x66, + 0x84, 0x15, 0x25, 0x42, 0xdf, 0xb0, 0x4d, 0xab, 0x86, 0xf5, 0x36, 0xe5, 0x4a, 0x27, 0x01, 0x29, + 0x99, 0x86, 0x7e, 0xdc, 0x5e, 0x55, 0x4c, 0x4b, 0x64, 0x7c, 0x1d, 0x8b, 0xec, 0xc5, 0x2a, 0xa6, + 0xc5, 0x47, 0x3f, 0x56, 0xce, 0xac, 0xb5, 0x22, 0x75, 0x11, 0x83, 0xa6, 0xe4, 0x83, 0x90, 0x57, + 0xf4, 0x9a, 0x0f, 0x4c, 0x36, 0x66, 0x1e, 0xc6, 0x31, 0x13, 0x4b, 0xcf, 0xef, 0x13, 0x4c, 0x64, + 0x99, 0x02, 0x6b, 0x1d, 0xdb, 0x93, 0xab, 0x30, 0x88, 0xdf, 0xca, 0x43, 0x9e, 0x30, 0xe3, 0x6b, + 0x3f, 0xaf, 0xf7, 0xcd, 0xa5, 0xc3, 0xe1, 0x9a, 0x4c, 0x44, 0xe6, 0x61, 0x6c, 0xc2, 0xf6, 0xf0, + 0xf5, 0x16, 0x96, 0x79, 0xdd, 0x46, 0xcc, 0xec, 0x3a, 0x30, 0xf1, 0xc8, 0xd6, 0x66, 0xe1, 0x3c, + 0x6b, 0xc8, 0x3f, 0xc8, 0x5e, 0x8e, 0x97, 0x7b, 0x8c, 0x35, 0x2d, 0x7e, 0x79, 0x3a, 0x14, 0x13, + 0x29, 0xf8, 0x55, 0x6f, 0xb9, 0x65, 0x37, 0xb0, 0xb5, 0x59, 0xe0, 0x75, 0x45, 0x45, 0xad, 0x5b, + 0xb2, 0x08, 0x83, 0x61, 0xbc, 0xad, 0x9b, 0x4f, 0xe3, 0xf7, 0x3f, 0xa2, 0x8a, 0xf5, 0xb2, 0x44, + 0xc3, 0x6b, 0xc2, 0x63, 0x7e, 0x7f, 0x3d, 0x84, 0x6a, 0x32, 0x1b, 0xf2, 0x08, 0x0c, 0xf1, 0xbc, + 0xd4, 0x35, 0x27, 0x34, 0xf7, 0xb4, 0x41, 0x0e, 0x43, 0xfb, 0xee, 0xec, 0x47, 0x60, 0x4c, 0x65, + 0x9a, 0x50, 0xe2, 0xfc, 0x59, 0xb9, 0xc4, 0xb9, 0xec, 0xab, 0x2d, 0x5e, 0xcc, 0xa2, 0x86, 0xc4, + 0x45, 0x2e, 0x81, 0xfe, 0x02, 0x9c, 0x4e, 0x26, 0x62, 0x32, 0xe1, 0x46, 0x68, 0x0a, 0x47, 0x3c, + 0xca, 0x04, 0x5f, 0x53, 0x98, 0x9e, 0xc5, 0xff, 0x9c, 0x91, 0xed, 0x03, 0x72, 0x0e, 0xb2, 0x38, + 0x12, 0x53, 0x61, 0xc1, 0x52, 0xac, 0x09, 0x9f, 0x15, 0xe5, 0x9d, 0x07, 0xfc, 0x1b, 0x3e, 0xbf, + 0xf4, 0x0b, 0x16, 0xe5, 0x0f, 0x8b, 0x58, 0x6a, 0x21, 0x01, 0xb9, 0x02, 0x50, 0x69, 0x2f, 0x35, + 0xcc, 0x3a, 0x56, 0xfd, 0xcf, 0x48, 0xa5, 0x12, 0x10, 0xca, 0x8b, 0xfe, 0x4b, 0x24, 0x4c, 0xa1, + 0xfc, 0x28, 0x9c, 0xb0, 0xb8, 0x2f, 0x2a, 0x94, 0xb0, 0x26, 0x44, 0x7d, 0x7a, 0x89, 0x88, 0xbc, + 0x17, 0x20, 0x5c, 0xae, 0xc4, 0x4e, 0x00, 0x4d, 0x19, 0x79, 0x75, 0x93, 0x4d, 0x99, 0x90, 0x9a, + 0x2d, 0xec, 0xf2, 0x74, 0xb9, 0x21, 0x6a, 0xfe, 0xe2, 0xc2, 0x1e, 0x99, 0x63, 0xe5, 0x09, 0x2c, + 0xda, 0x84, 0x2c, 0xc0, 0x78, 0x6c, 0x86, 0x14, 0xa5, 0x80, 0x51, 0xa3, 0x13, 0xa6, 0x57, 0xd9, + 0x70, 0x8c, 0xb5, 0x25, 0x8f, 0x42, 0xe6, 0xa6, 0x56, 0x16, 0x25, 0x46, 0x79, 0x75, 0xda, 0x48, + 0x81, 0x1f, 0x86, 0x25, 0x2f, 0x00, 0xf0, 0xbd, 0x73, 0xc5, 0x76, 0x3c, 0xb4, 0x78, 0x87, 0x79, + 0xf1, 0x76, 0x0f, 0xa1, 0x35, 0x66, 0x66, 0xc9, 0x1f, 0x1d, 0x12, 0x17, 0xff, 0x46, 0x3a, 0x66, + 0x76, 0x31, 0xc1, 0x8b, 0xb7, 0x90, 0x3a, 0x1f, 0x05, 0xef, 0xbf, 0x3a, 0x17, 0xbc, 0x44, 0x44, + 0x2e, 0x42, 0x7f, 0x85, 0x2d, 0x7a, 0x75, 0xbb, 0x21, 0x54, 0x01, 0xeb, 0x4c, 0xb5, 0x04, 0x4c, + 0x0b, 0xb0, 0xe4, 0x2a, 0xaf, 0xa8, 0x6d, 0x29, 0x75, 0xc4, 0xdb, 0x02, 0xa6, 0x16, 0xd4, 0x66, + 0x30, 0xd6, 0x26, 0x30, 0x0a, 0xb3, 0x61, 0x9b, 0x04, 0x93, 0x2f, 0xa0, 0x0b, 0x37, 0x5c, 0xbd, + 0xdb, 0x6d, 0xb8, 0x8a, 0x3f, 0x9e, 0x8a, 0x2f, 0x21, 0xe4, 0xd9, 0x78, 0xed, 0x5d, 0x9c, 0x61, + 0x03, 0xa0, 0xfc, 0xd4, 0xa0, 0x0a, 0x6f, 0xa4, 0x8a, 0x6e, 0x7a, 0xcf, 0x55, 0x74, 0x33, 0xbb, + 0xac, 0xa2, 0x5b, 0xfc, 0x6f, 0xd9, 0xae, 0x89, 0xba, 0x0e, 0xa5, 0xda, 0xda, 0x0b, 0x30, 0xcc, + 0x9f, 0x5e, 0x72, 0x63, 0x5b, 0x5f, 0x9e, 0x87, 0xa8, 0xa6, 0xf3, 0x51, 0xe9, 0x6a, 0x51, 0x4a, + 0xf2, 0x32, 0x0c, 0xf9, 0x1f, 0x80, 0xd5, 0x99, 0xa5, 0xaa, 0xc2, 0x81, 0xc1, 0xa6, 0xd4, 0x31, + 0x8e, 0x34, 0x20, 0xcf, 0xc1, 0x00, 0x9a, 0xeb, 0x2d, 0xbd, 0xee, 0x97, 0xf9, 0xe6, 0x75, 0xc1, + 0x7d, 0xa0, 0xbc, 0xce, 0x06, 0x94, 0xe4, 0xc3, 0x90, 0xc3, 0xed, 0x18, 0xdf, 0x17, 0x4b, 0x4e, + 0x1d, 0x5d, 0x64, 0xcb, 0x37, 0x70, 0x62, 0x71, 0xe0, 0x1b, 0x70, 0x04, 0x44, 0x36, 0xe0, 0x08, + 0x21, 0x8b, 0x70, 0xa2, 0xe2, 0x50, 0x03, 0x73, 0xe8, 0x4d, 0xdf, 0x6d, 0x39, 0x7c, 0x72, 0x14, + 0x13, 0x04, 0xda, 0x5f, 0x2d, 0x1f, 0xcd, 0x2c, 0x43, 0x81, 0x97, 0xeb, 0x07, 0x27, 0x34, 0x67, + 0x46, 0x39, 0x7f, 0x93, 0xeb, 0x74, 0x63, 0xdd, 0x76, 0x0c, 0xb6, 0x8d, 0x0e, 0x8c, 0x72, 0x21, + 0xe8, 0x35, 0x81, 0x92, 0x8d, 0xf2, 0x68, 0xa3, 0xb3, 0x2f, 0xc0, 0xa0, 0xf4, 0x25, 0x09, 0x2b, + 0xd2, 0x49, 0x79, 0x45, 0x1a, 0x90, 0x57, 0x9c, 0xef, 0x4b, 0x77, 0x48, 0x79, 0x79, 0x28, 0x7a, + 0xf7, 0x32, 0xf4, 0x89, 0x65, 0x47, 0x1c, 0x7a, 0xed, 0xf0, 0x5c, 0xc9, 0x6f, 0x45, 0x26, 0x94, + 0xe9, 0x46, 0xb6, 0x24, 0x05, 0xbc, 0xd3, 0xcb, 0x07, 0xd3, 0x4f, 0x01, 0x7a, 0x79, 0x5a, 0xf1, + 0xde, 0xd0, 0xfc, 0xf8, 0x28, 0xe6, 0x20, 0xe0, 0xf0, 0xe2, 0x1f, 0xa5, 0x3b, 0xe4, 0xf3, 0xbc, + 0x7f, 0x65, 0xc6, 0x16, 0x1e, 0x5f, 0x18, 0xe5, 0x29, 0x94, 0xdc, 0xb0, 0x58, 0x78, 0x7c, 0x30, + 0x33, 0x2a, 0x64, 0x22, 0x72, 0x19, 0xa0, 0xa2, 0x3b, 0x7a, 0x93, 0x7a, 0x6c, 0x2f, 0xce, 0x4f, + 0xb3, 0xd0, 0x0a, 0x69, 0x05, 0x50, 0x4d, 0xa2, 0x28, 0x7e, 0x47, 0xa6, 0x5b, 0xbe, 0xd3, 0x63, + 0xd9, 0xef, 0x46, 0xf6, 0x57, 0x61, 0x30, 0x90, 0x6c, 0x79, 0x0a, 0xed, 0x25, 0xd1, 0x26, 0x10, + 0x3e, 0xb6, 0x91, 0x88, 0xc8, 0x25, 0xfe, 0xae, 0x55, 0xf3, 0x4d, 0x8a, 0xf3, 0xde, 0xb0, 0xa8, + 0x68, 0xaf, 0x7b, 0x7a, 0xcd, 0x35, 0xdf, 0xa4, 0x5a, 0x80, 0x2e, 0xfe, 0x64, 0x3a, 0x31, 0x69, + 0xec, 0x71, 0x1f, 0xed, 0xa2, 0x8f, 0x12, 0x84, 0xc8, 0xd3, 0xdd, 0x1e, 0x0b, 0x71, 0x17, 0x42, + 0xfc, 0xfd, 0x74, 0x62, 0x72, 0xe0, 0x63, 0x21, 0xee, 0x66, 0xb6, 0x78, 0x02, 0x06, 0x34, 0x7b, + 0xdd, 0x9d, 0xc4, 0x3d, 0x11, 0x9f, 0x2b, 0x70, 0xa2, 0x76, 0xec, 0x75, 0xb7, 0x86, 0xbb, 0x1d, + 0x2d, 0x24, 0x28, 0xfe, 0x69, 0xba, 0x4b, 0xfa, 0xe4, 0x63, 0xc1, 0xbf, 0x95, 0x4b, 0xe4, 0xf7, + 0xa7, 0x23, 0xe9, 0x99, 0xef, 0x5f, 0x61, 0x5f, 0x01, 0xa8, 0xd6, 0x57, 0x69, 0x53, 0xc7, 0x7d, + 0x70, 0x6f, 0x78, 0x64, 0xe1, 0x22, 0x94, 0x6f, 0x83, 0x25, 0x92, 0xe2, 0x0f, 0xa6, 0x95, 0xfc, + 0xd4, 0xc7, 0xb2, 0xdb, 0xb1, 0xec, 0x02, 0xad, 0x13, 0x29, 0xb7, 0x8f, 0x25, 0xb7, 0x53, 0xc9, + 0x7d, 0x75, 0x5a, 0xc9, 0x4e, 0x7e, 0xdf, 0xca, 0x8e, 0x0d, 0xc0, 0x78, 0xd6, 0xf4, 0xfb, 0x56, + 0x93, 0x9e, 0x80, 0x01, 0x21, 0x87, 0x60, 0xa9, 0xe0, 0xf3, 0x3e, 0x07, 0xe2, 0x01, 0x6d, 0x40, + 0x50, 0xfc, 0x8a, 0x34, 0x44, 0xb3, 0xc6, 0xdf, 0xa7, 0x3a, 0xf4, 0xfd, 0xe9, 0x68, 0xbe, 0xfc, + 0xfb, 0x57, 0x7f, 0x2e, 0x03, 0x54, 0xdb, 0x4b, 0x75, 0x11, 0x95, 0xde, 0x2b, 0x9d, 0xf0, 0x07, + 0x50, 0x4d, 0xa2, 0x28, 0xfe, 0x79, 0x3a, 0x31, 0x89, 0xff, 0xfd, 0x2b, 0xc0, 0x67, 0xf0, 0x54, + 0xbc, 0x6e, 0x85, 0x13, 0x39, 0x1e, 0x42, 0xb2, 0xf1, 0xa7, 0x5e, 0x67, 0x06, 0x84, 0xe4, 0x3d, + 0x09, 0xe6, 0x1a, 0x5e, 0x12, 0x86, 0xe6, 0x9a, 0x7c, 0x98, 0x2f, 0x19, 0x6e, 0x3f, 0x93, 0xde, + 0xae, 0xe6, 0xc1, 0xfd, 0xbc, 0xaa, 0xf6, 0x55, 0xf4, 0x0d, 0xac, 0xcd, 0xc7, 0x7a, 0x62, 0x68, + 0xe2, 0xd4, 0xd6, 0x66, 0x61, 0xbc, 0xc5, 0x41, 0xf2, 0xb5, 0xb2, 0xa0, 0x2a, 0xfe, 0x6e, 0x6f, + 0x72, 0xc2, 0xfd, 0xfb, 0x57, 0x84, 0xe7, 0x20, 0x5b, 0xd1, 0xbd, 0x55, 0xa1, 0xc9, 0x78, 0x1b, + 0xd8, 0xd2, 0xbd, 0x55, 0x0d, 0xa1, 0xe4, 0x12, 0xf4, 0x6b, 0xfa, 0x3a, 0x3f, 0xf3, 0xe4, 0x37, + 0x67, 0x78, 0xb0, 0xe3, 0xe8, 0xeb, 0x35, 0x7e, 0xee, 0x19, 0xa0, 0x49, 0x11, 0x72, 0xf3, 0xd4, + 0x5b, 0xb5, 0x0d, 0x71, 0xf2, 0x0d, 0x5b, 0x9b, 0x85, 0x5c, 0x13, 0x21, 0x9a, 0xc0, 0xb0, 0x87, + 0x4d, 0xd8, 0xc6, 0x06, 0xde, 0x7c, 0x0d, 0xf1, 0x87, 0x2d, 0xd9, 0xc6, 0x86, 0x86, 0x50, 0xf2, + 0xf1, 0x14, 0xf4, 0xcd, 0x52, 0xdd, 0x60, 0x23, 0x64, 0xa0, 0x9b, 0x43, 0xd5, 0xeb, 0x07, 0xe3, + 0x50, 0x35, 0xbe, 0xca, 0x1f, 0x26, 0x2b, 0x8a, 0x78, 0x3e, 0x79, 0x15, 0xfa, 0x27, 0x75, 0x8f, + 0xae, 0xd8, 0xce, 0x06, 0xba, 0x88, 0x8d, 0x84, 0xae, 0xc9, 0x11, 0xfd, 0xf1, 0x89, 0xf8, 0xcd, + 0x58, 0x5d, 0xfc, 0xd2, 0x82, 0xc6, 0x4c, 0x2c, 0xfc, 0x66, 0x0e, 0x7d, 0xc4, 0x84, 0x58, 0xf8, + 0x15, 0x9e, 0x26, 0x30, 0xe1, 0xb1, 0xf2, 0x50, 0xf2, 0xb1, 0x32, 0x5a, 0x8f, 0xe8, 0x46, 0x3a, + 0x69, 0x1b, 0x14, 0x7d, 0xb1, 0x86, 0x85, 0xf5, 0x88, 0xd0, 0x5a, 0xdd, 0x36, 0x98, 0xf5, 0x18, + 0x90, 0x14, 0xbf, 0xd8, 0x0b, 0x89, 0xe9, 0xb9, 0x8f, 0x95, 0xfc, 0x58, 0xc9, 0x43, 0x25, 0x9f, + 0x8a, 0x29, 0xf9, 0xd9, 0x78, 0xc2, 0xf7, 0x7b, 0x54, 0xc3, 0x3f, 0x97, 0x8d, 0x95, 0x8b, 0xb8, + 0xbf, 0x77, 0x97, 0xa1, 0xf4, 0x7a, 0xb7, 0x95, 0x5e, 0x30, 0x20, 0x72, 0xdb, 0x0e, 0x88, 0xbe, + 0x9d, 0x0e, 0x88, 0xfe, 0x8e, 0x03, 0x22, 0x54, 0x90, 0x81, 0x8e, 0x0a, 0x52, 0x16, 0x83, 0x06, + 0x84, 0x0c, 0x55, 0x9f, 0xbb, 0xaa, 0xe7, 0xb4, 0xeb, 0x1e, 0xfa, 0x6d, 0x8c, 0xb0, 0xd1, 0x14, + 0xaa, 0xf3, 0x9f, 0x6d, 0x16, 0x72, 0x1c, 0xcb, 0x47, 0x58, 0xf1, 0x67, 0xb3, 0x5d, 0x6a, 0xbc, + 0x1c, 0x8a, 0x8e, 0x3c, 0x03, 0x99, 0x52, 0xab, 0x25, 0xf4, 0xe3, 0x84, 0x14, 0x41, 0xd1, 0xa1, + 0x15, 0xa3, 0x26, 0xef, 0x85, 0x4c, 0xe9, 0x76, 0x55, 0xa8, 0xc4, 0x59, 0x29, 0xaf, 0x8e, 0xf8, + 0x92, 0x8e, 0x6d, 0x6f, 0x57, 0xc9, 0x4b, 0x61, 0xc9, 0xc8, 0xd5, 0xb6, 0xb5, 0x26, 0x36, 0x8a, + 0xc2, 0x93, 0xdc, 0xf7, 0xe4, 0xa9, 0x33, 0x14, 0xdb, 0x2e, 0x2a, 0xb4, 0x8a, 0x36, 0xe5, 0x76, + 0xae, 0x4d, 0x7d, 0xdb, 0x6a, 0x53, 0xff, 0x4e, 0xb5, 0x69, 0x60, 0x07, 0xda, 0x04, 0xdb, 0x6a, + 0xd3, 0xe0, 0xfe, 0xb5, 0xa9, 0x05, 0x67, 0xe3, 0x75, 0xb9, 0x02, 0x8d, 0xd0, 0x80, 0xc4, 0xb1, + 0xc2, 0xb1, 0x04, 0xaf, 0xfe, 0xdb, 0x1c, 0x5b, 0x5b, 0x47, 0x74, 0xcd, 0x65, 0x78, 0xd9, 0xf5, + 0x32, 0xde, 0xba, 0xf8, 0x7d, 0xe9, 0xce, 0xe5, 0xc4, 0x8e, 0xe6, 0x14, 0xf7, 0xa5, 0x89, 0x52, + 0xca, 0x2a, 0x89, 0xcc, 0x3a, 0x4a, 0x59, 0x61, 0x9b, 0x24, 0x33, 0x1b, 0xce, 0xc6, 0x63, 0x2a, + 0x83, 0x0f, 0x7f, 0x0d, 0x48, 0x1c, 0x2b, 0x7a, 0x09, 0x3d, 0xb8, 0x68, 0x88, 0xad, 0xb5, 0x38, + 0x5a, 0xee, 0xa4, 0x78, 0x63, 0xec, 0xa4, 0x8e, 0x51, 0x9c, 0x47, 0xb5, 0x93, 0x12, 0x84, 0xa4, + 0x74, 0x52, 0x67, 0x21, 0xab, 0x9d, 0x94, 0x20, 0xb3, 0xef, 0x4d, 0x77, 0x2a, 0x44, 0xb7, 0x2f, + 0x89, 0x3d, 0x1e, 0xf7, 0x58, 0xc4, 0x38, 0x21, 0x37, 0xea, 0xaa, 0x38, 0x03, 0x43, 0xb2, 0x10, + 0x85, 0x94, 0x76, 0x22, 0xe0, 0x48, 0x3b, 0xf2, 0x12, 0x0c, 0xfa, 0xa1, 0x6d, 0xa1, 0x13, 0x13, + 0xba, 0x23, 0xfa, 0x13, 0xab, 0xe2, 0xc3, 0x24, 0x93, 0x93, 0x27, 0x20, 0x37, 0x83, 0x65, 0xd4, + 0xe5, 0x19, 0x99, 0x17, 0x56, 0x97, 0x5d, 0x8b, 0x38, 0x4d, 0xf1, 0xdf, 0xf5, 0x27, 0x04, 0xd9, + 0xef, 0x4b, 0x58, 0x92, 0x9a, 0xa4, 0xf7, 0xa4, 0x26, 0xcf, 0x42, 0x8e, 0xcf, 0xfc, 0x42, 0x80, + 0x23, 0x72, 0x26, 0xd2, 0xb6, 0xab, 0x34, 0x13, 0xb4, 0xfb, 0x14, 0xda, 0xf3, 0x30, 0x80, 0x21, + 0xe7, 0xd2, 0xd9, 0x39, 0x9e, 0x9d, 0x34, 0x19, 0x50, 0x3d, 0x73, 0x09, 0x49, 0xc9, 0x1d, 0x20, + 0x61, 0x0e, 0x07, 0x36, 0x4f, 0x50, 0xa3, 0xc4, 0x6f, 0x29, 0xbb, 0xbb, 0xf5, 0xbf, 0xd3, 0x8f, + 0x27, 0x30, 0xfd, 0xd6, 0x7c, 0xe2, 0xa6, 0x46, 0x4d, 0xee, 0x26, 0xf4, 0xef, 0x4f, 0x78, 0x02, + 0xd9, 0x80, 0x13, 0x01, 0x74, 0xc6, 0xb4, 0x4c, 0x77, 0x15, 0x1f, 0xdc, 0xb7, 0xed, 0x83, 0xdf, + 0x25, 0x1e, 0x5c, 0x08, 0x1f, 0xbc, 0x2c, 0xda, 0xc7, 0x9f, 0x9c, 0xf4, 0x0c, 0x72, 0x1d, 0xc6, + 0xaa, 0xab, 0xb6, 0xe3, 0x4d, 0x51, 0xb7, 0xee, 0x98, 0x2d, 0x4c, 0x9a, 0xc4, 0x17, 0x5d, 0xf4, + 0x8e, 0x77, 0x19, 0xae, 0x66, 0x84, 0x48, 0xd9, 0xf5, 0x5a, 0x6d, 0xc8, 0xe4, 0xae, 0x99, 0xee, + 0xda, 0x1c, 0xbd, 0x43, 0x1b, 0x62, 0x45, 0x46, 0xb9, 0x3b, 0xa6, 0xbb, 0x56, 0x6b, 0x30, 0xa8, + 0x2c, 0xf7, 0x80, 0x94, 0xbc, 0x04, 0x39, 0x7e, 0x5c, 0x28, 0xcc, 0x39, 0xa9, 0x52, 0x35, 0x83, + 0x76, 0x50, 0x31, 0xd1, 0x86, 0x7c, 0x38, 0x29, 0x4c, 0x6b, 0x30, 0x9a, 0x0e, 0x29, 0x46, 0xd0, + 0x81, 0x67, 0x42, 0xd4, 0x96, 0x6c, 0x6f, 0x0f, 0xed, 0xd1, 0xde, 0x7e, 0x0d, 0x86, 0x43, 0xc6, + 0x15, 0xdb, 0xc0, 0x0d, 0x8b, 0x14, 0xbd, 0x1c, 0x41, 0x76, 0xe0, 0x16, 0xe5, 0x40, 0x66, 0xa0, + 0x4f, 0xe4, 0x1b, 0x10, 0x11, 0x52, 0x0f, 0x25, 0x17, 0x76, 0xef, 0x34, 0x3e, 0x05, 0x3a, 0xe2, + 0x89, 0x3b, 0xba, 0x33, 0x4f, 0xdc, 0xe2, 0x8f, 0xa7, 0xe0, 0x04, 0x7b, 0x1b, 0x45, 0x96, 0xe4, + 0x0d, 0x00, 0x06, 0x16, 0xce, 0x93, 0x29, 0x74, 0x9e, 0x7c, 0x97, 0xfc, 0x8d, 0x4a, 0x83, 0xcb, + 0x21, 0x35, 0x77, 0x9c, 0x3c, 0xef, 0xc7, 0xc7, 0xac, 0xb5, 0x97, 0x68, 0x2d, 0xe6, 0x41, 0x29, + 0x71, 0x3f, 0xfb, 0x3e, 0x1e, 0x79, 0xb8, 0x57, 0x67, 0xc5, 0xef, 0x49, 0x77, 0x2c, 0x45, 0x7a, + 0x28, 0xcb, 0xf1, 0x04, 0xf4, 0xfb, 0x0e, 0xa5, 0x62, 0xa2, 0x8c, 0x05, 0x61, 0x74, 0x7a, 0xb6, + 0x8f, 0x27, 0x1f, 0x4a, 0xec, 0x15, 0xb1, 0x24, 0x3f, 0xd8, 0xa5, 0x1f, 0x14, 0x8e, 0x49, 0x5c, + 0x92, 0x05, 0x76, 0x88, 0xf6, 0xcb, 0x3d, 0x2f, 0xb0, 0x5f, 0x4e, 0x75, 0x2c, 0x19, 0x7b, 0x54, + 0x05, 0x56, 0xfc, 0x4c, 0xaf, 0x5f, 0xa9, 0x76, 0x5f, 0x9f, 0xf0, 0x04, 0x0c, 0x88, 0xe4, 0xc5, + 0xd1, 0x98, 0x11, 0x71, 0x85, 0x84, 0x57, 0x92, 0x01, 0x01, 0xdb, 0x5e, 0x4a, 0x01, 0x57, 0x52, + 0xcc, 0x88, 0x14, 0x6c, 0xa5, 0x49, 0x24, 0x6c, 0x03, 0x39, 0x7d, 0xd7, 0xf4, 0x70, 0x37, 0xca, + 0xfa, 0x32, 0xc3, 0x37, 0x90, 0xf4, 0xae, 0xe9, 0xf1, 0xbd, 0x68, 0x80, 0x66, 0x9b, 0x43, 0x61, + 0x9d, 0xf4, 0x86, 0x9b, 0x43, 0xbe, 0x6d, 0x0d, 0x6c, 0x91, 0x27, 0x60, 0x40, 0x04, 0x62, 0x08, + 0xf7, 0x46, 0xf1, 0xb6, 0x22, 0x74, 0x03, 0xdf, 0x36, 0x20, 0x60, 0x1c, 0x35, 0xba, 0x12, 0x3a, + 0x74, 0x23, 0x47, 0x07, 0x21, 0x9a, 0xc0, 0x90, 0xab, 0x30, 0x52, 0xf5, 0x74, 0xcb, 0xd0, 0x1d, + 0x63, 0xa1, 0xed, 0xb5, 0xda, 0x9e, 0x7c, 0x18, 0xe2, 0x7a, 0x86, 0xdd, 0xf6, 0x34, 0x85, 0x82, + 0x3c, 0x05, 0xc3, 0x3e, 0x64, 0xda, 0x71, 0x6c, 0x47, 0xde, 0xf1, 0xba, 0x9e, 0x41, 0x1d, 0x47, + 0x8b, 0x12, 0x90, 0xf7, 0xc0, 0x70, 0xd9, 0x0a, 0x72, 0xf1, 0x6a, 0x73, 0x62, 0xff, 0x8b, 0xd1, + 0xed, 0x66, 0x80, 0xa8, 0xb5, 0x9d, 0x86, 0x16, 0x25, 0x24, 0xcf, 0xc1, 0x50, 0xa5, 0xa1, 0x7b, + 0xcc, 0xcc, 0x44, 0x13, 0x6a, 0x30, 0x0c, 0x3c, 0x69, 0x09, 0x38, 0x77, 0x40, 0x88, 0x90, 0xc9, + 0xcd, 0xd0, 0x6a, 0x1b, 0x4a, 0x68, 0xc6, 0xcc, 0x36, 0x2d, 0x42, 0x46, 0xde, 0x0f, 0xa3, 0xfe, + 0x6f, 0x91, 0x7d, 0x5f, 0x44, 0x0f, 0xa3, 0xad, 0x1b, 0xb4, 0xbc, 0xc3, 0x71, 0x9a, 0x4a, 0x5c, + 0xfc, 0xcd, 0x94, 0x88, 0xd9, 0x09, 0xf4, 0xeb, 0x45, 0x18, 0xa9, 0xb6, 0x97, 0x02, 0xbb, 0xc4, + 0xcf, 0xa0, 0x20, 0x82, 0x0e, 0x24, 0x0c, 0x3f, 0xcd, 0x88, 0x90, 0x32, 0xb1, 0xf9, 0xfa, 0x8e, + 0x41, 0xee, 0x42, 0x41, 0x51, 0x6c, 0x41, 0xd8, 0x01, 0x86, 0xc6, 0x6b, 0x51, 0x42, 0xa6, 0xa8, + 0x61, 0xa4, 0xa8, 0xac, 0xa8, 0x52, 0x78, 0xa9, 0x26, 0x91, 0x48, 0xba, 0x92, 0xed, 0xa4, 0x2b, + 0xc5, 0x9b, 0x22, 0xec, 0xf3, 0xd6, 0x7c, 0xf0, 0x79, 0xe7, 0x21, 0x7b, 0x6b, 0x3e, 0xf8, 0x28, + 0x3c, 0x1b, 0xbd, 0xd3, 0x64, 0xbc, 0x11, 0x4c, 0x1e, 0x85, 0xdc, 0xad, 0x79, 0xec, 0x37, 0xfe, + 0xe6, 0x83, 0x5b, 0x9b, 0x85, 0xbe, 0x3b, 0xa2, 0xc7, 0x04, 0xaa, 0xf8, 0xd9, 0x6c, 0x98, 0x87, + 0x69, 0x5f, 0x63, 0xb9, 0xac, 0x08, 0x5f, 0xcc, 0x4b, 0xa7, 0x22, 0x39, 0x9d, 0x3a, 0x99, 0x34, + 0xd1, 0x6e, 0x5b, 0x88, 0x7d, 0xaa, 0xba, 0x35, 0x55, 0xd0, 0x6a, 0x4a, 0x08, 0x55, 0x50, 0xea, + 0x44, 0xd0, 0xdb, 0x79, 0x22, 0x78, 0x11, 0x46, 0xb8, 0x5b, 0x21, 0x4f, 0x0b, 0xe5, 0xf9, 0xfb, + 0x06, 0x54, 0x19, 0xea, 0x63, 0x6a, 0xe8, 0x5f, 0xa7, 0x29, 0xa4, 0x09, 0xe3, 0x39, 0xb7, 0xfb, + 0xf1, 0xdc, 0xb7, 0xdd, 0x78, 0x7e, 0x06, 0xfa, 0x4b, 0x95, 0x32, 0x27, 0xee, 0x97, 0x82, 0x8a, + 0x5a, 0x66, 0x8d, 0x32, 0xa0, 0x6c, 0xaa, 0xf9, 0x84, 0x6c, 0xff, 0x28, 0x26, 0xb8, 0x01, 0xe9, + 0x44, 0x0f, 0x21, 0xf2, 0xfe, 0x91, 0xd3, 0x14, 0xb7, 0xd2, 0xf1, 0x4a, 0xde, 0xf7, 0xef, 0x29, + 0xf9, 0xa5, 0x68, 0xf4, 0x06, 0xaa, 0x02, 0x9e, 0x40, 0xca, 0xc1, 0x63, 0xfc, 0x20, 0xf2, 0x2a, + 0xf4, 0x5f, 0xa7, 0x1b, 0x3c, 0xd0, 0x28, 0x17, 0x5a, 0xd1, 0x6b, 0x02, 0x26, 0x77, 0x8d, 0x4f, + 0x57, 0xfc, 0xd1, 0x74, 0xbc, 0x46, 0xf9, 0xfd, 0x2b, 0xec, 0xa7, 0xa0, 0x0f, 0x45, 0x59, 0xf6, + 0x7d, 0x4c, 0x50, 0x80, 0x28, 0xee, 0x68, 0xc8, 0x9b, 0x4f, 0x56, 0xfc, 0xd6, 0x9c, 0x5a, 0xb8, + 0xfe, 0xfe, 0x95, 0xde, 0x8b, 0x30, 0x38, 0x69, 0x5b, 0x2e, 0x66, 0xf3, 0xad, 0xfb, 0x0a, 0x8b, + 0xd1, 0x9f, 0xf5, 0x10, 0x2c, 0x1f, 0x97, 0x48, 0xd4, 0x7b, 0x51, 0x5e, 0xb6, 0xd5, 0x47, 0x91, + 0xe3, 0x42, 0xdf, 0x17, 0x6e, 0xf5, 0x97, 0x18, 0x50, 0x3d, 0x9c, 0x09, 0x49, 0xc9, 0x4d, 0xe8, + 0x9f, 0x5c, 0x35, 0x1b, 0x86, 0x43, 0x2d, 0x0c, 0x50, 0x93, 0xf6, 0xe8, 0xd1, 0xbe, 0xbc, 0x8c, + 0xff, 0x22, 0x2d, 0x7f, 0x9d, 0xba, 0x68, 0x16, 0xc9, 0x68, 0x21, 0x60, 0x67, 0xbf, 0x3e, 0x0d, + 0x10, 0x36, 0x20, 0x0f, 0x43, 0x3a, 0x58, 0x1f, 0xd1, 0x2f, 0x3a, 0xa2, 0x41, 0x69, 0xb4, 0x11, + 0xc5, 0xd8, 0x4e, 0x6f, 0x3b, 0xb6, 0x6f, 0x42, 0x8e, 0x5f, 0xb1, 0x62, 0xe8, 0xa2, 0x94, 0x43, + 0xab, 0xe3, 0x0b, 0x5f, 0x46, 0x7a, 0x3e, 0xd7, 0xe2, 0x96, 0x33, 0x32, 0xd7, 0x72, 0x66, 0x67, + 0xeb, 0xd0, 0x8b, 0x7f, 0x91, 0x0b, 0x90, 0x45, 0x29, 0xa6, 0xf0, 0xe2, 0x04, 0xed, 0x0c, 0x45, + 0x7e, 0x88, 0x67, 0xdd, 0x34, 0x69, 0x5b, 0x9e, 0x88, 0x53, 0x4f, 0x5d, 0x1c, 0x12, 0x72, 0x11, + 0xb0, 0x88, 0x5c, 0x04, 0xac, 0xf8, 0x53, 0x69, 0x18, 0x0f, 0x5e, 0x93, 0x59, 0x14, 0x78, 0xa4, + 0x71, 0xdf, 0x0e, 0x93, 0x17, 0x00, 0x30, 0x3d, 0x16, 0x93, 0xa7, 0x1f, 0x13, 0x8c, 0xa3, 0x04, + 0x19, 0xa1, 0xda, 0x46, 0xce, 0x1b, 0x42, 0xe2, 0xe2, 0x4f, 0xa7, 0x62, 0x75, 0xf8, 0xf7, 0x25, + 0x47, 0x79, 0x3b, 0x96, 0xde, 0xe3, 0xfe, 0xd5, 0xef, 0x8b, 0xcc, 0xee, 0xfa, 0x22, 0xfa, 0x2d, + 0x07, 0xb0, 0x25, 0x3d, 0xcc, 0x6f, 0xf9, 0x62, 0x1a, 0x88, 0xa8, 0x58, 0x5f, 0x6a, 0x7b, 0xab, + 0x25, 0x0f, 0xfb, 0xee, 0x68, 0xaa, 0x78, 0x78, 0x56, 0x9e, 0xdd, 0xc5, 0x59, 0xf9, 0x47, 0x60, + 0x54, 0xa9, 0xd5, 0x8f, 0xf3, 0xbf, 0x94, 0xbb, 0x30, 0xb9, 0xa2, 0x7f, 0xe7, 0xc4, 0x6a, 0x11, + 0xb2, 0xe2, 0x5f, 0xa4, 0xe0, 0x5c, 0x32, 0x87, 0x7b, 0x64, 0x18, 0x24, 0x08, 0x20, 0xf3, 0x97, + 0x23, 0x80, 0x03, 0x38, 0xff, 0x3a, 0xda, 0x02, 0xb8, 0x47, 0x26, 0x8f, 0xb7, 0x5a, 0x00, 0xdf, + 0x9a, 0x82, 0x42, 0x37, 0x01, 0x94, 0xf6, 0x19, 0x60, 0x70, 0x00, 0x32, 0x28, 0xfe, 0x9b, 0x14, + 0x9c, 0x58, 0x58, 0xf3, 0x74, 0x1f, 0xe0, 0x1e, 0x80, 0x72, 0x86, 0x17, 0x33, 0xe9, 0x3d, 0x5c, + 0xcc, 0xcc, 0x41, 0x1f, 0x7f, 0x07, 0x43, 0xf4, 0xc6, 0x63, 0x72, 0x25, 0x6a, 0xe5, 0x3d, 0x3b, + 0x5d, 0x7b, 0xf8, 0x2c, 0x8a, 0x3f, 0x91, 0x82, 0x51, 0xd6, 0xae, 0xba, 0x61, 0xd5, 0x67, 0x74, + 0xb3, 0xd1, 0x76, 0x0e, 0xf3, 0xdb, 0xf6, 0x74, 0xad, 0x59, 0xfc, 0xed, 0x34, 0x9c, 0xc4, 0x12, + 0xe3, 0x41, 0x3a, 0x1e, 0x51, 0xb1, 0xf8, 0xf0, 0x3e, 0xe4, 0x20, 0xce, 0xd1, 0xf7, 0xb6, 0x6e, + 0x2d, 0xc2, 0x48, 0x54, 0x16, 0x62, 0xd9, 0x7a, 0x28, 0x52, 0xa2, 0x3d, 0xc0, 0x76, 0x78, 0x0b, + 0x85, 0x47, 0xf1, 0xb3, 0x69, 0x18, 0x0b, 0x8b, 0xd7, 0xdd, 0x23, 0x2b, 0xd4, 0xde, 0x2e, 0xc1, + 0xcb, 0x30, 0x1a, 0x7e, 0xc9, 0xa2, 0xe9, 0x35, 0xfc, 0x8b, 0x70, 0xbc, 0x9a, 0x15, 0x89, 0xf6, + 0x1a, 0xa6, 0xeb, 0xd5, 0x3c, 0x86, 0x8c, 0xa4, 0x7c, 0x8b, 0xb6, 0x53, 0xa4, 0x72, 0x8f, 0x2c, + 0x5b, 0xf7, 0x98, 0x54, 0xee, 0x91, 0xb5, 0xec, 0xd0, 0xa5, 0xf2, 0x9d, 0x69, 0x38, 0x1d, 0xc2, + 0xe6, 0x69, 0x73, 0xe9, 0x9e, 0xb1, 0xf4, 0x3e, 0x28, 0xf7, 0x17, 0x7f, 0x33, 0x21, 0xa5, 0x87, + 0xa3, 0x39, 0x7a, 0x42, 0x7c, 0x07, 0x9e, 0x31, 0x3e, 0x7b, 0x9b, 0xc4, 0x12, 0x85, 0x75, 0x8f, + 0x0c, 0xaf, 0xa3, 0x21, 0xac, 0x7b, 0x64, 0xd4, 0xdd, 0x7b, 0xc2, 0xfa, 0xa1, 0x34, 0x5c, 0x48, + 0x16, 0x56, 0xa9, 0xd1, 0x98, 0xb1, 0x9d, 0x10, 0x77, 0x2c, 0xbc, 0x98, 0xf0, 0x3e, 0x1d, 0x99, + 0xd9, 0x35, 0x7a, 0xc7, 0xa4, 0xeb, 0x87, 0x2e, 0xa6, 0x19, 0xc8, 0xf1, 0x37, 0xe9, 0x2c, 0x1c, + 0x8e, 0xef, 0x64, 0xb2, 0x89, 0xef, 0x78, 0x16, 0xc4, 0x15, 0xf5, 0xce, 0x44, 0xc2, 0x69, 0x8b, + 0x7f, 0x9a, 0x82, 0xe1, 0x52, 0xdb, 0x30, 0x3d, 0x3c, 0xd5, 0xdc, 0xef, 0xb5, 0xdf, 0x5e, 0xcf, + 0x48, 0xf6, 0xb6, 0xba, 0xbd, 0xcf, 0x3f, 0xad, 0xcd, 0x2a, 0x55, 0x63, 0x83, 0xef, 0x99, 0xa2, + 0x9e, 0x6e, 0x36, 0xd4, 0xf6, 0x22, 0xcb, 0xd6, 0x8f, 0xa6, 0x61, 0x3c, 0x46, 0x4a, 0x2e, 0x44, + 0xf2, 0x5a, 0xe2, 0xb9, 0xab, 0xe2, 0xf1, 0xc4, 0x33, 0x5c, 0xee, 0xe2, 0xa8, 0xf8, 0x02, 0x64, + 0xa7, 0xf4, 0x0d, 0xfe, 0x6d, 0xbd, 0x9c, 0xa5, 0xa1, 0x6f, 0xc8, 0x47, 0x8a, 0x88, 0x27, 0x4b, + 0x70, 0x2a, 0xb8, 0x42, 0x5c, 0x34, 0x9b, 0xb4, 0x6c, 0xcd, 0x9b, 0x8d, 0x86, 0xe9, 0x0a, 0x77, + 0x85, 0x27, 0xb6, 0x36, 0x0b, 0x17, 0x3d, 0xdb, 0xd3, 0x1b, 0xb5, 0xf0, 0xea, 0xd1, 0x33, 0x9b, + 0xb4, 0x66, 0x5a, 0xb5, 0x26, 0x52, 0x4a, 0x2c, 0x93, 0x59, 0x91, 0x32, 0x10, 0x4c, 0xa2, 0x54, + 0xd7, 0x2d, 0x8b, 0x1a, 0x65, 0x6b, 0x62, 0xc3, 0xa3, 0xdc, 0xcd, 0x21, 0xc3, 0xcf, 0x3c, 0x79, + 0xb6, 0x25, 0x8e, 0x66, 0x8c, 0x97, 0x18, 0x81, 0x96, 0xd0, 0xa8, 0xf8, 0xc3, 0x59, 0x18, 0xaf, + 0xd2, 0x7a, 0xdb, 0x31, 0xbd, 0x0d, 0x0d, 0x43, 0x88, 0x8e, 0x96, 0xfa, 0xf8, 0x3d, 0x9d, 0xdd, + 0xa6, 0xa7, 0xaf, 0x40, 0x9f, 0xef, 0x81, 0xc0, 0x6f, 0x50, 0x30, 0x3c, 0x54, 0x38, 0x1e, 0xc8, + 0x57, 0x50, 0x82, 0x8a, 0x34, 0xe0, 0xec, 0x22, 0xeb, 0xa6, 0xe4, 0xce, 0xcc, 0xed, 0xa1, 0x33, + 0xbb, 0xf0, 0x23, 0x1f, 0x80, 0x33, 0x88, 0x4d, 0xe8, 0xd6, 0x3e, 0x7c, 0x14, 0xda, 0x7a, 0xfc, + 0x51, 0xc9, 0x9d, 0xdb, 0xa9, 0x3d, 0xf9, 0x20, 0x0c, 0x05, 0x03, 0xc4, 0xa4, 0xae, 0xb8, 0x9a, + 0xe9, 0x32, 0xce, 0x78, 0xb6, 0x70, 0x06, 0xc6, 0xa0, 0x8c, 0x68, 0xc6, 0xe9, 0x08, 0xaf, 0xe2, + 0x6f, 0xa6, 0xe0, 0xec, 0xf4, 0x5d, 0x9e, 0xec, 0x17, 0x11, 0x55, 0xcf, 0x76, 0xf4, 0x15, 0x3a, + 0x6d, 0xe9, 0x4b, 0x8d, 0xc3, 0x5f, 0xf9, 0xdf, 0x07, 0x7d, 0x06, 0xff, 0x28, 0xa1, 0x53, 0x8f, + 0x06, 0xce, 0xeb, 0x09, 0x2f, 0x2d, 0xbe, 0x5f, 0xf3, 0xdb, 0x14, 0x7f, 0x2b, 0x05, 0x0f, 0x26, + 0x12, 0x9a, 0xee, 0xdb, 0xe1, 0xf3, 0x3e, 0x9d, 0xe9, 0xf0, 0x79, 0x62, 0x12, 0xbd, 0x04, 0x63, + 0xa6, 0xe5, 0xd1, 0x15, 0x87, 0x3b, 0x13, 0x85, 0x09, 0x5d, 0xb5, 0x51, 0x09, 0x8e, 0xa3, 0xeb, + 0x59, 0x08, 0x9c, 0xb7, 0x1d, 0xdf, 0x9d, 0xdf, 0xad, 0xb5, 0x1d, 0x93, 0x8f, 0x4b, 0xed, 0xa4, + 0xab, 0xf8, 0xfa, 0xbb, 0x37, 0x1d, 0x93, 0x3d, 0x40, 0xf7, 0x56, 0xa9, 0xa5, 0xd7, 0xd6, 0x6d, + 0x67, 0x0d, 0xfd, 0x6e, 0xf8, 0xe0, 0xd4, 0x46, 0x39, 0xfc, 0xb6, 0x0f, 0x26, 0x8f, 0xc2, 0xf0, + 0x4a, 0xa3, 0x4d, 0x83, 0x22, 0x00, 0xfc, 0x32, 0x53, 0x1b, 0x62, 0xc0, 0xe0, 0x0a, 0xe8, 0x3c, + 0x00, 0x12, 0x61, 0xed, 0x78, 0x7e, 0x73, 0xa9, 0x0d, 0x30, 0xc8, 0xa2, 0xe8, 0xae, 0xb3, 0x5c, + 0xab, 0xb9, 0x90, 0x6a, 0x0d, 0xdb, 0x5a, 0xa9, 0x79, 0xd4, 0x69, 0xe2, 0x8b, 0xa2, 0xdb, 0x85, + 0x76, 0x1a, 0x29, 0xf0, 0x6e, 0xc8, 0x9d, 0xb3, 0xad, 0x95, 0x45, 0xea, 0x34, 0xd9, 0xab, 0x3e, + 0x01, 0x44, 0xbc, 0xaa, 0x83, 0xa7, 0x3a, 0xfc, 0xe3, 0xd0, 0xef, 0x42, 0x13, 0x1f, 0xc1, 0x8f, + 0x7b, 0xf0, 0xc3, 0x0a, 0x30, 0xc8, 0x33, 0xa1, 0x73, 0xa1, 0xa1, 0x73, 0x96, 0x06, 0x1c, 0x84, + 0xf2, 0x3a, 0x0d, 0xc2, 0x17, 0x88, 0xfb, 0x5f, 0x69, 0xe2, 0x57, 0xf1, 0x13, 0x19, 0x20, 0x78, + 0xe8, 0x11, 0x58, 0x0d, 0x58, 0x1a, 0x7d, 0x3f, 0x8a, 0x16, 0x4e, 0xab, 0xe9, 0x5d, 0x4d, 0xab, + 0xa3, 0x56, 0xbb, 0x59, 0xd3, 0x5b, 0xad, 0xda, 0xb2, 0xd9, 0xc0, 0x44, 0x05, 0xb8, 0xf0, 0x69, + 0xc3, 0x56, 0xbb, 0x59, 0x6a, 0xb5, 0x66, 0x38, 0x90, 0xbc, 0x13, 0xc6, 0x19, 0x1d, 0x76, 0x52, + 0x40, 0x89, 0xfe, 0x38, 0x1a, 0x63, 0x80, 0xce, 0x53, 0x3e, 0xed, 0x03, 0xd0, 0x2f, 0x78, 0xf2, + 0xb5, 0xaa, 0x57, 0xeb, 0xe3, 0xcc, 0x5c, 0xd6, 0x73, 0x01, 0x1b, 0x3e, 0xb9, 0xf6, 0x6a, 0x03, + 0x7e, 0x7b, 0x2c, 0x98, 0x63, 0xb5, 0x79, 0x72, 0x6d, 0xec, 0xd6, 0x5e, 0x2d, 0xf8, 0x4d, 0x2e, + 0xc0, 0x08, 0xe3, 0x12, 0x08, 0x8c, 0xd7, 0x18, 0xe9, 0xd5, 0x14, 0x28, 0xb9, 0x0a, 0x27, 0x23, + 0x10, 0x6e, 0x83, 0x72, 0xdf, 0x99, 0x5e, 0x2d, 0x11, 0x57, 0xfc, 0xc1, 0x0c, 0x0c, 0xb1, 0xee, + 0x60, 0xeb, 0xd3, 0x21, 0x75, 0xc4, 0x19, 0xe8, 0xb3, 0x9d, 0x95, 0x5a, 0xdb, 0x69, 0x88, 0xb1, + 0x97, 0xb3, 0x9d, 0x95, 0x9b, 0x4e, 0x83, 0x9c, 0x82, 0x1c, 0xeb, 0x1d, 0xd3, 0x10, 0x43, 0xac, + 0x57, 0x6f, 0xb5, 0xca, 0x06, 0x29, 0xf1, 0x0e, 0xc1, 0xfa, 0x14, 0xb5, 0x3a, 0x6e, 0xed, 0xb9, + 0xd7, 0x45, 0x2f, 0x5f, 0xf1, 0x62, 0x48, 0xec, 0x27, 0xac, 0x5a, 0xc1, 0x0f, 0x02, 0x14, 0x16, + 0x06, 0x6e, 0x4b, 0x0c, 0xde, 0x27, 0x2a, 0x0b, 0x81, 0x0c, 0x59, 0xf0, 0x4d, 0x8c, 0x41, 0xa6, + 0x80, 0x84, 0x54, 0x4d, 0xdb, 0x30, 0x97, 0x4d, 0xca, 0xe3, 0xa4, 0x7b, 0xf9, 0xcd, 0x76, 0x1c, + 0xab, 0x8d, 0xf9, 0x4c, 0xe6, 0x05, 0x84, 0xbc, 0xc8, 0x95, 0x90, 0xd3, 0xe1, 0xda, 0xc7, 0xfb, + 0x96, 0xdb, 0x69, 0x0a, 0x0a, 0x35, 0x13, 0xdb, 0xe3, 0x42, 0x58, 0xbc, 0x01, 0xc3, 0xe8, 0x40, + 0x5e, 0xa5, 0x0d, 0x5a, 0xf7, 0x6c, 0x87, 0x3c, 0x00, 0x99, 0xeb, 0xbe, 0x0b, 0x39, 0x8f, 0x0a, + 0x5a, 0xa3, 0x1b, 0x1a, 0x83, 0x91, 0x62, 0xe0, 0x06, 0x90, 0xc6, 0x7b, 0x63, 0xf4, 0xd4, 0xe2, + 0xb7, 0xfb, 0xfe, 0x9d, 0x7e, 0xf1, 0x6f, 0x0e, 0xc0, 0x58, 0xb5, 0x52, 0x9e, 0x99, 0x99, 0xae, + 0xde, 0x2a, 0x4f, 0x61, 0x0d, 0x7a, 0xe3, 0x50, 0xec, 0xa4, 0x59, 0x80, 0x49, 0xdb, 0xb2, 0x28, + 0xd6, 0x1c, 0x0e, 0xaa, 0x3e, 0xf9, 0x7e, 0x0b, 0x01, 0xa6, 0x03, 0x0f, 0xa9, 0x2d, 0xb9, 0x04, + 0xfd, 0xfc, 0x8b, 0xca, 0x53, 0xc2, 0x7e, 0x42, 0x1f, 0x3a, 0xb7, 0x65, 0x2e, 0x2f, 0xa3, 0x43, + 0x63, 0x80, 0x26, 0x17, 0xa0, 0x6f, 0xea, 0x46, 0xb5, 0x5a, 0xba, 0xe1, 0x5f, 0xad, 0x63, 0x04, + 0xb8, 0x61, 0xb9, 0x35, 0x57, 0xb7, 0x5c, 0xcd, 0x47, 0x92, 0x47, 0x21, 0x57, 0xae, 0x20, 0x19, + 0xcf, 0x6b, 0x82, 0x0e, 0x8a, 0x66, 0x8b, 0x53, 0x09, 0x14, 0x3e, 0xf7, 0x56, 0x79, 0x4a, 0xf2, + 0x2f, 0xe1, 0xcf, 0xbd, 0x63, 0x1a, 0xdc, 0x89, 0x34, 0x40, 0x93, 0xe7, 0x60, 0xa8, 0x4a, 0x1d, + 0x53, 0x6f, 0xdc, 0x68, 0xe3, 0xd6, 0x53, 0xca, 0x93, 0xee, 0x22, 0xbc, 0x66, 0x21, 0x42, 0x8b, + 0x90, 0x91, 0x73, 0x90, 0x9d, 0x35, 0x2d, 0x3f, 0xc8, 0x18, 0xa3, 0x50, 0x57, 0x4d, 0xcb, 0xd3, + 0x10, 0x4a, 0x1e, 0x85, 0xcc, 0xb5, 0xc5, 0xb2, 0xf0, 0x99, 0x45, 0x5e, 0x6f, 0x78, 0x91, 0x9c, + 0xeb, 0xd7, 0x16, 0xcb, 0xe4, 0x39, 0x18, 0x60, 0x8b, 0x22, 0xb5, 0xea, 0xd4, 0xcd, 0x0f, 0xe2, + 0xc7, 0x70, 0x9f, 0x3c, 0x1f, 0x28, 0x3b, 0xc1, 0x04, 0x94, 0xe4, 0x3a, 0x8c, 0xf9, 0x25, 0x83, + 0x83, 0x3c, 0xf3, 0x43, 0xe1, 0x69, 0xdd, 0xba, 0xc0, 0x25, 0xa5, 0x9a, 0x8f, 0x35, 0x24, 0x06, + 0xe4, 0x55, 0x18, 0xdb, 0x27, 0x4a, 0x7e, 0xb4, 0x17, 0xb7, 0x36, 0x0b, 0x8f, 0xc5, 0x98, 0xd6, + 0x1c, 0x41, 0x25, 0x71, 0xef, 0xc8, 0x89, 0x7c, 0x08, 0xa0, 0xe4, 0x79, 0x8e, 0xb9, 0xd4, 0x66, + 0xe6, 0xe6, 0x48, 0xf7, 0x38, 0xd9, 0xe2, 0xd6, 0x66, 0xe1, 0xa4, 0x1e, 0x90, 0x27, 0x46, 0xcb, + 0x4a, 0xec, 0xc8, 0xcb, 0x30, 0xc4, 0x56, 0x3c, 0x7f, 0x14, 0x8a, 0x38, 0x14, 0x34, 0x31, 0xd9, + 0xda, 0x58, 0x73, 0x05, 0x42, 0x36, 0x31, 0xe5, 0x06, 0xe4, 0x4b, 0x60, 0x24, 0x32, 0x8e, 0x79, + 0x3d, 0x1e, 0xc9, 0x0b, 0x35, 0x82, 0xe5, 0x39, 0xb1, 0x31, 0xba, 0x24, 0x60, 0x1d, 0xc9, 0x89, + 0x1d, 0xe5, 0x45, 0x3e, 0x14, 0x16, 0x88, 0xf6, 0xe5, 0xc2, 0x8b, 0x4a, 0xf0, 0x82, 0x3d, 0x8f, + 0x6f, 0x6d, 0x16, 0x1e, 0x89, 0x8b, 0x17, 0xcb, 0x4c, 0xc8, 0xdb, 0xb4, 0x44, 0x1e, 0xa4, 0x06, + 0x30, 0x59, 0xf2, 0xcb, 0xce, 0x62, 0x09, 0x1f, 0xe9, 0x9e, 0x2c, 0xc4, 0x4c, 0x52, 0x47, 0xd4, + 0xd0, 0xf7, 0x4d, 0x2f, 0xe1, 0xde, 0xa5, 0xd7, 0x6c, 0x41, 0x22, 0x3b, 0xae, 0x84, 0x0d, 0x8b, + 0x7f, 0x90, 0x86, 0x93, 0xa5, 0xb6, 0xb7, 0x5a, 0x71, 0xa8, 0x08, 0xff, 0x3a, 0x80, 0xf3, 0xc9, + 0xbd, 0xad, 0x53, 0x7b, 0xf4, 0xf1, 0x50, 0x66, 0xb3, 0xec, 0x3e, 0x66, 0xb3, 0x65, 0x18, 0x2d, + 0x19, 0x4d, 0xd3, 0x2a, 0xf1, 0x62, 0xec, 0xf3, 0x33, 0x25, 0x5c, 0xf7, 0xa4, 0x6c, 0x2d, 0x0a, + 0x5a, 0x7c, 0x0f, 0x2f, 0x6d, 0xc4, 0x50, 0x35, 0x9d, 0xe3, 0x6a, 0xcd, 0x65, 0xbd, 0x56, 0x5f, + 0xd5, 0xad, 0x15, 0xb6, 0xb6, 0x29, 0xad, 0x8a, 0x9f, 0x4d, 0xc3, 0x79, 0xbf, 0x24, 0x05, 0xf5, + 0x98, 0x72, 0x98, 0xd6, 0x4a, 0xa4, 0x42, 0xea, 0x7d, 0x28, 0xfd, 0xe2, 0x67, 0xd2, 0x70, 0x4e, + 0x0d, 0xe8, 0xbd, 0xef, 0x85, 0xf2, 0xfb, 0x69, 0x18, 0xe7, 0x56, 0x65, 0x45, 0xf7, 0x56, 0x27, + 0xb9, 0x46, 0xed, 0x4b, 0x12, 0x97, 0xa0, 0x9f, 0xb3, 0x09, 0xa2, 0x6b, 0x70, 0xe9, 0xe4, 0xca, + 0x8a, 0x4b, 0xb6, 0x8f, 0x26, 0x37, 0xe0, 0x64, 0x69, 0x79, 0x99, 0xd6, 0xbd, 0xb0, 0x98, 0xcf, + 0x8d, 0xb0, 0x26, 0x07, 0x2f, 0x9a, 0x22, 0xf0, 0x61, 0x91, 0x2c, 0x0c, 0x24, 0x48, 0x6c, 0x47, + 0x16, 0xe1, 0xb4, 0x0a, 0xaf, 0xf2, 0xfd, 0x6b, 0x56, 0xaa, 0xa3, 0x12, 0xe3, 0xc8, 0xff, 0xd3, + 0x3a, 0xb4, 0x4d, 0x7a, 0x4b, 0xb4, 0x0b, 0x7a, 0xbb, 0xbd, 0x25, 0x1a, 0x09, 0x89, 0xed, 0x8a, + 0x3f, 0x9a, 0x01, 0xa8, 0xb6, 0x74, 0xcb, 0xc2, 0x04, 0x75, 0xf7, 0xaf, 0x3b, 0xe4, 0xb3, 0x91, + 0xe8, 0xa7, 0x9d, 0x0e, 0x99, 0xe7, 0x44, 0x42, 0x49, 0xa3, 0xed, 0xf8, 0xfe, 0xc2, 0x41, 0x42, + 0x3b, 0x04, 0xca, 0x46, 0x4f, 0x40, 0x49, 0xca, 0x90, 0x2d, 0x39, 0x2b, 0xae, 0x88, 0x6a, 0xee, + 0x9e, 0x63, 0x43, 0x77, 0x56, 0x92, 0xad, 0x06, 0x64, 0x51, 0xfc, 0xba, 0x34, 0x3c, 0xc0, 0x87, + 0xcc, 0xab, 0x8e, 0xde, 0x5a, 0xad, 0x52, 0xcf, 0xc3, 0xe3, 0x82, 0xfb, 0x77, 0x12, 0xf9, 0xc6, + 0x34, 0x9c, 0xe6, 0x76, 0xf8, 0x0c, 0x35, 0x28, 0x3f, 0x74, 0x39, 0xda, 0x4e, 0xaa, 0x6f, 0xb1, + 0x70, 0x8e, 0xb6, 0xd7, 0xeb, 0x01, 0x0a, 0xe7, 0x97, 0xd2, 0x70, 0x3a, 0x2c, 0x75, 0x1c, 0x29, + 0x08, 0x7f, 0xdf, 0x0b, 0x67, 0x6f, 0x13, 0x64, 0xa2, 0x48, 0xef, 0x63, 0x9b, 0x5b, 0x56, 0x85, + 0xde, 0x3d, 0x3a, 0x37, 0x26, 0x89, 0xf4, 0x78, 0x08, 0xef, 0x4b, 0x4b, 0x7f, 0x39, 0x0d, 0x67, + 0x42, 0x91, 0x8a, 0x1b, 0xad, 0xe3, 0x91, 0x7f, 0xf0, 0x32, 0x3d, 0x1e, 0xfa, 0xfb, 0x1a, 0xfa, + 0x89, 0x32, 0x3d, 0x1e, 0xfb, 0xfb, 0xd2, 0xd3, 0xef, 0x4a, 0xc3, 0xf9, 0x50, 0xa6, 0xa5, 0x15, + 0x6a, 0x79, 0x9a, 0xdd, 0x68, 0xd8, 0x6d, 0x6f, 0xd1, 0x31, 0x57, 0x56, 0x0e, 0x29, 0x44, 0xec, + 0xe0, 0x0e, 0xad, 0x4f, 0x43, 0x4e, 0x5c, 0x13, 0x61, 0xf5, 0x7c, 0x4d, 0xfc, 0xda, 0xa3, 0xb4, + 0xfe, 0x71, 0x1a, 0x0a, 0xc9, 0xd2, 0x9a, 0xb1, 0x9d, 0x3a, 0x9d, 0xb2, 0x2d, 0x7a, 0x2c, 0x2f, + 0x49, 0x5e, 0xdf, 0x9d, 0x86, 0x87, 0x92, 0xe5, 0xc5, 0xfe, 0x5b, 0xd2, 0xeb, 0x6b, 0xc7, 0xe2, + 0x92, 0xc4, 0xf5, 0x0b, 0x69, 0x38, 0xc9, 0xfe, 0x34, 0xeb, 0xb3, 0xb6, 0xeb, 0xb1, 0x57, 0x3d, + 0x80, 0x55, 0x78, 0x6f, 0x2b, 0xc6, 0x41, 0x04, 0x03, 0xf8, 0xdd, 0x93, 0xdd, 0x57, 0xf7, 0xf4, + 0xee, 0x63, 0x4b, 0x13, 0x17, 0xe8, 0xa1, 0x2d, 0xc1, 0x6f, 0x57, 0x81, 0x1e, 0xc0, 0xfa, 0x7b, + 0x3f, 0x0b, 0xf4, 0x6b, 0x33, 0x30, 0x36, 0xe9, 0xd8, 0xeb, 0xd6, 0x35, 0xba, 0x4e, 0x1b, 0x87, + 0x36, 0xdc, 0xef, 0x09, 0x03, 0xd1, 0xd9, 0xa3, 0x81, 0xe8, 0xb7, 0x23, 0x2f, 0xc3, 0x68, 0x28, + 0x4b, 0x39, 0x2b, 0x3b, 0x3a, 0x22, 0xd4, 0x19, 0xaa, 0xf6, 0x06, 0xc3, 0x89, 0xf4, 0xc1, 0x2a, + 0x75, 0xf1, 0x4f, 0x23, 0xbd, 0x71, 0x7f, 0x9b, 0xeb, 0xfb, 0xee, 0x8d, 0x9b, 0x70, 0x7a, 0xb2, + 0xed, 0x38, 0xd4, 0xf2, 0x92, 0x3b, 0x05, 0x6f, 0xd2, 0xea, 0x9c, 0xa2, 0x16, 0xef, 0x9c, 0x0e, + 0x8d, 0x19, 0x5b, 0x11, 0x08, 0xa8, 0xb2, 0xed, 0x0b, 0xd9, 0xb6, 0x39, 0x45, 0x12, 0xdb, 0xe4, + 0xc6, 0xc5, 0x9f, 0x49, 0xcb, 0x5d, 0x7f, 0x68, 0xb3, 0xda, 0xdb, 0xa2, 0xeb, 0x8b, 0x9f, 0xc8, + 0xc0, 0x08, 0x7b, 0xad, 0x45, 0xdd, 0x5d, 0x3b, 0x36, 0x61, 0xf6, 0xb3, 0x40, 0xb0, 0xaf, 0xf0, + 0x25, 0x29, 0xf2, 0xcf, 0xe6, 0xe5, 0xb7, 0x60, 0xf0, 0x4e, 0x5f, 0xe1, 0xe3, 0x8b, 0xdf, 0x93, + 0x0d, 0xbb, 0xe3, 0xd8, 0x00, 0x3a, 0xec, 0xee, 0x20, 0x0b, 0x70, 0x52, 0xcc, 0x6d, 0x3e, 0x88, + 0xe7, 0x19, 0xeb, 0x93, 0xaa, 0xfc, 0x8b, 0x69, 0xb1, 0xed, 0x52, 0xa7, 0xe6, 0xe9, 0xee, 0x9a, + 0xc8, 0x37, 0x96, 0xd8, 0x90, 0x31, 0x14, 0xb3, 0x5a, 0x94, 0x61, 0x7f, 0xc8, 0xd0, 0x9f, 0x10, + 0x63, 0x0c, 0x93, 0x1a, 0x16, 0xbf, 0x3f, 0x05, 0x63, 0xea, 0xe7, 0x90, 0xcb, 0xd0, 0xcf, 0x7e, + 0x07, 0x19, 0x6a, 0x84, 0xff, 0x7c, 0xc8, 0x91, 0x3b, 0x63, 0xf9, 0x34, 0xe4, 0x79, 0x18, 0x40, + 0xbf, 0x37, 0x6c, 0x90, 0x0e, 0x13, 0x03, 0x85, 0x0d, 0x4c, 0x86, 0xe6, 0xcd, 0x42, 0x52, 0xf2, + 0x22, 0x0c, 0x96, 0x43, 0x87, 0x61, 0x71, 0x01, 0x8d, 0x2e, 0x2e, 0x52, 0xcb, 0x90, 0x40, 0x93, + 0xa9, 0x8b, 0x5f, 0x48, 0x87, 0xaa, 0x7e, 0x6c, 0x9a, 0xee, 0xcb, 0x34, 0xfd, 0xbe, 0x0c, 0x0c, + 0x4f, 0xda, 0x96, 0xa7, 0xd7, 0xbd, 0xe3, 0xc3, 0xe0, 0xfd, 0x1c, 0xb2, 0x91, 0x02, 0xf4, 0x4e, + 0x37, 0x75, 0xb3, 0x21, 0x0c, 0x1f, 0xcc, 0xd3, 0x48, 0x19, 0x40, 0xe3, 0x70, 0xf2, 0x2a, 0x26, + 0xed, 0x62, 0x92, 0x0e, 0xbc, 0x1c, 0x47, 0xc2, 0x72, 0x1f, 0x12, 0x8a, 0xe7, 0xb0, 0xaa, 0x73, + 0x00, 0x1f, 0x39, 0x72, 0x4b, 0xb9, 0xcf, 0x8e, 0x0f, 0x46, 0x8f, 0x48, 0x9f, 0x7d, 0x2e, 0x03, + 0xa7, 0x55, 0x8f, 0xc0, 0xe3, 0x01, 0x27, 0x3a, 0xef, 0xaf, 0xc0, 0x49, 0x55, 0x36, 0x53, 0x4c, + 0x1a, 0xbd, 0xdd, 0x7d, 0x47, 0x2e, 0x6f, 0x6d, 0x16, 0x1e, 0x8e, 0x7b, 0x62, 0xb2, 0x87, 0x25, + 0x7a, 0x93, 0x24, 0x3e, 0x24, 0xb1, 0x67, 0xee, 0x91, 0x90, 0xee, 0xfb, 0xbc, 0x67, 0xbe, 0x31, + 0x1d, 0xef, 0x99, 0xe3, 0x09, 0x4f, 0x2c, 0xdc, 0x3f, 0x97, 0x86, 0xc7, 0x54, 0xe1, 0xbc, 0xfe, + 0xdc, 0x53, 0x2f, 0x68, 0xd4, 0x4f, 0xed, 0x7c, 0x3c, 0xbd, 0x08, 0x25, 0xc6, 0xbc, 0xcb, 0xba, + 0x1b, 0x84, 0x79, 0x8a, 0xbc, 0xcb, 0x0c, 0xa2, 0x09, 0xcc, 0x0e, 0xc4, 0x79, 0x3c, 0x27, 0xec, + 0x42, 0x9c, 0xdf, 0xb1, 0xad, 0x38, 0x8f, 0x07, 0xb2, 0xef, 0xae, 0x96, 0x05, 0x78, 0xd5, 0xf4, + 0x44, 0x02, 0xfc, 0x23, 0x7e, 0x55, 0x26, 0xf9, 0xb9, 0x66, 0xf7, 0xe4, 0xe7, 0x1a, 0x66, 0xb7, + 0xea, 0xdd, 0x43, 0x76, 0xab, 0x97, 0xa1, 0x4f, 0xc8, 0x51, 0xec, 0xdb, 0xcf, 0x84, 0x5f, 0x81, + 0xe0, 0x4e, 0x8f, 0xf7, 0xa5, 0xff, 0x38, 0xf4, 0xb9, 0x3c, 0xe3, 0x9b, 0xd8, 0x57, 0x63, 0xb0, + 0x92, 0x00, 0x69, 0xfe, 0x1f, 0xe4, 0x1c, 0x60, 0x05, 0x3b, 0x39, 0x96, 0x88, 0x57, 0xb4, 0x63, + 0xff, 0x92, 0x32, 0xf4, 0x89, 0xa8, 0x81, 0x3c, 0x60, 0x5c, 0x4a, 0xa0, 0x96, 0x61, 0x3f, 0xf3, + 0xe0, 0x01, 0x7e, 0x66, 0x2d, 0x88, 0xe5, 0x88, 0x73, 0x01, 0x2a, 0xfe, 0x44, 0x0a, 0xc6, 0xd4, + 0x46, 0xe4, 0x09, 0xc8, 0xf1, 0xbf, 0xc4, 0x0e, 0x1d, 0x13, 0xcf, 0xf2, 0x16, 0x72, 0xe2, 0x59, + 0x41, 0xfd, 0x1c, 0x0c, 0x68, 0x7e, 0x28, 0x88, 0xd8, 0xa1, 0xa3, 0xff, 0x6e, 0x10, 0x1f, 0x12, + 0x29, 0xd2, 0xe3, 0x03, 0xc9, 0xa3, 0x90, 0x59, 0x68, 0x18, 0x62, 0x63, 0x8e, 0x01, 0x51, 0x76, + 0x43, 0xce, 0xaa, 0xcb, 0xb0, 0x8c, 0xe8, 0x06, 0x5d, 0x17, 0xce, 0xde, 0x48, 0x64, 0xd1, 0x75, + 0x99, 0xe8, 0x06, 0x5d, 0x2f, 0xfe, 0x7c, 0xca, 0x77, 0xdf, 0x9d, 0x33, 0x5d, 0xaf, 0x6c, 0xdd, + 0xd1, 0x1b, 0x66, 0xd0, 0x11, 0xe4, 0x55, 0x18, 0x09, 0x91, 0x52, 0x82, 0x86, 0x58, 0x22, 0x23, + 0x0c, 0xe1, 0x7f, 0x58, 0x0a, 0xdb, 0x89, 0x36, 0x23, 0x17, 0x24, 0xe5, 0x97, 0x4e, 0x2d, 0xe4, + 0xa8, 0x7f, 0xe1, 0x8a, 0x3d, 0x34, 0x6f, 0xba, 0xae, 0x69, 0xad, 0xf0, 0xe0, 0xd1, 0x0c, 0xc6, + 0x71, 0xe1, 0xf9, 0x49, 0x93, 0xc3, 0x6b, 0x0e, 0x43, 0xc8, 0xd1, 0x47, 0x72, 0x83, 0xe2, 0x9f, + 0xa7, 0xe0, 0x2c, 0xe3, 0x84, 0x29, 0x55, 0x63, 0x1f, 0xb6, 0xaf, 0x01, 0xdc, 0xec, 0x22, 0x29, + 0x31, 0xaa, 0x1f, 0x89, 0x67, 0x11, 0x51, 0x08, 0x15, 0xee, 0x5d, 0x64, 0xbf, 0xb7, 0xac, 0x76, + 0x3f, 0x97, 0xc2, 0xeb, 0xc1, 0xa5, 0x06, 0xbd, 0x79, 0xa3, 0xfc, 0xfa, 0x01, 0x5d, 0x60, 0xef, + 0x75, 0xea, 0x7a, 0x05, 0xc6, 0x5c, 0x7c, 0x97, 0x5a, 0xdb, 0x32, 0xef, 0xe2, 0xc9, 0x97, 0xf8, + 0x98, 0xd3, 0xd2, 0xc7, 0x48, 0xef, 0xaa, 0x8d, 0x70, 0xfa, 0x9b, 0x96, 0x79, 0x17, 0x33, 0xca, + 0xbe, 0x1f, 0xb3, 0xf9, 0x4b, 0x14, 0xe4, 0x2c, 0xf4, 0xfb, 0xf5, 0x90, 0x44, 0x75, 0xa1, 0xe0, + 0x37, 0x19, 0x83, 0x4c, 0xdb, 0x34, 0xf0, 0x35, 0x7b, 0x35, 0xf6, 0x67, 0xf1, 0x57, 0x33, 0x30, + 0x5e, 0xba, 0x5d, 0x2d, 0x4f, 0x06, 0x51, 0x0c, 0x22, 0x2a, 0xb8, 0xb9, 0x4b, 0x59, 0xf8, 0xf4, + 0x64, 0x12, 0x46, 0x78, 0x56, 0x07, 0x51, 0x72, 0x84, 0x9f, 0x4b, 0xf5, 0xf2, 0x68, 0x8a, 0x28, + 0x46, 0x52, 0xd2, 0x61, 0xc4, 0x88, 0xca, 0x24, 0x2e, 0xa9, 0xc3, 0x03, 0x11, 0xd2, 0x9a, 0x1e, + 0x24, 0xcd, 0xf3, 0x13, 0x96, 0xbc, 0x63, 0x6b, 0xb3, 0xf0, 0x68, 0x47, 0x22, 0x89, 0xf5, 0x19, + 0x99, 0x75, 0x98, 0x7c, 0xcf, 0x25, 0xd7, 0x61, 0x9c, 0xb7, 0xc7, 0x43, 0xbb, 0xc0, 0x49, 0x82, + 0x31, 0x97, 0x92, 0x53, 0x48, 0x48, 0x39, 0x11, 0x19, 0x22, 0x99, 0xc0, 0x45, 0x44, 0xf7, 0x6d, + 0x38, 0xc5, 0xe9, 0x5b, 0xd4, 0xc1, 0x91, 0x68, 0x5b, 0x35, 0x97, 0x7a, 0x22, 0x30, 0x7c, 0xe2, + 0xd1, 0xad, 0xcd, 0x42, 0x21, 0x91, 0x40, 0x62, 0x7a, 0x02, 0x09, 0x2a, 0x01, 0xbe, 0x4a, 0x3d, + 0xd9, 0x4f, 0x23, 0xb7, 0x0b, 0x35, 0xff, 0xa6, 0x34, 0x9c, 0x99, 0xa5, 0x7a, 0xc3, 0x5b, 0x9d, + 0x5c, 0xa5, 0xf5, 0xb5, 0x63, 0x57, 0xe9, 0x88, 0xd5, 0x92, 0x28, 0x9d, 0x63, 0x13, 0xb9, 0x9b, + 0x74, 0x8e, 0x2d, 0x5e, 0x21, 0x9d, 0xef, 0x4e, 0xc3, 0xc5, 0xa4, 0xcd, 0x01, 0xde, 0x0e, 0x38, + 0x41, 0x08, 0xec, 0xe1, 0x2d, 0x2a, 0x07, 0x67, 0x0f, 0xcb, 0x1d, 0x96, 0xdd, 0xa3, 0x47, 0xec, + 0xce, 0xc4, 0x75, 0x88, 0xa5, 0xc6, 0xee, 0x2d, 0x71, 0x7d, 0x43, 0x1a, 0x4e, 0x56, 0xcd, 0x15, + 0xd7, 0xb3, 0x1d, 0x5a, 0xc1, 0xf4, 0x2a, 0xc7, 0x9a, 0xd4, 0x51, 0x34, 0x87, 0x58, 0xd1, 0xef, + 0x5e, 0x17, 0xcd, 0xf1, 0x80, 0x12, 0xa2, 0xf9, 0x8f, 0x59, 0x18, 0x9d, 0x9f, 0xac, 0xf8, 0x85, + 0x86, 0x3d, 0xdd, 0x39, 0xa2, 0xf5, 0x16, 0xc2, 0xb3, 0x85, 0xec, 0x1e, 0xce, 0x16, 0x0e, 0xce, + 0xbd, 0x40, 0xd4, 0xfb, 0xcf, 0xed, 0xaa, 0xde, 0xff, 0x63, 0x30, 0xd2, 0xac, 0xb7, 0x6a, 0x7e, + 0x4a, 0x2d, 0xd3, 0x10, 0xc9, 0xac, 0x86, 0x9a, 0xf5, 0x96, 0x5f, 0xc5, 0xda, 0x20, 0x05, 0x18, + 0xac, 0x37, 0x4c, 0x6a, 0x79, 0x35, 0xd3, 0x5a, 0xb6, 0x45, 0x02, 0x2b, 0xe0, 0xa0, 0xb2, 0xb5, + 0x6c, 0x33, 0x02, 0x17, 0xbf, 0x87, 0x13, 0xf0, 0x6c, 0x55, 0xc0, 0x41, 0x48, 0x70, 0x09, 0xc6, + 0xf0, 0x34, 0xbf, 0x6e, 0x37, 0xfc, 0x1a, 0x7c, 0x22, 0x59, 0xd5, 0xa8, 0x0f, 0xf7, 0xd3, 0xe1, + 0xbd, 0x13, 0xc6, 0x4d, 0x6b, 0xc5, 0x61, 0x3b, 0x74, 0xbd, 0xed, 0xf1, 0x32, 0x40, 0x22, 0x79, + 0xd5, 0xa8, 0x40, 0x94, 0xda, 0x1e, 0x2f, 0x04, 0x74, 0x11, 0xc6, 0xa8, 0x4a, 0x8a, 0x39, 0x50, + 0xb4, 0x11, 0x1a, 0xa1, 0x2c, 0x7e, 0x3e, 0x0b, 0xc3, 0xa1, 0xba, 0x4d, 0x1f, 0xd2, 0x91, 0xd8, + 0xfd, 0xad, 0x6c, 0xe1, 0x8e, 0xa9, 0x6f, 0x17, 0xb7, 0xa9, 0x1f, 0x4f, 0x41, 0xdf, 0x2a, 0xd5, + 0x0d, 0xea, 0xf0, 0x84, 0x5b, 0x83, 0x57, 0x4f, 0x5d, 0x5e, 0x77, 0xf4, 0x56, 0x8b, 0x3a, 0x22, + 0x25, 0x0b, 0xcf, 0x9b, 0x34, 0xf1, 0x3a, 0x6b, 0xfe, 0x1b, 0x9b, 0x85, 0x17, 0x57, 0x4c, 0x6f, + 0xb5, 0xbd, 0x74, 0xb9, 0x6e, 0x37, 0xaf, 0xac, 0x38, 0xfa, 0x1d, 0xd3, 0xc3, 0xa3, 0x61, 0xbd, + 0x71, 0xc5, 0xa3, 0x0d, 0x4c, 0x3c, 0x79, 0x45, 0x6f, 0x99, 0x57, 0xb0, 0x4e, 0xcf, 0x95, 0x80, + 0xd3, 0xa2, 0xa3, 0x9b, 0x9e, 0xbb, 0xb5, 0x59, 0x18, 0x17, 0x0f, 0x93, 0x0f, 0xce, 0x04, 0xa8, + 0xf8, 0xcb, 0x29, 0x18, 0x9f, 0x9f, 0xac, 0x5c, 0xab, 0x2e, 0xdc, 0xd0, 0x2a, 0x93, 0xf3, 0xd4, + 0x75, 0xf5, 0x15, 0x4a, 0x1e, 0x87, 0x3e, 0x01, 0x11, 0xa7, 0x4c, 0x78, 0xbc, 0xf7, 0x86, 0x6b, + 0x5b, 0x4e, 0xab, 0xae, 0xf9, 0x38, 0x51, 0x4f, 0x2a, 0xdd, 0xa5, 0x9e, 0x54, 0x11, 0x72, 0x4d, + 0xea, 0xad, 0xda, 0xfe, 0x01, 0x19, 0x9e, 0x81, 0x73, 0x88, 0x26, 0xfe, 0x27, 0x0b, 0x90, 0x6b, + 0xe9, 0x8e, 0xde, 0x74, 0xb7, 0xbb, 0x2d, 0x7b, 0x08, 0x0b, 0x5f, 0x22, 0x69, 0xe2, 0xed, 0x98, + 0x60, 0x53, 0xfc, 0xba, 0x2c, 0x7e, 0x53, 0x90, 0x52, 0xe3, 0xa3, 0x6d, 0xca, 0xb3, 0x01, 0xef, + 0xf9, 0xa4, 0xe1, 0x79, 0xc8, 0xb6, 0x77, 0x39, 0x3a, 0xda, 0x62, 0x74, 0xb8, 0x7b, 0x1a, 0x1d, + 0xa2, 0x95, 0xaf, 0x95, 0xd9, 0xdd, 0x6a, 0xa5, 0xbb, 0x8b, 0x3b, 0x7e, 0x4e, 0x4b, 0x6e, 0x40, + 0x5f, 0x93, 0x77, 0xbf, 0x18, 0x04, 0x41, 0x9a, 0xcb, 0x98, 0x7e, 0x4c, 0x3c, 0x20, 0x0a, 0x52, + 0x8f, 0x8b, 0x16, 0xb2, 0x66, 0x09, 0x50, 0x44, 0xcb, 0xfb, 0x0e, 0x59, 0xcb, 0xff, 0x56, 0x16, + 0x4e, 0x87, 0x1a, 0x71, 0xc3, 0x16, 0x39, 0x83, 0x98, 0x84, 0xef, 0x1f, 0xb5, 0x90, 0x3a, 0xb8, + 0xf7, 0x20, 0x3a, 0x38, 0x54, 0xb3, 0xdc, 0x2e, 0xd4, 0xec, 0x5e, 0x52, 0x8b, 0x9f, 0xca, 0xc0, + 0x03, 0xa1, 0x5a, 0xcc, 0x61, 0xe1, 0xbf, 0x6a, 0x75, 0xba, 0xea, 0x39, 0x54, 0x6f, 0x1e, 0x59, + 0xcd, 0xd0, 0x77, 0xa5, 0x19, 0xfa, 0x9e, 0x27, 0x0c, 0xb9, 0x27, 0x73, 0x87, 0xdc, 0x93, 0xdf, + 0x92, 0x85, 0x73, 0x61, 0x4f, 0x8a, 0xf3, 0xfb, 0xd9, 0xc5, 0xc5, 0xca, 0x51, 0x9f, 0xfd, 0x77, + 0xdf, 0x99, 0xfe, 0x9d, 0x5b, 0x6f, 0xe2, 0x9d, 0x5b, 0xb8, 0x20, 0xe7, 0x3a, 0x2e, 0xc8, 0xef, + 0x84, 0x01, 0x47, 0x5f, 0xe7, 0x21, 0x02, 0x72, 0x92, 0xc1, 0x00, 0xa8, 0xf5, 0x3b, 0xfa, 0x3a, + 0x0f, 0x40, 0x38, 0x07, 0xd9, 0x25, 0xdb, 0xd8, 0x40, 0x3b, 0x66, 0x88, 0x3f, 0x8d, 0xfd, 0xd6, + 0xf0, 0xdf, 0x88, 0x8a, 0x0c, 0x1c, 0xb2, 0x8a, 0x7c, 0x67, 0x16, 0x4e, 0x4e, 0xd8, 0x6d, 0xcb, + 0xb8, 0x4e, 0x37, 0x5a, 0xba, 0xe9, 0x68, 0xb4, 0x6e, 0xdf, 0x61, 0x9f, 0xf0, 0x97, 0xef, 0x12, + 0x7b, 0x70, 0xfb, 0xd6, 0x27, 0x60, 0x60, 0xd1, 0x5e, 0xa3, 0x96, 0x94, 0xc7, 0x1b, 0x8b, 0xb0, + 0x7b, 0x0c, 0xc8, 0x33, 0x52, 0x85, 0x04, 0xe4, 0x29, 0xe8, 0x9b, 0xb0, 0xf9, 0xe5, 0xa1, 0x54, + 0x4c, 0x76, 0xc9, 0x16, 0x97, 0x86, 0x92, 0xd0, 0x04, 0x19, 0x79, 0x1e, 0x06, 0x2a, 0xed, 0xa5, + 0x86, 0x59, 0xbf, 0x4e, 0xfd, 0x48, 0x15, 0x74, 0x5b, 0x6e, 0x21, 0xb0, 0xb6, 0x46, 0x37, 0x22, + 0x59, 0x8d, 0x7c, 0x52, 0x5e, 0x2d, 0x9c, 0xcb, 0x77, 0xd2, 0x6e, 0x5b, 0x1e, 0xaa, 0xd1, 0xb0, + 0x5f, 0x2d, 0x9c, 0x23, 0x6a, 0x78, 0x4d, 0xa2, 0x45, 0x09, 0xc9, 0x73, 0x30, 0xe4, 0x03, 0xe6, + 0x6d, 0x83, 0xca, 0x59, 0x2b, 0x83, 0x86, 0x4d, 0xdb, 0xa0, 0x5a, 0x84, 0x8c, 0xcc, 0xc3, 0xd8, + 0x84, 0xed, 0x61, 0xee, 0xc0, 0x85, 0xe5, 0x05, 0xc7, 0x5c, 0x31, 0x2d, 0x71, 0xeb, 0xfc, 0xc8, + 0xd6, 0x66, 0xe1, 0x3c, 0xfb, 0x46, 0xcc, 0x3c, 0x58, 0xb3, 0x97, 0x6b, 0x36, 0xa2, 0xe5, 0xec, + 0x91, 0x6a, 0x53, 0x66, 0x30, 0x44, 0x95, 0xc5, 0xf6, 0x02, 0x73, 0xe1, 0x58, 0x59, 0x3a, 0x28, + 0xcb, 0x02, 0x8c, 0x57, 0x1c, 0x7a, 0xc7, 0xb4, 0xdb, 0xae, 0xaa, 0x34, 0xd8, 0x09, 0x2d, 0x81, + 0xac, 0x25, 0x6a, 0x4f, 0xbc, 0x2d, 0x79, 0x05, 0x86, 0x6e, 0xd0, 0xf5, 0x90, 0x57, 0x5f, 0x98, + 0x2c, 0xcd, 0xa2, 0xeb, 0xc9, 0x6c, 0x22, 0x2d, 0x12, 0xd5, 0xa2, 0x7f, 0x1f, 0x6a, 0x91, 0x81, + 0x77, 0xc8, 0x6a, 0x71, 0xcd, 0x36, 0x79, 0xad, 0xf3, 0x5b, 0xd4, 0x09, 0x4c, 0xca, 0x19, 0xdd, + 0x6c, 0xec, 0x33, 0x55, 0xdd, 0xdb, 0x5d, 0x53, 0x92, 0xba, 0x25, 0xb7, 0xf7, 0x6e, 0xf9, 0x9d, + 0x34, 0x0c, 0x56, 0x27, 0xcb, 0xf3, 0xfe, 0x62, 0xbf, 0x7d, 0x9d, 0xe3, 0x09, 0x18, 0xe6, 0x49, + 0xf4, 0x4a, 0x86, 0xe1, 0x50, 0xd7, 0x15, 0x9b, 0x58, 0x54, 0x2d, 0x91, 0x28, 0x4f, 0xe7, 0x18, + 0xf9, 0xe6, 0x38, 0xd2, 0x84, 0xcd, 0x8d, 0xcc, 0x04, 0xc0, 0x40, 0x7e, 0xb1, 0xbd, 0x0d, 0x43, + 0x3a, 0x74, 0x06, 0x95, 0xe7, 0xc6, 0x80, 0x94, 0x3c, 0x01, 0xb9, 0x79, 0xbe, 0x04, 0x67, 0x43, + 0xb7, 0x14, 0xbe, 0xf4, 0xca, 0x6e, 0x29, 0x9c, 0x86, 0x5c, 0x80, 0x6c, 0x25, 0x5c, 0xce, 0x71, + 0x02, 0x65, 0x0b, 0xb9, 0xec, 0xae, 0x51, 0xe1, 0xce, 0x34, 0xd9, 0x09, 0xb6, 0x10, 0xe7, 0x76, + 0x90, 0x47, 0x8e, 0xad, 0xcd, 0xc9, 0x79, 0xe4, 0x18, 0x0b, 0x72, 0xd2, 0xaf, 0xec, 0xc1, 0x4f, + 0xce, 0x44, 0xb5, 0x90, 0x0d, 0x18, 0xe2, 0x32, 0x76, 0x5b, 0xb6, 0xe5, 0x52, 0xf2, 0x10, 0x00, + 0xd7, 0xbb, 0x49, 0x36, 0x4d, 0x63, 0x95, 0x66, 0x4d, 0x82, 0x04, 0x2f, 0x94, 0xde, 0xf7, 0x0b, + 0x15, 0xff, 0x5e, 0x1a, 0x46, 0xd8, 0xb3, 0x27, 0xed, 0x66, 0xd3, 0xb6, 0xa6, 0xd8, 0x08, 0x99, + 0x82, 0x3e, 0xd1, 0xdb, 0x42, 0xd1, 0x03, 0xf3, 0x48, 0x52, 0x04, 0xee, 0x36, 0xe4, 0xf0, 0x1f, + 0xb2, 0x1e, 0xfa, 0x8a, 0xa2, 0x44, 0xd7, 0x64, 0xc3, 0xe8, 0x1a, 0x29, 0xa6, 0x46, 0xae, 0x0f, + 0x2e, 0x51, 0x93, 0x97, 0xd9, 0x4a, 0x15, 0x4b, 0xbb, 0x88, 0x0e, 0x32, 0x91, 0x6c, 0x8b, 0xf2, + 0xe4, 0x24, 0x37, 0x20, 0xb3, 0x78, 0xe8, 0x8c, 0xd2, 0x14, 0xdd, 0x76, 0x32, 0xfa, 0x11, 0x1c, + 0xc7, 0x87, 0x93, 0x23, 0x7e, 0xc9, 0xf5, 0xac, 0x7d, 0x8a, 0xe2, 0x37, 0x66, 0x60, 0x8c, 0x35, + 0x61, 0x5b, 0x18, 0xd3, 0x5a, 0xc1, 0x1c, 0xfc, 0x87, 0x30, 0x01, 0xbd, 0x04, 0x39, 0xde, 0x45, + 0x31, 0xd7, 0x92, 0x48, 0xe7, 0xa9, 0xad, 0x39, 0x86, 0x8d, 0x0b, 0x9e, 0xee, 0x5e, 0x1e, 0x17, + 0x3c, 0x2f, 0xbe, 0x3c, 0x2e, 0x38, 0x0d, 0xb9, 0x04, 0xbd, 0xdc, 0xb2, 0xe8, 0x0d, 0xd3, 0x9a, + 0xa3, 0x41, 0x21, 0x97, 0x9f, 0xe1, 0x26, 0xc5, 0x0b, 0xa8, 0xa9, 0x8e, 0x57, 0xb6, 0x0c, 0x7a, + 0x57, 0x64, 0x63, 0xc7, 0x4e, 0x76, 0x19, 0xb4, 0x66, 0x32, 0xb0, 0x9c, 0x25, 0x38, 0x24, 0x66, + 0xf3, 0x84, 0xdf, 0x65, 0xb2, 0x1d, 0x83, 0xf3, 0x44, 0xd0, 0xc9, 0xea, 0x63, 0xa3, 0x4d, 0x8a, + 0x5b, 0x69, 0x18, 0x17, 0xfd, 0x89, 0xd0, 0xa3, 0xd9, 0x3b, 0x2f, 0x00, 0x2c, 0x0a, 0x13, 0x3c, + 0x48, 0x79, 0x8e, 0x42, 0xf4, 0x0d, 0xf3, 0x5a, 0x64, 0x92, 0x95, 0x88, 0xb1, 0xbc, 0xb8, 0xa8, + 0x95, 0x51, 0x9e, 0x92, 0x8b, 0xf0, 0x53, 0x01, 0x55, 0x9a, 0x86, 0xc4, 0xe4, 0x0a, 0xf4, 0x4d, + 0x99, 0x6e, 0xab, 0xa1, 0x47, 0x82, 0xd7, 0x0d, 0x0e, 0x92, 0x47, 0xb4, 0xa0, 0x2a, 0x7e, 0x3e, + 0x0b, 0x67, 0x27, 0xf9, 0x31, 0x7e, 0x45, 0xa3, 0xae, 0xe7, 0x98, 0xdc, 0x43, 0xf0, 0xf8, 0x02, + 0x2f, 0x56, 0x47, 0x71, 0x37, 0x01, 0x40, 0x37, 0xe0, 0x64, 0x92, 0x54, 0x45, 0xa6, 0x7a, 0x4c, + 0x35, 0xeb, 0xdf, 0xa7, 0xb4, 0x6a, 0x8e, 0x44, 0xa1, 0x25, 0xb6, 0x63, 0xab, 0x1a, 0x5a, 0xf7, + 0x7d, 0xe1, 0xaa, 0xc6, 0x8c, 0x7a, 0x79, 0x55, 0x43, 0xb3, 0xbe, 0x0d, 0x64, 0xda, 0x5a, 0xb6, + 0x9d, 0x3a, 0x6d, 0x52, 0xcb, 0x9b, 0xbe, 0xdb, 0x32, 0x1d, 0xea, 0x1f, 0x9a, 0x9f, 0x8d, 0x2d, + 0x29, 0x8b, 0x66, 0x93, 0xba, 0x9e, 0xde, 0x6c, 0x4d, 0x5c, 0x12, 0xe7, 0x57, 0xe7, 0x69, 0xd8, + 0xba, 0x46, 0x79, 0xf3, 0xf0, 0x21, 0x9f, 0xfe, 0x62, 0x21, 0xa5, 0x25, 0x3c, 0xa0, 0xf8, 0x95, + 0x69, 0x18, 0xbb, 0x65, 0x51, 0x4f, 0x75, 0x66, 0xda, 0xf3, 0x11, 0x42, 0x78, 0xc2, 0x92, 0xde, + 0xc5, 0x09, 0x8b, 0x7f, 0xf0, 0x90, 0xd9, 0xe5, 0xc1, 0xc3, 0x2c, 0x40, 0x7d, 0x1f, 0x2e, 0x28, + 0x61, 0x5b, 0x45, 0x10, 0xe1, 0x20, 0xba, 0xcf, 0x05, 0x11, 0xde, 0x79, 0xdf, 0x77, 0x82, 0xf8, + 0xfb, 0x99, 0x30, 0xed, 0xbf, 0x48, 0xe5, 0x7e, 0xec, 0xec, 0xb7, 0xaf, 0xe0, 0x4a, 0x0d, 0xfa, + 0x2a, 0xfa, 0x06, 0x93, 0xe7, 0x76, 0xc6, 0x7b, 0x81, 0x2d, 0x7e, 0x2d, 0x4e, 0x9b, 0x68, 0x2e, + 0xfb, 0x8c, 0x92, 0x7a, 0xe9, 0x38, 0xdf, 0xe2, 0x7e, 0xf2, 0x2d, 0xbe, 0x25, 0xbd, 0xf4, 0x8b, + 0xe9, 0x58, 0x2f, 0x1d, 0x3b, 0x3f, 0xee, 0x2b, 0x83, 0xe3, 0xb7, 0x65, 0xe0, 0x64, 0xd9, 0x12, + 0xb1, 0x19, 0xcc, 0x82, 0x68, 0x1c, 0x4f, 0x4e, 0xf7, 0xdc, 0xe4, 0x14, 0xef, 0xa4, 0x63, 0x87, + 0xe8, 0x7b, 0xae, 0x93, 0x7e, 0x21, 0xad, 0x76, 0xd2, 0xf1, 0xd4, 0xb4, 0xaf, 0xa9, 0x89, 0x4d, + 0xf6, 0x81, 0x40, 0xab, 0xb4, 0xee, 0xd0, 0xe3, 0x4c, 0x22, 0x07, 0x2e, 0xd1, 0xe3, 0x89, 0xe4, + 0xa0, 0x25, 0x7a, 0x3c, 0xea, 0xf7, 0x25, 0x51, 0x66, 0x88, 0x07, 0x12, 0x3d, 0x30, 0x37, 0xfb, + 0xe3, 0xc5, 0xee, 0xe0, 0xb7, 0x4b, 0x4a, 0x2f, 0x1d, 0xcf, 0x24, 0xf7, 0x5c, 0x2f, 0x45, 0x66, + 0xa7, 0x03, 0x0b, 0x3e, 0xb8, 0xdf, 0xb7, 0x4b, 0x1a, 0xf5, 0x1c, 0x93, 0xde, 0xd1, 0x1b, 0xc7, + 0xdb, 0xa5, 0x7b, 0x77, 0xbb, 0x14, 0xed, 0xa4, 0xe3, 0xb9, 0xe9, 0x9e, 0xdc, 0x2e, 0x45, 0x3b, + 0xe9, 0x78, 0x6a, 0xda, 0xd7, 0xd4, 0xf4, 0x9f, 0xd2, 0x70, 0x76, 0x92, 0x3a, 0x5e, 0xa9, 0xed, + 0xad, 0xda, 0x8e, 0xe9, 0x6d, 0xf8, 0x01, 0x9c, 0xc1, 0xf5, 0xe9, 0x5f, 0xba, 0x3f, 0xa7, 0x9c, + 0x0c, 0x38, 0xb3, 0xc7, 0x3c, 0xd0, 0xe1, 0xb1, 0x7f, 0x76, 0x17, 0xc7, 0xfe, 0x3a, 0x0c, 0x4a, + 0x15, 0x6d, 0x85, 0x1c, 0x1f, 0x0f, 0x7a, 0x23, 0x49, 0x4c, 0xc1, 0x9b, 0x74, 0xab, 0x87, 0x5b, + 0xd7, 0x83, 0x7a, 0xb8, 0x9f, 0x4c, 0xc3, 0xf9, 0xae, 0x8c, 0xc8, 0x65, 0xe8, 0xab, 0xeb, 0x3c, + 0x22, 0x29, 0x25, 0x25, 0x12, 0xd7, 0x55, 0x27, 0x87, 0x5c, 0x5d, 0x47, 0xf7, 0x86, 0xf7, 0xc1, + 0x50, 0x9d, 0x1f, 0x6a, 0xf2, 0x52, 0xfe, 0xe9, 0xb0, 0x2c, 0xa5, 0x0c, 0x97, 0xdd, 0x2b, 0x04, + 0x1c, 0x7d, 0x84, 0xfe, 0x2a, 0x9c, 0xaa, 0x87, 0xd5, 0x7d, 0x83, 0xd7, 0xe7, 0x89, 0x48, 0x06, + 0xaf, 0x3e, 0x2a, 0x7f, 0xbd, 0x20, 0x8a, 0x7d, 0x3b, 0xe6, 0x42, 0x48, 0xe4, 0x22, 0x3d, 0xf5, + 0x64, 0x3d, 0xce, 0xc1, 0x2d, 0xfe, 0x71, 0x0a, 0x1e, 0xec, 0xc2, 0x9a, 0x7c, 0x00, 0x06, 0xa5, + 0x76, 0x42, 0x07, 0xcf, 0xf9, 0x2f, 0xf5, 0xfa, 0x73, 0x4f, 0xbd, 0xd0, 0xa1, 0x27, 0xc2, 0x46, + 0x91, 0x2f, 0x0f, 0xc1, 0xe4, 0x55, 0xe8, 0xad, 0xaf, 0xea, 0xff, 0x1f, 0x7b, 0xd7, 0xd3, 0xdb, + 0x38, 0x11, 0xc5, 0xeb, 0xa4, 0x49, 0xd3, 0x97, 0x76, 0x3b, 0x9d, 0xb6, 0xb4, 0xdd, 0xdd, 0xa6, + 0xdb, 0xb0, 0xcb, 0x9f, 0x08, 0x69, 0xa5, 0x45, 0xe2, 0xc4, 0x81, 0x89, 0x3d, 0x6e, 0x86, 0xfa, + 0x4f, 0xf0, 0x38, 0x2d, 0xe5, 0x62, 0xb9, 0xa9, 0x77, 0x6b, 0xc8, 0x36, 0x91, 0x1d, 0xba, 0x70, + 0x44, 0x42, 0xe2, 0x2b, 0x20, 0x0e, 0x5c, 0xf8, 0x0e, 0x9c, 0x39, 0xee, 0x91, 0x4f, 0x80, 0xa0, + 0x47, 0xbe, 0x01, 0x17, 0x84, 0x66, 0xec, 0x24, 0x76, 0xd4, 0x80, 0x0a, 0x97, 0x45, 0xed, 0x6d, + 0xe6, 0xf7, 0x7e, 0x33, 0xf3, 0x3c, 0xf3, 0x9c, 0xd8, 0x9e, 0xf7, 0xde, 0x84, 0xe7, 0xf2, 0x44, + 0xf5, 0x7f, 0xea, 0x34, 0x71, 0x7c, 0x10, 0xf4, 0xac, 0xe3, 0x83, 0x04, 0xf0, 0x13, 0xa8, 0x9c, + 0x86, 0xb1, 0x7f, 0xd2, 0x0b, 0x92, 0xf8, 0x9b, 0x4a, 0xe2, 0x4a, 0x32, 0xc2, 0xb2, 0xae, 0x24, + 0x23, 0xac, 0xfe, 0xa7, 0x02, 0xeb, 0x57, 0x0d, 0x84, 0xdf, 0x81, 0xb2, 0x4c, 0x51, 0x1b, 0x65, + 0xb3, 0xe9, 0x24, 0x48, 0x76, 0xf1, 0x13, 0x04, 0x3f, 0x86, 0x85, 0xf8, 0xf3, 0x93, 0x4f, 0x83, + 0xee, 0x30, 0x5d, 0x77, 0x69, 0x2c, 0x29, 0x94, 0xdd, 0xb8, 0x4f, 0x21, 0xfc, 0x01, 0xe4, 0x4f, + 0x25, 0x4f, 0x3d, 0xaa, 0xa4, 0x3b, 0x4d, 0x4e, 0x90, 0x75, 0xa7, 0x89, 0xb3, 0x07, 0x97, 0x53, + 0x58, 0x99, 0x78, 0x04, 0x7a, 0x67, 0x7e, 0x7c, 0x96, 0xba, 0x29, 0xc8, 0x24, 0xe8, 0x53, 0xa2, + 0xac, 0xbb, 0xc6, 0x60, 0xe4, 0x2e, 0xd8, 0xf2, 0xe3, 0xb3, 0xfa, 0x37, 0x05, 0x58, 0x6d, 0x06, + 0xfe, 0xf3, 0xf8, 0xc6, 0x6f, 0xfe, 0x4e, 0xcd, 0xc4, 0x0d, 0xde, 0xfd, 0x9d, 0x9a, 0x89, 0x1b, + 0xbc, 0xfd, 0xfb, 0x7d, 0x11, 0x90, 0xda, 0xf3, 0xe3, 0x38, 0x7c, 0x1a, 0xde, 0xee, 0xfc, 0xbe, + 0x82, 0x0f, 0xa2, 0xf9, 0x05, 0xba, 0x7d, 0x53, 0x78, 0xe5, 0x16, 0x48, 0x9e, 0xad, 0x31, 0x5e, + 0xa0, 0xdb, 0xb7, 0x84, 0xff, 0xf4, 0x96, 0xf0, 0x53, 0x01, 0x96, 0xa4, 0x57, 0xb5, 0xde, 0x8f, + 0x5e, 0xf8, 0xd1, 0xff, 0x3d, 0x2d, 0xe4, 0x64, 0x12, 0xe6, 0xaf, 0xe7, 0xd8, 0xf9, 0xef, 0x73, + 0x41, 0xd6, 0xbf, 0x2e, 0xc1, 0x5d, 0x32, 0x18, 0xe5, 0x46, 0x70, 0xfd, 0xe8, 0x59, 0x30, 0xd4, + 0x42, 0xbf, 0xa7, 0x05, 0xe7, 0x61, 0x70, 0x23, 0x93, 0x0a, 0xa4, 0xd1, 0xb5, 0xa5, 0x6b, 0x45, + 0xd7, 0x3e, 0x06, 0x48, 0xe6, 0xae, 0xd5, 0x8f, 0x87, 0xa9, 0xb3, 0xea, 0xca, 0xef, 0xbf, 0xec, + 0x56, 0x87, 0x12, 0xf5, 0xce, 0xfa, 0xf1, 0xd0, 0xc9, 0x50, 0x26, 0x0d, 0xda, 0xfd, 0x68, 0x98, + 0x3a, 0x42, 0x66, 0x1b, 0x0c, 0xfa, 0xd1, 0xb8, 0x81, 0xa0, 0xe0, 0xf7, 0xa1, 0x2a, 0xee, 0xd7, + 0xde, 0x45, 0x70, 0xca, 0xda, 0xf1, 0x56, 0x65, 0xe2, 0x7a, 0x19, 0xa5, 0xb0, 0x17, 0xe6, 0x32, + 0xc7, 0x65, 0xe9, 0xb8, 0x0e, 0xe5, 0xe4, 0x93, 0x69, 0x1a, 0x16, 0x25, 0x03, 0xff, 0x06, 0x12, + 0x71, 0x52, 0x09, 0x7e, 0x0f, 0x16, 0x9b, 0xbd, 0x7e, 0xf7, 0x33, 0xd1, 0x22, 0x49, 0x66, 0x91, + 0x44, 0x34, 0x9c, 0x24, 0xa0, 0x17, 0x0e, 0xb2, 0x11, 0x0d, 0x63, 0x2a, 0x6e, 0xc2, 0x72, 0x5a, + 0x69, 0x47, 0xc1, 0xd3, 0xf0, 0x8b, 0x24, 0xb9, 0x45, 0xe2, 0x25, 0x3d, 0x6a, 0x3b, 0x90, 0x92, + 0xec, 0x63, 0x77, 0xae, 0x49, 0xa3, 0x9b, 0x9c, 0x25, 0x72, 0x10, 0x9e, 0x9f, 0xe2, 0x6d, 0xd8, + 0xe8, 0x70, 0xea, 0x78, 0x07, 0xcc, 0xd2, 0xbc, 0x8e, 0xc5, 0xdb, 0x54, 0x65, 0x3a, 0xa3, 0x1a, + 0x9a, 0xc3, 0x6b, 0xb0, 0x32, 0x11, 0xb5, 0x3a, 0x26, 0xb1, 0x90, 0x82, 0x57, 0x61, 0x79, 0x02, + 0x36, 0x6d, 0x17, 0x15, 0xf0, 0x3a, 0xa0, 0x09, 0xc4, 0x8f, 0xb9, 0x4b, 0x4d, 0x54, 0x6c, 0x7c, + 0xa7, 0x00, 0x88, 0x51, 0x92, 0xb8, 0x11, 0x7c, 0x0f, 0x36, 0x25, 0xc9, 0x76, 0xd8, 0x3e, 0xb3, + 0xa6, 0x46, 0xda, 0x80, 0xd5, 0xac, 0xd0, 0xb0, 0x55, 0x62, 0x20, 0x65, 0xac, 0x40, 0x0a, 0x73, + 0x6e, 0x67, 0x46, 0x4b, 0x41, 0xfb, 0xc0, 0x25, 0xa8, 0x38, 0x8d, 0x72, 0x95, 0x99, 0x68, 0x1e, + 0x6f, 0xc2, 0x5a, 0x16, 0xa5, 0x96, 0xeb, 0x10, 0xa6, 0xa1, 0x52, 0xe3, 0x4d, 0xa8, 0xca, 0x6f, + 0x1b, 0x69, 0x3e, 0xd2, 0x25, 0xa8, 0xd8, 0x4d, 0x4e, 0x9d, 0x43, 0xa9, 0x0d, 0x40, 0x59, 0xa3, + 0x96, 0xd0, 0x4c, 0x69, 0x44, 0x50, 0x35, 0x75, 0xa2, 0xf7, 0xfa, 0x2f, 0xe4, 0x7b, 0xf6, 0x0e, + 0x6c, 0x9b, 0x3a, 0xf1, 0x74, 0xc3, 0x3e, 0xf2, 0xdc, 0xe3, 0x36, 0x9d, 0xba, 0x8e, 0xb7, 0xe1, + 0x51, 0x5e, 0xdc, 0xa6, 0x8e, 0xc7, 0x29, 0xe7, 0xcc, 0xb6, 0x3c, 0x95, 0x3a, 0x2e, 0xd3, 0x99, + 0x4a, 0x5c, 0x8a, 0x14, 0x31, 0xef, 0x79, 0x2a, 0xb3, 0xbc, 0x26, 0xb1, 0x34, 0x54, 0x68, 0xfc, + 0xa1, 0x00, 0x70, 0xdd, 0x6d, 0xa7, 0xca, 0x55, 0x61, 0x81, 0x59, 0x87, 0xc4, 0x60, 0x62, 0x84, + 0x0a, 0xcc, 0xdb, 0x6d, 0x2a, 0x16, 0x62, 0x11, 0x4a, 0xaa, 0x61, 0x73, 0x8a, 0x0a, 0x02, 0x74, + 0x28, 0xd1, 0x50, 0x51, 0x80, 0x47, 0x0e, 0x73, 0x29, 0x9a, 0x17, 0x45, 0x83, 0xbb, 0xc4, 0x45, + 0x25, 0x51, 0xd4, 0x65, 0xb1, 0x2c, 0x3a, 0xe3, 0xd4, 0x95, 0x95, 0x05, 0x71, 0xd9, 0xfa, 0xa8, + 0x56, 0x11, 0x22, 0xd1, 0xb5, 0xc6, 0x1c, 0xb4, 0x28, 0x2a, 0xa2, 0x4b, 0x51, 0x01, 0x31, 0x21, + 0x0e, 0x35, 0xed, 0x43, 0x8a, 0xaa, 0xa2, 0x2f, 0xf3, 0x40, 0xc0, 0x4b, 0xa2, 0xe8, 0x98, 0xa2, + 0xb8, 0x2c, 0x7a, 0x72, 0x28, 0x31, 0xda, 0xc4, 0x6d, 0xa1, 0x3b, 0x42, 0x1f, 0xd9, 0xe7, 0x4a, + 0xd2, 0xd2, 0x22, 0x26, 0x45, 0x28, 0xe5, 0x68, 0x06, 0xb3, 0x0e, 0xd0, 0xaa, 0x54, 0xe4, 0xd8, + 0x94, 0x15, 0x2c, 0x1a, 0xc8, 0xd2, 0x5a, 0xe3, 0x2b, 0x05, 0xca, 0x36, 0x97, 0x73, 0xbd, 0x09, + 0x6b, 0x36, 0xbf, 0x6a, 0x96, 0x57, 0x61, 0x79, 0x24, 0x30, 0x98, 0xd5, 0xf9, 0x38, 0xb1, 0xca, + 0x11, 0x64, 0x12, 0xd5, 0xe6, 0xa8, 0x20, 0x8c, 0x67, 0x04, 0x1d, 0x31, 0x4b, 0xb3, 0x8f, 0x38, + 0x2a, 0xe2, 0x15, 0xa8, 0x8e, 0x40, 0x66, 0x73, 0x34, 0x8f, 0x31, 0xdc, 0x19, 0x03, 0x6d, 0xa2, + 0xd9, 0x1c, 0x95, 0x1a, 0x17, 0xb0, 0xa4, 0x05, 0x17, 0x61, 0x37, 0x48, 0x4d, 0x77, 0x07, 0xb6, + 0x35, 0x7a, 0xc8, 0x54, 0x3a, 0xd3, 0x78, 0xf3, 0x62, 0xd2, 0x66, 0x48, 0xc1, 0xaf, 0x01, 0xce, + 0xc3, 0x1f, 0x12, 0x53, 0x47, 0x05, 0xbc, 0x05, 0xeb, 0x79, 0x9c, 0x59, 0x6e, 0xc7, 0xa2, 0xa8, + 0xd8, 0xf8, 0x41, 0x81, 0x0d, 0xda, 0xf3, 0xe3, 0x61, 0xd8, 0x8d, 0x03, 0x3f, 0xea, 0x9e, 0xa9, + 0xfe, 0x30, 0x78, 0xd6, 0x8f, 0xbe, 0xc4, 0x75, 0xa8, 0x51, 0x83, 0x70, 0x97, 0xa9, 0x9c, 0x12, + 0x47, 0x6d, 0x79, 0xc2, 0x88, 0xf6, 0x6d, 0xe7, 0xd8, 0xdb, 0xa7, 0x16, 0x75, 0x88, 0x81, 0xe6, + 0xf0, 0xeb, 0xb0, 0x3b, 0x83, 0xc3, 0xa9, 0xda, 0x71, 0x98, 0x7b, 0x8c, 0x14, 0xbc, 0x07, 0x3b, + 0x33, 0x49, 0xa2, 0x8e, 0x0a, 0xb8, 0x06, 0x77, 0x67, 0x51, 0x3e, 0x32, 0x50, 0xb1, 0xf1, 0xad, + 0x02, 0xd8, 0x1e, 0x04, 0xe7, 0x3c, 0xaf, 0xe2, 0x03, 0xb8, 0x2f, 0xac, 0xc7, 0x9b, 0xad, 0xe0, + 0x1e, 0xec, 0x5c, 0xc9, 0xc8, 0xa8, 0xb7, 0x0b, 0xf7, 0x66, 0x50, 0x52, 0xe5, 0xee, 0xc3, 0xd6, + 0xd5, 0x04, 0xa9, 0xda, 0x8f, 0x0a, 0x6c, 0x90, 0xd3, 0xe7, 0xe1, 0x79, 0x72, 0xe7, 0xc4, 0xa6, + 0x4e, 0xd2, 0xbf, 0xed, 0xb7, 0xe0, 0x21, 0xd1, 0x4c, 0xb1, 0x36, 0xaa, 0xcb, 0x6c, 0x8b, 0x7b, + 0xe2, 0xde, 0x13, 0x36, 0xda, 0xe1, 0x53, 0xab, 0xf9, 0x06, 0xd4, 0xff, 0x86, 0xa9, 0xb6, 0x88, + 0xb5, 0x2f, 0x7e, 0x18, 0xf0, 0x43, 0x78, 0x30, 0x93, 0x47, 0x2d, 0xd2, 0x34, 0xa8, 0x86, 0x0a, + 0xf8, 0x11, 0xec, 0xcd, 0x64, 0x69, 0x8c, 0x27, 0xb4, 0x62, 0xc3, 0xcf, 0x1d, 0xe9, 0x21, 0xae, + 0x52, 0xb5, 0x2d, 0x97, 0xa8, 0xee, 0x55, 0xe6, 0xbf, 0x0d, 0x1b, 0x39, 0x69, 0xb3, 0xc3, 0x99, + 0x45, 0x39, 0x4f, 0x7e, 0x54, 0x72, 0xa2, 0xf1, 0xd4, 0x16, 0x9a, 0xda, 0xcb, 0xdf, 0x6a, 0x73, + 0x2f, 0x2f, 0x6b, 0xca, 0xcf, 0x97, 0x35, 0xe5, 0xd7, 0xcb, 0x9a, 0xf2, 0xc9, 0x93, 0xeb, 0xc4, + 0x7f, 0x27, 0x7f, 0xcc, 0x27, 0x65, 0xf9, 0x2c, 0xfe, 0xee, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, + 0xa2, 0xe0, 0x2f, 0xe1, 0xf9, 0x3c, 0x02, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -31035,6 +31039,15 @@ func (m *AppSessionChunk) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.Participants) > 0 { + for iNdEx := len(m.Participants) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Participants[iNdEx]) + copy(dAtA[i:], m.Participants[iNdEx]) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Participants[iNdEx]))) + i-- + dAtA[i] = 0x42 + } + } { size, err := m.AppMetadata.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -58492,6 +58505,12 @@ func (m *AppSessionChunk) Size() (n int) { } l = m.AppMetadata.Size() n += 1 + l + sovEvents(uint64(l)) + if len(m.Participants) > 0 { + for _, s := range m.Participants { + l = len(s) + n += 1 + l + sovEvents(uint64(l)) + } + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -91190,6 +91209,38 @@ func (m *AppSessionChunk) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Participants", 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.Participants = append(m.Participants, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/docs/pages/reference/access-controls/roles.mdx b/docs/pages/reference/access-controls/roles.mdx index 894f2aba1d30..a4cacddd96e0 100644 --- a/docs/pages/reference/access-controls/roles.mdx +++ b/docs/pages/reference/access-controls/roles.mdx @@ -595,9 +595,8 @@ resource type and the session creator (user who initiated the session) roles and | Session Field | Description | Type | |---|---|---| | `session.id` | The unique identifier of the session. | `string` | -| `session.kind` | The kind of session (e.g., "ssh", "kube", "db", "rdp"). | `string` | | `session.participants` | The list of users who participated in the session. | `array` | -| `session.proto` | The proto used for the session (e.g., "ssh", "kube", "db", "rdp"). | `string` | +| `session.proto` | The proto used for the session (e.g., "ssh", "kube", "db", "tdp", "app"). | `string` | | `session.user` | The user who initiated the session. | `string` | | `session.user_roles` | The roles assigned to the user who initiated the session. | `array` | | `session.user_traits` | The traits assigned to the user who initiated the session. | `dict>` | @@ -669,6 +668,22 @@ for Windows Desktop sessions together with the user and session fields above. | `session.windows_user` | The username used to log in to the Windows desktop during the session. | `string` | | `session.desktop_labels` | The labels assigned to the Windows desktop where the session took place. | `dict` | + + + +The following application session fields can be used in `where` expressions to filter access +for application sessions together with the user and session fields above. + +| Application Session Field | Description | Type | +|---|---|---| +| `session.app_name` | The name of the application where the session took place. | `string` | +| `session.app_uri` | The endpoint of the application where the session took place. | `string` | +| `session.app_public_addr` | The public address of the application where the session took place. | `string` | +| `session.app_labels` | The labels assigned to the application where the session took place. | `dict` | + +Only HTTP/HTTPS applications produce session recordings, so these fields are not +available for TCP applications. + diff --git a/integration/appaccess/appaccess_test.go b/integration/appaccess/appaccess_test.go index 013848751e96..48a8678c6d5b 100644 --- a/integration/appaccess/appaccess_test.go +++ b/integration/appaccess/appaccess_test.go @@ -753,6 +753,7 @@ func testAuditEvents(p *Pack, t *testing.T) { AppPublicAddr: p.rootAppPublicAddr, AppName: p.rootAppName, }, + Participants: []string{p.username}, } return len(cmp.Diff( expectedEvent, @@ -760,6 +761,7 @@ func testAuditEvents(p *Pack, t *testing.T) { cmpopts.IgnoreTypes(apievents.ServerMetadata{}, apievents.SessionMetadata{}, apievents.UserMetadata{}, apievents.ConnectionMetadata{}), cmpopts.IgnoreFields(apievents.Metadata{}, "ID", "Time", "Index"), cmpopts.IgnoreFields(apievents.AppSessionChunk{}, "SessionChunkID"), + cmpopts.IgnoreFields(apievents.AppMetadata{}, "AppLabels"), )) == 0 }) } diff --git a/integration/appaccess/pack.go b/integration/appaccess/pack.go index fd0bbf3819c3..6454fab5016e 100644 --- a/integration/appaccess/pack.go +++ b/integration/appaccess/pack.go @@ -843,10 +843,10 @@ func (p *Pack) startRootAppServers(t *testing.T, count int, opts AppTestOptions) URI: p.rootTCPMultiPortAppURI, PublicAddr: p.rootTCPMultiPortPublicAddr, TCPPorts: []servicecfg.PortRange{ - servicecfg.PortRange{ + { Port: p.rootTCPMultiPortAppPortAlpha, }, - servicecfg.PortRange{ + { Port: p.rootTCPMultiPortAppPortBeta, }, }, @@ -1008,10 +1008,10 @@ func (p *Pack) startLeafAppServers(t *testing.T, count int, opts AppTestOptions) URI: p.leafTCPMultiPortAppURI, PublicAddr: p.leafTCPMultiPortPublicAddr, TCPPorts: []servicecfg.PortRange{ - servicecfg.PortRange{ + { Port: p.leafTCPMultiPortAppPortAlpha, }, - servicecfg.PortRange{ + { Port: p.leafTCPMultiPortAppPortBeta, }, }, diff --git a/lib/events/api.go b/lib/events/api.go index b3e8792a9cb6..240f9e219e3b 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -60,6 +60,8 @@ const ( EventProtocolTDP = apievents.EventProtocolTDP // EventProtocolDB specifies database as a type of captured protocol EventProtocolDB = apievents.EventProtocolDB + // EventProtocolApp specifies app as a type of captured protocol + EventProtocolApp = apievents.EventProtocolApp // LocalAddr is a target address on the host LocalAddr = "addr.local" // RemoteAddr is a client (user's) address diff --git a/lib/services/parser.go b/lib/services/parser.go index 2365f56fe619..e76c20937929 100644 --- a/lib/services/parser.go +++ b/lib/services/parser.go @@ -480,7 +480,7 @@ func (ctx *Context) GetIdentifier(fields []string) (any, error) { case SessionIdentifier: var session events.AuditEvent = &events.SessionEnd{} switch ctx.Session.(type) { - case *events.SessionEnd, *events.WindowsDesktopSessionEnd, *events.LinuxDesktopSessionEnd, *events.DatabaseSessionEnd: + case *events.SessionEnd, *events.WindowsDesktopSessionEnd, *events.LinuxDesktopSessionEnd, *events.DatabaseSessionEnd, *events.AppSessionChunk: session = ctx.Session } v, origErr := predicate.GetFieldByTag(session, teleport.JSON, fields[1:]) @@ -537,7 +537,7 @@ func (ctx *Context) GetIdentifier(fields []string) (any, error) { } func getMissingEmptyFieldForSessionEnd(fields []string) (any, error) { - for _, emptySession := range []events.AuditEvent{&events.SessionEnd{}, &events.WindowsDesktopSessionEnd{}, &events.DatabaseSessionEnd{}} { + for _, emptySession := range []events.AuditEvent{&events.SessionEnd{}, &events.WindowsDesktopSessionEnd{}, &events.DatabaseSessionEnd{}, &events.AppSessionChunk{}} { v, err := predicate.GetFieldByTag(emptySession, teleport.JSON, fields[1:]) if err == nil { return v, nil diff --git a/lib/services/role_test.go b/lib/services/role_test.go index b6c6d5fbcf34..223b4bb56777 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -11245,6 +11245,9 @@ func TestSessionRecordingRBAC(t *testing.T) { NodeLabels: types.Labels{ "env": []string{"prod"}, }, + AppLabels: types.Labels{ + "env": []string{"prod"}, + }, Rules: allowRules, }, Deny: types.RoleConditions{ @@ -11270,6 +11273,25 @@ func TestSessionRecordingRBAC(t *testing.T) { "env": "dev", }) + newApp := func(name string, labels map[string]string) types.Application { + app, err := types.NewAppV3(types.Metadata{ + Name: name, + Labels: labels, + }, types.AppSpecV3{ + URI: "http://localhost:8080", + }) + require.NoError(t, err) + return app + } + + appWithAccess := newApp("app-with-access", map[string]string{ + "env": "prod", + }) + + appWithoutAccess := newApp("app-without-access", map[string]string{ + "env": "dev", + }) + alice := newUser( "alice", map[string][]string{ @@ -11764,6 +11786,70 @@ func TestSessionRecordingRBAC(t *testing.T) { }, errCheck: require.NoError, }, + { + name: "24 - bob can read app session because his group matches with env label for app", + roles: RoleSet{ + newRole(&types.Rule{ + Resources: []string{types.KindSession}, + Verbs: []string{types.VerbRead}, + Where: `contains(user.spec.traits["group"], session.app_labels["env"])`, + }, nil), + }, + context: Context{ + User: bob, + Session: newAppSessionChunkEvent(), + Resource: appWithAccess, + }, + errCheck: require.NoError, + }, + { + name: "25 - john can not read app session because his group does not match with env label for app", + roles: RoleSet{ + newRole(&types.Rule{ + Resources: []string{types.KindSession}, + Verbs: []string{types.VerbRead}, + Where: `contains(user.spec.traits["group"], session.app_labels["env"])`, + }, nil), + }, + context: Context{ + User: john, + Session: newAppSessionChunkEvent(), + Resource: appWithAccess, + }, + errCheck: requireAccessDenied, + }, + { + name: "26 - bob can read app session because he has access to the app", + roles: RoleSet{ + newRole(&types.Rule{ + Resources: []string{types.KindSession}, + Verbs: []string{types.VerbRead}, + Where: `can_view()`, + }, nil), + }, + context: Context{ + User: bob, + Session: newAppSessionChunkEvent(), + Resource: appWithAccess, + }, + errCheck: require.NoError, + }, + { + name: "27 - bob can not read app session because he has no access to the app", + roles: RoleSet{ + newRole(&types.Rule{ + Resources: []string{types.KindSession}, + Verbs: []string{types.VerbRead}, + Where: `can_view()`, + }, nil), + }, + context: Context{ + User: bob, + Session: newAppSessionChunkEvent(), + Resource: appWithoutAccess, + }, + errCheck: requireAccessDenied, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -11865,6 +11951,37 @@ func newDatabaseSessionEndEvent() *apievents.DatabaseSessionEnd { } } +func newAppSessionChunkEvent() *apievents.AppSessionChunk { + return &apievents.AppSessionChunk{ + Metadata: apievents.Metadata{ + Index: 20, + Type: "app.session.chunk", + ID: "da455e0f-c27d-459f-a218-4e83b3db9426", + Code: "T2008I", + Time: time.Date(2020, 3, 30, 15, 58, 54, 561*int(time.Millisecond), time.UTC), + }, + AppMetadata: apievents.AppMetadata{ + AppName: "app-with-access", + AppURI: "http://localhost:8080", + AppPublicAddr: "app.example.com", + AppLabels: map[string]string{"env": "prod"}, + }, + ConnectionMetadata: apievents.ConnectionMetadata{ + Protocol: apievents.EventProtocolApp, + }, + UserMetadata: apievents.UserMetadata{ + User: "alice", + UserRoles: []string{"role1"}, + UserTraits: map[string][]string{ + "group": {"prod"}, + "team": {"engineering"}, + }, + }, + Participants: []string{"alice"}, + SessionChunkID: "cb116fb0-9227-4889-9392-aedd13a914a6", + } +} + func TestCheckSubmitForUser(t *testing.T) { t.Parallel() diff --git a/lib/services/session_recording.go b/lib/services/session_recording.go index f3d8b331c68b..7cbff45572ed 100644 --- a/lib/services/session_recording.go +++ b/lib/services/session_recording.go @@ -106,6 +106,23 @@ func rebuildResourceFromSessionEndEvent(event apievents.AuditEvent) types.Resour Domain: sEnd.Domain, }, } + case *apievents.AppSessionChunk: + if sEnd == nil { + return nil + } + return &types.AppV3{ + Kind: types.KindApp, + Version: types.V3, + Metadata: types.Metadata{ + Name: sEnd.AppName, + Namespace: apidefaults.Namespace, + Labels: sEnd.AppLabels, + }, + Spec: types.AppSpecV3{ + URI: sEnd.AppURI, + PublicAddr: sEnd.AppPublicAddr, + }, + } case *apievents.DatabaseSessionEnd: if sEnd == nil { return nil diff --git a/lib/services/session_recording_test.go b/lib/services/session_recording_test.go index 699bd34a4110..232a452e578a 100644 --- a/lib/services/session_recording_test.go +++ b/lib/services/session_recording_test.go @@ -53,6 +53,11 @@ func TestRebuildResourceFromSessionEndEvent(t *testing.T) { event: (*apievents.DatabaseSessionEnd)(nil), want: nil, }, + { + name: "nil with type set app.session.chunk", + event: (*apievents.AppSessionChunk)(nil), + want: nil, + }, { name: "non session.end event", event: &apievents.UserLogin{ @@ -188,6 +193,36 @@ func TestRebuildResourceFromSessionEndEvent(t *testing.T) { Spec: types.DatabaseSpecV3{}, }, }, + { + name: "app session chunk", + event: &apievents.AppSessionChunk{ + AppMetadata: apievents.AppMetadata{ + AppName: "app-name", + AppURI: "http://localhost:8080", + AppPublicAddr: "app.example.com", + AppLabels: map[string]string{ + "env": "prod", + "team": "web", + }, + }, + }, + want: &types.AppV3{ + Kind: types.KindApp, + Version: types.V3, + Metadata: types.Metadata{ + Name: "app-name", + Namespace: "default", + Labels: map[string]string{ + "env": "prod", + "team": "web", + }, + }, + Spec: types.AppSpecV3{ + URI: "http://localhost:8080", + PublicAddr: "app.example.com", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/lib/srv/app/common/audit.go b/lib/srv/app/common/audit.go index 6d415f72ca77..152e9de67fc2 100644 --- a/lib/srv/app/common/audit.go +++ b/lib/srv/app/common/audit.go @@ -29,7 +29,9 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" awsutils "github.com/gravitational/teleport/lib/utils/aws" ) @@ -156,6 +158,14 @@ func (a *audit) OnSessionEnd(ctx context.Context, serverID string, identity *tls // OnSessionChunk is called when a new session chunk is created. func (a *audit) OnSessionChunk(ctx context.Context, serverID, chunkID string, identity *tlsca.Identity, app types.Application) error { + // Enforce IP Pinning if it is present in the user's certificate. + var clientAddr string + if clientSrcAddr, err := authz.ClientSrcAddrFromContext(ctx); err == nil { + clientAddr = clientSrcAddr.String() + } else { + clientAddr = identity.LoginIP + } + event := &apievents.AppSessionChunk{ Metadata: apievents.Metadata{ Type: events.AppSessionChunkEvent, @@ -167,15 +177,28 @@ func (a *audit) OnSessionChunk(ctx context.Context, serverID, chunkID string, id ServerID: serverID, ServerNamespace: apidefaults.Namespace, }, + ConnectionMetadata: apievents.ConnectionMetadata{ + LocalAddr: clientAddr, + RemoteAddr: app.GetURI(), + Protocol: events.EventProtocolApp, + }, SessionMetadata: getSessionMetadata(identity), UserMetadata: identity.GetUserMetadata(), AppMetadata: apievents.AppMetadata{ AppURI: app.GetURI(), AppPublicAddr: app.GetPublicAddr(), AppName: app.GetName(), + AppLabels: app.GetAllLabels(), // Session chunks are not created for TCP apps, so there's no need to pass TargetPort here. }, SessionChunkID: chunkID, + Participants: []string{services.UsernameForCluster( + services.UsernameForClusterConfig{ + User: identity.Username, + OriginClusterName: identity.OriginClusterName, + LocalClusterName: identity.TeleportCluster, + }, + )}, } return trace.Wrap(a.EmitEvent(ctx, event)) } diff --git a/lib/srv/app/server_test.go b/lib/srv/app/server_test.go index 347306076449..94a0d2cc964a 100644 --- a/lib/srv/app/server_test.go +++ b/lib/srv/app/server_test.go @@ -644,7 +644,6 @@ func TestShutdown(t *testing.T) { return } }, 10*time.Second, 100*time.Millisecond) - } }) } @@ -1037,7 +1036,6 @@ func TestRewriteJWT(t *testing.T) { }) }) } - } // TestAuthorize verifies that only authorized requests are handled. @@ -1293,7 +1291,12 @@ func TestRequestAuditEvents(t *testing.T) { AppURI: app.Spec.URI, AppPublicAddr: app.Spec.PublicAddr, AppName: app.Metadata.Name, + AppLabels: map[string]string{ + "bar": "baz", + "qux": "4", + }, }, + Participants: []string{"foo"}, } require.Empty(t, gocmp.Diff( expectedEvent, @@ -1370,7 +1373,12 @@ func TestRequestAuditEvents(t *testing.T) { AppURI: app.Spec.URI, AppPublicAddr: app.Spec.PublicAddr, AppName: app.Metadata.Name, + AppLabels: map[string]string{ + "bar": "baz", + "qux": "4", + }, }, + Participants: []string{"foo"}, } require.Empty(t, gocmp.Diff( expectedEvent, From 133844070f9019570a360248f5ace76796cc6ffb Mon Sep 17 00:00:00 2001 From: Chris Thach <12981621+cthach@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:26:48 +0000 Subject: [PATCH 292/347] MFA service stores and propagates the device ID used to satisfy a challenge. (#69572) * MFA service stores and propagates the device ID used to satisfy a challenge. Signed-off-by: Chris Thach * Wrap device ID with MFADevice. Signed-off-by: Chris Thach --------- Signed-off-by: Chris Thach --- .../teleport/mfa/v2/service_protohybrid.pb.go | 119 +++++++++++---- .../teleport/mfa/v2/service_protoopaque.pb.go | 117 +++++++++++---- .../v2/validated_challenge_protohybrid.pb.go | 121 +++++++++++++-- .../v2/validated_challenge_protoopaque.pb.go | 116 +++++++++++++-- api/proto/teleport/mfa/v2/service.proto | 9 +- .../teleport/mfa/v2/validated_challenge.proto | 9 ++ lib/auth/mfa/mfav2/service.go | 8 +- lib/auth/mfa/mfav2/service_test.go | 140 +++++++++++------- lib/reversetunnel/leaf_cluster.go | 1 + lib/reversetunnel/leaf_cluster_test.go | 4 + lib/reversetunnel/watcher_mfa_test.go | 3 + lib/services/local/events_test.go | 6 + lib/services/local/mfa.go | 2 + lib/services/local/mfa_test.go | 13 ++ 14 files changed, 536 insertions(+), 132 deletions(-) diff --git a/api/gen/proto/go/teleport/mfa/v2/service_protohybrid.pb.go b/api/gen/proto/go/teleport/mfa/v2/service_protohybrid.pb.go index 05630ac324d2..4009c0f70713 100644 --- a/api/gen/proto/go/teleport/mfa/v2/service_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/mfa/v2/service_protohybrid.pb.go @@ -643,7 +643,10 @@ type ReplicateValidatedMFAChallengeRequest struct { TargetCluster string `protobuf:"bytes,4,opt,name=target_cluster,json=targetCluster,proto3" json:"target_cluster,omitempty"` // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified by source_cluster. - Username string `protobuf:"bytes,5,opt,name=username,proto3" json:"username,omitempty"` + Username string `protobuf:"bytes,5,opt,name=username,proto3" json:"username,omitempty"` + // MFA device that satisfied the challenge in source_cluster. Must match the mfa_device of the validated challenge + // being replicated so that mfa_device locks are enforced in the target cluster. + MfaDevice *MFADevice `protobuf:"bytes,6,opt,name=mfa_device,json=mfaDevice,proto3" json:"mfa_device,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -708,6 +711,13 @@ func (x *ReplicateValidatedMFAChallengeRequest) GetUsername() string { return "" } +func (x *ReplicateValidatedMFAChallengeRequest) GetMfaDevice() *MFADevice { + if x != nil { + return x.MfaDevice + } + return nil +} + func (x *ReplicateValidatedMFAChallengeRequest) SetName(v string) { x.Name = v } @@ -728,6 +738,10 @@ func (x *ReplicateValidatedMFAChallengeRequest) SetUsername(v string) { x.Username = v } +func (x *ReplicateValidatedMFAChallengeRequest) SetMfaDevice(v *MFADevice) { + x.MfaDevice = v +} + func (x *ReplicateValidatedMFAChallengeRequest) HasPayload() bool { if x == nil { return false @@ -735,10 +749,21 @@ func (x *ReplicateValidatedMFAChallengeRequest) HasPayload() bool { return x.Payload != nil } +func (x *ReplicateValidatedMFAChallengeRequest) HasMfaDevice() bool { + if x == nil { + return false + } + return x.MfaDevice != nil +} + func (x *ReplicateValidatedMFAChallengeRequest) ClearPayload() { x.Payload = nil } +func (x *ReplicateValidatedMFAChallengeRequest) ClearMfaDevice() { + x.MfaDevice = nil +} + type ReplicateValidatedMFAChallengeRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -756,6 +781,9 @@ type ReplicateValidatedMFAChallengeRequest_builder struct { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified by source_cluster. Username string + // MFA device that satisfied the challenge in source_cluster. Must match the mfa_device of the validated challenge + // being replicated so that mfa_device locks are enforced in the target cluster. + MfaDevice *MFADevice } func (b0 ReplicateValidatedMFAChallengeRequest_builder) Build() *ReplicateValidatedMFAChallengeRequest { @@ -767,6 +795,7 @@ func (b0 ReplicateValidatedMFAChallengeRequest_builder) Build() *ReplicateValida x.SourceCluster = b.SourceCluster x.TargetCluster = b.TargetCluster x.Username = b.Username + x.MfaDevice = b.MfaDevice return m0 } @@ -974,7 +1003,10 @@ func (b0 VerifyValidatedMFAChallengeRequest_builder) Build() *VerifyValidatedMFA // VerifyValidatedMFAChallengeResponse is the response message for VerifyValidatedMFAChallenge. type VerifyValidatedMFAChallengeResponse struct { - state protoimpl.MessageState `protogen:"hybrid.v1"` + state protoimpl.MessageState `protogen:"hybrid.v1"` + // MFA device that satisfied the validated challenge. Callers MUST use this to enforce mfa_device locks for the + // session and MUST reject a nil or empty value. + MfaDevice *MFADevice `protobuf:"bytes,1,opt,name=mfa_device,json=mfaDevice,proto3" json:"mfa_device,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1004,15 +1036,41 @@ func (x *VerifyValidatedMFAChallengeResponse) ProtoReflect() protoreflect.Messag return mi.MessageOf(x) } +func (x *VerifyValidatedMFAChallengeResponse) GetMfaDevice() *MFADevice { + if x != nil { + return x.MfaDevice + } + return nil +} + +func (x *VerifyValidatedMFAChallengeResponse) SetMfaDevice(v *MFADevice) { + x.MfaDevice = v +} + +func (x *VerifyValidatedMFAChallengeResponse) HasMfaDevice() bool { + if x == nil { + return false + } + return x.MfaDevice != nil +} + +func (x *VerifyValidatedMFAChallengeResponse) ClearMfaDevice() { + x.MfaDevice = nil +} + type VerifyValidatedMFAChallengeResponse_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + // MFA device that satisfied the validated challenge. Callers MUST use this to enforce mfa_device locks for the + // session and MUST reject a nil or empty value. + MfaDevice *MFADevice } func (b0 VerifyValidatedMFAChallengeResponse_builder) Build() *VerifyValidatedMFAChallengeResponse { m0 := &VerifyValidatedMFAChallengeResponse{} b, x := &b0, m0 _, _ = b, x + x.MfaDevice = b.MfaDevice return m0 } @@ -1175,21 +1233,25 @@ const file_teleport_mfa_v2_service_proto_rawDesc = "" + "\x06filter\x18\x03 \x01(\v21.teleport.mfa.v2.ListValidatedMFAChallengesFilterR\x06filter\"\xa7\x01\n" + "\"ListValidatedMFAChallengesResponse\x12Y\n" + "\x14validated_challenges\x18\x01 \x03(\v2&.teleport.mfa.v2.ValidatedMFAChallengeR\x13validatedChallenges\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xeb\x01\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa6\x02\n" + "%ReplicateValidatedMFAChallengeRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12D\n" + "\apayload\x18\x02 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x03 \x01(\tR\rsourceCluster\x12%\n" + "\x0etarget_cluster\x18\x04 \x01(\tR\rtargetCluster\x12\x1a\n" + - "\busername\x18\x05 \x01(\tR\busername\"\x83\x01\n" + + "\busername\x18\x05 \x01(\tR\busername\x129\n" + + "\n" + + "mfa_device\x18\x06 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"\x83\x01\n" + "&ReplicateValidatedMFAChallengeResponse\x12Y\n" + "\x14replicated_challenge\x18\x01 \x01(\v2&.teleport.mfa.v2.ValidatedMFAChallengeR\x13replicatedChallenge\"\xc1\x01\n" + "\"VerifyValidatedMFAChallengeRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12D\n" + "\apayload\x18\x02 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x03 \x01(\tR\rsourceCluster\x12\x1a\n" + - "\busername\x18\x04 \x01(\tR\busername\"%\n" + - "#VerifyValidatedMFAChallengeResponse\"{\n" + + "\busername\x18\x04 \x01(\tR\busername\"`\n" + + "#VerifyValidatedMFAChallengeResponse\x129\n" + + "\n" + + "mfa_device\x18\x01 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"{\n" + "\"CompleteBrowserMFAChallengeRequest\x12U\n" + "\x14browser_mfa_response\x18\x01 \x01(\v2#.teleport.mfa.v2.BrowserMFAResponseR\x12browserMfaResponse\"O\n" + "#CompleteBrowserMFAChallengeResponse\x12(\n" + @@ -1222,7 +1284,8 @@ var file_teleport_mfa_v2_service_proto_goTypes = []any{ (*AuthenticateChallenge)(nil), // 14: teleport.mfa.v2.AuthenticateChallenge (*AuthenticateResponse)(nil), // 15: teleport.mfa.v2.AuthenticateResponse (*ValidatedMFAChallenge)(nil), // 16: teleport.mfa.v2.ValidatedMFAChallenge - (*BrowserMFAResponse)(nil), // 17: teleport.mfa.v2.BrowserMFAResponse + (*MFADevice)(nil), // 17: teleport.mfa.v2.MFADevice + (*BrowserMFAResponse)(nil), // 18: teleport.mfa.v2.BrowserMFAResponse } var file_teleport_mfa_v2_service_proto_depIdxs = []int32{ 13, // 0: teleport.mfa.v2.CreateSessionChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload @@ -1231,26 +1294,28 @@ var file_teleport_mfa_v2_service_proto_depIdxs = []int32{ 4, // 3: teleport.mfa.v2.ListValidatedMFAChallengesRequest.filter:type_name -> teleport.mfa.v2.ListValidatedMFAChallengesFilter 16, // 4: teleport.mfa.v2.ListValidatedMFAChallengesResponse.validated_challenges:type_name -> teleport.mfa.v2.ValidatedMFAChallenge 13, // 5: teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 16, // 6: teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse.replicated_challenge:type_name -> teleport.mfa.v2.ValidatedMFAChallenge - 13, // 7: teleport.mfa.v2.VerifyValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 17, // 8: teleport.mfa.v2.CompleteBrowserMFAChallengeRequest.browser_mfa_response:type_name -> teleport.mfa.v2.BrowserMFAResponse - 0, // 9: teleport.mfa.v2.MFAService.CreateSessionChallenge:input_type -> teleport.mfa.v2.CreateSessionChallengeRequest - 2, // 10: teleport.mfa.v2.MFAService.ValidateSessionChallenge:input_type -> teleport.mfa.v2.ValidateSessionChallengeRequest - 5, // 11: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:input_type -> teleport.mfa.v2.ListValidatedMFAChallengesRequest - 7, // 12: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:input_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest - 9, // 13: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:input_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeRequest - 11, // 14: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:input_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeRequest - 1, // 15: teleport.mfa.v2.MFAService.CreateSessionChallenge:output_type -> teleport.mfa.v2.CreateSessionChallengeResponse - 3, // 16: teleport.mfa.v2.MFAService.ValidateSessionChallenge:output_type -> teleport.mfa.v2.ValidateSessionChallengeResponse - 6, // 17: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:output_type -> teleport.mfa.v2.ListValidatedMFAChallengesResponse - 8, // 18: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:output_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse - 10, // 19: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:output_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeResponse - 12, // 20: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:output_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeResponse - 15, // [15:21] is the sub-list for method output_type - 9, // [9:15] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 17, // 6: teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 16, // 7: teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse.replicated_challenge:type_name -> teleport.mfa.v2.ValidatedMFAChallenge + 13, // 8: teleport.mfa.v2.VerifyValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload + 17, // 9: teleport.mfa.v2.VerifyValidatedMFAChallengeResponse.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 18, // 10: teleport.mfa.v2.CompleteBrowserMFAChallengeRequest.browser_mfa_response:type_name -> teleport.mfa.v2.BrowserMFAResponse + 0, // 11: teleport.mfa.v2.MFAService.CreateSessionChallenge:input_type -> teleport.mfa.v2.CreateSessionChallengeRequest + 2, // 12: teleport.mfa.v2.MFAService.ValidateSessionChallenge:input_type -> teleport.mfa.v2.ValidateSessionChallengeRequest + 5, // 13: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:input_type -> teleport.mfa.v2.ListValidatedMFAChallengesRequest + 7, // 14: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:input_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest + 9, // 15: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:input_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeRequest + 11, // 16: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:input_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeRequest + 1, // 17: teleport.mfa.v2.MFAService.CreateSessionChallenge:output_type -> teleport.mfa.v2.CreateSessionChallengeResponse + 3, // 18: teleport.mfa.v2.MFAService.ValidateSessionChallenge:output_type -> teleport.mfa.v2.ValidateSessionChallengeResponse + 6, // 19: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:output_type -> teleport.mfa.v2.ListValidatedMFAChallengesResponse + 8, // 20: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:output_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse + 10, // 21: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:output_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeResponse + 12, // 22: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:output_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeResponse + 17, // [17:23] is the sub-list for method output_type + 11, // [11:17] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_teleport_mfa_v2_service_proto_init() } diff --git a/api/gen/proto/go/teleport/mfa/v2/service_protoopaque.pb.go b/api/gen/proto/go/teleport/mfa/v2/service_protoopaque.pb.go index cd97fc149d88..b4bc313380fe 100644 --- a/api/gen/proto/go/teleport/mfa/v2/service_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/mfa/v2/service_protoopaque.pb.go @@ -624,6 +624,7 @@ type ReplicateValidatedMFAChallengeRequest struct { xxx_hidden_SourceCluster string `protobuf:"bytes,3,opt,name=source_cluster,json=sourceCluster,proto3"` xxx_hidden_TargetCluster string `protobuf:"bytes,4,opt,name=target_cluster,json=targetCluster,proto3"` xxx_hidden_Username string `protobuf:"bytes,5,opt,name=username,proto3"` + xxx_hidden_MfaDevice *MFADevice `protobuf:"bytes,6,opt,name=mfa_device,json=mfaDevice,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -688,6 +689,13 @@ func (x *ReplicateValidatedMFAChallengeRequest) GetUsername() string { return "" } +func (x *ReplicateValidatedMFAChallengeRequest) GetMfaDevice() *MFADevice { + if x != nil { + return x.xxx_hidden_MfaDevice + } + return nil +} + func (x *ReplicateValidatedMFAChallengeRequest) SetName(v string) { x.xxx_hidden_Name = v } @@ -708,6 +716,10 @@ func (x *ReplicateValidatedMFAChallengeRequest) SetUsername(v string) { x.xxx_hidden_Username = v } +func (x *ReplicateValidatedMFAChallengeRequest) SetMfaDevice(v *MFADevice) { + x.xxx_hidden_MfaDevice = v +} + func (x *ReplicateValidatedMFAChallengeRequest) HasPayload() bool { if x == nil { return false @@ -715,10 +727,21 @@ func (x *ReplicateValidatedMFAChallengeRequest) HasPayload() bool { return x.xxx_hidden_Payload != nil } +func (x *ReplicateValidatedMFAChallengeRequest) HasMfaDevice() bool { + if x == nil { + return false + } + return x.xxx_hidden_MfaDevice != nil +} + func (x *ReplicateValidatedMFAChallengeRequest) ClearPayload() { x.xxx_hidden_Payload = nil } +func (x *ReplicateValidatedMFAChallengeRequest) ClearMfaDevice() { + x.xxx_hidden_MfaDevice = nil +} + type ReplicateValidatedMFAChallengeRequest_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -736,6 +759,9 @@ type ReplicateValidatedMFAChallengeRequest_builder struct { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified by source_cluster. Username string + // MFA device that satisfied the challenge in source_cluster. Must match the mfa_device of the validated challenge + // being replicated so that mfa_device locks are enforced in the target cluster. + MfaDevice *MFADevice } func (b0 ReplicateValidatedMFAChallengeRequest_builder) Build() *ReplicateValidatedMFAChallengeRequest { @@ -747,6 +773,7 @@ func (b0 ReplicateValidatedMFAChallengeRequest_builder) Build() *ReplicateValida x.xxx_hidden_SourceCluster = b.SourceCluster x.xxx_hidden_TargetCluster = b.TargetCluster x.xxx_hidden_Username = b.Username + x.xxx_hidden_MfaDevice = b.MfaDevice return m0 } @@ -943,9 +970,10 @@ func (b0 VerifyValidatedMFAChallengeRequest_builder) Build() *VerifyValidatedMFA // VerifyValidatedMFAChallengeResponse is the response message for VerifyValidatedMFAChallenge. type VerifyValidatedMFAChallengeResponse struct { - state protoimpl.MessageState `protogen:"opaque.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_MfaDevice *MFADevice `protobuf:"bytes,1,opt,name=mfa_device,json=mfaDevice,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *VerifyValidatedMFAChallengeResponse) Reset() { @@ -973,15 +1001,41 @@ func (x *VerifyValidatedMFAChallengeResponse) ProtoReflect() protoreflect.Messag return mi.MessageOf(x) } +func (x *VerifyValidatedMFAChallengeResponse) GetMfaDevice() *MFADevice { + if x != nil { + return x.xxx_hidden_MfaDevice + } + return nil +} + +func (x *VerifyValidatedMFAChallengeResponse) SetMfaDevice(v *MFADevice) { + x.xxx_hidden_MfaDevice = v +} + +func (x *VerifyValidatedMFAChallengeResponse) HasMfaDevice() bool { + if x == nil { + return false + } + return x.xxx_hidden_MfaDevice != nil +} + +func (x *VerifyValidatedMFAChallengeResponse) ClearMfaDevice() { + x.xxx_hidden_MfaDevice = nil +} + type VerifyValidatedMFAChallengeResponse_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + // MFA device that satisfied the validated challenge. Callers MUST use this to enforce mfa_device locks for the + // session and MUST reject a nil or empty value. + MfaDevice *MFADevice } func (b0 VerifyValidatedMFAChallengeResponse_builder) Build() *VerifyValidatedMFAChallengeResponse { m0 := &VerifyValidatedMFAChallengeResponse{} b, x := &b0, m0 _, _ = b, x + x.xxx_hidden_MfaDevice = b.MfaDevice return m0 } @@ -1142,21 +1196,25 @@ const file_teleport_mfa_v2_service_proto_rawDesc = "" + "\x06filter\x18\x03 \x01(\v21.teleport.mfa.v2.ListValidatedMFAChallengesFilterR\x06filter\"\xa7\x01\n" + "\"ListValidatedMFAChallengesResponse\x12Y\n" + "\x14validated_challenges\x18\x01 \x03(\v2&.teleport.mfa.v2.ValidatedMFAChallengeR\x13validatedChallenges\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xeb\x01\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa6\x02\n" + "%ReplicateValidatedMFAChallengeRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12D\n" + "\apayload\x18\x02 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x03 \x01(\tR\rsourceCluster\x12%\n" + "\x0etarget_cluster\x18\x04 \x01(\tR\rtargetCluster\x12\x1a\n" + - "\busername\x18\x05 \x01(\tR\busername\"\x83\x01\n" + + "\busername\x18\x05 \x01(\tR\busername\x129\n" + + "\n" + + "mfa_device\x18\x06 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"\x83\x01\n" + "&ReplicateValidatedMFAChallengeResponse\x12Y\n" + "\x14replicated_challenge\x18\x01 \x01(\v2&.teleport.mfa.v2.ValidatedMFAChallengeR\x13replicatedChallenge\"\xc1\x01\n" + "\"VerifyValidatedMFAChallengeRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12D\n" + "\apayload\x18\x02 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x03 \x01(\tR\rsourceCluster\x12\x1a\n" + - "\busername\x18\x04 \x01(\tR\busername\"%\n" + - "#VerifyValidatedMFAChallengeResponse\"{\n" + + "\busername\x18\x04 \x01(\tR\busername\"`\n" + + "#VerifyValidatedMFAChallengeResponse\x129\n" + + "\n" + + "mfa_device\x18\x01 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"{\n" + "\"CompleteBrowserMFAChallengeRequest\x12U\n" + "\x14browser_mfa_response\x18\x01 \x01(\v2#.teleport.mfa.v2.BrowserMFAResponseR\x12browserMfaResponse\"O\n" + "#CompleteBrowserMFAChallengeResponse\x12(\n" + @@ -1189,7 +1247,8 @@ var file_teleport_mfa_v2_service_proto_goTypes = []any{ (*AuthenticateChallenge)(nil), // 14: teleport.mfa.v2.AuthenticateChallenge (*AuthenticateResponse)(nil), // 15: teleport.mfa.v2.AuthenticateResponse (*ValidatedMFAChallenge)(nil), // 16: teleport.mfa.v2.ValidatedMFAChallenge - (*BrowserMFAResponse)(nil), // 17: teleport.mfa.v2.BrowserMFAResponse + (*MFADevice)(nil), // 17: teleport.mfa.v2.MFADevice + (*BrowserMFAResponse)(nil), // 18: teleport.mfa.v2.BrowserMFAResponse } var file_teleport_mfa_v2_service_proto_depIdxs = []int32{ 13, // 0: teleport.mfa.v2.CreateSessionChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload @@ -1198,26 +1257,28 @@ var file_teleport_mfa_v2_service_proto_depIdxs = []int32{ 4, // 3: teleport.mfa.v2.ListValidatedMFAChallengesRequest.filter:type_name -> teleport.mfa.v2.ListValidatedMFAChallengesFilter 16, // 4: teleport.mfa.v2.ListValidatedMFAChallengesResponse.validated_challenges:type_name -> teleport.mfa.v2.ValidatedMFAChallenge 13, // 5: teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 16, // 6: teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse.replicated_challenge:type_name -> teleport.mfa.v2.ValidatedMFAChallenge - 13, // 7: teleport.mfa.v2.VerifyValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 17, // 8: teleport.mfa.v2.CompleteBrowserMFAChallengeRequest.browser_mfa_response:type_name -> teleport.mfa.v2.BrowserMFAResponse - 0, // 9: teleport.mfa.v2.MFAService.CreateSessionChallenge:input_type -> teleport.mfa.v2.CreateSessionChallengeRequest - 2, // 10: teleport.mfa.v2.MFAService.ValidateSessionChallenge:input_type -> teleport.mfa.v2.ValidateSessionChallengeRequest - 5, // 11: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:input_type -> teleport.mfa.v2.ListValidatedMFAChallengesRequest - 7, // 12: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:input_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest - 9, // 13: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:input_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeRequest - 11, // 14: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:input_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeRequest - 1, // 15: teleport.mfa.v2.MFAService.CreateSessionChallenge:output_type -> teleport.mfa.v2.CreateSessionChallengeResponse - 3, // 16: teleport.mfa.v2.MFAService.ValidateSessionChallenge:output_type -> teleport.mfa.v2.ValidateSessionChallengeResponse - 6, // 17: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:output_type -> teleport.mfa.v2.ListValidatedMFAChallengesResponse - 8, // 18: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:output_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse - 10, // 19: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:output_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeResponse - 12, // 20: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:output_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeResponse - 15, // [15:21] is the sub-list for method output_type - 9, // [9:15] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 17, // 6: teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 16, // 7: teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse.replicated_challenge:type_name -> teleport.mfa.v2.ValidatedMFAChallenge + 13, // 8: teleport.mfa.v2.VerifyValidatedMFAChallengeRequest.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload + 17, // 9: teleport.mfa.v2.VerifyValidatedMFAChallengeResponse.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 18, // 10: teleport.mfa.v2.CompleteBrowserMFAChallengeRequest.browser_mfa_response:type_name -> teleport.mfa.v2.BrowserMFAResponse + 0, // 11: teleport.mfa.v2.MFAService.CreateSessionChallenge:input_type -> teleport.mfa.v2.CreateSessionChallengeRequest + 2, // 12: teleport.mfa.v2.MFAService.ValidateSessionChallenge:input_type -> teleport.mfa.v2.ValidateSessionChallengeRequest + 5, // 13: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:input_type -> teleport.mfa.v2.ListValidatedMFAChallengesRequest + 7, // 14: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:input_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeRequest + 9, // 15: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:input_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeRequest + 11, // 16: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:input_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeRequest + 1, // 17: teleport.mfa.v2.MFAService.CreateSessionChallenge:output_type -> teleport.mfa.v2.CreateSessionChallengeResponse + 3, // 18: teleport.mfa.v2.MFAService.ValidateSessionChallenge:output_type -> teleport.mfa.v2.ValidateSessionChallengeResponse + 6, // 19: teleport.mfa.v2.MFAService.ListValidatedMFAChallenges:output_type -> teleport.mfa.v2.ListValidatedMFAChallengesResponse + 8, // 20: teleport.mfa.v2.MFAService.ReplicateValidatedMFAChallenge:output_type -> teleport.mfa.v2.ReplicateValidatedMFAChallengeResponse + 10, // 21: teleport.mfa.v2.MFAService.VerifyValidatedMFAChallenge:output_type -> teleport.mfa.v2.VerifyValidatedMFAChallengeResponse + 12, // 22: teleport.mfa.v2.MFAService.CompleteBrowserMFAChallenge:output_type -> teleport.mfa.v2.CompleteBrowserMFAChallengeResponse + 17, // [17:23] is the sub-list for method output_type + 11, // [11:17] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_teleport_mfa_v2_service_proto_init() } diff --git a/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protohybrid.pb.go b/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protohybrid.pb.go index 8042de362dbd..00265b94c36c 100644 --- a/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protohybrid.pb.go +++ b/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protohybrid.pb.go @@ -194,7 +194,9 @@ type ValidatedMFAChallengeSpec struct { TargetCluster string `protobuf:"bytes,3,opt,name=target_cluster,json=targetCluster,proto3" json:"target_cluster,omitempty"` // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified to source_cluster. - Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` + Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` + // MFA device that satisfied the challenge, as registered in source_cluster. Replicated verbatim to target_cluster. + MfaDevice *MFADevice `protobuf:"bytes,5,opt,name=mfa_device,json=mfaDevice,proto3" json:"mfa_device,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -252,6 +254,13 @@ func (x *ValidatedMFAChallengeSpec) GetUsername() string { return "" } +func (x *ValidatedMFAChallengeSpec) GetMfaDevice() *MFADevice { + if x != nil { + return x.MfaDevice + } + return nil +} + func (x *ValidatedMFAChallengeSpec) SetPayload(v *SessionIdentifyingPayload) { x.Payload = v } @@ -268,6 +277,10 @@ func (x *ValidatedMFAChallengeSpec) SetUsername(v string) { x.Username = v } +func (x *ValidatedMFAChallengeSpec) SetMfaDevice(v *MFADevice) { + x.MfaDevice = v +} + func (x *ValidatedMFAChallengeSpec) HasPayload() bool { if x == nil { return false @@ -275,10 +288,21 @@ func (x *ValidatedMFAChallengeSpec) HasPayload() bool { return x.Payload != nil } +func (x *ValidatedMFAChallengeSpec) HasMfaDevice() bool { + if x == nil { + return false + } + return x.MfaDevice != nil +} + func (x *ValidatedMFAChallengeSpec) ClearPayload() { x.Payload = nil } +func (x *ValidatedMFAChallengeSpec) ClearMfaDevice() { + x.MfaDevice = nil +} + type ValidatedMFAChallengeSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -291,6 +315,8 @@ type ValidatedMFAChallengeSpec_builder struct { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified to source_cluster. Username string + // MFA device that satisfied the challenge, as registered in source_cluster. Replicated verbatim to target_cluster. + MfaDevice *MFADevice } func (b0 ValidatedMFAChallengeSpec_builder) Build() *ValidatedMFAChallengeSpec { @@ -301,6 +327,7 @@ func (b0 ValidatedMFAChallengeSpec_builder) Build() *ValidatedMFAChallengeSpec { x.SourceCluster = b.SourceCluster x.TargetCluster = b.TargetCluster x.Username = b.Username + x.MfaDevice = b.MfaDevice return m0 } @@ -494,6 +521,68 @@ func (*SessionIdentifyingPayload_SshSessionId) isSessionIdentifyingPayload_Paylo func (*SessionIdentifyingPayload_TlsSessionId) isSessionIdentifyingPayload_Payload() {} +// MFADevice references the MFA device that satisfied a challenge. +type MFADevice struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // ID of the MFA device, matching the ID of the device registered to the user. For SSO MFA devices this is the auth + // connector ID rather than a generated UUID. Used as the LockTarget.MFADevice when authorizing the session. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MFADevice) Reset() { + *x = MFADevice{} + mi := &file_teleport_mfa_v2_validated_challenge_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MFADevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MFADevice) ProtoMessage() {} + +func (x *MFADevice) ProtoReflect() protoreflect.Message { + mi := &file_teleport_mfa_v2_validated_challenge_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MFADevice) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *MFADevice) SetId(v string) { + x.Id = v +} + +type MFADevice_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // ID of the MFA device, matching the ID of the device registered to the user. For SSO MFA devices this is the auth + // connector ID rather than a generated UUID. Used as the LockTarget.MFADevice when authorizing the session. + Id string +} + +func (b0 MFADevice_builder) Build() *MFADevice { + m0 := &MFADevice{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + return m0 +} + var File_teleport_mfa_v2_validated_challenge_proto protoreflect.FileDescriptor const file_teleport_mfa_v2_validated_challenge_proto_rawDesc = "" + @@ -504,33 +593,39 @@ const file_teleport_mfa_v2_validated_challenge_proto_rawDesc = "" + "\bsub_kind\x18\x02 \x01(\tR\asubKind\x12\x18\n" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12>\n" + - "\x04spec\x18\x05 \x01(\v2*.teleport.mfa.v2.ValidatedMFAChallengeSpecR\x04spec\"\xcb\x01\n" + + "\x04spec\x18\x05 \x01(\v2*.teleport.mfa.v2.ValidatedMFAChallengeSpecR\x04spec\"\x86\x02\n" + "\x19ValidatedMFAChallengeSpec\x12D\n" + "\apayload\x18\x01 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x02 \x01(\tR\rsourceCluster\x12%\n" + "\x0etarget_cluster\x18\x03 \x01(\tR\rtargetCluster\x12\x1a\n" + - "\busername\x18\x04 \x01(\tR\busername\"v\n" + + "\busername\x18\x04 \x01(\tR\busername\x129\n" + + "\n" + + "mfa_device\x18\x05 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"v\n" + "\x19SessionIdentifyingPayload\x12&\n" + "\x0essh_session_id\x18\x01 \x01(\fH\x00R\fsshSessionId\x12&\n" + "\x0etls_session_id\x18\x02 \x01(\fH\x00R\ftlsSessionIdB\t\n" + - "\apayloadBJZHgithub.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v2;mfav2b\x06proto3" + "\apayload\"\x1b\n" + + "\tMFADevice\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02idBJZHgithub.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v2;mfav2b\x06proto3" -var file_teleport_mfa_v2_validated_challenge_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_teleport_mfa_v2_validated_challenge_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_teleport_mfa_v2_validated_challenge_proto_goTypes = []any{ (*ValidatedMFAChallenge)(nil), // 0: teleport.mfa.v2.ValidatedMFAChallenge (*ValidatedMFAChallengeSpec)(nil), // 1: teleport.mfa.v2.ValidatedMFAChallengeSpec (*SessionIdentifyingPayload)(nil), // 2: teleport.mfa.v2.SessionIdentifyingPayload - (*v1.Metadata)(nil), // 3: teleport.header.v1.Metadata + (*MFADevice)(nil), // 3: teleport.mfa.v2.MFADevice + (*v1.Metadata)(nil), // 4: teleport.header.v1.Metadata } var file_teleport_mfa_v2_validated_challenge_proto_depIdxs = []int32{ - 3, // 0: teleport.mfa.v2.ValidatedMFAChallenge.metadata:type_name -> teleport.header.v1.Metadata + 4, // 0: teleport.mfa.v2.ValidatedMFAChallenge.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.mfa.v2.ValidatedMFAChallenge.spec:type_name -> teleport.mfa.v2.ValidatedMFAChallengeSpec 2, // 2: teleport.mfa.v2.ValidatedMFAChallengeSpec.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 3, // 3: teleport.mfa.v2.ValidatedMFAChallengeSpec.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_mfa_v2_validated_challenge_proto_init() } @@ -548,7 +643,7 @@ func file_teleport_mfa_v2_validated_challenge_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_mfa_v2_validated_challenge_proto_rawDesc), len(file_teleport_mfa_v2_validated_challenge_proto_rawDesc)), NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protoopaque.pb.go b/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protoopaque.pb.go index 80ff2307dbc0..bed723464467 100644 --- a/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protoopaque.pb.go +++ b/api/gen/proto/go/teleport/mfa/v2/validated_challenge_protoopaque.pb.go @@ -185,6 +185,7 @@ type ValidatedMFAChallengeSpec struct { xxx_hidden_SourceCluster string `protobuf:"bytes,2,opt,name=source_cluster,json=sourceCluster,proto3"` xxx_hidden_TargetCluster string `protobuf:"bytes,3,opt,name=target_cluster,json=targetCluster,proto3"` xxx_hidden_Username string `protobuf:"bytes,4,opt,name=username,proto3"` + xxx_hidden_MfaDevice *MFADevice `protobuf:"bytes,5,opt,name=mfa_device,json=mfaDevice,proto3"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -242,6 +243,13 @@ func (x *ValidatedMFAChallengeSpec) GetUsername() string { return "" } +func (x *ValidatedMFAChallengeSpec) GetMfaDevice() *MFADevice { + if x != nil { + return x.xxx_hidden_MfaDevice + } + return nil +} + func (x *ValidatedMFAChallengeSpec) SetPayload(v *SessionIdentifyingPayload) { x.xxx_hidden_Payload = v } @@ -258,6 +266,10 @@ func (x *ValidatedMFAChallengeSpec) SetUsername(v string) { x.xxx_hidden_Username = v } +func (x *ValidatedMFAChallengeSpec) SetMfaDevice(v *MFADevice) { + x.xxx_hidden_MfaDevice = v +} + func (x *ValidatedMFAChallengeSpec) HasPayload() bool { if x == nil { return false @@ -265,10 +277,21 @@ func (x *ValidatedMFAChallengeSpec) HasPayload() bool { return x.xxx_hidden_Payload != nil } +func (x *ValidatedMFAChallengeSpec) HasMfaDevice() bool { + if x == nil { + return false + } + return x.xxx_hidden_MfaDevice != nil +} + func (x *ValidatedMFAChallengeSpec) ClearPayload() { x.xxx_hidden_Payload = nil } +func (x *ValidatedMFAChallengeSpec) ClearMfaDevice() { + x.xxx_hidden_MfaDevice = nil +} + type ValidatedMFAChallengeSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -281,6 +304,8 @@ type ValidatedMFAChallengeSpec_builder struct { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified to source_cluster. Username string + // MFA device that satisfied the challenge, as registered in source_cluster. Replicated verbatim to target_cluster. + MfaDevice *MFADevice } func (b0 ValidatedMFAChallengeSpec_builder) Build() *ValidatedMFAChallengeSpec { @@ -291,6 +316,7 @@ func (b0 ValidatedMFAChallengeSpec_builder) Build() *ValidatedMFAChallengeSpec { x.xxx_hidden_SourceCluster = b.SourceCluster x.xxx_hidden_TargetCluster = b.TargetCluster x.xxx_hidden_Username = b.Username + x.xxx_hidden_MfaDevice = b.MfaDevice return m0 } @@ -473,6 +499,66 @@ func (*sessionIdentifyingPayload_SshSessionId) isSessionIdentifyingPayload_Paylo func (*sessionIdentifyingPayload_TlsSessionId) isSessionIdentifyingPayload_Payload() {} +// MFADevice references the MFA device that satisfied a challenge. +type MFADevice struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MFADevice) Reset() { + *x = MFADevice{} + mi := &file_teleport_mfa_v2_validated_challenge_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MFADevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MFADevice) ProtoMessage() {} + +func (x *MFADevice) ProtoReflect() protoreflect.Message { + mi := &file_teleport_mfa_v2_validated_challenge_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MFADevice) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *MFADevice) SetId(v string) { + x.xxx_hidden_Id = v +} + +type MFADevice_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // ID of the MFA device, matching the ID of the device registered to the user. For SSO MFA devices this is the auth + // connector ID rather than a generated UUID. Used as the LockTarget.MFADevice when authorizing the session. + Id string +} + +func (b0 MFADevice_builder) Build() *MFADevice { + m0 := &MFADevice{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + return m0 +} + var File_teleport_mfa_v2_validated_challenge_proto protoreflect.FileDescriptor const file_teleport_mfa_v2_validated_challenge_proto_rawDesc = "" + @@ -483,33 +569,39 @@ const file_teleport_mfa_v2_validated_challenge_proto_rawDesc = "" + "\bsub_kind\x18\x02 \x01(\tR\asubKind\x12\x18\n" + "\aversion\x18\x03 \x01(\tR\aversion\x128\n" + "\bmetadata\x18\x04 \x01(\v2\x1c.teleport.header.v1.MetadataR\bmetadata\x12>\n" + - "\x04spec\x18\x05 \x01(\v2*.teleport.mfa.v2.ValidatedMFAChallengeSpecR\x04spec\"\xcb\x01\n" + + "\x04spec\x18\x05 \x01(\v2*.teleport.mfa.v2.ValidatedMFAChallengeSpecR\x04spec\"\x86\x02\n" + "\x19ValidatedMFAChallengeSpec\x12D\n" + "\apayload\x18\x01 \x01(\v2*.teleport.mfa.v2.SessionIdentifyingPayloadR\apayload\x12%\n" + "\x0esource_cluster\x18\x02 \x01(\tR\rsourceCluster\x12%\n" + "\x0etarget_cluster\x18\x03 \x01(\tR\rtargetCluster\x12\x1a\n" + - "\busername\x18\x04 \x01(\tR\busername\"v\n" + + "\busername\x18\x04 \x01(\tR\busername\x129\n" + + "\n" + + "mfa_device\x18\x05 \x01(\v2\x1a.teleport.mfa.v2.MFADeviceR\tmfaDevice\"v\n" + "\x19SessionIdentifyingPayload\x12&\n" + "\x0essh_session_id\x18\x01 \x01(\fH\x00R\fsshSessionId\x12&\n" + "\x0etls_session_id\x18\x02 \x01(\fH\x00R\ftlsSessionIdB\t\n" + - "\apayloadBJZHgithub.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v2;mfav2b\x06proto3" + "\apayload\"\x1b\n" + + "\tMFADevice\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02idBJZHgithub.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v2;mfav2b\x06proto3" -var file_teleport_mfa_v2_validated_challenge_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_teleport_mfa_v2_validated_challenge_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_teleport_mfa_v2_validated_challenge_proto_goTypes = []any{ (*ValidatedMFAChallenge)(nil), // 0: teleport.mfa.v2.ValidatedMFAChallenge (*ValidatedMFAChallengeSpec)(nil), // 1: teleport.mfa.v2.ValidatedMFAChallengeSpec (*SessionIdentifyingPayload)(nil), // 2: teleport.mfa.v2.SessionIdentifyingPayload - (*v1.Metadata)(nil), // 3: teleport.header.v1.Metadata + (*MFADevice)(nil), // 3: teleport.mfa.v2.MFADevice + (*v1.Metadata)(nil), // 4: teleport.header.v1.Metadata } var file_teleport_mfa_v2_validated_challenge_proto_depIdxs = []int32{ - 3, // 0: teleport.mfa.v2.ValidatedMFAChallenge.metadata:type_name -> teleport.header.v1.Metadata + 4, // 0: teleport.mfa.v2.ValidatedMFAChallenge.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.mfa.v2.ValidatedMFAChallenge.spec:type_name -> teleport.mfa.v2.ValidatedMFAChallengeSpec 2, // 2: teleport.mfa.v2.ValidatedMFAChallengeSpec.payload:type_name -> teleport.mfa.v2.SessionIdentifyingPayload - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 3, // 3: teleport.mfa.v2.ValidatedMFAChallengeSpec.mfa_device:type_name -> teleport.mfa.v2.MFADevice + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_mfa_v2_validated_challenge_proto_init() } @@ -527,7 +619,7 @@ func file_teleport_mfa_v2_validated_challenge_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_mfa_v2_validated_challenge_proto_rawDesc), len(file_teleport_mfa_v2_validated_challenge_proto_rawDesc)), NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/mfa/v2/service.proto b/api/proto/teleport/mfa/v2/service.proto index 66a5a65a6e51..c07302905a2f 100644 --- a/api/proto/teleport/mfa/v2/service.proto +++ b/api/proto/teleport/mfa/v2/service.proto @@ -132,6 +132,9 @@ message ReplicateValidatedMFAChallengeRequest { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified by source_cluster. string username = 5; + // MFA device that satisfied the challenge in source_cluster. Must match the mfa_device of the validated challenge + // being replicated so that mfa_device locks are enforced in the target cluster. + MFADevice mfa_device = 6; } // ReplicateValidatedMFAChallengeResponse is the response message for ReplicateValidatedMFAChallenge. @@ -159,7 +162,11 @@ message VerifyValidatedMFAChallengeRequest { } // VerifyValidatedMFAChallengeResponse is the response message for VerifyValidatedMFAChallenge. -message VerifyValidatedMFAChallengeResponse {} +message VerifyValidatedMFAChallengeResponse { + // MFA device that satisfied the validated challenge. Callers MUST use this to enforce mfa_device locks for the + // session and MUST reject a nil or empty value. + MFADevice mfa_device = 1; +} // CompleteBrowserMFAChallengeRequest is used to complete an MFA response // during a browser-based MFA authentication flow. diff --git a/api/proto/teleport/mfa/v2/validated_challenge.proto b/api/proto/teleport/mfa/v2/validated_challenge.proto index 390e55a18480..c8d262ca12e2 100644 --- a/api/proto/teleport/mfa/v2/validated_challenge.proto +++ b/api/proto/teleport/mfa/v2/validated_challenge.proto @@ -45,6 +45,8 @@ message ValidatedMFAChallengeSpec { // Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH // login name) and must correspond to a user in the cluster specified to source_cluster. string username = 4; + // MFA device that satisfied the challenge, as registered in source_cluster. Replicated verbatim to target_cluster. + MFADevice mfa_device = 5; } // SessionIdentifyingPayload contains a value that uniquely identifies a user's session. It must be computed by the @@ -59,3 +61,10 @@ message SessionIdentifyingPayload { bytes tls_session_id = 2; } } + +// MFADevice references the MFA device that satisfied a challenge. +message MFADevice { + // ID of the MFA device, matching the ID of the device registered to the user. For SSO MFA devices this is the auth + // connector ID rather than a generated UUID. Used as the LockTarget.MFADevice when authorizing the session. + string id = 1; +} diff --git a/lib/auth/mfa/mfav2/service.go b/lib/auth/mfa/mfav2/service.go index 8d44d4a59159..c99ef16fd18c 100644 --- a/lib/auth/mfa/mfav2/service.go +++ b/lib/auth/mfa/mfav2/service.go @@ -404,6 +404,9 @@ func (s *Service) ValidateSessionChallenge( SourceCluster: details.SourceCluster, TargetCluster: details.TargetCluster, Username: username, + MfaDevice: mfav2.MFADevice_builder{ + Id: details.Device.Id, + }.Build(), }.Build(), }.Build(), ) @@ -503,6 +506,7 @@ func (s *Service) ReplicateValidatedMFAChallenge( SourceCluster: req.GetSourceCluster(), TargetCluster: req.GetTargetCluster(), Username: req.GetUsername(), + MfaDevice: req.GetMfaDevice(), }.Build(), }.Build() @@ -566,7 +570,9 @@ func (s *Service) VerifyValidatedMFAChallenge( return nil, trace.AccessDenied("request payload does not match validated challenge payload") } - return &mfav2.VerifyValidatedMFAChallengeResponse{}, nil + return mfav2.VerifyValidatedMFAChallengeResponse_builder{ + MfaDevice: chal.GetSpec().GetMfaDevice(), + }.Build(), nil } func validateCreateSessionChallengeRequest(req *mfav2.CreateSessionChallengeRequest) error { diff --git a/lib/auth/mfa/mfav2/service_test.go b/lib/auth/mfa/mfav2/service_test.go index 09eedc3605b5..0324e4a2711c 100644 --- a/lib/auth/mfa/mfav2/service_test.go +++ b/lib/auth/mfa/mfav2/service_test.go @@ -47,6 +47,8 @@ const ( chalName = "test-challenge" sourceCluster = "test-cluster" targetCluster = "test-cluster" + deviceID = "test-device-id" + username = "test-user" ) var payload = mfav2.SessionIdentifyingPayload_builder{ @@ -165,6 +167,9 @@ func testCreateValidateSessionChallengeWebauthn(t *testing.T, payload *mfav2.Ses SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: device.MFA.Id, + }.Build(), }.Build(), }.Build() @@ -190,6 +195,7 @@ func TestCreateValidateSessionChallenge_SSO(t *testing.T) { Metadata: types.Metadata{ Name: "sso-device", }, + Id: deviceID, Device: &types.MFADevice_Sso{ Sso: &types.SSOMFADevice{ DisplayName: "test-display-name", @@ -274,6 +280,9 @@ func TestCreateValidateSessionChallenge_SSO(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build() @@ -696,6 +705,9 @@ func TestListValidatedMFAChallenges_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build(), mfav2.ValidatedMFAChallenge_builder{ @@ -709,6 +721,9 @@ func TestListValidatedMFAChallenges_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build(), mfav2.ValidatedMFAChallenge_builder{ @@ -722,6 +737,9 @@ func TestListValidatedMFAChallenges_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build(), } @@ -832,6 +850,9 @@ func TestListValidatedMFAChallenges_FilterByTargetCluster(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build(), } @@ -882,6 +903,9 @@ func TestReplicateValidatedMFAChallenge_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build()) require.NoError(t, err) @@ -897,6 +921,9 @@ func TestReplicateValidatedMFAChallenge_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build(), }.Build() @@ -925,7 +952,7 @@ func TestReplicateValidatedMFAChallenge_NonRemoteProxyDenied(t *testing.T) { Payload: payload, SourceCluster: sourceCluster, TargetCluster: targetCluster, - Username: "test-user", + Username: username, }.Build()) require.Error(t, err) require.ErrorIs(t, err, trace.AccessDenied("identity is not a remote builtin role, cannot be a remote proxy")) @@ -964,7 +991,7 @@ func TestReplicateValidatedMFAChallenge_RemoteBuiltinWrongRoleDenied(t *testing. Payload: payload, SourceCluster: sourceCluster, TargetCluster: targetCluster, - Username: "test-user", + Username: username, }.Build()) require.Error(t, err) require.ErrorIs( @@ -988,7 +1015,7 @@ func TestReplicateValidatedMFAChallenge_RemoteProxyWrongClusterDenied(t *testing Payload: payload, SourceCluster: sourceCluster, TargetCluster: targetCluster, - Username: "test-user", + Username: username, }.Build()) require.Error(t, err) require.ErrorIs( @@ -1015,7 +1042,10 @@ func TestReplicateValidatedMFAChallenge_TargetClusterMismatch(t *testing.T) { Payload: payload, SourceCluster: sourceCluster, TargetCluster: "different-cluster", - Username: "test-user", + Username: username, + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build()) require.Error(t, err) require.ErrorIs(t, err, trace.BadParameter(`target cluster "different-cluster" does not match current cluster "test-cluster"`)) @@ -1029,6 +1059,15 @@ func TestReplicateValidatedMFAChallenge_InvalidRequest(t *testing.T) { ctx := authz.ContextWithUser(t.Context(), authtest.TestRemoteBuiltin(types.RoleProxy, targetCluster).I) + baseReq := mfav2.ReplicateValidatedMFAChallengeRequest_builder{ + Name: chalName, + Payload: payload, + SourceCluster: sourceCluster, + TargetCluster: targetCluster, + Username: username, + MfaDevice: mfav2.MFADevice_builder{Id: deviceID}.Build(), + } + for _, testCase := range []struct { name string req *mfav2.ReplicateValidatedMFAChallengeRequest @@ -1036,70 +1075,58 @@ func TestReplicateValidatedMFAChallenge_InvalidRequest(t *testing.T) { }{ { name: "missing Name", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: "", - Payload: payload, - SourceCluster: sourceCluster, - TargetCluster: targetCluster, - Username: "test-user", - }.Build(), + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.Name = "" + return req.Build() + }(), expectedError: trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest name"), }, { name: "missing Payload", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: chalName, - Payload: nil, - SourceCluster: sourceCluster, - TargetCluster: targetCluster, - Username: "test-user", - }.Build(), + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.Payload = nil + return req.Build() + }(), expectedError: trace.BadParameter("missing SessionIdentifyingPayload in request"), }, { name: "missing SourceCluster", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: chalName, - Payload: payload, - SourceCluster: "", - TargetCluster: targetCluster, - Username: "test-user", - }.Build(), + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.SourceCluster = "" + return req.Build() + }(), expectedError: trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest source_cluster"), }, { name: "missing TargetCluster", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: chalName, - Payload: payload, - SourceCluster: sourceCluster, - TargetCluster: "", - Username: "test-user", - }.Build(), + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.TargetCluster = "" + return req.Build() + }(), expectedError: trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest target_cluster"), }, { name: "missing Username", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: chalName, - Payload: payload, - SourceCluster: sourceCluster, - TargetCluster: targetCluster, - Username: "", - }.Build(), + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.Username = "" + return req.Build() + }(), expectedError: trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest username"), }, { name: "empty SshSessionId in Payload", - req: mfav2.ReplicateValidatedMFAChallengeRequest_builder{ - Name: chalName, - Payload: mfav2.SessionIdentifyingPayload_builder{ + req: func() *mfav2.ReplicateValidatedMFAChallengeRequest { + req := baseReq + req.Payload = mfav2.SessionIdentifyingPayload_builder{ SshSessionId: []byte{}, - }.Build(), - SourceCluster: sourceCluster, - TargetCluster: targetCluster, - Username: "test-user", - }.Build(), + }.Build() + return req.Build() + }(), expectedError: trace.BadParameter("ssh_session_id must not be empty"), }, } { @@ -1157,6 +1184,9 @@ func TestVerifyValidatedMFAChallenge_Success(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build() @@ -1187,6 +1217,10 @@ func TestVerifyValidatedMFAChallenge_Success(t *testing.T) { return trace.BadParameter("expected non-nil response") } + if resp.GetMfaDevice().GetId() != deviceID { + return trace.BadParameter("mfa_device.id mismatch: got %q, expected %q", resp.GetMfaDevice().GetId(), deviceID) + } + return nil }) @@ -1234,6 +1268,9 @@ func TestVerifyValidatedMFAChallenge_PayloadMismatch(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build() @@ -1273,6 +1310,9 @@ func TestVerifyValidatedMFAChallenge_SourceClusterMismatch(t *testing.T) { SourceCluster: sourceCluster, TargetCluster: targetCluster, Username: user.GetName(), + MfaDevice: mfav2.MFADevice_builder{ + Id: deviceID, + }.Build(), }.Build(), }.Build() _, err := authServer.Auth().MFAService.CreateValidatedMFAChallenge(ctx, targetCluster, chal) @@ -1335,7 +1375,7 @@ func TestVerifyValidatedMFAChallenge_InvalidRequest(t *testing.T) { { name: "Missing name", req: mfav2.VerifyValidatedMFAChallengeRequest_builder{ - Username: "test-user", + Username: username, Name: "", Payload: payload, SourceCluster: sourceCluster, @@ -1345,7 +1385,7 @@ func TestVerifyValidatedMFAChallenge_InvalidRequest(t *testing.T) { { name: "Missing payload", req: mfav2.VerifyValidatedMFAChallengeRequest_builder{ - Username: "test-user", + Username: username, Name: chalName, Payload: nil, SourceCluster: sourceCluster, @@ -1355,7 +1395,7 @@ func TestVerifyValidatedMFAChallenge_InvalidRequest(t *testing.T) { { name: "Empty SshSessionId", req: mfav2.VerifyValidatedMFAChallengeRequest_builder{ - Username: "test-user", + Username: username, Name: chalName, Payload: mfav2.SessionIdentifyingPayload_builder{SshSessionId: []byte{}}.Build(), SourceCluster: sourceCluster, diff --git a/lib/reversetunnel/leaf_cluster.go b/lib/reversetunnel/leaf_cluster.go index aebbd0baab0c..0139d6fea151 100644 --- a/lib/reversetunnel/leaf_cluster.go +++ b/lib/reversetunnel/leaf_cluster.go @@ -1203,6 +1203,7 @@ func (s *leafCluster) syncValidatedMFAChallenges( SourceCluster: challenge.GetSpec().GetSourceCluster(), TargetCluster: challenge.GetSpec().GetTargetCluster(), Username: challenge.GetSpec().GetUsername(), + MfaDevice: challenge.GetSpec().GetMfaDevice(), }.Build(), ); err != nil && !trace.IsAlreadyExists(err) { log.ErrorContext( diff --git a/lib/reversetunnel/leaf_cluster_test.go b/lib/reversetunnel/leaf_cluster_test.go index 048fa14b4d91..3a6ab1119d55 100644 --- a/lib/reversetunnel/leaf_cluster_test.go +++ b/lib/reversetunnel/leaf_cluster_test.go @@ -487,6 +487,7 @@ func replicateValidatedMFAChallengeRequest( SourceCluster: chal.GetSpec().GetSourceCluster(), TargetCluster: chal.GetSpec().GetTargetCluster(), Username: chal.GetSpec().GetUsername(), + MfaDevice: chal.GetSpec().GetMfaDevice(), }.Build() } @@ -507,6 +508,9 @@ func newValidatedMFAChallenge(name string) *mfav2.ValidatedMFAChallenge { SourceCluster: "root.example.com", TargetCluster: "leaf.example.com", Username: "alice", + MfaDevice: mfav2.MFADevice_builder{ + Id: "test-device-id", + }.Build(), }.Build(), }.Build() } diff --git a/lib/reversetunnel/watcher_mfa_test.go b/lib/reversetunnel/watcher_mfa_test.go index d9341c2a5b1a..ead1d22daf30 100644 --- a/lib/reversetunnel/watcher_mfa_test.go +++ b/lib/reversetunnel/watcher_mfa_test.go @@ -261,6 +261,9 @@ func newValidatedMFAChallengeWithTargetCluster(name, targetCluster string) *mfav SourceCluster: "root", TargetCluster: targetCluster, Username: "alice", + MfaDevice: mfav2.MFADevice_builder{ + Id: "device-id", + }.Build(), }.Build(), }.Build() } diff --git a/lib/services/local/events_test.go b/lib/services/local/events_test.go index c7715aa6d812..1c059ba575a0 100644 --- a/lib/services/local/events_test.go +++ b/lib/services/local/events_test.go @@ -472,6 +472,9 @@ func TestWatchers(t *testing.T) { SourceCluster: "root.example.com", TargetCluster: "leaf.example.com", Username: "alice", + MfaDevice: mfav2.MFADevice_builder{ + Id: "device-id", + }.Build(), }.Build(), }.Build(), ) @@ -507,6 +510,9 @@ func TestWatchers(t *testing.T) { SourceCluster: "root.example.com", TargetCluster: "leaf.example.com", Username: "alice", + MfaDevice: mfav2.MFADevice_builder{ + Id: "device-id", + }.Build(), }.Build(), }.Build()) require.NoError(subtestT, err) diff --git a/lib/services/local/mfa.go b/lib/services/local/mfa.go index e8f72d318939..85bb5741b044 100644 --- a/lib/services/local/mfa.go +++ b/lib/services/local/mfa.go @@ -217,6 +217,8 @@ func checkValidatedMFAChallenge(chal *mfav2.ValidatedMFAChallenge) error { return trace.BadParameter("target_cluster must be set") case chal.GetSpec().GetUsername() == "": return trace.BadParameter("username must be set") + case chal.GetSpec().GetMfaDevice().GetId() == "": + return trace.BadParameter("mfa_device.id must be set") } switch chal.GetSpec().GetPayload().WhichPayload() { diff --git a/lib/services/local/mfa_test.go b/lib/services/local/mfa_test.go index aebeb1adab04..13ca5dd4602b 100644 --- a/lib/services/local/mfa_test.go +++ b/lib/services/local/mfa_test.go @@ -253,6 +253,16 @@ func TestMFAService_CreateValidatedMFAChallenge_Validation(t *testing.T) { }(), wantErr: trace.BadParameter("username must be set"), }, + { + name: "missing mfa_device.id", + targetCluster: &defaultTargetCluster, + chal: func() *mfav2.ValidatedMFAChallenge { + c := newValidatedMFAChallenge() + c.GetSpec().SetMfaDevice(mfav2.MFADevice_builder{Id: ""}.Build()) + return c + }(), + wantErr: trace.BadParameter("mfa_device.id must be set"), + }, { name: "request target_cluster mismatch", targetCluster: &defaultTargetCluster, @@ -401,6 +411,9 @@ func newValidatedMFAChallenge() *mfav2.ValidatedMFAChallenge { SourceCluster: "src", TargetCluster: "tgt", Username: "alice", + MfaDevice: mfav2.MFADevice_builder{ + Id: "device-id", + }.Build(), }.Build(), }.Build() } From 286ce6d8d892d77df231b6fb494fa152c3f033e5 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Thu, 13 Aug 2026 15:32:59 +0000 Subject: [PATCH 293/347] Change the way e2e logs in for tests, enable unauthenticated tests (#69637) * Change the way e2e logs in for tests, enable unauthenticated tests * Change the SSH clipboard test to use the browser selector instead of skipping * Remove workaround in darktheme test * Share login state for connect tests * Keep the DisposableTempDir instead of the path --- e2e/config/teleport.yaml.tmpl | 6 + e2e/global-setup.ts | 66 ---------- e2e/helpers/authState.ts | 56 ++++++++ e2e/helpers/clientIp.ts | 49 +++++++ e2e/helpers/connect.ts | 122 +++++++++++++++--- e2e/helpers/env.ts | 2 + e2e/helpers/login.ts | 14 +- e2e/helpers/test.ts | 31 +++-- e2e/playwright.config.ts | 11 +- e2e/runner/bootstrap.go | 6 +- e2e/runner/bootstrap_test.go | 45 +++++++ e2e/runner/playwright.go | 10 +- e2e/runner/usercredentials.go | 13 ++ e2e/scripts/open-with-webauthn.ts | 18 ++- e2e/tests/connect/kube.spec.ts | 13 +- e2e/tests/connect/stateRestoration.spec.ts | 43 +----- .../web/authenticated/ssh-clipboard.spec.ts | 11 +- .../web/unauthenticated/darktheme.spec.ts | 11 +- e2e/tests/web/unauthenticated/login.spec.ts | 3 - e2e/tests/web/unauthenticated/signup.spec.ts | 25 +--- 20 files changed, 347 insertions(+), 208 deletions(-) delete mode 100644 e2e/global-setup.ts create mode 100644 e2e/helpers/authState.ts create mode 100644 e2e/helpers/clientIp.ts diff --git a/e2e/config/teleport.yaml.tmpl b/e2e/config/teleport.yaml.tmpl index 75a16279d17c..f47dbaefd4ff 100644 --- a/e2e/config/teleport.yaml.tmpl +++ b/e2e/config/teleport.yaml.tmpl @@ -39,6 +39,12 @@ kubernetes_service: proxy_service: enabled: "yes" + # The web API's rate limiter buckets by client IP, so without this the whole run shares one bucket. + trust_x_forwarded_for: true + # The tests claim IPv6 addresses, and the proxy signs a PROXY header pairing that source with the destination + # of the connection it arrived on. That destination is whatever `localhost` resolves to, which is ::1 on some + # machines and 127.0.0.1 in CI containers. Downgrading covers the IPv4 case; signing would reject it outright. + proxy_protocol_allow_downgrade: true public_addr: localhost:{{ .ProxyPort }} acme: {} web_listen_addr: 0.0.0.0:{{ .ProxyPort }} diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts deleted file mode 100644 index 185ad8e04dab..000000000000 --- a/e2e/global-setup.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Teleport - * Copyright (C) 2026 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 { mkdirSync, writeFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; - -const e2eDir = process.env.E2E_DIR ?? dirname(fileURLToPath(import.meta.url)); -const authDir = join(e2eDir, '.auth'); - -async function globalSetup() { - const browser = process.env.E2E_BROWSER; - if (!browser) { - throw new Error('E2E_BROWSER must be set by the runner'); - } - - // No bootstrapped users — e.g. runs against an existing cluster via - // --teleport-url, or unauthenticated/connect-only runs. There's nothing to - // set up, and helpers/env throws on the missing E2E_USERS_FILE, so bail - // before importing it. - if (!process.env.E2E_USERS_FILE) { - return; - } - - // Imported lazily so reading E2E_USERS_FILE only happens once we know there - // are credentials to generate auth state from. - const { startUrl, users } = await import('./helpers/env'); - const { directLogin } = await import('./helpers/login'); - - mkdirSync(authDir, { recursive: true }); - - for (const [username, creds] of Object.entries(users)) { - const state = await directLogin(startUrl, username, creds.password); - writeFileSync( - join(authDir, `${browser}-${username}.json`), - JSON.stringify(state, null, 2) - ); - } -} - -export { globalSetup as default }; - -// When executed directly (e.g. `tsx global-setup.ts`) rather than imported as -// Playwright's configured globalSetup, run immediately. The runner's browse and -// codegen modes use this to generate auth state without a full test run. -if ( - process.argv[1] && - import.meta.url === pathToFileURL(process.argv[1]).href -) { - await globalSetup(); -} diff --git a/e2e/helpers/authState.ts b/e2e/helpers/authState.ts new file mode 100644 index 000000000000..e7c477aa848b --- /dev/null +++ b/e2e/helpers/authState.ts @@ -0,0 +1,56 @@ +/** + * Teleport + * Copyright (C) 2026 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 type { StorageState } from './login'; + +// Sessions are minted on first use rather than up front for every declared user. Bunching them together drew +// the whole run's worth of tokens from the proxy's rate limiter inside a few hundred milliseconds, and the auth +// side's challenge limiter buckets every login together no matter which address the request claims. +// +// The promise is cached rather than the result, so concurrent callers share one login. Module scope survives +// across tests within a Playwright worker, which is what makes the cache worth having. +const states = new Map>(); + +// authStateFor returns a logged-in storage state for the user, reusing the one already minted in this process. +export function authStateFor(username: string) { + let state = states.get(username); + if (state) { + return state; + } + + state = login(username); + states.set(username, state); + + return state; +} + +async function login(username: string) { + // Imported lazily because helpers/env throws on a missing E2E_USERS_FILE, and runs against an existing + // cluster never reach this path. + const [{ startUrl, users }, { directLogin }] = await Promise.all([ + import('./env'), + import('./login'), + ]); + + const creds = users[username]; + if (!creds) { + throw new Error(`no credentials found for user "${username}"`); + } + + return directLogin(startUrl, username, creds.password, creds.clientIp); +} diff --git a/e2e/helpers/clientIp.ts b/e2e/helpers/clientIp.ts new file mode 100644 index 000000000000..578256cd0bf1 --- /dev/null +++ b/e2e/helpers/clientIp.ts @@ -0,0 +1,49 @@ +/** + * Teleport + * Copyright (C) 2026 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 . + */ + +// The proxy's rate limiter buckets by client IP, so a whole run sharing one address lets setup logins and +// unauthenticated specs exhaust each other's budget. With trust_x_forwarded_for enabled on the test cluster, +// every browser context claims its own address instead. +// +// IPv6 is required, not cosmetic: the proxy signs a PROXY header pairing this source with the destination of +// the browser's connection, which is ::1 because the tests reach the proxy over localhost, and signing rejects +// a version mismatch. The runner hands bootstrapped users fd00:e2e:1:: (assignClientIP in +// runner/usercredentials.go), so per-test addresses sit under a different prefix. +const TEST_PREFIX = 'fd00:e2e:2'; + +// The hash fills the two trailing groups, giving 32 bits of address per test. +export function testClientIp(testId: string) { + const h = fnv1a(testId); + + const hi = (h >>> 16).toString(16); + const lo = (h & 0xffff).toString(16); + + return `${TEST_PREFIX}:${hi}::${lo}`; +} + +// fnv1a generates a short string hash for a test ID. +function fnv1a(s: string) { + let h = 0x811c9dc5; + + for (let i = 0; i < s.length; i++) { + h ^= s.charCodeAt(i); + h = Math.imul(h, 0x01000193); + } + + return h >>> 0; +} diff --git a/e2e/helpers/connect.ts b/e2e/helpers/connect.ts index 90b88d204179..0fb8b481f188 100644 --- a/e2e/helpers/connect.ts +++ b/e2e/helpers/connect.ts @@ -17,6 +17,7 @@ */ import fs from 'node:fs/promises'; +import type { DisposableTempDir } from 'node:fs/promises'; import module from 'node:module'; import os from 'node:os'; import path from 'node:path'; @@ -125,16 +126,104 @@ export interface App { appConfigPath: string; } -export const test = fixtureBase.extend<{ - autoLogin: boolean; - /** - * Sets app config before launching the app. - * - * Use `withDefaultAppConfig` for normal config overrides. - */ - appConfig: AppConfigSetup; - app: App; -}>({ +// Connect's login goes through webapi/mfa/login/{begin,finish}, which are rate limited per client IP. tshd +// makes those requests itself, so it can't carry the per-test X-Forwarded-For the browser tests use, and every +// Connect login shares one bucket. Logging in once per user and copying the resulting data dir keeps the count +// to one regardless of how many specs need a session. +const snapshots = new Map>(); + +function loggedInSnapshot(username: string, creds: UserCredentials) { + let snapshot = snapshots.get(username); + if (snapshot) { + return snapshot; + } + + snapshot = createLoggedInSnapshot(username, creds); + snapshots.set(username, snapshot); + + return snapshot; +} + +async function createLoggedInSnapshot( + username: string, + creds: UserCredentials +) { + const dir = await fs.mkdtempDisposable( + path.join(os.tmpdir(), 'connect-e2e-snapshot-') + ); + + try { + await initializeDataDir(dir.path, withDefaultAppConfig({})); + + // Scoped so the app closes, flushing the tsh profile and app state to disk, before anything copies the dir. + { + await using app = await launchApp(dir.path, creds); + await login(app.page, username, creds.password); + } + } catch (error) { + await dir.remove(); + throw error; + } + + return dir; +} + +/** + * loggedInDataDir returns a disposable data dir already holding a logged-in session. Defaults to the same user + * as `launchApp` and `login` when none is given. + */ +export async function loggedInDataDir( + username?: string, + creds?: UserCredentials +) { + const name = username ?? defaultUsername(); + const snapshot = await loggedInSnapshot(name, creds ?? lookupCreds(name)); + + const temp = await fs.mkdtempDisposable( + path.join(os.tmpdir(), 'connect-e2e-test-') + ); + + try { + await fs.cp(snapshot.path, temp.path, { recursive: true }); + } catch (error) { + await temp.remove(); + throw error; + } + + return temp; +} + +export const test = fixtureBase.extend< + { + autoLogin: boolean; + /** + * Sets app config before launching the app. + * + * Use `withDefaultAppConfig` for normal config overrides. + */ + appConfig: AppConfigSetup; + app: App; + }, + { snapshotCleanup: void } +>({ + // Snapshots hold live certs and keys, so drop them when the worker exits rather than leaving them behind in + // the temp dir. + snapshotCleanup: [ + // oxlint-disable-next-line no-empty-pattern + async ({}, use) => { + await use(); + + const dirs = await Promise.allSettled(snapshots.values()); + snapshots.clear(); + + await Promise.all( + dirs.map(dir => + dir.status === 'fulfilled' ? dir.value.remove() : undefined + ) + ); + }, + { scope: 'worker', auto: true }, + ], autoLogin: [false, { option: true }], appConfig: [withDefaultAppConfig({}), { option: true }], app: async ({ autoLogin, appConfig, username }, use, testInfo) => { @@ -145,14 +234,15 @@ export const test = fixtureBase.extend<{ } const creds = lookupCreds(username); - await using temp = await fs.mkdtempDisposable( - path.join(os.tmpdir(), 'connect-e2e-test-') - ); + // Start from a copy of the user's logged-in session rather than driving the login UI per test. The test's + // own appConfig is applied on top, since the snapshot carries the default one. + await using temp = autoLogin + ? await loggedInDataDir(username, creds) + : await fs.mkdtempDisposable(path.join(os.tmpdir(), 'connect-e2e-test-')); + const { appConfigPath } = await initializeDataDir(temp.path, appConfig); await using launchedApp = await launchApp(temp.path, creds); - if (autoLogin) { - await login(launchedApp.page, username, creds.password); - } + await use({ electronApp: launchedApp.electronApp, userDataDir: temp.path, diff --git a/e2e/helpers/env.ts b/e2e/helpers/env.ts index a79b1b275033..777aedf7bfb9 100644 --- a/e2e/helpers/env.ts +++ b/e2e/helpers/env.ts @@ -34,6 +34,8 @@ export type UserCredentials = { password: string; webauthnPrivateKey: string; webauthnCredentialId: string; + // Assigned by the runner so each user's login gets its own rate limiter bucket. + clientIp: string; }; export const users: Record = JSON.parse( diff --git a/e2e/helpers/login.ts b/e2e/helpers/login.ts index 37119ef6aa6e..b17ff0f94e3a 100644 --- a/e2e/helpers/login.ts +++ b/e2e/helpers/login.ts @@ -122,15 +122,23 @@ interface LoginFinishResponse { export async function directLogin( startUrl: string, username: string, - password: string + password: string, + clientIp?: string ): Promise { const url = new URL(startUrl); + // Claims this user's own bucket in the proxy's rate limiter, so bootstrapping N users doesn't drain the + // budget the tests then need. + const headers: Record = { + 'content-type': 'application/json', + ...(clientIp ? { 'x-forwarded-for': clientIp } : {}), + }; + const beginRes = await undiciFetch( new URL('/v1/webapi/mfa/login/begin', url).toString(), { method: 'POST', - headers: { 'content-type': 'application/json' }, + headers, body: JSON.stringify({ passwordless: false, user: username, @@ -163,7 +171,7 @@ export async function directLogin( new URL('/v1/webapi/mfa/login/finishsession', url).toString(), { method: 'POST', - headers: { 'content-type': 'application/json' }, + headers, body: JSON.stringify({ user: username, webauthnAssertionResponse: assertion, diff --git a/e2e/helpers/test.ts b/e2e/helpers/test.ts index a2820cd58712..12cef4fc213d 100644 --- a/e2e/helpers/test.ts +++ b/e2e/helpers/test.ts @@ -20,9 +20,10 @@ import { readFileSync } from 'node:fs'; import { dirname, join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { authStateFor } from './authState'; import { canonicalUserKey } from './canonicalKey'; +import { testClientIp } from './clientIp'; import { test as base } from './fixtures'; -import type { StorageState } from './login'; import { PlayerPage } from './pages/Player'; import { RecordingsPage } from './pages/Recordings'; import { RolesPage } from './pages/Roles'; @@ -104,6 +105,20 @@ export interface LoginAsResult { } export const test = base.extend({ + extraHTTPHeaders: async ({ extraHTTPHeaders }, use, testInfo) => { + // Only the runner's own cluster is configured to trust X-Forwarded-For. Tagging a cluster we don't own + // (--teleport-url) would corrupt its client IPs, and E2E_USERS_FILE is set only when the runner + // bootstrapped this instance. + if (!process.env.E2E_USERS_FILE) { + await use(extraHTTPHeaders); + return; + } + + await use({ + ...extraHTTPHeaders, + 'X-Forwarded-For': testClientIp(testInfo.testId), + }); + }, recordings: [[], { option: true }], user: [undefined as unknown as UserDefinition, { option: true }], users: [[], { option: true }], @@ -140,8 +155,6 @@ export const test = base.extend({ ); } - const browser = testInfo.project.name.split(':')[0]; - await use(async (index: number): Promise => { const definition = users[index]; if (!definition) { @@ -160,9 +173,7 @@ export const test = base.extend({ ); } - const state: StorageState = JSON.parse( - readFileSync(join(authDir, `${browser}-${name}.json`), 'utf-8') - ); + const state = await authStateFor(name); const ctx = page.context(); await ctx.clearCookies(); @@ -214,8 +225,8 @@ export const test = base.extend({ }, storageState: async ({ username }, use, testInfo) => { // Connect tests drive login through the Electron app, and unauthenticated - // tests intentionally start without a session, so neither has a storage - // state file to load even when the test declares a user. + // tests intentionally start without a session, so neither needs a session + // minted even when the test declares a user. if ( !username || testInfo.project.name === 'connect' || @@ -225,9 +236,7 @@ export const test = base.extend({ return; } - const browser = testInfo.project.name.split(':')[0]; - - await use(join(authDir, `${browser}-${username}.json`)); + await use(await authStateFor(username)); }, unifiedResourcesPage: async ({ page }, use) => { await use(new UnifiedResourcesPage(page)); diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index c70157955da8..6834d1e511a3 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import { fileURLToPath } from 'node:url'; - import { defineConfig, devices } from '@playwright/test'; // Default to localhost:3080/web/login if START_URL is not defined. @@ -38,7 +36,8 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: process.env.CI ? 1 : undefined, - globalSetup: fileURLToPath(new URL('global-setup.ts', import.meta.url)), + // No globalSetup: sessions are minted per user on first use (helpers/authState.ts) so the logins spread + // across the run instead of drawing the whole run's rate limiter budget at once. reporter: [ ['html', { open: 'never' }], ['json', { outputFile: 'test-results/results.json' }], @@ -62,12 +61,6 @@ export default defineConfig({ name: `${browser}:unauthenticated`, testDir: './tests/web/unauthenticated', use: { ...browserDevices[browser] }, - // TODO(ryan): fix these tests - // Unauthenticated Firefox tests sometimes get rate-limited and sometimes think the cluster name is - // `localhost`, for reasons I haven't been able to figure out, so don't run them for now. The project - // stays defined because the runner selects projects by exact name, and Playwright fails selection - // outright on an unknown one. - ...(browser === 'firefox' ? { testIgnore: /.*/ } : {}), }, ]), diff --git a/e2e/runner/bootstrap.go b/e2e/runner/bootstrap.go index cdb309f7c5e7..f36b3ae97a3f 100644 --- a/e2e/runner/bootstrap.go +++ b/e2e/runner/bootstrap.go @@ -57,6 +57,7 @@ type credentialsJSON struct { Password string `json:"password"` WebauthnPrivateKey string `json:"webauthnPrivateKey"` WebauthnCredentialId string `json:"webauthnCredentialId"` + ClientIP string `json:"clientIp"` } // readRoleFile reads e2eDir/testdata/roles/ and extracts @@ -247,7 +248,7 @@ func buildBootstrapState(e2eDir string, scannedUsers []scannedUser) (*bootstrapR // Track custom role files already loaded to deduplicate. customRolesByFile := make(map[string]*customRole) - for _, g := range groups { + for i, g := range groups { su := g.su name := nameGen.Generate() userMapping[g.key] = name @@ -257,6 +258,8 @@ func buildBootstrapState(e2eDir string, scannedUsers []scannedUser) (*bootstrapR return nil, fmt.Errorf("generating credentials for %s: %w", name, err) } + userCredentials.clientIP = assignClientIP(i) + creds[name] = userCredentials traits := su.traits @@ -341,6 +344,7 @@ func writeCredentialsFile(path string, creds map[string]*credentials) error { Password: c.password, WebauthnPrivateKey: c.privateKeyPKCS8Base64, WebauthnCredentialId: c.credentialIDBase64, + ClientIP: c.clientIP, } } diff --git a/e2e/runner/bootstrap_test.go b/e2e/runner/bootstrap_test.go index 514e40665c63..a99e33957df7 100644 --- a/e2e/runner/bootstrap_test.go +++ b/e2e/runner/bootstrap_test.go @@ -20,6 +20,7 @@ package main import ( "encoding/json" + "net/netip" "os" "path/filepath" "strings" @@ -557,3 +558,47 @@ func TestWriteCredentialsFile(t *testing.T) { t.Errorf("webauthnCredentialId = %q, want %q", entry.WebauthnCredentialId, "cid-1") } } + +func TestAssignClientIP(t *testing.T) { + tests := []struct { + name string + index int + want string + }{ + {name: "first", index: 0, want: "fd00:e2e:1:0::0"}, + {name: "last of group", index: 0xffff, want: "fd00:e2e:1:0::ffff"}, + {name: "rolls into next group", index: 0x10000, want: "fd00:e2e:1:1::0"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := assignClientIP(tt.index) + if got != tt.want { + t.Errorf("assignClientIP(%d) = %q, want %q", tt.index, got, tt.want) + } + }) + } +} + +// A malformed address makes the proxy reject the request outright rather than bucketing it, so every generated +// address has to parse as IPv6. +func TestAssignClientIPIsUniqueIPv6(t *testing.T) { + seen := make(map[string]int, 1000) + + for i := range 1000 { + got := assignClientIP(i) + + addr, err := netip.ParseAddr(got) + if err != nil { + t.Fatalf("assignClientIP(%d) = %q, not a valid address: %v", i, got, err) + } + if addr.Is4() { + t.Fatalf("assignClientIP(%d) = %q, want IPv6", i, got) + } + + if prev, ok := seen[got]; ok { + t.Fatalf("assignClientIP(%d) = %q, already assigned to index %d", i, got, prev) + } + seen[got] = i + } +} diff --git a/e2e/runner/playwright.go b/e2e/runner/playwright.go index b9f040e4b604..69fc895b07d8 100644 --- a/e2e/runner/playwright.go +++ b/e2e/runner/playwright.go @@ -317,9 +317,8 @@ func (p *playwrightRunner) codegen(ctx context.Context) error { return p.openWebAuthenticated(ctx, "codegen") } -// openWebAuthenticated runs the global setup to generate auth state, then opens -// a Chromium browser with a virtual WebAuthn authenticator pre-loaded so that -// MFA challenges resolve automatically. +// openWebAuthenticated opens a Chromium browser logged in as the default user, with a virtual WebAuthn +// authenticator pre-loaded so that MFA challenges resolve automatically. func (p *playwrightRunner) openWebAuthenticated(ctx context.Context, playwrightCmd string) error { if len(p.config.instances) == 0 { return fmt.Errorf("no test instances configured") @@ -336,11 +335,6 @@ func (p *playwrightRunner) openWebAuthenticated(ctx context.Context, playwrightC return err } - slog.DebugContext(ctx, "running global setup to generate auth state") - if err := p.pnpm(ctx, []string{"exec", "tsx", filepath.Join(p.config.sharedDir, "global-setup.ts")}, env); err != nil { - return err - } - slog.InfoContext(ctx, "opening playwright with auth and WebAuthn", "command", playwrightCmd) return p.pnpm(ctx, []string{ diff --git a/e2e/runner/usercredentials.go b/e2e/runner/usercredentials.go index f92943097ba7..d25ecbecd72d 100644 --- a/e2e/runner/usercredentials.go +++ b/e2e/runner/usercredentials.go @@ -38,6 +38,19 @@ type credentials struct { credentialIDBase64 string publicKeyCBORBase64 string privateKeyPKCS8Base64 string + + // clientIP is the X-Forwarded-For this user's setup login claims, giving it its own rate limiter bucket. + clientIP string +} + +// Addresses have to be IPv6: the proxy signs a PROXY header pairing this source with the destination of the +// browser's own connection, which is ::1 because the tests reach the proxy over localhost, and signing rejects +// a version mismatch. Playwright claims fd00:e2e:2:: for per-test addresses. +const userIPPrefix = "fd00:e2e:1" + +// assignClientIP maps a bootstrap list position to a unique address. +func assignClientIP(index int) string { + return fmt.Sprintf("%s:%x::%x", userIPPrefix, index/0x10000, index%0x10000) } // generateUserCredentials creates a fresh password, bcrypt hash, and ECDSA key pair diff --git a/e2e/scripts/open-with-webauthn.ts b/e2e/scripts/open-with-webauthn.ts index 3d7fe623312a..ba3a42fc4ace 100644 --- a/e2e/scripts/open-with-webauthn.ts +++ b/e2e/scripts/open-with-webauthn.ts @@ -22,9 +22,6 @@ // // Usage: pnpm exec tsx scripts/open-with-webauthn.ts -import { join, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - import { chromium, firefox, @@ -32,7 +29,9 @@ import { type BrowserContext, } from '@playwright/test'; +import { authStateFor } from '../helpers/authState'; import { defaultUsername } from '../helpers/defaultUser'; +import type { StorageState } from '../helpers/login'; import { mockWebAuthn } from '../helpers/webauthn'; const bold = (s: string) => `\x1b[1m${s}\x1b[22m`; @@ -59,20 +58,25 @@ if (!mode || !startURL) { process.exit(1); } -const e2eDir = - process.env.E2E_DIR || join(dirname(fileURLToPath(import.meta.url)), '..'); const browserName = (process.env.E2E_BROWSERS || 'chromium').split(',')[0]; const browserTypes = { chromium, firefox, webkit }; const browserType = browserTypes[browserName as keyof typeof browserTypes] ?? chromium; const username = defaultUsername(); -const storageStatePath = join(e2eDir, `.auth/${browserName}-${username}.json`); + +// Runs against an existing cluster have no bootstrapped credentials to log in with, so open without a session +// rather than failing. +let storageState: StorageState | undefined; +if (process.env.E2E_USERS_FILE) { + storageState = await authStateFor(username); + info(`logged in as ${bold(username)}`); +} info(`launching ${browserName} ${dim(`(mode: ${mode})`)}`); const browser = await browserType.launch({ headless: false }); const context = await browser.newContext({ - storageState: storageStatePath, + storageState, ignoreHTTPSErrors: true, viewport: null, }); diff --git a/e2e/tests/connect/kube.spec.ts b/e2e/tests/connect/kube.spec.ts index 4e66f39e23e6..ad5354b1857a 100644 --- a/e2e/tests/connect/kube.spec.ts +++ b/e2e/tests/connect/kube.spec.ts @@ -24,10 +24,7 @@ import { UnifiedResourcesPage } from '@gravitational/e2e/helpers/pages/UnifiedRe test.use({ autoLogin: true, - fixtures: [ - // TODO(gzdunek): Uncomment when enabling these tests. - /*'kube'*/ - ], + fixtures: ['kube'], user: { roles: ['access'], traits: { kubernetes_groups: ['system:masters'] }, @@ -45,9 +42,7 @@ async function openKubeTerminal( await terminal.waitForText(kubePromptText); } -// TODO(gzdunek): Re-enable these tests once Connect E2E logins reuse authenticated state -// instead of hitting the rate limiter. -test.skip('creates kubeconfig under user data and reuses for the same kube session', async ({ +test('creates kubeconfig under user data and reuses for the same kube session', async ({ app, }) => { const { page, userDataDir } = app; @@ -74,7 +69,7 @@ test.skip('creates kubeconfig under user data and reuses for the same kube sessi expect(pathBeforeTabClose).toBe(pathAfterTabReopen); }); -test.skip('closing connection removes kubeconfig file', async ({ app }) => { +test('closing connection removes kubeconfig file', async ({ app }) => { const { page } = app; const resources = new UnifiedResourcesPage(page); const terminal = new TerminalPage(page); @@ -100,7 +95,7 @@ test.skip('closing connection removes kubeconfig file', async ({ app }) => { await expect.poll(() => fileExists(kubeconfigPath)).toBe(false); }); -test.skip('exec into a pod', async ({ app }) => { +test('exec into a pod', async ({ app }) => { const { page } = app; const resources = new UnifiedResourcesPage(page); const terminal = new TerminalPage(page); diff --git a/e2e/tests/connect/stateRestoration.spec.ts b/e2e/tests/connect/stateRestoration.spec.ts index caf5e0af448b..8e3bebb8e7cb 100644 --- a/e2e/tests/connect/stateRestoration.spec.ts +++ b/e2e/tests/connect/stateRestoration.spec.ts @@ -24,6 +24,7 @@ import { expect, initializeDataDir, launchApp, + loggedInDataDir, login, test, withDefaultAppConfig, @@ -33,48 +34,10 @@ import { startUrl } from '@gravitational/e2e/helpers/env'; // These tests manage the app lifecycle manually (multiple launches/closes), so they do not use the // `app` fixture. test.describe('state restoration from disk', () => { - // Run all tests in this block sequentially in a single worker so that beforeAll runs exactly - // once, not once per test (which fullyParallel would otherwise cause). + // Run all tests in this block sequentially in a single worker, since fullyParallel would otherwise + // give each its own worker and its own login. test.describe.configure({ mode: 'serial' }); - // A snapshot of a data dir with a logged-in session, created once in beforeAll. Tests that need - // logged-in state copy this snapshot instead of calling login again, keeping total login calls to - // a minimum (important to avoid rate-limiting when running full Connect suite). - let loggedInSnapshotPath: string; - - test.beforeAll(async () => { - loggedInSnapshotPath = await fs.mkdtemp( - path.join(os.tmpdir(), 'connect-e2e-state-snapshot-') - ); - await initializeDataDir(loggedInSnapshotPath, withDefaultAppConfig({})); - - // Login once to populate the data dir with session state. - { - await using app = await launchApp(loggedInSnapshotPath); - await login(app.page); - } - }); - - test.afterAll(async () => { - await fs.rm(loggedInSnapshotPath, { recursive: true, force: true }); - }); - - /** - * Creates a fresh disposable temp dir pre-populated with the logged-in snapshot. - */ - async function loggedInDataDir() { - const temp = await fs.mkdtempDisposable( - path.join(os.tmpdir(), 'connect-e2e-state-') - ); - try { - await fs.cp(loggedInSnapshotPath, temp.path, { recursive: true }); - } catch (error) { - temp.remove(); - throw error; - } - return temp; - } - test('relaunch restores tabs', async () => { await using temp = await loggedInDataDir(); diff --git a/e2e/tests/web/authenticated/ssh-clipboard.spec.ts b/e2e/tests/web/authenticated/ssh-clipboard.spec.ts index 92dc10021633..6bc6442651fc 100644 --- a/e2e/tests/web/authenticated/ssh-clipboard.spec.ts +++ b/e2e/tests/web/authenticated/ssh-clipboard.spec.ts @@ -23,14 +23,11 @@ const textToCopy = 'text-to-copy'; // Text that is pre-populated in the clipboard before the test's copy attempt const preCopiedText = 'pre-copied-text'; -test.describe('web terminal clipboard mode', () => { - // The clipboard-read/clipboard-write permissions and readText() method - // needed for this test only work on Chromium, so only run this test for Chromium. - test.skip( - ({ browserName }) => browserName !== 'chromium', - 'clipboard permissions and readText() are Chromium-only in Playwright' - ); +// The clipboard-read/clipboard-write permissions and readText() method +// needed for this test only work on Chromium, so only run this test for Chromium. +test.use({ browsers: ['chromium'] }); +test.describe('web terminal clipboard mode', () => { test.use({ fixtures: ['ssh-node'], permissions: ['clipboard-read', 'clipboard-write'], diff --git a/e2e/tests/web/unauthenticated/darktheme.spec.ts b/e2e/tests/web/unauthenticated/darktheme.spec.ts index 508fb780e4f7..6f6ebe4dd6bb 100644 --- a/e2e/tests/web/unauthenticated/darktheme.spec.ts +++ b/e2e/tests/web/unauthenticated/darktheme.spec.ts @@ -30,10 +30,6 @@ function username(testInfo: TestInfo) { } test('switching between dark and light theme', async ({ page }, testInfo) => { - // The re-login below can trip Teleport's challenge-generation rate limiter, - // so allow extra time to retry it. - test.slow(); - await signup(page, username(testInfo), defaultPassword); await expect(page.locator('body')).toHaveCSS('background-color', lightBody); @@ -57,12 +53,7 @@ test('switching between dark and light theme', async ({ page }, testInfo) => { // signing in on a fresh browser. await page.context().clearCookies(); await page.evaluate(() => localStorage.clear()); - // Logging back in right after signup can trip Teleport's challenge-generation - // rate limiter ("rate limit exceeded, try again in Ns"); retry until it lets - // us through. - await expect(async () => { - await login(page, username(testInfo), defaultPassword); - }).toPass({ timeout: 30_000, intervals: [3_000] }); + await login(page, username(testInfo), defaultPassword); await expect(page.locator('body')).toHaveCSS('background-color', darkBody); // Switch to light theme. diff --git a/e2e/tests/web/unauthenticated/login.spec.ts b/e2e/tests/web/unauthenticated/login.spec.ts index dd0a8ab1d00b..a424db9a217a 100644 --- a/e2e/tests/web/unauthenticated/login.spec.ts +++ b/e2e/tests/web/unauthenticated/login.spec.ts @@ -24,9 +24,6 @@ test.use({ user: { roles: ['access', 'editor'] } }); test('verify that a user can log in with username, password, and webauthn', async ({ page, username, - browserName, }) => { - // TODO(ryan): figure out a fix for firefox flakiness - test.skip(browserName === 'firefox', 'flaky on firefox'); await login(page, username); }); diff --git a/e2e/tests/web/unauthenticated/signup.spec.ts b/e2e/tests/web/unauthenticated/signup.spec.ts index 45562b9506fa..21f5be9a22e0 100644 --- a/e2e/tests/web/unauthenticated/signup.spec.ts +++ b/e2e/tests/web/unauthenticated/signup.spec.ts @@ -25,14 +25,9 @@ function username(testInfo: TestInfo) { return `testuser-${testInfo.workerIndex}`; } -// TODO(ryan): re-enable this test once Firefox flakiness is resolved. -test.skip('verify that a user can sign up with webauthn and login', async ({ +test('verify that a user can sign up with webauthn and login', async ({ page, }, testInfo) => { - // Signing up auto-logs-in and we then log straight back in; that burst can - // trip Teleport's challenge-generation rate limiter, so allow time to retry. - test.slow(); - await signup(page, username(testInfo)); await page.getByRole('button', { name: 'User Menu' }).click(); @@ -43,18 +38,12 @@ test.skip('verify that a user can sign up with webauthn and login', async ({ await page.getByRole('textbox', { name: 'Username' }).press('Tab'); await page.getByRole('textbox', { name: 'Password' }).fill('passwordtest123'); - // The web login challenge endpoint is rate limited; signing up then logging - // back in immediately can trip it ("rate limit exceeded, try again in Ns"). - // Retry the sign-in until the limiter lets it through. - await expect(async () => { - await page - .getByTestId('userpassword') - .getByRole('button', { name: 'Sign In' }) - .click(); - await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible({ - timeout: 5_000, - }); - }).toPass({ timeout: 30_000, intervals: [3_000] }); + await page + .getByTestId('userpassword') + .getByRole('button', { name: 'Sign In' }) + .click(); + + await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible(); }); // Clean the user before each attempt (and after) so retries start clean rather From adfaab0ceca436c5aeebc1fe79abccd31c38e0c9 Mon Sep 17 00:00:00 2001 From: Gustavo Medori Date: Thu, 13 Aug 2026 16:58:38 +0000 Subject: [PATCH 294/347] iOS: Add SystemClients library to retrieve device metadata (#69341) * Add TeleportKit package * Add TeleportKit as a package dependency * Update Package.swift to set swift settings all at once at the end * Add new SystemClients module to TeleportKit * Move the SerialNumberClient into SystemClients * Collect device data inside of EnrollClient.swift * Remove TeleportKit target --- mobile/TeleportKit/.gitignore | 8 ++ mobile/TeleportKit/Package.resolved | 105 ++++++++++++++++++ mobile/TeleportKit/Package.swift | 52 +++++++++ .../FakeSerialNumberGenerator.swift | 4 +- .../Serial Number/SerialNumberClient.swift | 8 +- .../Sources/SystemClients/SystemClients.swift | 23 ++++ .../ManagedAppConfiguration.swift | 0 .../UserDefaults/UserDefaults+Debug.swift | 6 +- mobile/Verify/Core/EnrollClient.swift | 99 ++++++++++++++++- .../Verify/Verify.xcodeproj/project.pbxproj | 35 ++++++ .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Verify/App Lifecycle/Dependencies.swift | 3 - .../DebugSettingsViewModel.swift | 1 + 13 files changed, 332 insertions(+), 14 deletions(-) create mode 100644 mobile/TeleportKit/.gitignore create mode 100644 mobile/TeleportKit/Package.resolved create mode 100644 mobile/TeleportKit/Package.swift rename mobile/{Verify/Verify/Device Trust => TeleportKit/Sources/SystemClients}/Serial Number/FakeSerialNumberGenerator.swift (93%) rename mobile/{Verify/Verify/Device Trust => TeleportKit/Sources/SystemClients}/Serial Number/SerialNumberClient.swift (86%) create mode 100644 mobile/TeleportKit/Sources/SystemClients/SystemClients.swift rename mobile/{Verify/Verify/Persistence => TeleportKit/Sources/SystemClients}/UserDefaults/ManagedAppConfiguration.swift (100%) rename mobile/{Verify/Verify/Persistence => TeleportKit/Sources/SystemClients}/UserDefaults/UserDefaults+Debug.swift (94%) diff --git a/mobile/TeleportKit/.gitignore b/mobile/TeleportKit/.gitignore new file mode 100644 index 000000000000..0023a5340637 --- /dev/null +++ b/mobile/TeleportKit/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/mobile/TeleportKit/Package.resolved b/mobile/TeleportKit/Package.resolved new file mode 100644 index 000000000000..01b8fc288d60 --- /dev/null +++ b/mobile/TeleportKit/Package.resolved @@ -0,0 +1,105 @@ +{ + "originHash" : "0436d1904a175d35d33c7428b00ec760a48cc971de9fd6eb7263375c3eb9da18", + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "dcccb979a2183b8df3334237e3dc1ae2b4116a86", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "72d749bf341b78851203066ab421869b783ec42a", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a", + "version" : "1.6.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "5fa253428866f2360c3754e88537f700ed2656b5", + "version" : "1.4.1" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "8dc1fbf2f6255a73dec53b4648164884898db4c5", + "version" : "1.14.1" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-perception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-perception", + "state" : { + "revision" : "de219a1cf34e958134e75a9ebb134cf09bf52fc6", + "version" : "2.0.11" + } + }, + { + "identity" : "swift-sharing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-sharing", + "state" : { + "revision" : "8244fe63bf43e58188ab13851ad693eecf6a9e90", + "version" : "2.9.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1", + "version" : "603.0.2" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "8f6abcf4c8950e2679d5b2fee4ca284fd7c34886", + "version" : "1.11.0" + } + } + ], + "version" : 3 +} diff --git a/mobile/TeleportKit/Package.swift b/mobile/TeleportKit/Package.swift new file mode 100644 index 000000000000..081b0252832d --- /dev/null +++ b/mobile/TeleportKit/Package.swift @@ -0,0 +1,52 @@ +// swift-tools-version: 6.3 + +import PackageDescription + +let package = Package( + name: "TeleportKit", + platforms: [.iOS(.v26)], + products: [ + .library(name: "SystemClients", targets: ["SystemClients"]), + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-dependencies", .upToNextMajor(from: "1.14.0")), + .package(url: "https://github.com/pointfreeco/swift-sharing", .upToNextMajor(from: "2.9.1")), + ], + targets: [ + .target( + name: "SystemClients", + dependencies: [ + .dependencies, + .dependenciesMacros, + .sharing, + ] + ), + ], + swiftLanguageModes: [.v6] +) + +// MARK: - Custom Target.Dependency Values + +private extension Target.Dependency { + static let dependencies: Self = .product(name: "Dependencies", package: "swift-dependencies") + static let dependenciesMacros: Self = .product(name: "DependenciesMacros", package: "swift-dependencies") + static let sharing: Self = .product(name: "Sharing", package: "swift-sharing") +} + +// MARK: - Build Settings + +// We enable these settings for all targets in this package so that the dialect of swift we're using is same across +// all our code. +let swiftSettings: [SwiftSetting] = [ + .enableUpcomingFeature("InferIsolatedConformances"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), +] + +for target in package.targets { + var settings = target.swiftSettings ?? [] + settings.append(contentsOf: swiftSettings) + target.swiftSettings = settings +} diff --git a/mobile/Verify/Verify/Device Trust/Serial Number/FakeSerialNumberGenerator.swift b/mobile/TeleportKit/Sources/SystemClients/Serial Number/FakeSerialNumberGenerator.swift similarity index 93% rename from mobile/Verify/Verify/Device Trust/Serial Number/FakeSerialNumberGenerator.swift rename to mobile/TeleportKit/Sources/SystemClients/Serial Number/FakeSerialNumberGenerator.swift index 247c81722ec8..b56989c082bf 100644 --- a/mobile/Verify/Verify/Device Trust/Serial Number/FakeSerialNumberGenerator.swift +++ b/mobile/TeleportKit/Sources/SystemClients/Serial Number/FakeSerialNumberGenerator.swift @@ -18,10 +18,10 @@ import Foundation /// A small utility for generating a fake serial number, mostly to support the debug-only serial number setting. - enum FakeSerialNumberGenerator { + public enum FakeSerialNumberGenerator { private static let base: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - static func generate() -> String { + public static func generate() -> String { func chunk() -> String { (0 ..< 4) .compactMap { _ in Self.base.randomElement() } diff --git a/mobile/Verify/Verify/Device Trust/Serial Number/SerialNumberClient.swift b/mobile/TeleportKit/Sources/SystemClients/Serial Number/SerialNumberClient.swift similarity index 86% rename from mobile/Verify/Verify/Device Trust/Serial Number/SerialNumberClient.swift rename to mobile/TeleportKit/Sources/SystemClients/Serial Number/SerialNumberClient.swift index 2281be06b902..793b95997236 100644 --- a/mobile/Verify/Verify/Device Trust/Serial Number/SerialNumberClient.swift +++ b/mobile/TeleportKit/Sources/SystemClients/Serial Number/SerialNumberClient.swift @@ -19,12 +19,12 @@ import Foundation import Sharing @DependencyClient -struct SerialNumberClient { - var getDeviceSerialNumber: @Sendable () throws(SerialNumberError) -> String = { "" } +public struct SerialNumberClient: Sendable { + public var getDeviceSerialNumber: @Sendable () throws(SerialNumberError) -> String = { "" } } extension SerialNumberClient { - static let liveValue = SerialNumberClient( + public static let liveValue = SerialNumberClient( getDeviceSerialNumber: { () throws(SerialNumberError) -> String in #if DEBUG @Shared(.debugStorage(.debugSerialNumber)) @@ -48,6 +48,6 @@ extension SerialNumberClient { // MARK: - Supporting Types -enum SerialNumberError: Error { +public enum SerialNumberError: Error { case missingSerialNumber } diff --git a/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift b/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift new file mode 100644 index 000000000000..ade0788a2e2f --- /dev/null +++ b/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift @@ -0,0 +1,23 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +public import Dependencies +import DependenciesMacros + +extension DependencyValues { + @DependencyEntry(liveValue: SerialNumberClient.liveValue) + public nonisolated var serialNumberClient = SerialNumberClient() +} diff --git a/mobile/Verify/Verify/Persistence/UserDefaults/ManagedAppConfiguration.swift b/mobile/TeleportKit/Sources/SystemClients/UserDefaults/ManagedAppConfiguration.swift similarity index 100% rename from mobile/Verify/Verify/Persistence/UserDefaults/ManagedAppConfiguration.swift rename to mobile/TeleportKit/Sources/SystemClients/UserDefaults/ManagedAppConfiguration.swift diff --git a/mobile/Verify/Verify/Persistence/UserDefaults/UserDefaults+Debug.swift b/mobile/TeleportKit/Sources/SystemClients/UserDefaults/UserDefaults+Debug.swift similarity index 94% rename from mobile/Verify/Verify/Persistence/UserDefaults/UserDefaults+Debug.swift rename to mobile/TeleportKit/Sources/SystemClients/UserDefaults/UserDefaults+Debug.swift index 671ded501886..9e28df6be332 100644 --- a/mobile/Verify/Verify/Persistence/UserDefaults/UserDefaults+Debug.swift +++ b/mobile/TeleportKit/Sources/SystemClients/UserDefaults/UserDefaults+Debug.swift @@ -16,7 +16,7 @@ #if DEBUG import Foundation - import Sharing + public import Sharing extension UserDefaults { static let debugSuiteName = "com.gravitational.teleport.verify.debug" @@ -36,13 +36,13 @@ // MARK: - Keys /// Type-safe keys for use when querying the debug user defaults - enum DebugUserDefaultsKey: String { + public enum DebugUserDefaultsKey: String { case debugSerialNumber } /// A small helper function to access the debug store via the Sharing library extension SharedReaderKey where Self == AppStorageKey { - static func debugStorage( + public static func debugStorage( _ key: DebugUserDefaultsKey, ) -> Self { .appStorage(key.rawValue, store: .debug) diff --git a/mobile/Verify/Core/EnrollClient.swift b/mobile/Verify/Core/EnrollClient.swift index bb74f2ad9f93..984606b3ce52 100644 --- a/mobile/Verify/Core/EnrollClient.swift +++ b/mobile/Verify/Core/EnrollClient.swift @@ -14,10 +14,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/ +import Darwin import Dependencies import DependenciesMacros import Enroll import Foundation +import OSLog +import SystemClients /// Handles requests around enrolling the current device in Device Trust @DependencyClient @@ -32,9 +35,12 @@ public struct EnrollClient: Sendable { public enum EnrollClientError: Error, Sendable { case clientCreationFailed + case deviceInformationUnavailable(String) } extension EnrollClient { + private static let logger = Logger(subsystem: "com.gravitational.teleport.Verify", category: "EnrollClient") + public static let liveValue = EnrollClient( requestEnrollmentToken: { hostName, port, pairingToken in try await Task.detached(priority: .userInitiated) { @@ -43,12 +49,103 @@ extension EnrollClient { throw EnrollClientError.clientCreationFailed } + let serialNumber = try getSerialNumber() + let collectedData = Enroll.EnrollDeviceCollectedData() + collectedData.serialNumber = serialNumber + collectedData.systemSerialNumber = serialNumber + collectedData.versionOS = getOSVersion() + collectedData.modelIdentifier = try getModelIdentifier() + collectedData.buildOS = try getOSBuild() + + logger.debug( + """ + Collected fields: + serialNumber=\(serialNumber) + deviceModel=\(collectedData.modelIdentifier) + osVersion=\(collectedData.versionOS) + osBuild=\(collectedData.buildOS) + """, + ) let token = try client.createMobileEnrollToken( pairingToken, - deviceData: Enroll.EnrollDeviceCollectedData(), + deviceData: collectedData, ) return token.token }.value }, ) } + +// MARK: - Private Helpers + +extension EnrollClient { + /// Returns the dotted numeric OS version reported by Foundation, such as `18.5.0`. + private static func getOSVersion() -> String { + let version = ProcessInfo.processInfo.operatingSystemVersion + return "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + } + + /// Reads Apple's OS build identifier, such as `22F76`, from `kern.osversion`. + private static func getOSBuild() throws -> String { + guard let build = sysctlString(named: "kern.osversion"), !build.isEmpty else { + throw EnrollClientError.deviceInformationUnavailable("OS build") + } + return build + } + + /// Returns the product-style model identifier used by device inventories, such as `iPhone15,2`. + /// + /// Simulators publish the simulated model in their environment. Physical devices expose it + /// through `hw.product`; `hw.machine` remains as a fallback for older OS versions. + private static func getModelIdentifier() throws -> String { + #if targetEnvironment(simulator) + let modelIdentifier = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] + #else + let modelIdentifier = sysctlString(named: "hw.product") ?? sysctlString(named: "hw.machine") + #endif + + guard let modelIdentifier, !modelIdentifier.isEmpty else { + throw EnrollClientError.deviceInformationUnavailable("model identifier") + } + + return modelIdentifier + } + + /// Returns the serial number supplied through the app's managed configuration. + /// + /// The serial number dependency provides a generated value when running a debug build. + private static func getSerialNumber() throws -> String { + @Dependency(\.serialNumberClient) + var serialNumberClient + + return try serialNumberClient.getDeviceSerialNumber() + } + + /// Reads a null-terminated string from the Darwin sysctl namespace. + /// + /// `sysctlbyname` requires one call to determine the buffer size and a second to copy the value. + /// Returns `nil` when either call fails or the reported value is empty. + private static func sysctlString(named name: String) -> String? { + var size: size_t = 0 + + guard sysctlbyname(name, nil, &size, nil, 0) == 0, size > 1 else { + return nil + } + + var buffer = [CChar](repeating: 0, count: size) + let status = buffer.withUnsafeMutableBufferPointer { pointer in + sysctlbyname(name, pointer.baseAddress, &size, nil, 0) + } + + guard status == 0 else { + return nil + } + + return buffer.withUnsafeBufferPointer { pointer in + guard let baseAddress = pointer.baseAddress else { + return nil + } + return String(cString: baseAddress) + } + } +} diff --git a/mobile/Verify/Verify.xcodeproj/project.pbxproj b/mobile/Verify/Verify.xcodeproj/project.pbxproj index a3025139c1c4..eb3d5db8d8ca 100644 --- a/mobile/Verify/Verify.xcodeproj/project.pbxproj +++ b/mobile/Verify/Verify.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ CD799A2A2FEB164100133048 /* Dependencies in Frameworks */ = {isa = PBXBuildFile; productRef = CD799A292FEB164100133048 /* Dependencies */; }; CD799A2C2FEB164300133048 /* DependenciesMacros in Frameworks */ = {isa = PBXBuildFile; productRef = CD799A2B2FEB164300133048 /* DependenciesMacros */; }; CD799A2E2FEB164600133048 /* DependenciesTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CD799A2D2FEB164600133048 /* DependenciesTestSupport */; }; + CD9AEFAC3022782200E2FC14 /* SystemClients in Frameworks */ = {isa = PBXBuildFile; productRef = CD9AEFAB3022782200E2FC14 /* SystemClients */; }; + CD9AEFAE3022782900E2FC14 /* SystemClients in Frameworks */ = {isa = PBXBuildFile; productRef = CD9AEFAD3022782900E2FC14 /* SystemClients */; }; CDAA8BB22FFD55CC00E6CA87 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB12FFD55CC00E6CA87 /* GRDB */; }; CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */; }; CDAA8BB72FFD5A1300E6CA87 /* SQLiteDataTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB62FFD5A1300E6CA87 /* SQLiteDataTestSupport */; }; @@ -98,6 +100,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CD9AEFAC3022782200E2FC14 /* SystemClients in Frameworks */, CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */, 0C94C8CE2FA35F3D00C13C9A /* libresolv.9.tbd in Frameworks */, CDAA8BB22FFD55CC00E6CA87 /* GRDB in Frameworks */, @@ -111,6 +114,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CD9AEFAE3022782900E2FC14 /* SystemClients in Frameworks */, CD79992C2FE33F0600133048 /* libresolv.9.tbd in Frameworks */, CD7999332FE345D400133048 /* Dependencies in Frameworks */, CD7999352FE346B800133048 /* DependenciesMacros in Frameworks */, @@ -199,6 +203,7 @@ CD65C8002FF711CE002FB1CD /* IdentifiedCollections */, CDAA8BB12FFD55CC00E6CA87 /* GRDB */, CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */, + CD9AEFAB3022782200E2FC14 /* SystemClients */, ); productName = Verify; productReference = 0C94C8522FA232E600C13C9A /* Verify.app */; @@ -226,6 +231,7 @@ packageProductDependencies = ( CD7999322FE345D400133048 /* Dependencies */, CD7999342FE346B800133048 /* DependenciesMacros */, + CD9AEFAD3022782900E2FC14 /* SystemClients */, ); productName = Core; productReference = CD79991B2FE33AE000133048 /* Core.framework */; @@ -296,6 +302,8 @@ CD65C7FF2FF711CE002FB1CD /* XCRemoteSwiftPackageReference "swift-identified-collections" */, CDAA8BB02FFD55CC00E6CA87 /* XCRemoteSwiftPackageReference "GRDB" */, CDAA8BB32FFD5A1200E6CA87 /* XCRemoteSwiftPackageReference "sqlite-data" */, + CD920CEE301D13500027C579 /* XCRemoteSwiftPackageReference "swift-sharing" */, + CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */, ); preferredProjectObjectVersion = 77; productRefGroup = 0C94C8532FA232E600C13C9A /* Products */; @@ -842,6 +850,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../TeleportKit; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ CD65C7FF2FF711CE002FB1CD /* XCRemoteSwiftPackageReference "swift-identified-collections" */ = { isa = XCRemoteSwiftPackageReference; @@ -867,6 +882,16 @@ minimumVersion = 2.8.0; }; }; + CD920CEE301D13500027C579 /* XCRemoteSwiftPackageReference "swift-sharing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-sharing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.9.1; + }; + traits = ( + ); + }; CDAA8BB02FFD55CC00E6CA87 /* XCRemoteSwiftPackageReference "GRDB" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/groue/GRDB.swift"; @@ -936,6 +961,16 @@ package = CD7999312FE345D400133048 /* XCRemoteSwiftPackageReference "swift-dependencies" */; productName = DependenciesTestSupport; }; + CD9AEFAB3022782200E2FC14 /* SystemClients */ = { + isa = XCSwiftPackageProductDependency; + package = CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */; + productName = SystemClients; + }; + CD9AEFAD3022782900E2FC14 /* SystemClients */ = { + isa = XCSwiftPackageProductDependency; + package = CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */; + productName = SystemClients; + }; CDAA8BB12FFD55CC00E6CA87 /* GRDB */ = { isa = XCSwiftPackageProductDependency; package = CDAA8BB02FFD55CC00E6CA87 /* XCRemoteSwiftPackageReference "GRDB" */; diff --git a/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 77e35ddd0ab7..ff7b5036c7e6 100644 --- a/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3c135f844575f775087cb3b572ff06d5055cb815400c705b246e7d008658dea0", + "originHash" : "84be995f129cdd4eceec20b1d3510b98048c43cd39db415fed17222ec06b3deb", "pins" : [ { "identity" : "combine-schedulers", diff --git a/mobile/Verify/Verify/App Lifecycle/Dependencies.swift b/mobile/Verify/Verify/App Lifecycle/Dependencies.swift index 6dfe14c4b4b1..4070c67f78ac 100644 --- a/mobile/Verify/Verify/App Lifecycle/Dependencies.swift +++ b/mobile/Verify/Verify/App Lifecycle/Dependencies.swift @@ -24,7 +24,4 @@ extension DependencyValues { @DependencyEntry(liveValue: DeviceTrustCredentialClient.liveValue) nonisolated var deviceTrustCredentialClient = DeviceTrustCredentialClient() - - @DependencyEntry(liveValue: SerialNumberClient.liveValue) - nonisolated var serialNumberClient = SerialNumberClient() } diff --git a/mobile/Verify/Verify/Features/Debugging/Debug Settings/DebugSettingsViewModel.swift b/mobile/Verify/Verify/Features/Debugging/Debug Settings/DebugSettingsViewModel.swift index 7ab0367b67ed..2f095e9d069e 100644 --- a/mobile/Verify/Verify/Features/Debugging/Debug Settings/DebugSettingsViewModel.swift +++ b/mobile/Verify/Verify/Features/Debugging/Debug Settings/DebugSettingsViewModel.swift @@ -20,6 +20,7 @@ import Foundation import Sharing import SwiftUI + import SystemClients @Observable @MainActor final class DebugSettingsViewModel { From 8ff3d34581424302182c217e4d8d3be45bf2b0b7 Mon Sep 17 00:00:00 2001 From: Gustavo Medori Date: Thu, 13 Aug 2026 17:18:21 +0000 Subject: [PATCH 295/347] =?UTF-8?q?iOS:=20Logging=20Part=201=20=E2=80=93?= =?UTF-8?q?=20Infrastructure=20(#69394)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add LogBackends module * Add basic demonstration of simple log handler * Dial in formatting of the ConsoleLogHandler * Naively update loggers from OSLog to swift-log * Update log statements to use structured metadata * Update complex types to expose log metadata instead of debug descriptions * Add comment explaining why we evaluate originTimestamp in ConsoleLogHandler's init * Move Logger.Level formatting into its own file * Update formatting in VerifyApp.swift * Update timestamp formatting for ConsoleLogHandler --- .../xcshareddata/IDETemplateMacros.plist | 23 +++++ mobile/TeleportKit/Package.resolved | 11 ++- mobile/TeleportKit/Package.swift | 83 ++++++++++++------- .../LogBackends/ConsoleLogHandler.swift | 56 +++++++++++++ .../Sources/LogBackends/Exports.swift | 17 ++++ .../LogBackends/Level+Formatting.swift | 44 ++++++++++ .../LogBackends/Metadata+Formatting.swift | 37 +++++++++ mobile/Verify/Core/EnrollClient.swift | 19 ++--- .../Verify/Verify.xcodeproj/project.pbxproj | 16 ++++ .../xcshareddata/IDETemplateMacros.plist | 23 +++++ .../xcshareddata/swiftpm/Package.resolved | 9 ++ .../Verify/App Lifecycle/VerifyApp.swift | 11 ++- .../Verify/App Lifecycle/VerifyAppModel.swift | 11 ++- .../Verify/Verify/Deep Linking/DeepLink.swift | 11 --- .../EnrollMobileDeviceDeepLink.swift | 18 ++-- .../DeviceTrustCredentialKeychain.swift | 14 +++- .../SecureEnclaveCredentialStore.swift | 6 +- .../EnrollCameraScannerViewModel.swift | 20 +++-- .../Enrollment/EnrollDeviceViewModel.swift | 11 ++- .../Features/Landing/LandingViewModel.swift | 8 +- .../Persistence/SQLite/AppDatabase.swift | 23 +++-- .../Persistence/SQLite/Models/Cluster.swift | 21 +++-- .../Verify/Verify/Utilities/Logger+Init.swift | 9 +- 23 files changed, 390 insertions(+), 111 deletions(-) create mode 100644 mobile/TeleportKit/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDETemplateMacros.plist create mode 100644 mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift create mode 100644 mobile/TeleportKit/Sources/LogBackends/Exports.swift create mode 100644 mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift create mode 100644 mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift create mode 100644 mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/IDETemplateMacros.plist diff --git a/mobile/TeleportKit/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDETemplateMacros.plist b/mobile/TeleportKit/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 000000000000..80cf49703a9d --- /dev/null +++ b/mobile/TeleportKit/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,23 @@ + + + + + FILEHEADER + Teleport +// Copyright (C) ___YEAR___ 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 http://www.gnu.org/licenses/ + + diff --git a/mobile/TeleportKit/Package.resolved b/mobile/TeleportKit/Package.resolved index 01b8fc288d60..9c2f3f440b47 100644 --- a/mobile/TeleportKit/Package.resolved +++ b/mobile/TeleportKit/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "0436d1904a175d35d33c7428b00ec760a48cc971de9fd6eb7263375c3eb9da18", + "originHash" : "bdb49105a0db4f4d4a0400eb0d5659cca7a09d6bc3862dfbd64ad1a30629942b", "pins" : [ { "identity" : "combine-schedulers", @@ -64,6 +64,15 @@ "version" : "1.1.1" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "a878e7f8f46cfc0e1125e565b5c08e7d5272dc9a", + "version" : "1.14.0" + } + }, { "identity" : "swift-perception", "kind" : "remoteSourceControl", diff --git a/mobile/TeleportKit/Package.swift b/mobile/TeleportKit/Package.swift index 081b0252832d..f0026b6537a6 100644 --- a/mobile/TeleportKit/Package.swift +++ b/mobile/TeleportKit/Package.swift @@ -3,46 +3,67 @@ import PackageDescription let package = Package( - name: "TeleportKit", - platforms: [.iOS(.v26)], - products: [ - .library(name: "SystemClients", targets: ["SystemClients"]), - ], - dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-dependencies", .upToNextMajor(from: "1.14.0")), - .package(url: "https://github.com/pointfreeco/swift-sharing", .upToNextMajor(from: "2.9.1")), - ], - targets: [ - .target( - name: "SystemClients", - dependencies: [ - .dependencies, - .dependenciesMacros, - .sharing, - ] - ), - ], - swiftLanguageModes: [.v6] + name: "TeleportKit", + platforms: [.iOS(.v26)], + products: [ + .library(name: "SystemClients", targets: ["SystemClients"]), + .library(name: "LogBackends", targets: ["LogBackends"]), + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-dependencies", .upToNextMajor(from: "1.14.0")), + .package(url: "https://github.com/pointfreeco/swift-sharing", .upToNextMajor(from: "2.9.1")), + .package(url: "https://github.com/apple/swift-log", .upToNextMajor(from: "1.14.0")), + ], + targets: [ + .target( + name: "SystemClients", + dependencies: [ + .dependencies, + .dependenciesMacros, + .sharing, + ], + ), + .target( + name: "LogBackends", + dependencies: [ + .logging, + ], + ), + ], + swiftLanguageModes: [.v6], ) // MARK: - Custom Target.Dependency Values -private extension Target.Dependency { - static let dependencies: Self = .product(name: "Dependencies", package: "swift-dependencies") - static let dependenciesMacros: Self = .product(name: "DependenciesMacros", package: "swift-dependencies") - static let sharing: Self = .product(name: "Sharing", package: "swift-sharing") +extension Target.Dependency { + fileprivate static let dependencies: Self = .product( + name: "Dependencies", + package: "swift-dependencies", + ) + fileprivate static let dependenciesMacros: Self = .product( + name: "DependenciesMacros", + package: "swift-dependencies", + ) + fileprivate static let sharing: Self = .product( + name: "Sharing", + package: "swift-sharing", + ) + fileprivate static let logging: Self = .product( + name: "Logging", + package: "swift-log", + ) } // MARK: - Build Settings -// We enable these settings for all targets in this package so that the dialect of swift we're using is same across -// all our code. +/// We enable these settings for all targets in this package so that the dialect of swift we're using is same across +/// all our code. let swiftSettings: [SwiftSetting] = [ - .enableUpcomingFeature("InferIsolatedConformances"), - .enableUpcomingFeature("NonisolatedNonsendingByDefault"), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault"), - .enableUpcomingFeature("MemberImportVisibility"), + .enableUpcomingFeature("InferIsolatedConformances"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), ] for target in package.targets { diff --git a/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift b/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift new file mode 100644 index 000000000000..bae5289b259a --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift @@ -0,0 +1,56 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Foundation +public import Logging + +/// A simple print-based log handler primarily used for live debugging sessions. +public struct ConsoleLogHandler: LogHandler { + public let label: String + + private let timestampFormmater = Date.ISO8601FormatStyle( + dateSeparator: .dash, + dateTimeSeparator: .space, + timeSeparator: .colon, + timeZoneSeparator: .colon, + includingFractionalSeconds: true, + timeZone: .current, + ) + + public init(label: String) { + self.label = label + } + + // MARK: LogHandler Conformance + + public var logLevel: Logging.Logger.Level = .info + + public var metadata: Logging.Logger.Metadata = [:] + + public subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? { + get { metadata[key] } + set { metadata[key] = newValue } + } + + public func log(event: LogEvent) { + let timestamp = timestampFormmater.format(.now) + let metadataToLog = metadata.merging(event.metadata ?? [:]) { _, new in new } + + let output = "\(timestamp) \(event.level.formatted) \(event.file):\(event.line) | \(event.message) \(metadataToLog.formatted)" + + print(output) + } +} diff --git a/mobile/TeleportKit/Sources/LogBackends/Exports.swift b/mobile/TeleportKit/Sources/LogBackends/Exports.swift new file mode 100644 index 000000000000..8c250ec875f6 --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Exports.swift @@ -0,0 +1,17 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +@_exported import Logging diff --git a/mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift b/mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift new file mode 100644 index 000000000000..d95b1b53fdc3 --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift @@ -0,0 +1,44 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Logging + +extension Logger.Level { + /// Formats the log level in a way that allows it to be easily distinguished in a console or a log file, using both + /// colors and text. + var formatted: String { + "\(indicator) [\(description.uppercased())]" + } + + private static let maxLevelStringLength = Logger.Level.allCases + .map(\.description.count) + .max() ?? 10 // Some reasonable default + + private var indicator: String { + switch self { + case .trace: + "⚪️" + case .debug: + "🔵" + case .info, .notice: + "🟢" + case .warning: + "🟡" + case .error, .critical: + "🔴" + } + } +} diff --git a/mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift b/mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift new file mode 100644 index 000000000000..3ef3d2e09653 --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift @@ -0,0 +1,37 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Foundation +import Logging + +extension Logger.Metadata { + /// Formats the metadata in a way suitable for appending to the end of a log message. + /// + /// All metadata is joined into key value pairs of the form `\(key)=\(value)` with each pair including a prefix that + /// allows the metadata to be easily scanned/parsed by the human eye. + var formatted: String { + guard !isEmpty else { return "" } + let metadataAsStrings = map { key, value in + let prefix = if key.lowercased().localizedStandardContains("error") { + "❌" + } else { + "🔸" + } + return "\(prefix) \(key)=\(value)" + } + return metadataAsStrings.joined(separator: " ") + } +} diff --git a/mobile/Verify/Core/EnrollClient.swift b/mobile/Verify/Core/EnrollClient.swift index 984606b3ce52..128776c2dddb 100644 --- a/mobile/Verify/Core/EnrollClient.swift +++ b/mobile/Verify/Core/EnrollClient.swift @@ -19,7 +19,7 @@ import Dependencies import DependenciesMacros import Enroll import Foundation -import OSLog +import Logging import SystemClients /// Handles requests around enrolling the current device in Device Trust @@ -39,7 +39,7 @@ public enum EnrollClientError: Error, Sendable { } extension EnrollClient { - private static let logger = Logger(subsystem: "com.gravitational.teleport.Verify", category: "EnrollClient") + private static let logger = Logger(label: "EnrollClient") public static let liveValue = EnrollClient( requestEnrollmentToken: { hostName, port, pairingToken in @@ -57,15 +57,12 @@ extension EnrollClient { collectedData.modelIdentifier = try getModelIdentifier() collectedData.buildOS = try getOSBuild() - logger.debug( - """ - Collected fields: - serialNumber=\(serialNumber) - deviceModel=\(collectedData.modelIdentifier) - osVersion=\(collectedData.versionOS) - osBuild=\(collectedData.buildOS) - """, - ) + logger.debug("Collected device information", metadata: [ + "serialNumber": "\(serialNumber)", + "deviceModel": "\(collectedData.modelIdentifier)", + "osVersion": "\(collectedData.versionOS)", + "osBuild": "\(collectedData.buildOS)", + ]) let token = try client.createMobileEnrollToken( pairingToken, deviceData: collectedData, diff --git a/mobile/Verify/Verify.xcodeproj/project.pbxproj b/mobile/Verify/Verify.xcodeproj/project.pbxproj index eb3d5db8d8ca..c798732a67e1 100644 --- a/mobile/Verify/Verify.xcodeproj/project.pbxproj +++ b/mobile/Verify/Verify.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ CDAA8BB22FFD55CC00E6CA87 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB12FFD55CC00E6CA87 /* GRDB */; }; CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */; }; CDAA8BB72FFD5A1300E6CA87 /* SQLiteDataTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB62FFD5A1300E6CA87 /* SQLiteDataTestSupport */; }; + CDFCE7053022A9D4002E6773 /* LogBackends in Frameworks */ = {isa = PBXBuildFile; productRef = CDFCE7043022A9D4002E6773 /* LogBackends */; }; + CDFCE7073022A9D4002E6773 /* LogBackends in Frameworks */ = {isa = PBXBuildFile; productRef = CDFCE7063022A9D4002E6773 /* LogBackends */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -100,6 +102,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CDFCE7053022A9D4002E6773 /* LogBackends in Frameworks */, CD9AEFAC3022782200E2FC14 /* SystemClients in Frameworks */, CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */, 0C94C8CE2FA35F3D00C13C9A /* libresolv.9.tbd in Frameworks */, @@ -114,6 +117,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CDFCE7073022A9D4002E6773 /* LogBackends in Frameworks */, CD9AEFAE3022782900E2FC14 /* SystemClients in Frameworks */, CD79992C2FE33F0600133048 /* libresolv.9.tbd in Frameworks */, CD7999332FE345D400133048 /* Dependencies in Frameworks */, @@ -204,6 +208,7 @@ CDAA8BB12FFD55CC00E6CA87 /* GRDB */, CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */, CD9AEFAB3022782200E2FC14 /* SystemClients */, + CDFCE7043022A9D4002E6773 /* LogBackends */, ); productName = Verify; productReference = 0C94C8522FA232E600C13C9A /* Verify.app */; @@ -232,6 +237,7 @@ CD7999322FE345D400133048 /* Dependencies */, CD7999342FE346B800133048 /* DependenciesMacros */, CD9AEFAD3022782900E2FC14 /* SystemClients */, + CDFCE7063022A9D4002E6773 /* LogBackends */, ); productName = Core; productReference = CD79991B2FE33AE000133048 /* Core.framework */; @@ -991,6 +997,16 @@ package = CDDEB8752FE1F5F10052BC27 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */; productName = "plugin:SwiftLintBuildToolPlugin"; }; + CDFCE7043022A9D4002E6773 /* LogBackends */ = { + isa = XCSwiftPackageProductDependency; + package = CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */; + productName = LogBackends; + }; + CDFCE7063022A9D4002E6773 /* LogBackends */ = { + isa = XCSwiftPackageProductDependency; + package = CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */; + productName = LogBackends; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0C94C84A2FA232E600C13C9A /* Project object */; diff --git a/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/IDETemplateMacros.plist b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 000000000000..80cf49703a9d --- /dev/null +++ b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,23 @@ + + + + + FILEHEADER + Teleport +// Copyright (C) ___YEAR___ 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 http://www.gnu.org/licenses/ + + diff --git a/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ff7b5036c7e6..a896bea56929 100644 --- a/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile/Verify/Verify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -91,6 +91,15 @@ "version" : "1.1.1" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "a878e7f8f46cfc0e1125e565b5c08e7d5272dc9a", + "version" : "1.14.0" + } + }, { "identity" : "swift-navigation", "kind" : "remoteSourceControl", diff --git a/mobile/Verify/Verify/App Lifecycle/VerifyApp.swift b/mobile/Verify/Verify/App Lifecycle/VerifyApp.swift index 7bbb42699b40..64b4ec89691a 100644 --- a/mobile/Verify/Verify/App Lifecycle/VerifyApp.swift +++ b/mobile/Verify/Verify/App Lifecycle/VerifyApp.swift @@ -15,6 +15,7 @@ // along with this program. If not, see . import Dependencies +import LogBackends import SQLiteData import SwiftUI @@ -69,11 +70,19 @@ struct VerifyApp: App { private var appModel: VerifyAppModel init() { + LoggingSystem.bootstrap { label in + var handler = ConsoleLogHandler(label: label) + #if DEBUG + handler.logLevel = CommandLine.arguments.contains("--sql-trace") ? .trace : .debug + #endif + return handler + } + prepareDependencies { $0.defaultDatabase = AppDatabase.makeLiveDatabase() } // Only initialize the model after our app's dependencies have been prepared - appModel = VerifyAppModel() + self.appModel = VerifyAppModel() } var body: some Scene { diff --git a/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift b/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift index 88b6f658e24f..4b37d00c1ef8 100644 --- a/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift +++ b/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift @@ -15,13 +15,13 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import Foundation +import Logging import Observation -import OSLog /// The root of our app's view model tree. @Observable @MainActor final class VerifyAppModel { - static var logger = Logger.forType(VerifyAppModel.self) + private let logger = Logger(label: "VerifyAppModel") // MARK: Child View Models @@ -38,11 +38,14 @@ extension VerifyAppModel { do { switch try DeepLink(from: url) { case let .enrollMobileDevice(deepLink): - Self.logger.debug("Correctly parsed deep link from \(url): \(String(describing: deepLink))") + logger.debug("Correctly parsed deep link", metadata: deepLink.logMetadata) landingViewModel.navigateToDeviceEnrollment(with: deepLink) } } catch { - Self.logger.warning("Failed to parse deep link \"\(url)\", error: \(String(describing: error))") + logger.warning("Failed to parse deep link", metadata: [ + "scannedURL": "\(url)", + "error": .string(String(describing: error)), + ]) landingViewModel.showParserError(errorMessage: error.localizedDescription) } } diff --git a/mobile/Verify/Verify/Deep Linking/DeepLink.swift b/mobile/Verify/Verify/Deep Linking/DeepLink.swift index d2b43c087b23..fdba4fd9a593 100644 --- a/mobile/Verify/Verify/Deep Linking/DeepLink.swift +++ b/mobile/Verify/Verify/Deep Linking/DeepLink.swift @@ -60,14 +60,3 @@ enum DeepLinkParseError: LocalizedError, Equatable { } } } - -// MARK: - DeepLink + CustomDebugStringConvertible - -extension DeepLink: CustomDebugStringConvertible { - var debugDescription: String { - switch self { - case let .enrollMobileDevice(enrollMobileDeviceDeepLink): - "enrollMobileDevice(\(enrollMobileDeviceDeepLink.debugDescription))" - } - } -} diff --git a/mobile/Verify/Verify/Deep Linking/EnrollMobileDeviceDeepLink.swift b/mobile/Verify/Verify/Deep Linking/EnrollMobileDeviceDeepLink.swift index 30beca80ae7c..b20030354362 100644 --- a/mobile/Verify/Verify/Deep Linking/EnrollMobileDeviceDeepLink.swift +++ b/mobile/Verify/Verify/Deep Linking/EnrollMobileDeviceDeepLink.swift @@ -15,6 +15,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import Foundation +import Logging struct EnrollMobileDeviceDeepLink: Equatable { var hostname: String @@ -45,15 +46,14 @@ extension EnrollMobileDeviceDeepLink { } } -// MARK: - CustomDebugStringConvertible +// MARK: - Logging -extension EnrollMobileDeviceDeepLink: CustomDebugStringConvertible { - var debugDescription: String { - var components = URLComponents() - components.scheme = "https" - components.host = hostname - components.port = port - components.queryItems = [URLQueryItem(name: Self.enrollPairingTokenKey, value: enrollPairingToken)] - return components.debugDescription +extension EnrollMobileDeviceDeepLink { + var logMetadata: Logger.Metadata { + [ + "deepLink.type": "enrollMobileDevice", + "deepLink.hostname": "\(hostname)", + "deepLink.port": "\(port)", + ] } } diff --git a/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift index 498501a51f87..847f92d79447 100644 --- a/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift +++ b/mobile/Verify/Verify/Device Trust/Credentials/DeviceTrustCredentialKeychain.swift @@ -15,7 +15,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import Foundation -import OSLog +import Logging import Security /// The versioned value stored as one Keychain item. @@ -94,7 +94,9 @@ struct DeviceTrustCredentialKeychain { throw DeviceTrustCredentialError.notFound default: - logger.error("Could not load Device Trust credential from Keychain: \(status)") + logger.error("Could not load Device Trust credential from Keychain", metadata: [ + "status": "\(status)", + ]) throw DeviceTrustCredentialError.keychain(status: status) } } @@ -121,7 +123,9 @@ struct DeviceTrustCredentialKeychain { let status = SecItemAdd(attributes as CFDictionary, nil) guard status == errSecSuccess else { - logger.error("Could not store Device Trust credential in Keychain: \(status)") + logger.error("Could not store Device Trust credential in Keychain", metadata: [ + "status": "\(status)", + ]) throw DeviceTrustCredentialError.keychain(status: status) } } @@ -135,7 +139,9 @@ struct DeviceTrustCredentialKeychain { return default: - logger.error("Could not delete Device Trust credential from Keychain: \(status)") + logger.error("Could not delete Device Trust credential from Keychain", metadata: [ + "status": "\(status)", + ]) throw DeviceTrustCredentialError.keychain(status: status) } } diff --git a/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift index 542dc66312fe..1e88baff162b 100644 --- a/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift +++ b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift @@ -17,7 +17,7 @@ import CryptoKit import Foundation import LocalAuthentication -import OSLog +import Logging import Security /// Implements Device Trust using two pieces of device-local storage. @@ -230,7 +230,9 @@ extension SecureEnclaveCredentialStore { ) else { if let error { - logger.error("Could not create the Device Trust access-control policy: \(error.takeRetainedValue())") + logger.error("Could not create the Device Trust access-control policy", metadata: [ + "error": "\(error.takeRetainedValue())", + ]) } throw DeviceTrustCredentialError.accessControlCreationFailed diff --git a/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift b/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift index c151cbe2b594..70a0b33fd95d 100644 --- a/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift +++ b/mobile/Verify/Verify/Features/Enrollment/EnrollCameraScannerViewModel.swift @@ -15,12 +15,12 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import AVFoundation +import Logging import Observation -import OSLog @Observable @MainActor final class EnrollCameraScannerViewModel { - private static let logger = Logger.forType(EnrollCameraScannerViewModel.self) + private let logger = Logger.forType(EnrollCameraScannerViewModel.self) var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined @@ -49,13 +49,15 @@ extension EnrollCameraScannerViewModel { guard let enrollMobileDeviceDeepLink = validateScannedCode(payload) else { return .continueScanning } - Self.logger.info("Scanned deep link: \(enrollMobileDeviceDeepLink.debugDescription)") + logger.info("Scanned deep link", metadata: enrollMobileDeviceDeepLink.logMetadata) delegate?.enrollCameraScannerViewModel(self, didReceiveEnrollMobileDeviceDeepLink: enrollMobileDeviceDeepLink) return .stopScanning } private func validateScannedCode(_ payload: String) -> EnrollMobileDeviceDeepLink? { - Self.logger.debug("Validating scanned QR code: \(payload)") + logger.debug("Validating scanned QR code", metadata: [ + "payload": "\(payload)", + ]) do { guard let url = URL(string: payload) else { return nil @@ -66,7 +68,9 @@ extension EnrollCameraScannerViewModel { } return enrollMobileDeviceDeepLink } catch { - Self.logger.debug("\(payload) did not pass validation") + logger.debug("Scanned QR code did not pass validation", metadata: [ + "payload": "\(payload)", + ]) return nil } } @@ -92,9 +96,9 @@ extension EnrollCameraScannerViewModel { case .restricted, .denied, .authorized: break @unknown default: - Self.logger.warning( - "Encountered unknown camera authorization status: \(self.cameraAuthorizationStatus.rawValue)", - ) + logger.warning("Encountered unknown camera authorization status", metadata: [ + "status": "\(cameraAuthorizationStatus.rawValue)", + ]) } } } diff --git a/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift b/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift index 93fc0e35162f..69273273fa4a 100644 --- a/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift +++ b/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift @@ -16,15 +16,16 @@ import Core import Dependencies +import Foundation +import Logging import Observation -import OSLog import SQLiteData import SwiftNavigation @Observable @MainActor class EnrollDeviceViewModel { - static let logger = Logger.forType(EnrollDeviceViewModel.self) + private let logger = Logger.forType(EnrollDeviceViewModel.self) var loadingState: LoadingState = .idle private let deepLink: EnrollMobileDeviceDeepLink @@ -84,12 +85,14 @@ class EnrollDeviceViewModel { .fetchOne(db) } - Self.logger.info("Successfully enrolled \(cluster.debugDescription)") + logger.info("Successfully enrolled cluster", metadata: cluster?.logMetadata) delegate?.enrollDeviceViewModelDidEnrollCluster(self) loadingState = .success("fake-token-\(cluster?.id.uuidString ?? "(nil)")") } catch { - Self.logger.error("Failed to request enrollment token: \(error)") + logger.error("Failed to request enrollment token", metadata: [ + "error": .string(String(describing: error)), + ]) loadingState = .failure(error) } } diff --git a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift index 56403dba39d9..c814893afbdd 100644 --- a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift +++ b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift @@ -15,14 +15,14 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import Foundation +import Logging import Observation -import OSLog import SQLiteData import SwiftNavigation @Observable @MainActor final class LandingViewModel { - static let logger = Logger.forType(LandingView.self) + private let logger = Logger.forType(LandingView.self) // swiftformat:sort @CasePathable @@ -181,7 +181,9 @@ extension LandingViewModel { try deleteOperation().execute(db) } } catch { - Self.logger.warning("Failed to forget clusters: \(error)") + logger.warning("Failed to forget clusters", metadata: [ + "error": .string(String(describing: error)), + ]) destination = .notice(AlertState( title: { TextState("Could Not Forget Clusters") diff --git a/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift b/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift index bbfe3182b3a5..9f8ebf3ad96f 100644 --- a/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift +++ b/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift @@ -16,7 +16,7 @@ import Dependencies import Foundation -import OSLog +import Logging import SQLiteData /// A namespace that provides us a place to put our persistence code. @@ -60,16 +60,21 @@ extension AppDatabase { // Create the database file by initializing a GRDB DatabaseQueue. let databaseFileName = "AppDatabase.sqlite" let databasePath = databaseDirectoryURL.appending(path: databaseFileName).path(percentEncoded: false) - logger.info("Initializing \(databaseFileName) database...") + logger.info("Initializing database...", metadata: [ + "databaseFileName": "\(databaseFileName)", + ]) database = try DatabaseQueue( path: databasePath, configuration: defaultConfiguration, ) - logger.info("Successfully initialized \(databaseFileName)") + logger.info("Successfully initialized database", metadata: [ + "databaseFileName": "\(databaseFileName)", + ]) - // This log provides a convenient line we can copy/paste into our terminal so that we can open up our SQLite - // client of choice. - logger.info("open '\(databasePath)'") + #if DEBUG + // Print a complete command so it can be selected and pasted directly into a terminal. + print("open '\(databasePath)'") + #endif logger.info("Running database migrations...") try migrate(db: database) @@ -82,7 +87,9 @@ extension AppDatabase { #if DEBUG fatalError("Database initialization failed: \(error)") #else - logger.critical("Database initialization failed. Falling back to in-memory database: \(error)") + logger.critical("Database initialization failed; falling back to in-memory database", metadata: [ + "error": .string(String(describing: error)), + ]) database = makeInMemoryDatabase() #endif } @@ -149,7 +156,7 @@ extension AppDatabase { if context == .preview { print("\($0.expandedDescription)") } else { - logger.debug("\($0.expandedDescription)") + logger.trace("\($0.expandedDescription)") } } } diff --git a/mobile/Verify/Verify/Persistence/SQLite/Models/Cluster.swift b/mobile/Verify/Verify/Persistence/SQLite/Models/Cluster.swift index a9290aae97ed..360737fabff2 100644 --- a/mobile/Verify/Verify/Persistence/SQLite/Models/Cluster.swift +++ b/mobile/Verify/Verify/Persistence/SQLite/Models/Cluster.swift @@ -15,6 +15,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/ import Foundation +import Logging import SQLiteData @Table("clusters") @@ -24,14 +25,6 @@ struct Cluster: Identifiable { var port: Int } -// MARK: - CustomDebugStringConvertible - -extension Cluster: CustomDebugStringConvertible { - var debugDescription: String { - "\(id):\(host):\(port)" - } -} - extension Cluster { var url: URL? { var components = URLComponents() @@ -42,6 +35,18 @@ extension Cluster { } } +// MARK: - Logging + +extension Cluster { + var logMetadata: Logger.Metadata { + [ + "cluster.id": "\(id)", + "cluster.host": "\(host)", + "cluster.port": "\(port)", + ] + } +} + // MARK: - Preview Data extension Cluster { diff --git a/mobile/Verify/Verify/Utilities/Logger+Init.swift b/mobile/Verify/Verify/Utilities/Logger+Init.swift index 4842d46a236d..3c95f8b8feac 100644 --- a/mobile/Verify/Verify/Utilities/Logger+Init.swift +++ b/mobile/Verify/Verify/Utilities/Logger+Init.swift @@ -14,19 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/ -import OSLog +import Logging extension Logger { - /// The subsystem that almost all loggers in this target should use. - static let defaultSubsystem = "com.gravitational.Verify" - /// Creates a logger for a specific type. static func forType(_ type: T.Type) -> Logger { - Logger(subsystem: defaultSubsystem, category: String(describing: type)) + Logger(label: String(describing: type)) } /// Creates a logger for the file in which it is declared. static func forFile(_ fileID: StaticString = #fileID) -> Logger { - Logger(subsystem: defaultSubsystem, category: "\(fileID)") + Logger(label: "\(fileID)") } } From a93db5895bf27df47933e3274dd82ad038c56eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20Cie=C5=9Blak?= <27113+ravicious@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:51:11 +0000 Subject: [PATCH 296/347] Add missing license header (#69648) --- mobile/TeleportKit/Package.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mobile/TeleportKit/Package.swift b/mobile/TeleportKit/Package.swift index f0026b6537a6..629d8a82dba2 100644 --- a/mobile/TeleportKit/Package.swift +++ b/mobile/TeleportKit/Package.swift @@ -1,3 +1,19 @@ +// Teleport +// Copyright (C) 2026 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 . + // swift-tools-version: 6.3 import PackageDescription From baf3ab1188ff328c680b514aaf5e3802291fa10b Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:58:27 +0000 Subject: [PATCH 297/347] fix instances ui bug (#69551) --- lib/auth/inventory/inventoryv1/service.go | 49 +++++++----- lib/web/inventory_test.go | 76 +++++++++++++++++++ .../components/Controls/MultiselectMenu.tsx | 1 + .../teleport/src/Instances/Instances.test.tsx | 46 +++++++++++ .../teleport/src/Instances/Instances.tsx | 23 ++++-- 5 files changed, 170 insertions(+), 25 deletions(-) diff --git a/lib/auth/inventory/inventoryv1/service.go b/lib/auth/inventory/inventoryv1/service.go index dc6b0fe63f53..02cc6e70abd3 100644 --- a/lib/auth/inventory/inventoryv1/service.go +++ b/lib/auth/inventory/inventoryv1/service.go @@ -81,28 +81,39 @@ func (s *Service) ListUnifiedInstances( return nil, trace.Wrap(err) } - // If no instance types are specified, default to all instance types - instanceTypes := req.GetFilter().GetInstanceTypes() - if len(instanceTypes) == 0 { - instanceTypes = []inventorypb.InstanceType{ - inventorypb.InstanceType_INSTANCE_TYPE_INSTANCE, - inventorypb.InstanceType_INSTANCE_TYPE_BOT_INSTANCE, - } + filter := req.GetFilter() + if filter == nil { + filter = inventorypb.ListUnifiedInstancesFilter_builder{}.Build() + req.SetFilter(filter) } - // Ensure that the instance types requested align with the user's permissions - for _, instanceType := range instanceTypes { - switch instanceType { - case inventorypb.InstanceType_INSTANCE_TYPE_INSTANCE: - if err := authCtx.CheckAccessToKind(types.KindInstance, types.VerbList, types.VerbRead); err != nil { - return nil, trace.Wrap(err) - } - case inventorypb.InstanceType_INSTANCE_TYPE_BOT_INSTANCE: - if err := authCtx.CheckAccessToKind(types.KindBotInstance, types.VerbList, types.VerbRead); err != nil { - return nil, trace.Wrap(err) + instanceErr := authCtx.CheckAccessToKind(types.KindInstance, types.VerbList, types.VerbRead) + botInstanceErr := authCtx.CheckAccessToKind(types.KindBotInstance, types.VerbList, types.VerbRead) + + if len(filter.GetInstanceTypes()) > 0 { + for _, instanceType := range filter.GetInstanceTypes() { + switch instanceType { + case inventorypb.InstanceType_INSTANCE_TYPE_INSTANCE: + if instanceErr != nil { + return nil, trace.Wrap(instanceErr) + } + case inventorypb.InstanceType_INSTANCE_TYPE_BOT_INSTANCE: + if botInstanceErr != nil { + return nil, trace.Wrap(botInstanceErr) + } + default: + return nil, trace.NotImplemented("instance type %v is not supported", instanceType) } - default: - return nil, trace.NotImplemented("instance type %v is not supported", instanceType) + } + } else { + // If the user didn't apply any filters but only has permissions for one of the two instance kinds, only return that kind + switch { + case instanceErr != nil && botInstanceErr != nil: + return nil, trace.NewAggregate(instanceErr, botInstanceErr) + case botInstanceErr != nil: + filter.SetInstanceTypes([]inventorypb.InstanceType{inventorypb.InstanceType_INSTANCE_TYPE_INSTANCE}) + case instanceErr != nil: + filter.SetInstanceTypes([]inventorypb.InstanceType{inventorypb.InstanceType_INSTANCE_TYPE_BOT_INSTANCE}) } } diff --git a/lib/web/inventory_test.go b/lib/web/inventory_test.go index df4e5f463751..1c18d98d3895 100644 --- a/lib/web/inventory_test.go +++ b/lib/web/inventory_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/timestamppb" @@ -242,3 +243,78 @@ func TestListUnifiedInstances(t *testing.T) { require.NoError(t, json.Unmarshal(resp.Bytes(), &listResp)) require.Empty(t, listResp.Instances, "should find no items matching the search") } + +// TestListUnifiedInstancesPartialPermissions verifies how the list treats a user who can only read one of the two +// instance kinds: with no type filter applied they still get a list of the kind they can read, while explicitly +// filtering for a kind they can't read is an access denied error. +func TestListUnifiedInstancesPartialPermissions(t *testing.T) { + t.Parallel() + + ctx := t.Context() + env := newWebPack(t, 1, func(cfg *WebPackOptions) { + cfg.enableAuthCache = true + }) + + require.NoError(t, env.server.Auth().UpsertInstance(ctx, &types.InstanceV1{ + ResourceHeader: types.ResourceHeader{Metadata: types.Metadata{Name: "instance-1"}}, + Spec: types.InstanceSpecV1{Hostname: "host-1", Version: "18.1.0"}, + })) + _, err := env.server.Auth().BotInstance.CreateBotInstance(ctx, machineidv1.BotInstance_builder{ + Metadata: headerv1.Metadata_builder{Name: "bot-1"}.Build(), + Spec: machineidv1.BotInstanceSpec_builder{BotName: "bot-1", InstanceId: "bot-1"}.Build(), + Status: machineidv1.BotInstanceStatus_builder{}.Build(), + }.Build()) + require.NoError(t, err) + + packWithAccessTo := func(username string, kinds ...string) *authPack { + rules := make([]types.Rule, 0, len(kinds)) + for _, kind := range kinds { + rules = append(rules, types.NewRule(kind, []string{types.VerbRead, types.VerbList})) + } + role, err := types.NewRole(services.RoleNameForUser(username), types.RoleSpecV6{ + Allow: types.RoleConditions{Rules: rules}, + }) + require.NoError(t, err) + return env.proxies[0].authPack(t, username, []types.Role{role}) + } + + instanceOnly := packWithAccessTo("instance-only", types.KindInstance) + botOnly := packWithAccessTo("bot-only", types.KindBotInstance) + noAccess := packWithAccessTo("no-access") + + endpoint := instanceOnly.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "instances") + + var listResp listUnifiedInstancesResponse + require.EventuallyWithT(t, func(t *assert.CollectT) { + resp, err := instanceOnly.clt.Get(ctx, endpoint, url.Values{}) + if !assert.NoError(t, err) { + return + } + assert.NoError(t, json.Unmarshal(resp.Bytes(), &listResp)) + assert.Len(t, listResp.Instances, 1) + }, 10*time.Second, 100*time.Millisecond, "inventory cache failed to become healthy and populated") + require.Equal(t, "instance-1", listResp.Instances[0].ID) + + // A user who can only read bot instances gets the bot instance + resp, err := botOnly.clt.Get(ctx, endpoint, url.Values{}) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(resp.Bytes(), &listResp)) + require.Len(t, listResp.Instances, 1) + require.Equal(t, "bot-1", listResp.Instances[0].ID) + + // If the user explicitly applies a filter, they should get access denied error if they don't have the necessary permissions + _, err = instanceOnly.clt.Get(ctx, endpoint, url.Values{"types": []string{"bot_instance"}}) + require.True(t, trace.IsAccessDenied(err), "expected access denied, got %v", err) + _, err = instanceOnly.clt.Get(ctx, endpoint, url.Values{"types": []string{"instance,bot_instance"}}) + require.True(t, trace.IsAccessDenied(err), "expected access denied, got %v", err) + + resp, err = instanceOnly.clt.Get(ctx, endpoint, url.Values{"types": []string{"instance"}}) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(resp.Bytes(), &listResp)) + require.Len(t, listResp.Instances, 1) + require.Equal(t, "instance-1", listResp.Instances[0].ID) + + // A user who can read neither is denied + _, err = noAccess.clt.Get(ctx, endpoint, url.Values{}) + require.True(t, trace.IsAccessDenied(err), "expected access denied, got %v", err) +} diff --git a/web/packages/shared/components/Controls/MultiselectMenu.tsx b/web/packages/shared/components/Controls/MultiselectMenu.tsx index 4bb1f0166a04..c75beb0b4818 100644 --- a/web/packages/shared/components/Controls/MultiselectMenu.tsx +++ b/web/packages/shared/components/Controls/MultiselectMenu.tsx @@ -193,6 +193,7 @@ export const MultiselectMenu = []>({ handleSelect(opt.value); }} id={opt.value} + data-testid={`option-${opt.value}`} checked={(buffered ? intSelected : selected)?.includes( opt.value )} diff --git a/web/packages/teleport/src/Instances/Instances.test.tsx b/web/packages/teleport/src/Instances/Instances.test.tsx index f8cc386693d2..5f4b00556c1a 100644 --- a/web/packages/teleport/src/Instances/Instances.test.tsx +++ b/web/packages/teleport/src/Instances/Instances.test.tsx @@ -142,6 +142,52 @@ it('having only instances permissions should show warning banner', async () => { }); }); +it('type filter option should be disabled for bot instances when the user can only read instances', async () => { + server.use(listOnlyRegularInstances); + const { user } = renderComponent({ + customAcl: makeAcl({ + instances: { + ...defaultAccess, + list: true, + read: true, + }, + botInstances: { + ...defaultAccess, + list: false, + read: false, + }, + }), + }); + + await user.click(screen.getByRole('button', { name: /Type/i })); + + expect(screen.getByTestId('option-instance')).toBeEnabled(); + expect(screen.getByTestId('option-bot_instance')).toBeDisabled(); +}); + +it('type filter option should be disabled for instances when the user can only read bot instances', async () => { + server.use(listOnlyBotInstances); + const { user } = renderComponent({ + customAcl: makeAcl({ + instances: { + ...defaultAccess, + list: false, + read: false, + }, + botInstances: { + ...defaultAccess, + list: true, + read: true, + }, + }), + }); + + await user.click(screen.getByRole('button', { name: /Type/i })); + + expect(screen.getByTestId('option-instance')).toBeDisabled(); + expect(screen.getByTestId('option-bot_instance')).toBeEnabled(); +}); + it('cache still initializing error should show correct error', async () => { server.use( listInstancesError( diff --git a/web/packages/teleport/src/Instances/Instances.tsx b/web/packages/teleport/src/Instances/Instances.tsx index 1512ae0c658f..452b8493c184 100644 --- a/web/packages/teleport/src/Instances/Instances.tsx +++ b/web/packages/teleport/src/Instances/Instances.tsx @@ -149,6 +149,23 @@ export function Instances() { const onlyBotInstancesSelected = selectedTypes.length === 1 && selectedTypes[0] === 'bot_instance'; + const typeOptions = [ + { + value: 'instance' as const, + label: 'Instances', + disabled: !hasInstancePermissions, + disabledTooltip: + 'Listing instances requires permissions instance.list and instance.read.', + }, + { + value: 'bot_instance' as const, + label: 'Bot Instances', + disabled: !hasBotInstancePermissions, + disabledTooltip: + 'Listing bot instances requires permissions bot_instance.list and bot_instance.read.', + }, + ]; + const { isSuccess, data, @@ -406,7 +423,6 @@ export function Instances() { label="Type" tooltip="Filter by instance type" buffered={true} - disabled={!hasInstancePermissions} /> Date: Thu, 13 Aug 2026 21:07:18 +0000 Subject: [PATCH 298/347] Adjust proto comments after implementing `Approve` and `Deny` RPCs (#69642) --- .../devicetrust/v1/devicetrust_service_grpc.pb.go | 14 ++++++++++---- .../devicetrust/v1/devicetrust_service.proto | 7 +++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go index 2f4ba4634a7e..e366af636e55 100644 --- a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go @@ -199,7 +199,9 @@ type DeviceTrustServiceClient interface { // service. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token, and CompareFailed if the pairing is not awaiting approval. + // pairing_token, and CompareFailed if the pairing is not yet awaiting + // approval. Approving an already approved pairing is idempotent and returns + // success. // // Requires the "mobile_device.create_enroll_token" permission. It is an // administrative action for which reusable challenges are not accepted. @@ -207,7 +209,8 @@ type DeviceTrustServiceClient interface { // DenyEnrollPairing deletes the EnrollPairing for the calling user. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token. + // pairing_token, and CompareFailed if the pairing changed while the denial + // was in flight. // // Requires the "mobile_device.create_enroll_token" permission. Unlike // ApproveEnrollPairing, it is not an administrative action and thus doesn't @@ -557,7 +560,9 @@ type DeviceTrustServiceServer interface { // service. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token, and CompareFailed if the pairing is not awaiting approval. + // pairing_token, and CompareFailed if the pairing is not yet awaiting + // approval. Approving an already approved pairing is idempotent and returns + // success. // // Requires the "mobile_device.create_enroll_token" permission. It is an // administrative action for which reusable challenges are not accepted. @@ -565,7 +570,8 @@ type DeviceTrustServiceServer interface { // DenyEnrollPairing deletes the EnrollPairing for the calling user. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token. + // pairing_token, and CompareFailed if the pairing changed while the denial + // was in flight. // // Requires the "mobile_device.create_enroll_token" permission. Unlike // ApproveEnrollPairing, it is not an administrative action and thus doesn't diff --git a/api/proto/teleport/devicetrust/v1/devicetrust_service.proto b/api/proto/teleport/devicetrust/v1/devicetrust_service.proto index 9f62dc052ff8..e875649dc01b 100644 --- a/api/proto/teleport/devicetrust/v1/devicetrust_service.proto +++ b/api/proto/teleport/devicetrust/v1/devicetrust_service.proto @@ -191,7 +191,9 @@ service DeviceTrustService { // service. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token, and CompareFailed if the pairing is not awaiting approval. + // pairing_token, and CompareFailed if the pairing is not yet awaiting + // approval. Approving an already approved pairing is idempotent and returns + // success. // // Requires the "mobile_device.create_enroll_token" permission. It is an // administrative action for which reusable challenges are not accepted. @@ -200,7 +202,8 @@ service DeviceTrustService { // DenyEnrollPairing deletes the EnrollPairing for the calling user. // // Returns NotFound if the caller has no EnrollPairing or if it doesn't match - // pairing_token. + // pairing_token, and CompareFailed if the pairing changed while the denial + // was in flight. // // Requires the "mobile_device.create_enroll_token" permission. Unlike // ApproveEnrollPairing, it is not an administrative action and thus doesn't From d16e99f57061bc19c03381b5e7c5b12fa38bb0d0 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Thu, 13 Aug 2026 21:31:59 +0000 Subject: [PATCH 299/347] remove all uses of fetchUsers() v1 (#64719) --- .../shared/components/FieldSelect/shared.tsx | 3 ++ .../ServerSideSupportedList.tsx | 17 ++++++++- .../Users.tsx | 25 +++---------- .../ResourceList/SimpleList/SimpleList.tsx | 7 ---- .../teleport/src/LocksV2/NewLock/common.tsx | 2 +- .../src/Users/UserAddEdit/UserAddEdit.tsx | 24 ++----------- .../src/Users/UserDelete/UserDelete.tsx | 14 +------- web/packages/teleport/src/config.ts | 2 +- .../teleport/src/services/user/hooks.ts | 23 ------------ .../teleport/src/services/user/user.ts | 36 ++++++++++++++----- 10 files changed, 56 insertions(+), 97 deletions(-) rename web/packages/teleport/src/LocksV2/NewLock/ResourceList/{SimpleList => ServerSideSupportedList}/Users.tsx (77%) delete mode 100644 web/packages/teleport/src/services/user/hooks.ts diff --git a/web/packages/shared/components/FieldSelect/shared.tsx b/web/packages/shared/components/FieldSelect/shared.tsx index 886ff10ddc30..e81bf645b4f3 100644 --- a/web/packages/shared/components/FieldSelect/shared.tsx +++ b/web/packages/shared/components/FieldSelect/shared.tsx @@ -187,6 +187,7 @@ export function splitSelectProps< customProps, defaultValue, elevated, + getOptionValue, helperText, inputId, inputValue, @@ -236,6 +237,7 @@ export function splitSelectProps< customProps, defaultValue, elevated, + getOptionValue, inputValue, isClearable, isDisabled, @@ -284,6 +286,7 @@ type KeysRemovedFromOthers = | 'customProps' | 'defaultValue' | 'elevated' + | 'getOptionValue' | 'helperText' | 'inputId' | 'inputValue' diff --git a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/ServerSideSupportedList.tsx b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/ServerSideSupportedList.tsx index 3042c8ea22c2..23ddc7ef6462 100644 --- a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/ServerSideSupportedList.tsx +++ b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/ServerSideSupportedList.tsx @@ -36,6 +36,7 @@ import type { import { Desktop } from 'teleport/services/desktops'; import { Node } from 'teleport/services/nodes'; import { RoleResource } from 'teleport/services/resources'; +import { User } from 'teleport/services/user'; import Ctx from 'teleport/teleportContext'; import useTeleport from 'teleport/useTeleport'; @@ -44,6 +45,7 @@ import { ServerSideListProps, TableWrapper } from '../common'; import { Desktops } from './Desktops'; import { Nodes } from './Nodes'; import { Roles } from './Roles'; +import Users from './Users'; export function ServerSideSupportedList(props: CommonListProps) { const ctx = useTeleport(); @@ -119,6 +121,8 @@ export function ServerSideSupportedList(props: CommonListProps) { }; switch (props.selectedResourceKind) { + case 'user': + return ; case 'role': return ; case 'node': @@ -148,7 +152,11 @@ export function ServerSideSupportedList(props: CommonListProps) { to: pageIndicators.to, total: pageIndicators.totalCount, }} - hideAdvancedSearch={props.selectedResourceKind === 'role'} // Roles don't support advanced search. + // Roles and users don't support advanced search. + hideAdvancedSearch={ + props.selectedResourceKind === 'role' || + props.selectedResourceKind === 'user' + } filter={resourceFilter} disableSearch={fetchStatus === 'loading'} /> @@ -194,6 +202,13 @@ function getFetchFuncForServerSidePaginating( clusterId: string, params: UrlResourcesParams ) => Promise> { + if (resourceKind === 'user') { + return async (clusterId, params) => { + const { items, startKey } = await ctx.userService.fetchUsersV2(params); + return { agents: items, startKey }; + }; + } + if (resourceKind === 'role') { return async (clusterId, params) => { const { items, startKey } = await ctx.resourceService.fetchRoles(params); diff --git a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/Users.tsx b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/Users.tsx similarity index 77% rename from web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/Users.tsx rename to web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/Users.tsx index 54f663bb9676..a983c39d1ff4 100644 --- a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/Users.tsx +++ b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/ServerSideSupportedList/Users.tsx @@ -20,9 +20,11 @@ import Table, { Cell, LabelCell } from 'design/DataTable'; import { User } from 'teleport/services/user'; -import { renderActionCell, SimpleListProps } from '../common'; +import { renderActionCell, ServerSideListProps } from '../common'; -export default function UserList(props: SimpleListProps & { users: User[] }) { +export default function UserList( + props: ServerSideListProps & { users: User[] } +) { const { users = [], selectedResources, @@ -37,31 +39,15 @@ export default function UserList(props: SimpleListProps & { users: User[] }) { { key: 'name', headerText: 'Name', - isSortable: true, }, { key: 'roles', headerText: 'Roles', - isSortable: true, - onSort: (a, b) => { - const aStr = a.roles.toString(); - const bStr = b.roles.toString(); - - if (aStr < bStr) { - return -1; - } - if (aStr > bStr) { - return 1; - } - - return 0; - }, render: ({ roles }) => , }, { key: 'authType', headerText: 'Type', - isSortable: true, render: ({ authType }) => ( {renderAuthType(authType)} @@ -77,8 +63,7 @@ export default function UserList(props: SimpleListProps & { users: User[] }) { }, ]} emptyText="No Users Found" - isSearchable - pagination={{ pageSize: props.pageSize }} + disableFilter fetching={{ fetchStatus, }} diff --git a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/SimpleList.tsx b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/SimpleList.tsx index c293c75e5c93..e3576f510eb9 100644 --- a/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/SimpleList.tsx +++ b/web/packages/teleport/src/LocksV2/NewLock/ResourceList/SimpleList/SimpleList.tsx @@ -19,13 +19,11 @@ import React, { useEffect, useMemo, useState } from 'react'; import { MfaDevice } from 'teleport/services/mfa'; -import { User } from 'teleport/services/user'; import useTeleport from 'teleport/useTeleport'; import { CommonListProps, LockResourceKind } from '../../common'; import { SimpleListProps, TableWrapper } from '../common'; import { MfaDevices } from './MfaDevices'; -import Users from './Users'; export type SimpleListOpts = { getFetchFn( @@ -45,9 +43,6 @@ export function SimpleList(props: CommonListProps & { opts: SimpleListOpts }) { useEffect(() => { let fetchFn; switch (props.selectedResourceKind) { - case 'user': - fetchFn = ctx.userService.fetchUsers; - break; case 'mfa_device': fetchFn = ctx.mfaService.fetchDevices; break; @@ -81,8 +76,6 @@ export function SimpleList(props: CommonListProps & { opts: SimpleListOpts }) { toggleSelectResource: props.toggleSelectResource, }; switch (props.selectedResourceKind) { - case 'user': - return ; case 'mfa_device': return ( diff --git a/web/packages/teleport/src/LocksV2/NewLock/common.tsx b/web/packages/teleport/src/LocksV2/NewLock/common.tsx index d1e42deb9184..6411b1ee1078 100644 --- a/web/packages/teleport/src/LocksV2/NewLock/common.tsx +++ b/web/packages/teleport/src/LocksV2/NewLock/common.tsx @@ -102,7 +102,7 @@ export const baseResourceKindOpts: LockResourceOption[] = [ { value: 'user', label: 'Users', - listKind: 'simple', + listKind: 'server-side', }, { value: 'role', diff --git a/web/packages/teleport/src/Users/UserAddEdit/UserAddEdit.tsx b/web/packages/teleport/src/Users/UserAddEdit/UserAddEdit.tsx index e35c6ae74e3d..0ae8dda62404 100644 --- a/web/packages/teleport/src/Users/UserAddEdit/UserAddEdit.tsx +++ b/web/packages/teleport/src/Users/UserAddEdit/UserAddEdit.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { useState } from 'react'; import { Alert, Box, ButtonPrimary, ButtonSecondary } from 'design'; @@ -46,7 +46,6 @@ import userService, { type CreateUserVariables, type User, } from 'teleport/services/user'; -import { GetUsersQueryKey } from 'teleport/services/user/hooks'; import UserTokenLink from './../UserTokenLink'; @@ -67,8 +66,6 @@ export function UserAddEdit({ }: UserAddEditProps) { const ctx = useTeleport(); - const queryClient = useQueryClient(); - const createUser = useMutation({ mutationFn: async (variables: CreateUserVariables) => { const mfaResponse = @@ -87,30 +84,13 @@ export function UserAddEdit({ token, }; }, - onSuccess: ({ token, user }) => { + onSuccess: ({ token }) => { setToken(token); - - queryClient.setQueryData(GetUsersQueryKey, previous => { - if (!previous) { - return []; - } - - return [user, ...previous]; - }); }, }); const updateUser = useMutation({ mutationFn: userService.updateUser, - onSuccess: data => { - queryClient.setQueryData(GetUsersQueryKey, previous => { - if (!previous) { - return []; - } - - return [data, ...previous.filter(i => i.name !== data.name)]; - }); - }, }); const [name, setName] = useState(user.name); diff --git a/web/packages/teleport/src/Users/UserDelete/UserDelete.tsx b/web/packages/teleport/src/Users/UserDelete/UserDelete.tsx index e5996d1c2b41..27c6f874c04b 100644 --- a/web/packages/teleport/src/Users/UserDelete/UserDelete.tsx +++ b/web/packages/teleport/src/Users/UserDelete/UserDelete.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { Alert, ButtonSecondary, ButtonWarning, Text } from 'design'; import Dialog, { @@ -28,7 +28,6 @@ import Dialog, { import { ResourcesResponse } from 'teleport/services/agents'; import userService, { User } from 'teleport/services/user'; -import { GetUsersQueryKey } from 'teleport/services/user/hooks'; interface UserDeleteProps { username: string; @@ -43,19 +42,8 @@ export function UserDelete({ onClose, modifyFetchedData, }: UserDeleteProps) { - const queryClient = useQueryClient(); - const deleteUser = useMutation({ mutationFn: userService.deleteUser, - onSuccess: (_, name) => { - queryClient.setQueryData(GetUsersQueryKey, previous => { - if (!previous) { - return []; - } - - return previous.filter(user => user.name !== name); - }); - }, }); async function handleDelete() { diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index b9cbad91750d..9efedb0a6de0 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -1287,7 +1287,7 @@ const cfg = { return generatePath(cfg.routes.ssoConnector.create, { connectorType }); }, - // TODO(rudream): DELETE IN V21.0.0 + // TODO(rudream): DELETE IN v20.0 getUsersUrl() { return cfg.api.usersPath; }, diff --git a/web/packages/teleport/src/services/user/hooks.ts b/web/packages/teleport/src/services/user/hooks.ts deleted file mode 100644 index 5d7da13ca5ba..000000000000 --- a/web/packages/teleport/src/services/user/hooks.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Teleport - * Copyright (C) 2025 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 { createQueryHook } from 'teleport/services/queryHelpers'; -import userService from 'teleport/services/user'; - -export const { queryKey: GetUsersQueryKey, useQuery: useGetUsers } = - createQueryHook(['users', 'get'], userService.fetchUsers); diff --git a/web/packages/teleport/src/services/user/user.ts b/web/packages/teleport/src/services/user/user.ts index 4cb67937e27c..1bb618d314d8 100644 --- a/web/packages/teleport/src/services/user/user.ts +++ b/web/packages/teleport/src/services/user/user.ts @@ -66,11 +66,29 @@ const service = { .then(makeUser); }, - // TODO(rudream): DELETE IN v21.0 + /** + * @deprecated Use {@link service.fetchUsersV2} instead. + */ + // TODO(rudream): DELETE IN v20.0 fetchUsers(signal?: AbortSignal) { return api.get(cfg.getUsersUrl(), signal).then(makeUsers); }, + /** + * fetchAllUsers fetches all users page by page until there are no more. + * Only use this in cases where all users are needed upfront, otherwise, use {@link service.fetchUsersV2}. + */ + async fetchAllUsers(signal?: AbortSignal): Promise { + const allUsers: User[] = []; + let startKey = ''; + do { + const page = await service.fetchUsersV2({ limit: 200, startKey }, signal); + allUsers.push(...page.items); + startKey = page.startKey; + } while (startKey); + return allUsers; + }, + async fetchUsersV2( params?: UrlListUsersParams, signal?: AbortSignal @@ -89,11 +107,11 @@ const service = { .catch(err => { // If this v2 paginated endpoint isn't found, fallback to the v1 endpoint but paginate locally in order to // maintain compatibility with the paginated table component which expects a paginated response. - // TODO(rudream): DELETE IN v21.0 + // TODO(rudream): DELETE IN v20.0 if (isPathNotFoundError(err)) { - return this.fetchUsers().then(users => - makeUsersPageLocally(params, users) - ); + return service + .fetchUsers() + .then(users => makeUsersPageLocally(params, users)); } else { throw err; } @@ -202,7 +220,7 @@ function withExcludedField(user: User, excludeUserField: ExcludeUserField) { * makeUsersPageLocally mocks a paginated response for users so that a list of all users * can be handled by a serverside paginated table component. */ -// TODO(rudream): DELETE IN v21.0 +// TODO(rudream): DELETE IN v20.0 function makeUsersPageLocally( params: UrlListUsersParams, allUsers: User[] @@ -210,18 +228,18 @@ function makeUsersPageLocally( items: User[]; startKey: string; } { - if (params.search) { + if (params?.search) { allUsers = allUsers.filter(u => u.name.toLowerCase().includes(params.search.toLowerCase()) ); } - if (params.startKey) { + if (params?.startKey) { const startIndex = allUsers.findIndex(p => p.name === params.startKey); allUsers = allUsers.slice(startIndex); } - const limit = params.limit || 200; + const limit = params?.limit || 200; const nextKey = allUsers.at(limit)?.name; allUsers = allUsers.slice(0, limit); From c8e23661ca6e1515849fe393fb2af6ef89056582 Mon Sep 17 00:00:00 2001 From: Adam Carheden Date: Thu, 13 Aug 2026 21:36:57 +0000 Subject: [PATCH 300/347] Added proxy-only helm chart (#68037) * Added proxy-only helm chart * dleted unused text fixtures * Deleted empty snapshots * unused helmignore * deleted debugging * Switch empty join_params from values file to set to avoid lint-helm make target bug * Put static join tokens in secert * Remove PSP support * Added token secret to validation hook * fixed hook name in test * Made example use more realistic kube+oidc token * Set correct hook weight for secret * Better validation of token secerts * Fixed cluster ref in readme example * Fixed typo in Account * Preserve all join params * Make tmp secret for install only * Fixed wrong context for volumes calls * Don't support creating token secret for upgrades * Added optional audience to projected SA token * Deleted unneeded snapshot * Fixed merged proxy values for join token secret * Ensures hook secret is created for proxy override * Wrote docs * Allowed empty value for proxyProtocol * Fixed doc spelling errors * Wrote index page for teleport-proxy chart * Ensure bound keypair values are only in secrets * Fixed extra whitespace * fixed auth-token path and related tests * Removed token and bound_keypair support * Fixed lint version after rebase to helm4 link fix * Corrected overrides for serviceAccountToken* settings * corrected doc links * Documented validation intent * Fix doc links * Fixed tls-routing doc link * Fix arch doc ref * arch link fix again * forgot to checking vales * Fix vale findings * Update examples/chart/teleport-proxy/values.yaml Co-authored-by: Hugo Shaka * Fixed missing docs secions due to -- vs - * Address code review comments * Switched helm values to camelCase * Removed legacy proxy key * Remove legacy replica count munging * Added Azure support to joinParams * Fix unescaped doc chars * Fixed doc heading and link * fixed heading * fixed vale checks * code review fixes for values * Added updated generated doc page * Updated for helm unittest 1.0.3 * Fixed config version for scratch chart mode * Remove unneeded license from proxy chart * Clarified Anycast support in comment * Clarified cluster name docs * Noted limited purpose of chart * Made various bits of docs admonitions * Advised against using serviceAccountToken values * Removed ACME support * Fixed incorrect link * fixed mdx/mxd doc extention conflation * Fixed doc lint issues * Removed serviceAccountToken custom fields * Corrected docs on join token creation * Corrected example token expiry in docs and unneded doc link in README * Fixed spelling * Fixed tctl create command --------- Co-authored-by: Hugo Shaka --- build.assets/tooling/cmd/helm-janitor/main.go | 5 + .../zz_generated.teleport-proxy.mdx | 1150 +++++++++++++++++ .../helm-reference/helm-reference.mdx | 3 + .../helm-reference/teleport-proxy.mdx | 57 + .../tests/proxy_deployment_test.yaml | 31 + .../lib/teleport-proxy-lib-test/values.yaml | 3 + .../chart/lib/teleport-proxy-lib/README.md | 4 + .../templates/_config.common.tpl | 5 + .../templates/_deployment.tpl | 6 + .../teleport-proxy-lib/templates/_helpers.tpl | 14 + .../templates/_validate.tpl | 5 +- .../chart/teleport-cluster/templates/psp.yaml | 2 + .../teleport-proxy/.lint/annotations.yaml | 21 + .../teleport-proxy/.lint/cert-manager.yaml | 19 + .../chart/teleport-proxy/.lint/ingress.yaml | 10 + examples/chart/teleport-proxy/.lint/pdb.yaml | 16 + .../teleport-proxy/.lint/podmonitor.yaml | 10 + .../teleport-proxy/.lint/service-account.yaml | 8 + examples/chart/teleport-proxy/Chart.yaml | 14 + examples/chart/teleport-proxy/README.md | 97 ++ .../teleport-proxy/charts/teleport-proxy-lib | 1 + .../teleport-proxy/charts/teleport-util-lib | 1 + .../chart/teleport-proxy/templates/NOTES.txt | 21 + .../teleport-proxy/templates/_helpers.tpl | 26 + .../templates/_teleportConfig.tpl | 24 + .../teleport-proxy/templates/podmonitor.yaml | 32 + .../chart/teleport-proxy/templates/proxy.yaml | 21 + examples/chart/teleport-proxy/tests/README.md | 23 + .../proxy_wrapper_config_test.yaml.snap | 15 + .../teleport-proxy/tests/ingress_test.yaml | 25 + .../teleport-proxy/tests/podmonitor_test.yaml | 52 + .../teleport-proxy/tests/predeploy_test.yaml | 185 +++ .../tests/proxy_certificate_test.yaml | 25 + .../tests/proxy_config_test.yaml | 25 + .../tests/proxy_deployment_test.yaml | 254 ++++ .../teleport-proxy/tests/proxy_pdb_test.yaml | 25 + .../tests/proxy_service_test.yaml | 130 ++ .../tests/proxy_serviceaccount_name_test.yaml | 14 + .../tests/proxy_serviceaccount_test.yaml | 25 + .../tests/proxy_wrapper_config_test.yaml | 228 ++++ .../proxy_wrapper_serviceaccount_test.yaml | 38 + .../chart/teleport-proxy/values.schema.json | 697 ++++++++++ examples/chart/teleport-proxy/values.yaml | 714 ++++++++++ 43 files changed, 4079 insertions(+), 2 deletions(-) create mode 100644 docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx create mode 100644 docs/pages/reference/helm-reference/teleport-proxy.mdx create mode 100644 examples/chart/teleport-proxy/.lint/annotations.yaml create mode 100644 examples/chart/teleport-proxy/.lint/cert-manager.yaml create mode 100644 examples/chart/teleport-proxy/.lint/ingress.yaml create mode 100644 examples/chart/teleport-proxy/.lint/pdb.yaml create mode 100644 examples/chart/teleport-proxy/.lint/podmonitor.yaml create mode 100644 examples/chart/teleport-proxy/.lint/service-account.yaml create mode 100644 examples/chart/teleport-proxy/Chart.yaml create mode 100644 examples/chart/teleport-proxy/README.md create mode 120000 examples/chart/teleport-proxy/charts/teleport-proxy-lib create mode 120000 examples/chart/teleport-proxy/charts/teleport-util-lib create mode 100644 examples/chart/teleport-proxy/templates/NOTES.txt create mode 100644 examples/chart/teleport-proxy/templates/_helpers.tpl create mode 100644 examples/chart/teleport-proxy/templates/_teleportConfig.tpl create mode 100644 examples/chart/teleport-proxy/templates/podmonitor.yaml create mode 100644 examples/chart/teleport-proxy/templates/proxy.yaml create mode 100644 examples/chart/teleport-proxy/tests/README.md create mode 100644 examples/chart/teleport-proxy/tests/__snapshot__/proxy_wrapper_config_test.yaml.snap create mode 100644 examples/chart/teleport-proxy/tests/ingress_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/podmonitor_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/predeploy_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_certificate_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_config_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_deployment_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_pdb_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_service_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_serviceaccount_name_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_serviceaccount_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_wrapper_config_test.yaml create mode 100644 examples/chart/teleport-proxy/tests/proxy_wrapper_serviceaccount_test.yaml create mode 100644 examples/chart/teleport-proxy/values.schema.json create mode 100644 examples/chart/teleport-proxy/values.yaml diff --git a/build.assets/tooling/cmd/helm-janitor/main.go b/build.assets/tooling/cmd/helm-janitor/main.go index 290f44d7aab1..34f6730075d9 100644 --- a/build.assets/tooling/cmd/helm-janitor/main.go +++ b/build.assets/tooling/cmd/helm-janitor/main.go @@ -55,6 +55,11 @@ var charts = []Chart{ // teleport-cluster reference is still hand-written. ReferencePath: "", }, + { + Name: "teleport-proxy", + Path: "examples/chart/teleport-proxy", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx", + }, { Name: "teleport-kube-agent", Path: "examples/chart/teleport-kube-agent", diff --git a/docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx b/docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx new file mode 100644 index 000000000000..76a3c28de056 --- /dev/null +++ b/docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx @@ -0,0 +1,1150 @@ + +{/* Generated file. Do not edit.*/} +{/* Generate this file by navigating to examples/chart and running make render-chart-ref*/} +## `clusterName` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`clusterName` MUST match the clusterName used by the Teleport Auth +service ([clusterName in the teleport-cluster chart](../../reference/helm-reference/teleport-cluster.mdx#clustername) +/ [auth_service.cluster_name in the Auth Service config](../../reference/deployment/config.mdx#auth-service)) +It controls the name this proxy will use to refer to the Teleport cluster, +along with the externally-facing public address to use to access it. In most +setups this must be a fully-qualified domain name (e.g. `teleport.example.com`) +as this value is used as the cluster's public address by default. + +> [!NOTE] +> When using a fully qualified domain name as your `clusterName`, you will also +> need to configure the DNS provider for this domain to point to the external +> load balancer address of your Teleport cluster. If you're using this proxy-only +> chart, this typically means adding the external IP of this proxy's load-balancer +> to your geography-aware DNS service. We do not recommend using a load balancer +> with an Anycast VIP because Teleport clients maintain long-live persistent +> connections that may be disrupted during Anycast routing changes. + +## `authServer` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`authServer` the address this proxy should use to contact the Auth +service. This should be an \:\ for an internal network path to the +Auth Service. Port defaults to 3025 if not specified. +If running on the same kube cluster as the Auth Service, consider using the +teleport-cluster chart instead. + +## `joinParams` + +| Type | Default | +|------|---------| +| `object` | `{"azure":{"clientId":""},"method":"kubernetes","tokenName":""}` | + +`joinParams` are parameters for joining the Teleport Cluster. + +To use this chart, you must pre-provision a Teleport Provision Token this +proxy can use to authenticate to the cluster. Configure the token name and +other parameters here. Only delegated join methods that don't require secrets +are supported by this chart. This **excludes** the `token` and `bound_keypair` +join methods. + +See also: +* [Join Methods and Tokens](../../reference/deployment/join-methods.mdx) +* [Teleport Config Reference](../../reference/deployment/config.mdx#instance-wide-settings). + +To create the token, see docs ([Terraform](../../reference/infrastructure-as-code/terraform-provider/resources/provision_token.mdx) +or [tctl](../../reference/cli/tctl.mdx#tctl-create)): + +```bash +issuer="$(kubectl get --raw /.well-known/openid-configuration | yq -r .issuer)" +tctl create < [!NOTE] +> The teleport proxy service +> (`proxy_service.enabled` in [teleport.yaml](../../reference/deployment/config.mdx#proxy-service), +> as deployed by this chart), does not require the license file even when +> running an enterprise build. Your license file only needs to be deployed on +> the auth service with the +> [teleport-cluster chart](../../reference/helm-reference/teleport-cluster.mdx). + +## `chartMode` + +| Type | Default | +|------|---------| +| `string` | `"standalone"` | + +`chartMode` introduces cloud-specific settings. At present, these +are only needed for AWS, so all other modes are effectively the same. Chart +mode is reserved for potential future use and to parallel the config shape of +the `teleport-cluster` chart. +- "standalone": Generate config file, but no cloud-specific settings +- "aws": Use when running on EKS. +- "gcp": Use when running on GKE. +- "azure": Use when running on AKS. +- "scratch": Generate a minimal config file. Only values derived from the + mandatory helm values are inserted into the config. Use teleportConfig to + supply additional configuration. + + Generally avoid using this mode. Use `standalone` instead. + +## `validateConfigOnDeploy` + +| Type | Default | +|------|---------| +| `bool` | `true` | + +`validateConfigOnDeploy` enables a Kubernetes job before install and +upgrade that will verify if the teleport.yaml configuration is valid and will +block the deployment if it is not. + +## `podMonitor` + +| Type | Default | +|------|---------| +| `object` | `{"additionalLabels":{"prometheus":"default"},"enabled":false,"interval":"30s"}` | + +`podMonitor` controls the [PodMonitor CR](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api-reference/api.md#monitoring.coreos.com/v1.PodMonitor]. +This CRD is managed by the prometheus-operator and allows workload to +get monitored. To use this value, you need to run a `prometheus-operator` +in the cluster for this value to take effect. +See https://prometheus-operator.dev/docs/getting-started/introduction/ + +### `podMonitor.enabled` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`podMonitor.enabled` controls whether the chart should deploy a +PodMonitor. Disabled by default as it requires the PodMonitor CRD to be +installed. + +### `podMonitor.additionalLabels` + +| Type | Default | +|------|---------| +| `object` | `{"prometheus":"default"}` | + +`podMonitor.additionalLabels` labels to put on the PodMonitor. +This is used to be selected by a specific prometheus instance. +Defaults to `{prometheus: default}` which seems to be the common default prometheus selector + +### `podMonitor.interval` + +| Type | Default | +|------|---------| +| `string` | `"30s"` | + +`podMonitor.interval` is the interval between two metrics scrapes. Defaults to 30s + +## `highAvailability` + +| Type | Default | +|------|---------| +| `object` | `{"certManager":{"addCommonName":false,"addPublicAddrs":false,"enabled":false,"issuerGroup":"cert-manager.io","issuerKind":"Issuer","issuerName":""},"minReadySeconds":15,"podDisruptionBudget":{"enabled":false,"minAvailable":1},"replicaCount":2,"requireAntiAffinity":false}` | + +`highAvailability` contains settings controlling how Teleport pods are +replicated and scheduled. This allows Teleport to run in a highly-available +fashion: Teleport should sustain the crash/loss of a machine without interrupting +the service. + +Proxy pods need to be provided a certificate to be replicated (either via +`tls.existingSecretName` or via `highAvailability.certManager`) or be exposed +via an ingress (`ingress.enabled`). + +### `highAvailability.replicaCount` + +| Type | Default | +|------|---------| +| `int` | `2` | + +`highAvailability.replicaCount` controls the amount of pod replicas. The `highAvailability` comment describes +the replication requirements. + +> [!WARNING] +> You **must** meet the replication criteria, else the deployment will +> result in errors and inconsistent data. + +### `highAvailability.requireAntiAffinity` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`highAvailability.requireAntiAffinity` will use 'requiredDuringSchedulingIgnoredDuringExecution' to require that multiple Teleport pods must not be scheduled on the +same physical host. This will result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. +Setting 'requireAntiAffinity' to false (the default) uses 'preferredDuringSchedulingIgnoredDuringExecution' to make this a soft requirement. +This setting only has any effect when replicaCount is greater than 1. + +### `highAvailability.podDisruptionBudget` + +| Type | Default | +|------|---------| +| `object` | `{"enabled":false,"minAvailable":1}` | + +`highAvailability.podDisruptionBudget` will create a +[Pod Disruption Budget](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/) + +#### `highAvailability.podDisruptionBudget.enabled` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`highAvailability.podDisruptionBudget.enabled` controls whether +the chart creates a Pod Disruption Budget for proxy pods. + +#### `highAvailability.podDisruptionBudget.minAvailable` + +| Type | Default | +|------|---------| +| `int` | `1` | + +`highAvailability.podDisruptionBudget.minAvailable` is the minimum +number of pods that must remain available during a voluntary disruption. + +### `highAvailability.certManager` + +| Type | Default | +|------|---------| +| `object` | `{"addCommonName":false,"addPublicAddrs":false,"enabled":false,"issuerGroup":"cert-manager.io","issuerKind":"Issuer","issuerName":""}` | + +`highAvailability.certManager` settings for cert-manager used to +provision TLS certificates in HA mode. +These settings are mutually exclusive with the "tls" value below. + +#### `highAvailability.certManager.enabled` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`highAvailability.certManager.enabled` if set to true, use +cert-manager to get certificates for Teleport to use for TLS termination. + +#### `highAvailability.certManager.addCommonName` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`highAvailability.certManager.addCommonName` if set to true, a +common name matching the cluster name will be set in the certificate +signing request. This is mandatory for some CAs. + +#### `highAvailability.certManager.addPublicAddrs` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`highAvailability.certManager.addPublicAddrs` if set to true, any +additional public addresses configured under the `publicAddr` chart value +will be added to the certificate signing request. +This setting is not enabled by default to preserve backward compatibility. + +#### `highAvailability.certManager.issuerName` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`highAvailability.certManager.issuerName` name of the +Issuer/ClusterIssuer to use for certs. +NOTE: You will always need to create this yourself when certManager.enabled is true. + +#### `highAvailability.certManager.issuerKind` + +| Type | Default | +|------|---------| +| `string` | `"Issuer"` | + +`highAvailability.certManager.issuerKind` kind of Issuer that +cert-manager should look for. +This defaults to 'Issuer' to keep everything contained within the teleport namespace. + +#### `highAvailability.certManager.issuerGroup` + +| Type | Default | +|------|---------| +| `string` | `"cert-manager.io"` | + +`highAvailability.certManager.issuerGroup` group of Issuer that +cert-manager should look for. +This defaults to 'cert-manager.io' which is the default Issuer group. + +### `highAvailability.minReadySeconds` + +| Type | Default | +|------|---------| +| `int` | `15` | + +`highAvailability.minReadySeconds` injects delay when performing pod +rollouts to mitigate the loss of all agent tunnels at the same time. +See https://github.com/gravitational/teleport/issues/13129 + +## `tls` + +| Type | Default | +|------|---------| +| `object` | `{"existingCASecretKeyName":"ca.pem","existingCASecretName":"","existingSecretName":""}` | + +`tls` are settings for mounting your own TLS keypair to secure Teleport's web UI. +These settings are mutually exclusive with the "highAvailability.certManager" and "acme" values above. + +### `tls.existingSecretName` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`tls.existingSecretName` name of an existing secret to use which +contains a TLS keypair. This will automatically set the https_keypairs +section in teleport.yaml. +Create the secret in the same namespace as Teleport using: +```bash +kubectl create secret tls my-tls-secret --cert=/path/to/cert/file --key=/path/to/key/file +``` +See [Kubernetes TLS Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets) for more information. + +### `tls.existingCASecretName` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`tls.existingCASecretName` optional name of an existing secret to +use which contains a CA or trust bundle in x509 PEM format. +Useful for building trust when using intermediate certificate authorities. +This will automatically set the SSL_CERT_FILE environment variable to trust the CA. +Create the secret with: +```bash +kubectl create secret generic my-root-ca --from-file=ca.pem=/path/to/root-ca.pem +``` + +DANGER: The Teleport distroless container trusts by default CAs from the `ca-certificates` package (Mozilla PKI). +When `existingCASecretName` is set, Teleport only trusts the CA bundle from the secret. +If you need Teleport to interact with other systems (e.g. AWS, GitHub, ...), the secret must contain their CA. +Else, Teleport will fail to establish TLS connections with external services. + +### `tls.existingCASecretKeyName` + +| Type | Default | +|------|---------| +| `string` | `"ca.pem"` | + +`tls.existingCASecretKeyName` optional name of an existing key in +the CA secret which will be used as a trusted CA bundle file. + +## `log` + +| Type | Default | +|------|---------| +| `object` | `{"extraFields":["timestamp","level","component","caller"],"format":"text","level":"INFO","output":"stderr"}` | + +`log` is the teleport logging configuration + +### `log.level` + +| Type | Default | +|------|---------| +| `string` | `"INFO"` | + +`log.level` log level for the Teleport process. +Available log levels are: DEBUG, INFO, WARNING, ERROR. +The default is INFO, which is recommended in production. +DEBUG is useful during first-time setup or to see more detailed logs for debugging. + +### `log.output` + +| Type | Default | +|------|---------| +| `string` | `"stderr"` | + +`log.output` is where logs should go. +Use a file path to log to disk: e.g. '/var/lib/teleport/teleport.log' +Other supported values: 'stdout', 'stderr' and 'syslog' + +### `log.format` + +| Type | Default | +|------|---------| +| `string` | `"text"` | + +`log.format` is the log format configuration. +Possible output values are 'json' and 'text' (default). + +### `log.extraFields` + +| Type | Default | +|------|---------| +| `array` | `["timestamp","level","component","caller"]` | + +`log.extraFields` other fields to log. Possible extra_fields values include: timestamp, component, caller, and level. +All extra fields are included by default. + +## `disableTopologySpreadConstraints` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`disableTopologySpreadConstraints` turns off the topology spread constraints. +The feature is automatically turned off on Kubernetes versions below 1.18. + +## `affinity` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`affinity` for pod assignment +https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +NOTE: If affinity is set here, highAvailability.requireAntiAffinity cannot also be used - you can only set one or the other. + +## `annotations` + +| Type | Default | +|------|---------| +| `object` | `{"certSecret":{},"config":{},"deployment":{},"ingress":{},"pod":{},"service":{},"serviceAccount":{}}` | + +`annotations` to apply, see [kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +### `annotations.config` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.config` for the ConfigMap + +### `annotations.deployment` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.deployment` for the Deployment + +### `annotations.pod` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.pod` for each Pod in the Deployment + +### `annotations.service` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.service` for the Service object + +### `annotations.serviceAccount` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.serviceAccount` for the ServiceAccount object + +### `annotations.certSecret` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.certSecret` for the certificate secret generated by cert-manager v1.5+ when highAvailability.certManager.enabled is true + +### `annotations.ingress` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`annotations.ingress` for the Ingress object + +## `extraLabels` + +`extraLabels` contains additional Kubernetes labels to apply on the resources +created by the chart. +See [the Kubernetes label documentation +](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) +for more information. + +Note: for PodMonitor labels, see `podMonitor.additionalLabels` instead. + +### `extraLabels.certSecret` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.certSecret` are labels to set on the certificate secret +generated by cert-manager v1.5+ when `highAvailability.certManager.enabled` +is true. + +### `extraLabels.clusterRole` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.clusterRole` are labels to set on the ClusterRole. + +### `extraLabels.clusterRoleBinding` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.clusterRoleBinding` are labels to set on the ClusterRoleBinding. + +### `extraLabels.role` + +`extraLabels.role` are labels to set on the Role. + +### `extraLabels.deployment` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.deployment` are labels to set on the Deployment. + +### `extraLabels.ingress` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.ingress` are labels to set on the Ingress. + +### `extraLabels.job` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.job` are labels to set on the Job run by the Helm hook. + +### `extraLabels.jobPod` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.jobPod` are labels to set on the Pods created by the +Job run by the Helm hook. + +### `extraLabels.pod` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.pod` are labels to set on the Pods created by the +Deployment. + +### `extraLabels.podDisruptionBudget` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.podDisruptionBudget` are labels to set on the podDisruptionBudget. + +### `extraLabels.secret` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.secret` are labels to set on the Secret. + +### `extraLabels.service` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.service` are labels to set on the Service. + +### `extraLabels.serviceAccount` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`extraLabels.serviceAccount` are labels to set on the ServiceAccount. + +## `serviceAccount` + +| Type | Default | +|------|---------| +| `object` | `{"create":true,"name":""}` | + +`serviceAccount` is the kubernetes service account to create/use. + +To set annotations on the service account, use the +`annotations.serviceAccount` value. + +### `serviceAccount.create` + +| Type | Default | +|------|---------| +| `bool` | `true` | + +`serviceAccount.create` specifies whether a ServiceAccount should be created + +### `serviceAccount.name` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`serviceAccount.name` is the name of the ServiceAccount to use. +If not set and serviceAccount.create is true, the name is generated using the release name. +If create is false, the name will be used to reference an existing service account. + +## `rbac` + +| Type | Default | +|------|---------| +| `object` | `{"create":true}` | + +`rbac` Manage Kubernetes RBAC + +### `rbac.create` + +| Type | Default | +|------|---------| +| `bool` | `true` | + +`rbac.create` specifies whether a ClusterRole and ClusterRoleBinding should be created. +Set to false if your cluster level resources are managed separately. + +## `service` + +| Type | Default | +|------|---------| +| `object` | `{"spec":{},"type":"LoadBalancer"}` | + +`service` allows you to override the proxy's backend service to any service type (other than "LoadBalancer") here if really needed. +To use an Ingress, set service.type=ClusterIP and ingress.enabled=true + +### `service.type` + +| Type | Default | +|------|---------| +| `string` | `"LoadBalancer"` | + +`service.type` is the Kubernetes Load balancer type + +### `service.spec` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`service.spec` is additional entries to be added to the service spec. +Example: +```yaml +loadBalancerIP: "1.2.3.4" +``` + +## `ingress` + +| Type | Default | +|------|---------| +| `object` | `{"enabled":false,"spec":{},"suppressAutomaticWildcards":false,"useExisting":false}` | + +`ingress` is the Kubernetes Ingress object. + +If you set `ingress.enabled` to true, service.type MUST also be set to something other than "LoadBalancer" to prevent +additional unnecessary load balancers from being created. Ingress controllers should provision their own load balancer. +Using an Ingress also requires that you use the `tsh` client to connect to Kubernetes clusters and databases behind Teleport. +See [TLS Routing](../../reference/architecture/tls-routing.mdx#working-with-layer-7-load-balancers-or-reverse-proxies) for details. + +### `ingress.enabled` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`ingress.enabled` creates an ingress + +### `ingress.useExisting` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`ingress.useExisting` indicates that you are managing your own ingress. +(or HTTPRoute, or any other LoadBalancing method that terminates TLS). +The chart will configure Teleport like it's running behind an ingress, but will not create the ingress resource. +You are responsible for creating and managing the ingress. + +### `ingress.suppressAutomaticWildcards` + +| Type | Default | +|------|---------| +| `bool` | `false` | + +`ingress.suppressAutomaticWildcards` will not automatically add +`*.` as a hostname served by the Ingress. This may be +desirable if you don't use applications protected by Teleport. + +### `ingress.spec` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`ingress.spec` is additional entries here will be added to the ingress spec. +Example: +```yaml +ingressClassName: nginx +``` + +## `extraArgs` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`extraArgs` adds arguments to 'teleport start' for the main Teleport +pod + +## `extraEnv` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`extraEnv` adds environment variables to the Teleport pod. + +## `extraContainers` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`extraContainers` to be added to the Teleport pod + +Example: +```yaml +- name: my-extra-tool + command: + - /bin/bash + - -c + - sleep infinity & wait + image: network-tool + imagePullPolicy: IfNotPresent + securityContext: + privileged: true + runAsNonRoot: false +``` + +## `extraVolumeMounts` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`extraVolumeMounts` corresponding to the volumes mounted by +extraVolumes. + +Example: +```yaml +- name: myvolume + mountPath: /path/on/host +``` + +## `initContainers` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`initContainers` is a list of +[initContainers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) +to run before each Teleport pod starts + +Example: +```yaml +- name: "teleport-init" + image: "alpine" + args: ["echo test"] +``` + +## `postStart` + +| Type | Default | +|------|---------| +| `object` | `{"command":[]}` | + +`postStart` if set, will run the command as a +[postStart handler](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) + +### `postStart.command` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`postStart.command` an array of commands to run + +## `resources` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`resources` to request for the teleport container +https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + +> [!DANGER] +> Setting CPU limits is an anti-pattern and harmful in most cases. +> Unless you enabled [the Static CPU management policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy), +> a multithreaded workload with CPU limits will very likely not behave the way +> you expect when approaching its CPU limit. + +Teleport will become unstable once throttling starts. We recommend not to set CPU limits. +See [the GitHub PR](https://github.com/gravitational/teleport/pull/36251) for technical details. + +Example: +```yaml + requests: + cpu: "1" + memory: "2Gi" + limits: + memory: "2Gi" +``` + +## `jobResources` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`jobResources` to request for the teleport pre-deploy jobs. +https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + +Jobs currently include config validation and potentially migration hooks. +The resource requirements are typically lower than for the main teleport deployment. +In most cases, you should leave these limits unset. +Example: +```yaml + requests: + cpu: "20m" + memory: "500Mi" + limits: + memory: "500Mi" +``` + +## `goMemLimitRatio` + +| Type | Default | +|------|---------| +| `float` | `0.9` | + +`goMemLimitRatio` configures the GOMEMLIMIT env var set by the chart. +GOMEMLIMIT instructs the go garbage collector to try to keep allocated memory +below a given threshold. This is a best-effort attempt, but this helps +to prevent OOMs in case of bursts. + +When the memory limits are set and goMemLimitRatio is non-zero, +the chart sets the GOMEMLIMIT to `resources.memory.limits * goMemLimitRatio`. +The value must be between 0 and 1. +Set to 0 to unset GOMEMLIMIT. +This has no effect if GOMEMLIMIT is already set through `extraEnv`. + +## `podSecurityContext` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`podSecurityContext` for any pods created by the chart. +Example: +```yaml +fsGroup: 65532 +``` + +## `securityContext` + +| Type | Default | +|------|---------| +| `object` | `{}` | + +`securityContext` to add to the container. +Example: +```yaml +runAsUser: 99 +``` + +## `priorityClassName` + +| Type | Default | +|------|---------| +| `string` | `""` | + +`priorityClassName` to add to the deployment + +## `tolerations` + +| Type | Default | +|------|---------| +| `array` | `[]` | + +`tolerations` for pod assignment +https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + +## `probeTimeoutSeconds` + +| Type | Default | +|------|---------| +| `int` | `5` | + +`probeTimeoutSeconds` is the timeouts for the readiness and liveness probes +https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + +## `readinessProbe` + +| Type | Default | +|------|---------| +| `object` | `{"failureThreshold":12,"initialDelaySeconds":5,"periodSeconds":5,"successThreshold":1}` | + +`readinessProbe` configures the readiness probe settings. +This can be tuned to keep proxy pods ready even when the auth is unavailable. + +The default values mark the pod unready after one minute of failing readiness probe. + +### `readinessProbe.initialDelaySeconds` + +| Type | Default | +|------|---------| +| `int` | `5` | + +`readinessProbe.initialDelaySeconds` controls the number of seconds after the container has started before +liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + +### `readinessProbe.periodSeconds` + +| Type | Default | +|------|---------| +| `int` | `5` | + +`readinessProbe.periodSeconds` controls how often (in seconds) to perform the probe. Minimum value is 1. + +### `readinessProbe.failureThreshold` + +| Type | Default | +|------|---------| +| `int` | `12` | + +`readinessProbe.failureThreshold` is the minimum consecutive failures for the probe to be considered failed +after having succeeded. Minimum value is 1. + +### `readinessProbe.successThreshold` + +| Type | Default | +|------|---------| +| `int` | `1` | + +`readinessProbe.successThreshold` is the minimum consecutive successes for the probe to be considered +successful after having failed. Minimum value is 1. + +## `terminationGracePeriodSeconds` + +| Type | Default | +|------|---------| +| `int` | `60` | + +`terminationGracePeriodSeconds` Kubernetes termination grace period +https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution + +This should be greater than 30 seconds as pods are waiting 30 seconds in a preStop hook. diff --git a/docs/pages/reference/helm-reference/helm-reference.mdx b/docs/pages/reference/helm-reference/helm-reference.mdx index 43d6e9e86a83..f8c311f65421 100644 --- a/docs/pages/reference/helm-reference/helm-reference.mdx +++ b/docs/pages/reference/helm-reference/helm-reference.mdx @@ -15,6 +15,9 @@ tags: Kubernetes. - [teleport-operator](teleport-operator.mdx): Deploy the Teleport Kubernetes Operator. +- [teleport-proxy](teleport-proxy.mdx): Deploy the + `teleport` daemon on Kubernetes with preset configurations for Proxy Services + only (no Auth). Generally prefer the teleport-cluster chart above instead. - [teleport-relay](teleport-relay.mdx): Deploy the Teleport Relay Service. - [teleport-access-graph](teleport-access-graph.mdx): Deploy the Teleport Identity Security Access Graph service. diff --git a/docs/pages/reference/helm-reference/teleport-proxy.mdx b/docs/pages/reference/helm-reference/teleport-proxy.mdx new file mode 100644 index 000000000000..f7195bfc9721 --- /dev/null +++ b/docs/pages/reference/helm-reference/teleport-proxy.mdx @@ -0,0 +1,57 @@ +--- +title: teleport-proxy Chart Reference +sidebar_label: teleport-proxy +description: Values that can be set using the teleport-proxy Helm chart +tags: + - reference + - platform-wide +--- + +The `teleport-proxy` Helm chart deploys a Teleport proxy server on Kubernetes. +This chart should be used in conjunction with the +[teleport-cluster](./teleport-cluster.mdx) chart for cases where a Teleport +Proxy is needed without a Teleport Auth Service. This is generally in a different +Kubernetes cluster than ones where [teleport-cluster](./teleport-cluster.mdx) is +deployed. For example, deploying a Teleport Proxy to a region close to Teleport +agents or clients. + +The `teleport-proxy` chart configuration/values generally parallel the +`teleport-cluster` configuration/values. The three required fields are: + +* `clusterName` - Should match `teleport-cluster` +* `authServer` - The address these Proxies can use to connect to Teleport Auth + Service instances. As of this release, Proxies must have direct IP/L4 connectivity to + Teleport Auth Service instances -- routing through an L7 load balancer using SNI is not + yet supported. +* `joinParams` - How these Teleport Proxies can authenticate to the Teleport + Cluster. + +These `authServer` and `joinParams` fields differ from `teleport-cluster` in +that `teleport-cluster` can generate them automatically because its proxies are +running on the same Kubernetes cluster as the Auth Service. See the Full +Reference below for details. + +Source code is available +[on GitHub](https://github.com/gravitational/teleport/tree/branch/v(=teleport.major_version=)/examples/chart/teleport-proxy). + +`teleport-proxy` supports the same modes as `teleport-cluster` for compatibility +and future cloud-specific behavior. However, in this release only the aws mode +needs to provision cloud-specific behavior. + +| `chartMode` | Purpose | Guide | +| - | - | - | +| `standalone` | Runs by relying only on Kubernetes resources. | [Getting Started - Kubernetes](../../installation/self-hosted/helm-deployments/kubernetes-cluster.mdx) | +| `aws` | Adds AWS load balancer annotations to Service. | [Running an HA Teleport cluster using an AWS EKS Cluster](../../installation/self-hosted/helm-deployments/aws.mdx) | +| `gcp` | No custom behavior needed as of this release. | [Running an HA Teleport cluster using a Google Cloud GKE cluster](../../installation/self-hosted/helm-deployments/gcp.mdx) | +| `azure` | No custom behavior needed as of this release. | [Running an HA Teleport cluster using a Microsoft Azure AKS cluster](../../installation/self-hosted/helm-deployments/azure.mdx) | +| `scratch` (v12 and above) | Generates empty Teleport configuration. User must pass their own config. This is discouraged, use `standalone` mode [`teleportConfig`](#teleportconfig) instead. | [Running a Teleport cluster with a custom config](../../installation/self-hosted/helm-deployments/custom.mdx) | + + +The chart is versioned with Teleport. No compatibility guarantees are ensured +between new charts and previous major Teleport versions. It is strongly recommended +to always deploy a Teleport version with the same major version as the Helm chart. + + +## Full reference + +(!docs/pages/includes/helm-reference/zz_generated.teleport-proxy.mdx!) diff --git a/examples/chart/lib/teleport-proxy-lib-test/tests/proxy_deployment_test.yaml b/examples/chart/lib/teleport-proxy-lib-test/tests/proxy_deployment_test.yaml index deab3590741f..2cd130a01003 100644 --- a/examples/chart/lib/teleport-proxy-lib-test/tests/proxy_deployment_test.yaml +++ b/examples/chart/lib/teleport-proxy-lib-test/tests/proxy_deployment_test.yaml @@ -972,6 +972,37 @@ tests: name: proxy-serviceaccount-token readOnly: true + - it: sets projected token audience and ttl when configured + template: deployment.yaml + set: + clusterName: helm-lint + serviceAccountTokenAudience: teleport.example.com + serviceAccountTokenExpirationSeconds: 600 + capabilities: + majorVersion: 1 + minorVersion: 21 + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: proxy-serviceaccount-token + projected: + sources: + - serviceAccountToken: + path: token + audience: teleport.example.com + expirationSeconds: 600 + - configMap: + items: + - key: ca.crt + path: ca.crt + name: kube-root-ca.crt + - downwardAPI: + items: + - path: "namespace" + fieldRef: + fieldPath: metadata.namespace + - it: sets extraLabels on Deployment template: deployment.yaml values: diff --git a/examples/chart/lib/teleport-proxy-lib-test/values.yaml b/examples/chart/lib/teleport-proxy-lib-test/values.yaml index 5d77cbc73818..c0dbd0d1bfb8 100644 --- a/examples/chart/lib/teleport-proxy-lib-test/values.yaml +++ b/examples/chart/lib/teleport-proxy-lib-test/values.yaml @@ -100,6 +100,9 @@ serviceAccount: create: true name: "" +serviceAccountTokenAudience: "" +serviceAccountTokenExpirationSeconds: 0 + rbac: create: true diff --git a/examples/chart/lib/teleport-proxy-lib/README.md b/examples/chart/lib/teleport-proxy-lib/README.md index e93d8bd5b54a..5a9b918665f8 100644 --- a/examples/chart/lib/teleport-proxy-lib/README.md +++ b/examples/chart/lib/teleport-proxy-lib/README.md @@ -130,6 +130,10 @@ Inputs are read from top-level `.Values.*` (plus `.Release`, `.Chart`, - `serviceAccount` — `create` the [ServiceAccount](https://kubernetes.io/docs/concepts/security/service-accounts/) and its `name` (used verbatim — no suffix appended). +- `serviceAccountTokenAudience` — optional audience for the projected + Kubernetes service-account token mounted into the proxy pod. +- `serviceAccountTokenExpirationSeconds` — optional TTL for the projected + Kubernetes service-account token mounted into the proxy pod. - `sshPublicAddr` — [`proxy_service.ssh_public_addr`](https://goteleport.com/docs/reference/deployment/config/#proxy-service). - `teleportAuthService` — sets diff --git a/examples/chart/lib/teleport-proxy-lib/templates/_config.common.tpl b/examples/chart/lib/teleport-proxy-lib/templates/_config.common.tpl index 128786f03fd0..e69846ce3885 100644 --- a/examples/chart/lib/teleport-proxy-lib/templates/_config.common.tpl +++ b/examples/chart/lib/teleport-proxy-lib/templates/_config.common.tpl @@ -1,6 +1,11 @@ {{- define "teleport-proxy-lib.internal.config.common" -}} {{- $proxy := (mustDeepCopy .Values) -}} {{- $logLevel := (coalesce $proxy.logLevel $proxy.log.level "INFO") -}} +{{- /* + TODO(adamcarheden): Remove join_params and auth_server defaults from this lib + chart. Callers should always supply this. The values below only make sense + for teleport-cluster. +*/ -}} version: v3 teleport: join_params: diff --git a/examples/chart/lib/teleport-proxy-lib/templates/_deployment.tpl b/examples/chart/lib/teleport-proxy-lib/templates/_deployment.tpl index 2617240ddbe1..1bac5401a656 100644 --- a/examples/chart/lib/teleport-proxy-lib/templates/_deployment.tpl +++ b/examples/chart/lib/teleport-proxy-lib/templates/_deployment.tpl @@ -289,6 +289,12 @@ spec: sources: - serviceAccountToken: path: token +{{- if $proxy.serviceAccountTokenAudience }} + audience: {{ $proxy.serviceAccountTokenAudience | quote }} +{{- end }} +{{- if $proxy.serviceAccountTokenExpirationSeconds }} + expirationSeconds: {{ $proxy.serviceAccountTokenExpirationSeconds }} +{{- end }} - configMap: items: - key: ca.crt diff --git a/examples/chart/lib/teleport-proxy-lib/templates/_helpers.tpl b/examples/chart/lib/teleport-proxy-lib/templates/_helpers.tpl index decada286aee..866c3ac97fc3 100644 --- a/examples/chart/lib/teleport-proxy-lib/templates/_helpers.tpl +++ b/examples/chart/lib/teleport-proxy-lib/templates/_helpers.tpl @@ -1,6 +1,9 @@ {{- define "teleport-proxy-lib.internal.serviceAccountName" -}} {{- coalesce .Values.serviceAccount.name (printf "%s-proxy" .Release.Name) -}} {{- end -}} +{{- define "teleport-proxy-lib.serviceAccountName" -}} +{{- include "teleport-proxy-lib.internal.serviceAccountName" . -}} +{{- end -}} {{/* Create the name of the service account to use in the proxy config check hook. @@ -20,6 +23,9 @@ so we can use the same SA for deployments and hooks. -hook {{- end -}} {{- end -}} +{{- define "teleport-proxy-lib.hookServiceAccountName" -}} +{{- include "teleport-proxy-lib.internal.hookServiceAccountName" . -}} +{{- end -}} {{/* Proxy all labels */}} {{- define "teleport-proxy-lib.internal.labels" -}} @@ -30,9 +36,17 @@ app.kubernetes.io/version: '{{ include "teleport-util-lib.version" . }}' teleport.dev/majorVersion: '{{ include "teleport-util-lib.majorVersion" . }}' {{- end -}} +{{- define "teleport-proxy-lib.labels" -}} +{{- include "teleport-proxy-lib.internal.labels" . -}} +{{- end -}} + {{/* Proxy selector labels */}} {{- define "teleport-proxy-lib.internal.selectorLabels" -}} app.kubernetes.io/name: '{{ default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}' app.kubernetes.io/instance: '{{ .Release.Name }}' app.kubernetes.io/component: 'proxy' {{- end -}} + +{{- define "teleport-proxy-lib.selectorLabels" -}} +{{- include "teleport-proxy-lib.internal.selectorLabels" . -}} +{{- end -}} diff --git a/examples/chart/lib/teleport-proxy-lib/templates/_validate.tpl b/examples/chart/lib/teleport-proxy-lib/templates/_validate.tpl index 5e58c45dc4fd..90f0ea9b0859 100644 --- a/examples/chart/lib/teleport-proxy-lib/templates/_validate.tpl +++ b/examples/chart/lib/teleport-proxy-lib/templates/_validate.tpl @@ -39,6 +39,7 @@ validate their expected inputs at render time instead. "priorityClassName" "proxyListenerMode" "proxyProtocol" + "serviceAccountTokenAudience" "teleportVersionOverride" ) -}} {{- if and (hasKey $.Values $k) (not (kindIs "string" (index $.Values $k))) -}} @@ -65,7 +66,7 @@ validate their expected inputs at render time instead. "tunnelPublicAddr" ) -}} {{- if and (hasKey $.Values $k) (not (kindIs "slice" (index $.Values $k))) -}} -{{- fail (printf "%s must be a list in teleport-proxy-lib values" $k) -}} +{{- fail (printf "%s must be a list (got %s) in teleport-proxy-lib values" $k (kindOf (index $.Values $k))) -}} {{- end -}} {{- end -}} {{- /* Map inputs: must be a map when set. */ -}} @@ -99,7 +100,7 @@ validate their expected inputs at render time instead. {{- end -}} {{- end -}} {{- /* Numeric inputs: must be a number (int or float) when set. */ -}} -{{- range $k := (list "goMemLimitRatio" "probeTimeoutSeconds" "terminationGracePeriodSeconds") -}} +{{- range $k := (list "goMemLimitRatio" "probeTimeoutSeconds" "serviceAccountTokenExpirationSeconds" "terminationGracePeriodSeconds") -}} {{- if hasKey $.Values $k -}} {{- $v := index $.Values $k -}} {{- if not (or (kindIs "float64" $v) (kindIs "int" $v) (kindIs "int64" $v)) -}} diff --git a/examples/chart/teleport-cluster/templates/psp.yaml b/examples/chart/teleport-cluster/templates/psp.yaml index 8abd2d74e0e9..b20facaee70c 100644 --- a/examples/chart/teleport-cluster/templates/psp.yaml +++ b/examples/chart/teleport-cluster/templates/psp.yaml @@ -64,5 +64,7 @@ roleRef: name: {{ .Release.Name }}-psp subjects: - kind: ServiceAccount + # TODO(adamcarheden): This PSP binding only grants use to .Release.Name, but the chart uses distinct auth/proxy/hook service accounts. + # Fix this like teleport-proxy by binding the actual service-account names used by the chart. name: {{ .Release.Name }} {{- end -}} diff --git a/examples/chart/teleport-proxy/.lint/annotations.yaml b/examples/chart/teleport-proxy/.lint/annotations.yaml new file mode 100644 index 000000000000..b0da74c28327 --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/annotations.yaml @@ -0,0 +1,21 @@ +clusterName: helm-lint +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +annotations: + config: + kubernetes.io/config: "test-annotation" + kubernetes.io/config-different: 2 + deployment: + kubernetes.io/deployment: "test-annotation" + kubernetes.io/deployment-different: 3 + pod: + kubernetes.io/pod: "test-annotation" + kubernetes.io/pod-different: 4 + service: + kubernetes.io/service: "test-annotation" + kubernetes.io/service-different: 5 + serviceAccount: + kubernetes.io/serviceaccount: "test-annotation" + kubernetes.io/serviceaccount-different: 6 diff --git a/examples/chart/teleport-proxy/.lint/cert-manager.yaml b/examples/chart/teleport-proxy/.lint/cert-manager.yaml new file mode 100644 index 000000000000..7930496f0cfa --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/cert-manager.yaml @@ -0,0 +1,19 @@ +clusterName: test-cluster +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +chartMode: aws +aws: + region: us-west-2 + backendTable: test-dynamodb-backend-table + auditLogTable: test-dynamodb-auditlog-table + sessionRecordingBucket: test-s3-session-storage-bucket +highAvailability: + replicaCount: 3 + certManager: + addCommonName: true + enabled: true + issuerGroup: custom.cert-manager.io + issuerName: custom + issuerKind: CustomClusterIssuer diff --git a/examples/chart/teleport-proxy/.lint/ingress.yaml b/examples/chart/teleport-proxy/.lint/ingress.yaml new file mode 100644 index 000000000000..c5a636ccf5da --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/ingress.yaml @@ -0,0 +1,10 @@ +clusterName: teleport.example.com +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +ingress: + enabled: true +proxyListenerMode: multiplex +service: + type: ClusterIP diff --git a/examples/chart/teleport-proxy/.lint/pdb.yaml b/examples/chart/teleport-proxy/.lint/pdb.yaml new file mode 100644 index 000000000000..195ba5a144fc --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/pdb.yaml @@ -0,0 +1,16 @@ +clusterName: helm-lint +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +chartMode: aws +aws: + region: us-west-2 + backendTable: test-dynamodb-backend-table + auditLogTable: test-dynamodb-auditlog-table + sessionRecordingBucket: test-s3-session-storage-bucket +highAvailability: + replicaCount: 3 + podDisruptionBudget: + enabled: true + minAvailable: 2 diff --git a/examples/chart/teleport-proxy/.lint/podmonitor.yaml b/examples/chart/teleport-proxy/.lint/podmonitor.yaml new file mode 100644 index 000000000000..d6b30a3adbe8 --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/podmonitor.yaml @@ -0,0 +1,10 @@ +clusterName: test-kube-cluster-name +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +podMonitor: + enabled: true + additionalLabels: + prometheus: default + interval: 30s diff --git a/examples/chart/teleport-proxy/.lint/service-account.yaml b/examples/chart/teleport-proxy/.lint/service-account.yaml new file mode 100644 index 000000000000..ed79902adf8b --- /dev/null +++ b/examples/chart/teleport-proxy/.lint/service-account.yaml @@ -0,0 +1,8 @@ +clusterName: helm-lint +authServer: teleport-auth.example.com +joinParams: + method: kubernetes + tokenName: teleport-proxy +serviceAccount: + create: true + name: helm-lint diff --git a/examples/chart/teleport-proxy/Chart.yaml b/examples/chart/teleport-proxy/Chart.yaml new file mode 100644 index 000000000000..13f5927aacfb --- /dev/null +++ b/examples/chart/teleport-proxy/Chart.yaml @@ -0,0 +1,14 @@ +name: teleport-proxy +apiVersion: v2 +version: &version "19.0.0-prealpha.2" +appVersion: *version +description: Teleport Proxy Server +icon: https://goteleport.com/static/teleport-symbol-bimi.svg +keywords: + - Teleport + +dependencies: + - name: teleport-proxy-lib + version: *version + - name: teleport-util-lib + version: *version diff --git a/examples/chart/teleport-proxy/README.md b/examples/chart/teleport-proxy/README.md new file mode 100644 index 000000000000..281e9339f6c8 --- /dev/null +++ b/examples/chart/teleport-proxy/README.md @@ -0,0 +1,97 @@ +# Teleport Proxy + +This chart deploys Teleport Proxy instances that join an existing Teleport +cluster. + +Use this chart to add stand-alone Teleport Proxies without also deploying the +Teleport Auth on the same Kubernetes clusters, such as for regional proxies +closer to users. To deploy the Teleport Proxy and Teleport Auth together, use +[teleport-cluster](../teleport-cluster/) instead. + +> [!NOTE] This teleport-proxy chart is for self-hosted installs of Teleport. If +> you're using +> [Teleport Enterprise Cloud](https://goteleport.com/docs/reference/architecture/teleport-cloud-architecture/) +> Teleport Proxy servers are managed for you. + +## Prerequisites + +- An existing Teleport cluster on the same network where this proxy is being + deployed. + + > [!WARNING] Teleport Auth and Proxy servers must all share a **IP space with + > routing and firewall rules** allowing traffic between them. All Teleport + > Proxies must be able to reach all Teleport Auth servers and all other + > Teleport Proxies. + > + > Teleport Agents must **also** be able to reach all proxies, but may go + > through a load balancer, the address of which is configured in + > `proxy_server.public_addr`. Agents therefore need not share a contiguous, + > routable IP space with Proxy and Auth servers so long as all Proxies are + > reachable via the load balancer. + +- A Teleport provision token that allows Proxy joining. + - [Terraform](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/provision_token/) + +## Example Usage + +Create a token + +```bash +$ kubectl \ + --context my-proxy-only-k8s-cluster \ + -n my-teleport-cluster-namespace \ + get --raw /.well-known/openid-configuration | yq .issuer + +https://oidc.my-k8s-cluster.my-cloud-provider.com/not-real/dont-copy + +tctl create <= 1" }} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/examples/chart/teleport-proxy/templates/_teleportConfig.tpl b/examples/chart/teleport-proxy/templates/_teleportConfig.tpl new file mode 100644 index 000000000000..cd2e962d38b5 --- /dev/null +++ b/examples/chart/teleport-proxy/templates/_teleportConfig.tpl @@ -0,0 +1,24 @@ +{{/* + auth_server and join_params are treated special only because they're + required. +*/}} +{{- define "teleport-proxy.generatedTeleportConfig" -}} +version: v3 +teleport: + auth_server: {{ include "teleport-proxy.authServer" . | quote }} + join_params: + method: {{ .Values.joinParams.method | quote }} + token_name: {{ .Values.joinParams.tokenName | quote }} +{{- $azureClientId := dig "joinParams" "azure" "clientId" false .Values.AsMap -}} +{{- if $azureClientId }} + azure: + client_id: {{ $azureClientId | quote }} +{{- end }} +{{- end -}} + +{{- define "teleport-proxy.teleportConfig" -}} +{{- $generated := include "teleport-proxy.generatedTeleportConfig" . | fromYaml -}} +{{- $user := deepCopy (default dict .Values.teleportConfig) -}} +{{- $teleportConfig := mergeOverwrite $generated $user -}} +{{- toYaml $teleportConfig -}} +{{- end -}} diff --git a/examples/chart/teleport-proxy/templates/podmonitor.yaml b/examples/chart/teleport-proxy/templates/podmonitor.yaml new file mode 100644 index 000000000000..b6ae2ec939af --- /dev/null +++ b/examples/chart/teleport-proxy/templates/podmonitor.yaml @@ -0,0 +1,32 @@ +{{- $proxy := mustDeepCopy . -}} +{{- if .Values.podMonitor.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "teleport-proxy-lib.labels" $proxy | nindent 4 }} + {{- with .Values.podMonitor.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + jobLabel: {{ .Release.Name }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: {{- include "teleport-proxy-lib.selectorLabels" $proxy | nindent 6 }} + podMetricsEndpoints: + - port: diag + path: /metrics + {{- with .Values.podMonitor.interval }} + interval: {{ . | quote }} + {{- end }} + podTargetLabels: + - "app.kubernetes.io/name" + - "app.kubernetes.io/instance" + - "app.kubernetes.io/component" + - "app.kubernetes.io/version" + - "teleport.dev/majorVersion" +{{- end }} diff --git a/examples/chart/teleport-proxy/templates/proxy.yaml b/examples/chart/teleport-proxy/templates/proxy.yaml new file mode 100644 index 000000000000..9990cb373057 --- /dev/null +++ b/examples/chart/teleport-proxy/templates/proxy.yaml @@ -0,0 +1,21 @@ +{{/* We explicitly validate top-level values only, not the merged $proxy nor the +generated/overridden teleportConfig. This allows users to inject otherwise +disallowed values, but forces them to acknowledge that they're unsupported by +putting something in the top-level fields we do validate. +*/}} +{{- include "teleport-proxy.validate" . -}} +{{- $proxy := mustDeepCopy . -}} + +{{/* TODO:(adamcarheden): The lib chart shouldn't take auth service or token. The caller should specify it in the teleportConfig. */}} +{{- $_ := set $proxy.Values "teleportAuthService" (include "teleport-proxy.authServer" $proxy) -}} + +{{- $_ := set $proxy.Values "teleportConfig" (include "teleport-proxy.teleportConfig" $proxy | fromYaml) -}} +{{- $_ := set $proxy.Values.serviceAccount "name" (include "teleport-proxy.serviceAccountName" $proxy) -}} +{{- $_ := set $proxy.Values "forceHAReplicas" true -}}{{/* This feature is to support legacy behavior in teleport-cluster. For teleport-proxy we always set replicas*/}} +{{- if eq (coalesce (dig "teleportConfig" "teleport" "join_params" "method" "" $proxy.Values.AsMap) (dig "joinParams" "method" "" $proxy.Values.AsMap)) "kubernetes" -}} +{{- $_ := set $proxy.Values "serviceAccountTokenAudience" $proxy.Values.clusterName -}} +{{- $_ := set $proxy.Values "serviceAccountTokenExpirationSeconds" 600 -}} +{{- end -}} + +{{/* Render all proxy charts */}} +{{- include "teleport-proxy-lib.all" $proxy -}} diff --git a/examples/chart/teleport-proxy/tests/README.md b/examples/chart/teleport-proxy/tests/README.md new file mode 100644 index 000000000000..d81e65917c93 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/README.md @@ -0,0 +1,23 @@ +## Unit tests for Helm charts + +Helm chart unit tests run here using the [helm-unittest](https://github.com/quintush/helm-unittest/) Helm plugin. + +*Note: there are multiple forks for the helm-unittest plugin. +They are not compatible and don't provide the same featureset (e.g. including templates from sub-directories). +Our tests rely on features and bugfixes that are only available on the quintush fork +(which seems to be the most maintained at the time of writing)* + +If you get a snapshot error during your testing, you should verify that your changes intended to alter the output, then run +this command from the root of your Teleport checkout to update the snapshots: + +```bash +make -C build.assets test-helm-update-snapshots +``` + +After this, re-run the tests to make sure everything is fine: + +```bash +make -C build.assets test-helm +``` + +Commit the updated snapshots along with your changes. diff --git a/examples/chart/teleport-proxy/tests/__snapshot__/proxy_wrapper_config_test.yaml.snap b/examples/chart/teleport-proxy/tests/__snapshot__/proxy_wrapper_config_test.yaml.snap new file mode 100644 index 000000000000..194a7c2a9d32 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/__snapshot__/proxy_wrapper_config_test.yaml.snap @@ -0,0 +1,15 @@ +keeps wrapper-generated teleport settings in scratch mode: + 1: | + | + auth_service: + enabled: false + proxy_service: + enabled: true + ssh_service: + enabled: false + teleport: + auth_server: teleport-auth.example.com:3025 + join_params: + method: kubernetes + token_name: teleport-proxy + version: v3 diff --git a/examples/chart/teleport-proxy/tests/ingress_test.yaml b/examples/chart/teleport-proxy/tests/ingress_test.yaml new file mode 100644 index 000000000000..e23ef0019f1d --- /dev/null +++ b/examples/chart/teleport-proxy/tests/ingress_test.yaml @@ -0,0 +1,25 @@ +suite: Proxy Ingress +templates: + - proxy.yaml +tests: + - it: sets extraLabels on Ingress + values: + - ../.lint/ingress.yaml + set: + extraLabels: + ingress: + foo: bar + baz: override + documentIndex: 2 + asserts: + - isKind: + of: Ingress + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/podmonitor_test.yaml b/examples/chart/teleport-proxy/tests/podmonitor_test.yaml new file mode 100644 index 000000000000..46996cc8b2a7 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/podmonitor_test.yaml @@ -0,0 +1,52 @@ +suite: PodMonitor +templates: + - podmonitor.yaml +tests: + - it: does not create a PodMonitor by default + set: + clusterName: test-kube-cluster-name + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + asserts: + - hasDocuments: + count: 0 + + - it: creates a PodMonitor when enabled + set: + clusterName: test-kube-cluster-name + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + podMonitor: + enabled: true + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PodMonitor + + - it: configures scrape interval if provided + set: + clusterName: test-kube-cluster-name + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + podMonitor: + enabled: true + interval: 2m + asserts: + - equal: + path: spec.podMetricsEndpoints[0].interval + value: 2m + + - it: wears additional labels if provided + asserts: + - equal: + path: metadata.labels.prometheus + value: default + values: + - ../.lint/podmonitor.yaml \ No newline at end of file diff --git a/examples/chart/teleport-proxy/tests/predeploy_test.yaml b/examples/chart/teleport-proxy/tests/predeploy_test.yaml new file mode 100644 index 000000000000..6042b5e7a7d4 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/predeploy_test.yaml @@ -0,0 +1,185 @@ +suite: Pre-Deploy Config Test Hooks +templates: + - proxy.yaml +tests: + - it: Is executed as a pre-install and pre-upgrade hook + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + asserts: + - isKind: + of: ConfigMap + documentIndex: 2 + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + documentIndex: 2 + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + documentIndex: 2 + - isKind: + of: Job + documentIndex: 3 + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + documentIndex: 3 + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + documentIndex: 3 + - isKind: + of: ServiceAccount + documentIndex: 4 + - equal: + path: metadata.name + value: RELEASE-NAME-hook + documentIndex: 4 + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + documentIndex: 4 + - it: should set extraLabels on proxy predeploy job when set in values + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + extraLabels: + job: + foo: bar + baz: override + documentIndex: 3 + asserts: + - isKind: + of: Job + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override + + - it: should set extraLabels.jobPod on proxy predeploy job when set in values + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + extraLabels: + jobPod: + foo: bar + baz: override + documentIndex: 3 + asserts: + - isKind: + of: Job + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: spec.template.metadata.labels.foo + value: bar + - equal: + path: spec.template.metadata.labels.baz + value: override + + - it: should set extraLabels on proxy predeploy config when set in values + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + extraLabels: + config: + foo: bar + baz: override + documentIndex: 2 + asserts: + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override + + - it: should use default serviceAccount name suffixed with -hook for proxy predeploy job SA when not set in values and we're creating SAs + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + documentIndex: 3 + asserts: + - isKind: + of: Job + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: spec.template.spec.serviceAccountName + value: RELEASE-NAME-hook + + - it: should use serviceAccount.name suffixed with -hook for proxy predeploy job SA when set in values and we're creating SAs + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + serviceAccount: + name: helm-test-sa + documentIndex: 3 + asserts: + - isKind: + of: Job + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: spec.template.spec.serviceAccountName + value: helm-test-sa-hook + + - it: should use serviceAccount.name for proxy predeploy job SA when set in values and we're not creating SAs + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + serviceAccount: + name: helm-test-sa + create: false + documentIndex: 3 + asserts: + - isKind: + of: Job + - equal: + path: metadata.name + value: RELEASE-NAME-proxy-test + - equal: + path: spec.template.spec.serviceAccountName + value: helm-test-sa diff --git a/examples/chart/teleport-proxy/tests/proxy_certificate_test.yaml b/examples/chart/teleport-proxy/tests/proxy_certificate_test.yaml new file mode 100644 index 000000000000..0adebf67be5f --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_certificate_test.yaml @@ -0,0 +1,25 @@ +suite: Proxy Certificate +templates: + - proxy.yaml +tests: + - it: sets extraLabels on Certificate Secret + values: + - ../.lint/cert-manager.yaml + set: + extraLabels: + certSecret: + foo: bar + baz: override + documentIndex: 0 + asserts: + - isKind: + of: Certificate + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: spec.secretTemplate.labels.foo + value: bar + - equal: + path: spec.secretTemplate.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/proxy_config_test.yaml b/examples/chart/teleport-proxy/tests/proxy_config_test.yaml new file mode 100644 index 000000000000..c8a9258031a6 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_config_test.yaml @@ -0,0 +1,25 @@ +suite: ConfigMap +templates: + - proxy.yaml +tests: + - it: sets extraLabels on Configmap + values: + - ../.lint/annotations.yaml + set: + extraLabels: + config: + foo: bar + baz: override + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/proxy_deployment_test.yaml b/examples/chart/teleport-proxy/tests/proxy_deployment_test.yaml new file mode 100644 index 000000000000..5094051a4ef3 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_deployment_test.yaml @@ -0,0 +1,254 @@ +suite: Proxy Deployment +templates: + - proxy.yaml +tests: + - it: sets extraLabels on Deployment + template: proxy.yaml + values: + - ../.lint/annotations.yaml + set: + extraLabels: + deployment: + foo: bar + baz: extra + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: extra + + - it: sets projected token audience and ttl for kubernetes join + template: proxy.yaml + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + capabilities: + majorVersion: 1 + minorVersion: 21 + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - contains: + path: spec.template.spec.volumes + content: + name: proxy-serviceaccount-token + projected: + sources: + - serviceAccountToken: + path: token + audience: teleport.example.com + expirationSeconds: 600 + - configMap: + items: + - key: ca.crt + path: ca.crt + name: kube-root-ca.crt + - downwardAPI: + items: + - path: "namespace" + fieldRef: + fieldPath: metadata.namespace + + - it: derives projected token audience and ttl from overridden teleport join method + template: proxy.yaml + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: iam + tokenName: placeholder + teleportConfig: + teleport: + join_params: + method: kubernetes + token_name: real-token + capabilities: + majorVersion: 1 + minorVersion: 21 + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - matchRegex: + path: spec.template.spec.volumes[0].projected.sources[0].serviceAccountToken.audience + pattern: "^teleport\\.example\\.com$" + - equal: + path: spec.template.spec.volumes[0].projected.sources[0].serviceAccountToken.expirationSeconds + value: 600 + + - it: sets extraLabels on Deployment Pods + template: proxy.yaml + values: + - ../.lint/annotations.yaml + set: + extraLabels: + pod: + foo: bar + baz: override + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: spec.template.metadata.labels.foo + value: bar + - equal: + path: spec.template.metadata.labels.baz + value: override + + - it: sets readinessProbe values on Deployment Pods + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + readinessProbe: + initialDelaySeconds: 9 + periodSeconds: 10 + failureThreshold: 11 + successThreshold: 12 + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds + value: 9 + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: spec.template.spec.containers[0].readinessProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].readinessProbe.failureThreshold + value: 11 + - equal: + path: spec.template.spec.containers[0].readinessProbe.successThreshold + value: 12 + + - it: does not set GOMEMLIMIT if ratio is 0 + template: proxy.yaml + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + # we set an extra env so contrainers[0].env always exists + # this makes testing easier + extraEnv: + - name: FOO + value: bar + resources: + limits: + memory: "10.5G" + goMemLimitRatio: 0 + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - notContains: + path: spec.template.spec.containers[0].env + content: + name: "GOMEMLIMIT" + value: "9450000000" + + - it: should honor user-provided replicaCount + template: proxy.yaml + set: + clusterName: helm-lint.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + highAvailability: + replicaCount: 3 + tls: + existingSecretName: my-certs + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: spec.replicas + value: 3 + + - it: should default to multiple replicas when replicaCount is not set but deployment is not replicable + template: proxy.yaml + set: + clusterName: helm-lint.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: spec.replicas + value: 1 + + - it: should default to multiple replicas when replicaCount is not set but deployment is replicable + template: proxy.yaml + set: + clusterName: helm-lint.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + tls: + existingSecretName: my-certs + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: spec.replicas + value: 2 + + - it: should fail if replicaCount is set but not a postitive integer + template: proxy.yaml + set: + clusterName: helm-lint.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + tls: + existingSecretName: my-certs + highAvailability: + replicaCount: 0 + asserts: + - failedTemplate: + errorMessage: "highAvailability.replicaCount must be >= 1" diff --git a/examples/chart/teleport-proxy/tests/proxy_pdb_test.yaml b/examples/chart/teleport-proxy/tests/proxy_pdb_test.yaml new file mode 100644 index 000000000000..1df057b0a8c2 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_pdb_test.yaml @@ -0,0 +1,25 @@ +suite: Proxy PodDisruptionBudget +templates: + - proxy.yaml +tests: + - it: sets extraLabels on PodDisruptionBudget + values: + - ../.lint/pdb.yaml + set: + extraLabels: + podDisruptionBudget: + foo: bar + baz: override + documentIndex: 2 + asserts: + - isKind: + of: PodDisruptionBudget + - equal: + path: metadata.name + value: RELEASE-NAME-proxy + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/proxy_service_test.yaml b/examples/chart/teleport-proxy/tests/proxy_service_test.yaml new file mode 100644 index 000000000000..0cd4661a3319 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_service_test.yaml @@ -0,0 +1,130 @@ +suite: Proxy Service +templates: + - proxy.yaml +tests: + - it: uses a ClusterIP when service.type=ClusterIP + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + service: + type: ClusterIP + documentIndex: 5 + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: spec.type + value: ClusterIP + + - it: fails to deploy when ingress.enabled=true and service.type is set to LoadBalancer (default) + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + proxyListenerMode: multiplex + ingress: + enabled: true + service: + type: LoadBalancer + asserts: + - failedTemplate: + errorMessage: "service.type must not be LoadBalancer when using an ingress - any load balancer should be provisioned by your ingress controller. Set service.type=ClusterIP instead" + + - it: uses a ClusterIP when ingress.enabled=true and service.type=ClusterIP + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + proxyListenerMode: multiplex + ingress: + enabled: true + service: + type: ClusterIP + documentIndex: 6 + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: spec.type + value: ClusterIP + + - it: uses a NodePort when ingress.enabled=true and service.type=NodePort + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + proxyListenerMode: multiplex + ingress: + enabled: true + service: + type: NodePort + documentIndex: 6 + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: spec.type + value: NodePort + + - it: uses a NodePort when ingress.enabled=true and service.type is overridden + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + proxyListenerMode: multiplex + ingress: + enabled: true + service: + type: NodePort + documentIndex: 6 + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: spec.type + value: NodePort + + - it: sets extraLabels on Service + values: + - ../.lint/annotations.yaml + set: + extraLabels: + service: + foo: bar + baz: override + documentIndex: 5 + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/proxy_serviceaccount_name_test.yaml b/examples/chart/teleport-proxy/tests/proxy_serviceaccount_name_test.yaml new file mode 100644 index 000000000000..72796bd327c9 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_serviceaccount_name_test.yaml @@ -0,0 +1,14 @@ +suite: Proxy ServiceAccount Name +templates: + - proxy.yaml +tests: + - it: uses serviceAccount.name verbatim when set + values: + - ../.lint/service-account.yaml + documentIndex: 6 + asserts: + - isKind: + of: ServiceAccount + - equal: + path: metadata.name + value: helm-lint diff --git a/examples/chart/teleport-proxy/tests/proxy_serviceaccount_test.yaml b/examples/chart/teleport-proxy/tests/proxy_serviceaccount_test.yaml new file mode 100644 index 000000000000..b1aa5fb185f3 --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_serviceaccount_test.yaml @@ -0,0 +1,25 @@ +suite: Proxy ServiceAccount +templates: + - proxy.yaml +tests: + - it: sets extraLabels on ServiceAccount + values: + - ../.lint/annotations.yaml + set: + extraLabels: + serviceAccount: + foo: bar + baz: override + documentIndex: 6 + asserts: + - isKind: + of: ServiceAccount + - equal: + path: metadata.name + value: RELEASE-NAME + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.baz + value: override diff --git a/examples/chart/teleport-proxy/tests/proxy_wrapper_config_test.yaml b/examples/chart/teleport-proxy/tests/proxy_wrapper_config_test.yaml new file mode 100644 index 000000000000..3c9d7c81120c --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_wrapper_config_test.yaml @@ -0,0 +1,228 @@ +suite: Proxy Wrapper Config +templates: + - proxy.yaml +tests: + - it: appends :3025 to authServer when no port is set + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: teleport-auth\.example\.com:3025' + + - it: preserves authServer when an explicit port is set + set: + clusterName: teleport.example.com + authServer: auth.example.com:9999 + joinParams: + method: kubernetes + tokenName: teleport-proxy + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: auth\.example\.com:9999' + + - it: lets proxy.teleportConfig override generated teleport.authServer + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + teleportConfig: + teleport: + auth_server: auth.example.com:9999 + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: auth\.example\.com:9999' + - notMatchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: teleport-auth\.example\.com:3025' + + - it: writes join_params into the generated teleport config + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: iam + tokenName: proxy-join + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'join_params:\n\s+method: iam\n\s+token_name: proxy-join' + + - it: lets proxy.teleportConfig override generated teleport.joinParams + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: generated-token + teleportConfig: + teleport: + join_params: + method: iam + token_name: overridden-token + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'join_params:\n\s+method: iam\n\s+token_name: overridden-token' + - notMatchRegex: + path: data["teleport.yaml"] + pattern: 'join_params:\n\s+method: kubernetes\n\s+token_name: generated-token' + + - it: preserves generated defaults when proxy.teleportConfig overrides only one leaf + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + teleportConfig: + proxy_service: + public_addr: custom.example.com:443 + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'public_addr: custom\.example\.com:443' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: teleport-auth\.example\.com:3025' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'method: kubernetes' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'token_name: teleport-proxy' + + - it: fails when join_params.method is empty + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: "" + tokenName: teleport-proxy + asserts: + - failedTemplate: + errorMessage: "joinParams.method is required" + + - it: fails when join_params.tokenName is empty + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + join_params: + method: kubernetes + tokenName: "" + asserts: + - failedTemplate: + errorMessage: "joinParams.tokenName is required" + + - it: fails when joinParams.method is 'token' + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: "token" + tokenName: "my-token" + asserts: + - failedTemplate: + errorMessage: "Secret join methods ('token' and 'bound_keypair') aren't supported by this Helm chart. Please consider the more secure delegated join methods. See https://goteleport.com/docs/reference/deployment/join-methods/" + + - it: fails when joinParams.method is 'bound_keypair' + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: "bound_keypair" + tokenName: "my-token" + asserts: + - failedTemplate: + errorMessage: "Secret join methods ('token' and 'bound_keypair') aren't supported by this Helm chart. Please consider the more secure delegated join methods. See https://goteleport.com/docs/reference/deployment/join-methods/" + + - it: keeps wrapper-generated teleport settings in scratch mode + set: + chartMode: scratch + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchSnapshot: + path: data["teleport.yaml"] + - matchRegex: + path: data["teleport.yaml"] + pattern: 'version: v3' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_service:\n\s+enabled: false' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'proxy_service:\n\s+enabled: true' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'ssh_service:\n\s+enabled: false' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'auth_server: teleport-auth\.example\.com:3025' + - matchRegex: + path: data["teleport.yaml"] + pattern: 'token_name: teleport-proxy' + + - it: Sets user's Azure client ID + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: azure + tokenName: teleport-proxy + azure: + clientId: "abc-123" + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - matchRegex: + path: data["teleport.yaml"] + pattern: 'azure:\s*\n\s*client_id:\s*abc-123' + + - it: doesn't set join_params.azure if the user doesn't supply a value for clientId + set: + clusterName: teleport.example.com + authServer: teleport-auth.example.com + joinParams: + method: azure + tokenName: teleport-proxy + documentIndex: 0 + asserts: + - isKind: + of: ConfigMap + - notMatchRegex: + path: data["teleport.yaml"] + pattern: "azure:" diff --git a/examples/chart/teleport-proxy/tests/proxy_wrapper_serviceaccount_test.yaml b/examples/chart/teleport-proxy/tests/proxy_wrapper_serviceaccount_test.yaml new file mode 100644 index 000000000000..4195952368ba --- /dev/null +++ b/examples/chart/teleport-proxy/tests/proxy_wrapper_serviceaccount_test.yaml @@ -0,0 +1,38 @@ +suite: Proxy Wrapper ServiceAccount +templates: + - proxy.yaml +tests: + - it: does not create a ServiceAccount resource when serviceAccount.create is false + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + serviceAccount: + create: false + name: helm-test-sa + asserts: + - containsDocument: + kind: ServiceAccount + apiVersion: v1 + name: helm-test-sa + not: true + + - it: uses the verbatim serviceAccount name in the Deployment + set: + clusterName: helm-lint + authServer: teleport-auth.example.com + joinParams: + method: kubernetes + tokenName: teleport-proxy + serviceAccount: + create: false + name: helm-test-sa + documentIndex: 1 + asserts: + - isKind: + of: Deployment + - equal: + path: spec.template.spec.serviceAccountName + value: helm-test-sa diff --git a/examples/chart/teleport-proxy/values.schema.json b/examples/chart/teleport-proxy/values.schema.json new file mode 100644 index 000000000000..dc3e32625a18 --- /dev/null +++ b/examples/chart/teleport-proxy/values.schema.json @@ -0,0 +1,697 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "required": [ + "clusterName", + "authServer", + "joinParams", + "enterprise", + "chartMode", + "validateConfigOnDeploy", + "highAvailability", + "podMonitor", + "tls", + "image", + "enterpriseImage", + "log", + "affinity", + "nodeSelector", + "annotations", + "extraContainers", + "extraVolumes", + "extraVolumeMounts", + "imagePullPolicy", + "initContainers", + "resources", + "tolerations", + "probeTimeoutSeconds" + ], + "properties": { + "clusterName": { + "$id": "#/properties/clusterName", + "type": "string", + "default": "" + }, + "authServer": { + "$id": "#/properties/authServer", + "type": "string", + "default": "" + }, + "joinParams": { + "$id": "#/properties/joinParams", + "type": "object", + "required": [ + "method", + "tokenName" + ], + "properties": { + "method": { + "$id": "#/properties/joinParams/method", + "type": "string", + "default": "kubernetes" + }, + "tokenName": { + "$id": "#/properties/joinParams/tokenName", + "type": "string", + "default": "" + }, + "azure": { + "$id": "#/properties/joinParams/azure", + "type": "object", + "properties": { + "clientId": { + "$id": "#/properties/joinParams/azure/clientId", + "type": "string" + } + } + } + } + }, + "nameOverride": { + "$id": "#/properties/nameOverride", + "type": "string", + "default": "" + }, + "proxyProtocol": { + "$id": "#/properties/proxyProtocol", + "type": "string", + "default": "", + "enum": [ + "off", + "on", + "" + ] + }, + "teleportConfig": { + "$id": "#/properties/teleportConfig", + "type": "object" + }, + "podMonitor": { + "$id": "#/properties/podMonitor", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$id": "#/properties/podMonitor/enabled", + "type": "boolean", + "default": false + }, + "additionalLabels": { + "$id": "#/properties/podMonitor/additionalLabels", + "type": "object", + "default": { + "prometheus": "default" + }, + "additionalProperties": { + "type": ["null","boolean","integer","number","string","array","object"] + } + }, + "interval": { + "$id": "#/properties/podMonitor/interval", + "type": "string", + "default": "30s" + } + } + }, + "proxyListenerMode": { + "$id": "#/properties/proxyListenerMode", + "type": "string", + "default": "separate" + }, + "separatePostgresListener": { + "$id": "#/properties/separatePostgresListener", + "type": "boolean", + "default": false + }, + "separateMongoListener": { + "$id": "#/properties/separateMongoListener", + "type": "boolean", + "default": false + }, + "publicAddr": { + "$id": "#/properties/publicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "kubePublicAddr": { + "$id": "#/properties/kubePublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "mongoPublicAddr": { + "$id": "#/properties/mongoPublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "mysqlPublicAddr": { + "$id": "#/properties/mysqlPublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "postgresPublicAddr": { + "$id": "#/properties/postgresPublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "sshPublicAddr": { + "$id": "#/properties/sshPublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "tunnelPublicAddr": { + "$id": "#/properties/tunnelPublicAddr", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "teleportVersionOverride": { + "$id": "#/properties/teleportVersionOverride", + "type": "string", + "default": "" + }, + "enterprise": { + "$id": "#/properties/enterprise", + "type": "boolean", + "default": false + }, + "chartMode": { + "$id": "#/properties/chartMode", + "type": "string", + "enum": [ + "standalone", + "aws", + "azure", + "gcp", + "scratch" + ], + "default": "standalone" + }, + "validateConfigOnDeploy": { + "$id": "#/properties/validateConfigOnDeploy", + "type": "boolean", + "default": true + }, + "standalone": { + "$id": "#/properties/standalone", + "type": "object", + "required": [ + "volumeSize" + ], + "properties": { + "existingClaimName": { + "$id": "#/properties/standalone/properties/existingClaimName", + "type": "string", + "default": "" + }, + "volumeSize": { + "$id": "#/properties/standalone/properties/volumeSize", + "type": "string", + "default": "" + } + } + }, + "highAvailability": { + "$id": "#/properties/highAvailability", + "type": "object", + "required": [ + "replicaCount", + "requireAntiAffinity", + "certManager", + "minReadySeconds", + "podDisruptionBudget" + ], + "properties": { + "replicaCount": { + "$id": "#/properties/highAvailability/properties/replicaCount", + "type": "integer", + "default": 2 + }, + "requireAntiAffinity": { + "$id": "#/properties/highAvailability/properties/requireAntiAffinity", + "type": "boolean", + "default": false + }, + "certManager": { + "$id": "#/properties/highAvailability/properties/certManager", + "type": "object", + "required": [ + "enabled", + "issuerName", + "issuerKind", + "issuerGroup" + ], + "properties": { + "addCommonName": { + "$id": "#/properties/highAvailability/properties/certManager/properties/addCommonName", + "type": "boolean", + "default": false + }, + "addPublicAddrs": { + "$id": "#/properties/highAvailability/properties/certManager/properties/addPublicAddrs", + "type": "boolean", + "default": false + }, + "enabled": { + "$id": "#/properties/highAvailability/properties/certManager/properties/enabled", + "type": "boolean", + "default": false + }, + "issuerName": { + "$id": "#/properties/highAvailability/properties/certManager/properties/issuerName", + "type": "string", + "default": "" + }, + "issuerKind": { + "$id": "#/properties/highAvailability/properties/certManager/properties/issuerKind", + "type": "string", + "default": "Issuer" + }, + "issuerGroup": { + "$id": "#/properties/highAvailability/properties/certManager/properties/issuerGroup", + "type": "string", + "default": "cert-manager.io" + } + } + }, + "minReadySeconds": { + "$id": "#/properties/highAvailability/properties/minReadySeconds", + "type": "integer", + "default": 15 + }, + "podDisruptionBudget": { + "$id": "#/properties/highAvailability/properties/podDisruptionBudget", + "type": "object", + "required": [ + "enabled", + "minAvailable" + ], + "properties": { + "enabled": { + "$id": "#/properties/highAvailability/properties/podDisruptionBudget/properties/enabled", + "type": "boolean", + "default": false + }, + "minAvailable": { + "$id": "#/properties/highAvailability/properties/podDisruptionBudget/properties/minAvailable", + "type": "integer", + "default": 1 + } + } + } + } + }, + "tls": { + "$id": "#/properties/tls", + "type": "object", + "required": [ + "existingSecretName", + "existingCASecretName", + "existingCASecretKeyName" + ], + "properties": { + "existingSecretName": { + "$id": "#/properties/tls/properties/existingSecretName", + "type": "string", + "default": "" + }, + "existingCASecretName": { + "$id": "#/properties/tls/properties/existingCASecretName", + "type": "string", + "default": "" + }, + "existingCASecretKeyName": { + "$id": "#/properties/tls/properties/existingCASecretKeyName", + "type": "string", + "default": "ca.pem" + } + } + }, + "image": { + "$id": "#/properties/image", + "type": "string", + "default": "public.ecr.aws/gravitational/teleport-distroless" + }, + "enterpriseImage": { + "$id": "#/properties/enterpriseImage", + "type": "string", + "default": "public.ecr.aws/gravitational/teleport-ent-distroless" + }, + "imagePullSecrets": { + "$id": "#/properties/imagePullSecrets", + "type": "array", + "default": [] + }, + "logLevel": { + "$id": "#/properties/logLevel", + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARN", + "WARNING", + "ERROR" + ], + "default": "INFO" + }, + "log": { + "$id": "#/properties/log", + "type": "object", + "required": [ + "output", + "format", + "extraFields" + ], + "properties": { + "level": { + "$id": "#/properties/log/properties/level", + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARN", + "WARNING", + "ERROR" + ], + "default": "INFO" + }, + "output": { + "$id": "#/properties/log/properties/output", + "type": "string", + "default": "stderr" + }, + "format": { + "$id": "#/properties/log/properties/format", + "type": "string", + "default": "text" + }, + "extraFields": { + "$id": "#/properties/log/properties/extraFields", + "type": "array", + "default": ["timestamp","level","component","caller"] + } + } + }, + "affinity": { + "$id": "#/properties/affinity", + "type": "object", + "default": {} + }, + "nodeSelector": { + "$id": "#/properties/nodeSelector", + "type": "object", + "default": {} + }, + "annotations": { + "$id": "#/properties/annotations", + "type": "object", + "required": [ + "config", + "deployment", + "pod", + "service", + "serviceAccount", + "certSecret", + "ingress" + ], + "properties": { + "config": { + "$id": "#/properties/annotations/properties/config", + "type": "object", + "default": {} + }, + "deployment": { + "$id": "#/properties/annotations/properties/deployment", + "type": "object", + "default": {} + }, + "pod": { + "$id": "#/properties/annotations/properties/pod", + "type": "object", + "default": {} + }, + "service": { + "$id": "#/properties/annotations/properties/service", + "type": "object", + "default": {} + }, + "serviceAccount": { + "$id": "#/properties/annotations/properties/serviceAccount", + "type": "object", + "default": {} + }, + "certSecret": { + "$id": "#/properties/annotations/properties/certSecret", + "type": "object", + "default": {} + }, + "ingress": { + "$id": "#/properties/annotations/properties/ingress", + "type": "object", + "default": {} + } + } + }, + "service": { + "$id": "#/properties/service", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "$id": "#/properties/service/type", + "type": "string", + "default": "LoadBalancer" + }, + "spec": { + "$id": "#/properties/service/spec", + "type": "object", + "default": {} + } + } + }, + "ingress": { + "$id": "#/properties/ingress", + "type": "object", + "required": [ + "enabled", + "suppressAutomaticWildcards", + "useExisting" + ], + "properties": { + "enabled": { + "$id": "#/properties/ingress/enabled", + "type": "boolean", + "default": false + }, + "suppressAutomaticWildcards": { + "$id": "#/properties/ingress/suppressAutomaticWildcards", + "type": "boolean", + "default": false + }, + "useExisting": { + "$id": "#/properties/ingress/useExisting", + "type": "boolean", + "default": false + }, + "spec": { + "$id": "#/properties/ingress/spec", + "type": "object", + "default": {} + } + } + }, + "serviceAccount": { + "$id": "#/properties/serviceAccount", + "type": "object", + "required": [], + "properties": { + "name": { + "$id": "#/properties/serviceAccount/name", + "type": "string", + "default": "" + }, + "create": { + "$id": "#/properties/serviceAccount/create", + "type": "boolean", + "default": true + } + } + }, + "rbac": { + "$id": "#/properties/rbac", + "type": "object", + "required": [], + "properties": { + "create": { + "$id": "#/properties/rbac/create", + "type": "boolean", + "default": true + } + } + }, + "extraArgs": { + "$id": "#/properties/extraArgs", + "type": "array", + "default": [] + }, + "extraEnv": { + "$id": "#/properties/extraEnv", + "type": "array", + "default": [] + }, + "extraContainers": { + "$id": "#/properties/extraContainers", + "type": "array", + "default": [] + }, + "extraVolumes": { + "$id": "#/properties/extraVolumes", + "type": "array", + "default": [] + }, + "extraVolumeMounts": { + "$id": "#/properties/extraVolumeMounts", + "type": "array", + "default": [] + }, + "extraLabels": { + "$id": "#/properties/extraLabels", + "type": "object", + "default": {} + }, + "imagePullPolicy": { + "$id": "#/properties/imagePullPolicy", + "type": "string", + "enum": [ + "Never", + "IfNotPresent", + "Always" + ], + "default": "IfNotPresent" + }, + "initContainers": { + "$id": "#/properties/initContainers", + "type": "array", + "default": [] + }, + "postStart": { + "$id": "#/properties/postStart", + "type": "object", + "required": [ + "command" + ], + "properties": { + "command": { + "$id": "#/properties/postStart/command", + "type": "array", + "default": [] + } + } + }, + "resources": { + "$id": "#/properties/resources", + "type": "object", + "default": {} + }, + "jobResources": { + "$id": "#/properties/jobResources", + "type": "object", + "default": {} + }, + "goMemLimitRatio": { + "$id": "#/properties/goMemLimitRatio", + "type": "number", + "default": 0.9 + }, + "podSecurityContext": { + "$id": "#/properties/podSecurityContext", + "type": "object", + "default": {} + }, + "securityContext": { + "$id": "#/properties/securityContext", + "type": "object", + "default": {} + }, + "tolerations": { + "$id": "#/properties/tolerations", + "type": "array", + "default": [] + }, + "disableTopologySpreadConstraints": { + "$id": "#/properties/disableTopologySpreadConstraints", + "type": "boolean", + "default": false + }, + "priorityClassName": { + "$id": "#/properties/priorityClassName", + "type": "string", + "default": "" + }, + "probeTimeoutSeconds": { + "$id": "#/properties/probeTimeoutSeconds", + "type": "integer", + "default": 5 + }, + "terminationGracePeriodSeconds": { + "$id": "#/properties/terminationGracePeriodSeconds", + "type": "integer", + "default": 60 + }, + "topologySpreadConstraints": { + "$id": "#/properties/topologySpreadConstraints", + "type": "array", + "default": [] + }, + "readinessProbe": { + "$id": "#/properties/readinessProbe", + "type": "object", + "default": {}, + "properties": { + "initialDelaySeconds": { + "$id": "#/properties/readinessProbe/properties/initialDelaySeconds", + "type": "integer", + "default": 5 + }, + "periodSeconds": { + "$id": "#/properties/readinessProbe/properties/periodSeconds", + "type": "integer", + "default": 5 + }, + "failureThreshold": { + "$id": "#/properties/readinessProbe/properties/failureThreshold", + "type": "integer", + "default": 12 + }, + "successThreshold": { + "$id": "#/properties/readinessProbe/properties/successThreshold", + "type": "integer", + "default": 1 + } + } + } + } +} diff --git a/examples/chart/teleport-proxy/values.yaml b/examples/chart/teleport-proxy/values.yaml new file mode 100644 index 000000000000..d62030208097 --- /dev/null +++ b/examples/chart/teleport-proxy/values.yaml @@ -0,0 +1,714 @@ +################################################## +# This chart deploys the Teleport proxy only. It should be used only in complex +# setups. Please use the [teleport-cluster chart](../../reference/helm-reference/teleport-cluster.mdx) +# unless a Teleport engineer recommends you to do otherwise. +# +# When deploying standalone Teleport proxies with this chart and using proxy +# peering, you must guarantee that every proxy pod can dial every other proxy +# pod. The different Kube clusters pod subnets must be peered via VPC peering, +# service-mesh or simliar. +################################################## + + + +################################################## +# Values that must always be provided by the user. +################################################## + +# clusterName(string) -- MUST match the clusterName used by the Teleport Auth +# service ([clusterName in the teleport-cluster chart](../../reference/helm-reference/teleport-cluster.mdx#clustername) +# / [auth_service.cluster_name in the Auth Service config](../../reference/deployment/config.mdx#auth-service)) +# It controls the name this proxy will use to refer to the Teleport cluster, +# along with the externally-facing public address to use to access it. In most +# setups this must be a fully-qualified domain name (e.g. `teleport.example.com`) +# as this value is used as the cluster's public address by default. +# +# > [!NOTE] +# > When using a fully qualified domain name as your `clusterName`, you will also +# > need to configure the DNS provider for this domain to point to the external +# > load balancer address of your Teleport cluster. If you're using this proxy-only +# > chart, this typically means adding the external IP of this proxy's load-balancer +# > to your geography-aware DNS service. We do not recommend using a load balancer +# > with an Anycast VIP because Teleport clients maintain long-live persistent +# > connections that may be disrupted during Anycast routing changes. +clusterName: "" + +# authServer(string) -- the address this proxy should use to contact the Auth +# service. This should be an \:\ for an internal network path to the +# Auth Service. Port defaults to 3025 if not specified. +# If running on the same kube cluster as the Auth Service, consider using the +# teleport-cluster chart instead. +authServer: "" + +# joinParams(object) -- are parameters for joining the Teleport Cluster. +# +# To use this chart, you must pre-provision a Teleport Provision Token this +# proxy can use to authenticate to the cluster. Configure the token name and +# other parameters here. Only delegated join methods that don't require secrets +# are supported by this chart. This **excludes** the `token` and `bound_keypair` +# join methods. +# +# See also: +# * [Join Methods and Tokens](../../reference/deployment/join-methods.mdx) +# * [Teleport Config Reference](../../reference/deployment/config.mdx#instance-wide-settings). +# +# To create the token, see docs ([Terraform](../../reference/infrastructure-as-code/terraform-provider/resources/provision_token.mdx) +# or [tctl](../../reference/cli/tctl.mdx#tctl-create)): +# +# ```bash +# issuer="$(kubectl get --raw /.well-known/openid-configuration | yq -r .issuer)" +# tctl create < [!WARNING] +# > If you set `publicAddr` for users to access the cluster under a domain +# > different than clusterName, you must manually set the webauthn Relying +# > Party Identifier (RP ID) - https://www.w3.org/TR/webauthn-2/#relying-party-identifier +# > If you don't, RP ID will default to `clusterName` and users will fail +# > to register second factors. +# > +# > You can do this by setting `auth_service.authentication.webauthn.rp_id` in +# > the config for your Teleport Auth Service. +# > +# > RP ID must be both a valid domain, and part of the full domain users are +# > connecting to. For example, if users are accessing the cluster with the domain +# > "teleport.example.com", RP ID can be "teleport.example.com" or "example.com". +# +# > Changing the RP ID will invalidate all already registered webauthn second factors. +# +# Public cluster addresses, including port (e.g. teleport.example.com:443) +# Defaults to `clusterName` on port 443. +publicAddr: [] +# Public cluster kube addresses, including port. Defaults to `publicAddr` on port 3026. +# Only used when `proxyListenerMode` is not 'multiplex'. +kubePublicAddr: [] +# Public cluster mongo listener addresses, including port. Defaults to `publicAddr` on port 27017. +# Only used when `proxyListenerMode` is not 'multiplex' and `separateMongoListener` is true. +mongoPublicAddr: [] +# Public cluster MySQL addresses, including port. Defaults to `publicAddr` on port 3036. +# Only used when `proxyListenerMode` is not 'multiplex'. +mysqlPublicAddr: [] +# Public cluster postgres listener addresses, including port. Defaults to `publicAddr` on port 5432. +# Only used when `proxyListenerMode` is not 'multiplex' and `separatePostgresListener` is true. +postgresPublicAddr: [] +# Public cluster SSH addresses, including port. Defaults to `publicAddr` on port 3023. +# Only used when `proxyListenerMode` is not 'multiplex'. +sshPublicAddr: [] +# Public cluster tunnel SSH addresses, including port. Defaults to `publicAddr` on port 3024. +# Only used when `proxyListenerMode` is not 'multiplex'. +tunnelPublicAddr: [] + +# enterprise(bool) -- uses the enterprise image. +# You will need to download your Enterprise license from the Teleport dashboard and create a secret to use this: +# +# ```bash +# kubectl -n ${TELEPORT_NAMESPACE?} create secret generic license --from-file=/path/to/downloaded/license.pem +# ``` +# +# > [!NOTE] +# > The teleport proxy service +# > (`proxy_service.enabled` in [teleport.yaml](../../reference/deployment/config.mdx#proxy-service), +# > as deployed by this chart), does not require the license file even when +# > running an enterprise build. Your license file only needs to be deployed on +# > the auth service with the +# > [teleport-cluster chart](../../reference/helm-reference/teleport-cluster.mdx). +enterprise: false + +# chartMode(string) -- introduces cloud-specific settings. At present, these +# are only needed for AWS, so all other modes are effectively the same. Chart +# mode is reserved for potential future use and to parallel the config shape of +# the `teleport-cluster` chart. +# - "standalone": Generate config file, but no cloud-specific settings +# - "aws": Use when running on EKS. +# - "gcp": Use when running on GKE. +# - "azure": Use when running on AKS. +# - "scratch": Generate a minimal config file. Only values derived from the +# mandatory helm values are inserted into the config. Use teleportConfig to +# supply additional configuration. +# +# Generally avoid using this mode. Use `standalone` instead. +chartMode: standalone + +# validateConfigOnDeploy(bool) -- enables a Kubernetes job before install and +# upgrade that will verify if the teleport.yaml configuration is valid and will +# block the deployment if it is not. +validateConfigOnDeploy: true + +# podMonitor(object) -- controls the [PodMonitor CR](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api-reference/api.md#monitoring.coreos.com/v1.PodMonitor]. +# This CRD is managed by the prometheus-operator and allows workload to +# get monitored. To use this value, you need to run a `prometheus-operator` +# in the cluster for this value to take effect. +# See https://prometheus-operator.dev/docs/getting-started/introduction/ +podMonitor: + # podMonitor.enabled(bool) -- controls whether the chart should deploy a + # PodMonitor. Disabled by default as it requires the PodMonitor CRD to be + # installed. + enabled: false + # podMonitor.additionalLabels(object) -- labels to put on the PodMonitor. + # This is used to be selected by a specific prometheus instance. + # Defaults to `{prometheus: default}` which seems to be the common default prometheus selector + additionalLabels: + prometheus: default + # podMonitor.interval(string) -- is the interval between two metrics scrapes. Defaults to 30s + interval: 30s + +# highAvailability(object) -- contains settings controlling how Teleport pods are +# replicated and scheduled. This allows Teleport to run in a highly-available +# fashion: Teleport should sustain the crash/loss of a machine without interrupting +# the service. +# +# Proxy pods need to be provided a certificate to be replicated (either via +# `tls.existingSecretName` or via `highAvailability.certManager`) or be exposed +# via an ingress (`ingress.enabled`). +highAvailability: + # highAvailability.replicaCount(int) -- controls the amount of pod replicas. The `highAvailability` comment describes + # the replication requirements. + # + # > [!WARNING] + # > You **must** meet the replication criteria, else the deployment will + # > result in errors and inconsistent data. + replicaCount: 2 + # highAvailability.requireAntiAffinity(bool) -- will use 'requiredDuringSchedulingIgnoredDuringExecution' to require that multiple Teleport pods must not be scheduled on the + # same physical host. This will result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. + # Setting 'requireAntiAffinity' to false (the default) uses 'preferredDuringSchedulingIgnoredDuringExecution' to make this a soft requirement. + # This setting only has any effect when replicaCount is greater than 1. + requireAntiAffinity: false + # highAvailability.podDisruptionBudget(object) -- will create a + # [Pod Disruption Budget](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/) + podDisruptionBudget: + # highAvailability.podDisruptionBudget.enabled(bool) -- controls whether + # the chart creates a Pod Disruption Budget for proxy pods. + enabled: false + # highAvailability.podDisruptionBudget.minAvailable(int) -- is the minimum + # number of pods that must remain available during a voluntary disruption. + minAvailable: 1 + # highAvailability.certManager(object) -- settings for cert-manager used to + # provision TLS certificates in HA mode. + # These settings are mutually exclusive with the "tls" value below. + certManager: + # highAvailability.certManager.enabled(bool) -- if set to true, use + # cert-manager to get certificates for Teleport to use for TLS termination. + enabled: false + # highAvailability.certManager.addCommonName(bool) -- if set to true, a + # common name matching the cluster name will be set in the certificate + # signing request. This is mandatory for some CAs. + addCommonName: false + # highAvailability.certManager.addPublicAddrs(bool) -- if set to true, any + # additional public addresses configured under the `publicAddr` chart value + # will be added to the certificate signing request. + # This setting is not enabled by default to preserve backward compatibility. + addPublicAddrs: false + # highAvailability.certManager.issuerName(string) -- name of the + # Issuer/ClusterIssuer to use for certs. + # NOTE: You will always need to create this yourself when certManager.enabled is true. + issuerName: "" + # highAvailability.certManager.issuerKind(string) -- kind of Issuer that + # cert-manager should look for. + # This defaults to 'Issuer' to keep everything contained within the teleport namespace. + issuerKind: Issuer + # highAvailability.certManager.issuerGroup(string) -- group of Issuer that + # cert-manager should look for. + # This defaults to 'cert-manager.io' which is the default Issuer group. + issuerGroup: cert-manager.io + # highAvailability.minReadySeconds(int) -- injects delay when performing pod + # rollouts to mitigate the loss of all agent tunnels at the same time. + # See https://github.com/gravitational/teleport/issues/13129 + minReadySeconds: 15 + +# tls(object) -- are settings for mounting your own TLS keypair to secure Teleport's web UI. +# These settings are mutually exclusive with the "highAvailability.certManager" and "acme" values above. +tls: + # tls.existingSecretName(string) -- name of an existing secret to use which + # contains a TLS keypair. This will automatically set the https_keypairs + # section in teleport.yaml. + # Create the secret in the same namespace as Teleport using: + # ```bash + # kubectl create secret tls my-tls-secret --cert=/path/to/cert/file --key=/path/to/key/file + # ``` + # See [Kubernetes TLS Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets) for more information. + existingSecretName: "" + # tls.existingCASecretName(string) -- optional name of an existing secret to + # use which contains a CA or trust bundle in x509 PEM format. + # Useful for building trust when using intermediate certificate authorities. + # This will automatically set the SSL_CERT_FILE environment variable to trust the CA. + # Create the secret with: + # ```bash + # kubectl create secret generic my-root-ca --from-file=ca.pem=/path/to/root-ca.pem + # ``` + # + # DANGER: The Teleport distroless container trusts by default CAs from the `ca-certificates` package (Mozilla PKI). + # When `existingCASecretName` is set, Teleport only trusts the CA bundle from the secret. + # If you need Teleport to interact with other systems (e.g. AWS, GitHub, ...), the secret must contain their CA. + # Else, Teleport will fail to establish TLS connections with external services. + existingCASecretName: "" + # tls.existingCASecretKeyName(string) -- optional name of an existing key in + # the CA secret which will be used as a trusted CA bundle file. + existingCASecretKeyName: "ca.pem" + +################################################## +# Values that you shouldn't need to change. +################################################## + +# Container image for the cluster. By default, the image contains only the +# Teleport application and its runtime dependencies, and does not contain a +# shell. +image: public.ecr.aws/gravitational/teleport-distroless +# Enterprise version of the image. By default, the image contains only the +# Teleport application and its runtime dependencies, and does not contain a +# shell. +enterpriseImage: public.ecr.aws/gravitational/teleport-ent-distroless +# Optional array of imagePullSecrets, to use when pulling from a private registry +imagePullSecrets: [] +# log(object) -- is the teleport logging configuration +log: + # log.level(string) -- log level for the Teleport process. + # Available log levels are: DEBUG, INFO, WARNING, ERROR. + # The default is INFO, which is recommended in production. + # DEBUG is useful during first-time setup or to see more detailed logs for debugging. + level: INFO + # log.output(string) -- is where logs should go. + # Use a file path to log to disk: e.g. '/var/lib/teleport/teleport.log' + # Other supported values: 'stdout', 'stderr' and 'syslog' + output: stderr + # log.format(string) -- is the log format configuration. + # Possible output values are 'json' and 'text' (default). + format: text + # log.extraFields(array) -- other fields to log. Possible extra_fields values include: timestamp, component, caller, and level. + # All extra fields are included by default. + extraFields: ["timestamp", "level", "component", "caller"] + +################################## +# Extra Kubernetes configuration # +################################## + +# nodeSelector to apply for pod assignment +# https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector +nodeSelector: {} + +# disableTopologySpreadConstraints(bool) -- turns off the topology spread constraints. +# The feature is automatically turned off on Kubernetes versions below 1.18. +disableTopologySpreadConstraints: false + +# Pod topology spread constraints: +# https://kubernetes.io/fr/docs/concepts/workloads/pods/pod-topology-spread-constraints/ +# When unset, the chart defaults to a soft topology spread constraint +# that tries to spread pods across hosts and zones. +# +# ``` +# topologySpreadConstraints +# - maxSkew: 1 +# topologyKey: kubernetes.io/hostname +# whenUnsatisfiable: ScheduleAnyway +# labelSelector: +# matchLabels: # dynamically computed +# - maxSkew: 1 +# topologyKey: topology.kubernetes.io/zone +# whenUnsatisfiable: ScheduleAnyway +# labelSelector: +# matchLabels: # dynamically computed +# ``` +topologySpreadConstraints: [] + +# affinity(object) -- for pod assignment +# https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +# NOTE: If affinity is set here, highAvailability.requireAntiAffinity cannot also be used - you can only set one or the other. +affinity: {} + +# annotations(object) -- to apply, see [kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) +annotations: + # annotations.config(object) -- for the ConfigMap + config: {} + # annotations.deployment(object) -- for the Deployment + deployment: {} + # annotations.pod(object) -- for each Pod in the Deployment + pod: {} + # annotations.service(object) -- for the Service object + service: {} + # annotations.serviceAccount(object) -- for the ServiceAccount object + serviceAccount: {} + # annotations.certSecret(object) -- for the certificate secret generated by cert-manager v1.5+ when highAvailability.certManager.enabled is true + certSecret: {} + # annotations.ingress(object) -- for the Ingress object + ingress: {} + +# extraLabels -- contains additional Kubernetes labels to apply on the resources +# created by the chart. +# See [the Kubernetes label documentation +# ](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) +# for more information. +# +# Note: for PodMonitor labels, see `podMonitor.additionalLabels` instead. +extraLabels: + # extraLabels.certSecret(object) -- are labels to set on the certificate secret + # generated by cert-manager v1.5+ when `highAvailability.certManager.enabled` + # is true. + certSecret: {} + # extraLabels.clusterRole(object) -- are labels to set on the ClusterRole. + clusterRole: {} + # extraLabels.clusterRoleBinding(object) -- are labels to set on the ClusterRoleBinding. + clusterRoleBinding: {} + # extraLabels.role(object) -- are labels to set on the Role. + config: {} + # extraLabels.deployment(object) -- are labels to set on the Deployment. + deployment: {} + # extraLabels.ingress(object) -- are labels to set on the Ingress. + ingress: {} + # extraLabels.job(object) -- are labels to set on the Job run by the Helm hook. + job: {} + # extraLabels.jobPod(object) -- are labels to set on the Pods created by the + # Job run by the Helm hook. + jobPod: {} + # extraLabels.pod(object) -- are labels to set on the Pods created by the + # Deployment. + pod: {} + # extraLabels.podDisruptionBudget(object) -- are labels to set on the podDisruptionBudget. + podDisruptionBudget: {} + # extraLabels.secret(object) -- are labels to set on the Secret. + secret: {} + # extraLabels.service(object) -- are labels to set on the Service. + service: {} + # extraLabels.serviceAccount(object) -- are labels to set on the ServiceAccount. + serviceAccount: {} + +# serviceAccount(object) -- is the kubernetes service account to create/use. +# +# To set annotations on the service account, use the +# `annotations.serviceAccount` value. +serviceAccount: + # serviceAccount.create(bool) -- specifies whether a ServiceAccount should be created + create: true + # serviceAccount.name(string) -- is the name of the ServiceAccount to use. + # If not set and serviceAccount.create is true, the name is generated using the release name. + # If create is false, the name will be used to reference an existing service account. + name: "" + +# rbac(object) -- Manage Kubernetes RBAC +rbac: + # rbac.create(bool) -- specifies whether a ClusterRole and ClusterRoleBinding should be created. + # Set to false if your cluster level resources are managed separately. + create: true + +# service(object) -- allows you to override the proxy's backend service to any service type (other than "LoadBalancer") here if really needed. +# To use an Ingress, set service.type=ClusterIP and ingress.enabled=true +service: + # service.type(string) -- is the Kubernetes Load balancer type + type: LoadBalancer + # service.spec(object) -- is additional entries to be added to the service spec. + # Example: + # ```yaml + # loadBalancerIP: "1.2.3.4" + # ``` + spec: {} + +# ingress(object) -- is the Kubernetes Ingress object. +# +# If you set `ingress.enabled` to true, service.type MUST also be set to something other than "LoadBalancer" to prevent +# additional unnecessary load balancers from being created. Ingress controllers should provision their own load balancer. +# Using an Ingress also requires that you use the `tsh` client to connect to Kubernetes clusters and databases behind Teleport. +# See [TLS Routing](../../reference/architecture/tls-routing.mdx#working-with-layer-7-load-balancers-or-reverse-proxies) for details. +ingress: + # ingress.enabled(bool) -- creates an ingress + enabled: false + # ingress.useExisting(bool) -- indicates that you are managing your own ingress. + # (or HTTPRoute, or any other LoadBalancing method that terminates TLS). + # The chart will configure Teleport like it's running behind an ingress, but will not create the ingress resource. + # You are responsible for creating and managing the ingress. + useExisting: false + # ingress.suppressAutomaticWildcards(bool) -- will not automatically add + # `*.` as a hostname served by the Ingress. This may be + # desirable if you don't use applications protected by Teleport. + suppressAutomaticWildcards: false + # ingress.spec(object) -- is additional entries here will be added to the ingress spec. + # Example: + # ```yaml + # ingressClassName: nginx + # ``` + spec: {} + +# extraArgs(array) -- adds arguments to 'teleport start' for the main Teleport +# pod +extraArgs: [] + +# extraEnv(array) -- adds environment variables to the Teleport pod. +extraEnv: [] + +# extraContainers(array) -- to be added to the Teleport pod +# +# Example: +# ```yaml +# - name: my-extra-tool +# command: +# - /bin/bash +# - -c +# - sleep infinity & wait +# image: network-tool +# imagePullPolicy: IfNotPresent +# securityContext: +# privileged: true +# runAsNonRoot: false +# ``` +extraContainers: [] + +# Extra [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) +# to mount into the Teleport pods. +# +# Example: +# +# ```yaml +# - name: myvolume +# secret: +# secretName: testSecret +# ``` +extraVolumes: [] + +# extraVolumeMounts(array) -- corresponding to the volumes mounted by +# extraVolumes. +# +# Example: +# ```yaml +# - name: myvolume +# mountPath: /path/on/host +# ``` +extraVolumeMounts: [] + +# Allow the imagePullPolicy to be overridden +imagePullPolicy: IfNotPresent + +# initContainers(array) -- is a list of +# [initContainers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) +# to run before each Teleport pod starts +# +# Example: +# ```yaml +# - name: "teleport-init" +# image: "alpine" +# args: ["echo test"] +# ``` +initContainers: [] + +# postStart(object) -- if set, will run the command as a +# [postStart handler](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) +postStart: + # postStart.command(array) -- an array of commands to run + command: [] + +# resources(object) -- to request for the teleport container +# https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +# +# > [!DANGER] +# > Setting CPU limits is an anti-pattern and harmful in most cases. +# > Unless you enabled [the Static CPU management policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy), +# > a multithreaded workload with CPU limits will very likely not behave the way +# > you expect when approaching its CPU limit. +# +# Teleport will become unstable once throttling starts. We recommend not to set CPU limits. +# See [the GitHub PR](https://github.com/gravitational/teleport/pull/36251) for technical details. +# +# Example: +# ```yaml +# requests: +# cpu: "1" +# memory: "2Gi" +# limits: +# memory: "2Gi" +# ``` +resources: {} + +# jobResources(object) -- to request for the teleport pre-deploy jobs. +# https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +# +# Jobs currently include config validation and potentially migration hooks. +# The resource requirements are typically lower than for the main teleport deployment. +# In most cases, you should leave these limits unset. +# Example: +# ```yaml +# requests: +# cpu: "20m" +# memory: "500Mi" +# limits: +# memory: "500Mi" +# ``` +jobResources: {} + +# goMemLimitRatio(float) -- configures the GOMEMLIMIT env var set by the chart. +# GOMEMLIMIT instructs the go garbage collector to try to keep allocated memory +# below a given threshold. This is a best-effort attempt, but this helps +# to prevent OOMs in case of bursts. +# +# When the memory limits are set and goMemLimitRatio is non-zero, +# the chart sets the GOMEMLIMIT to `resources.memory.limits * goMemLimitRatio`. +# The value must be between 0 and 1. +# Set to 0 to unset GOMEMLIMIT. +# This has no effect if GOMEMLIMIT is already set through `extraEnv`. +goMemLimitRatio: 0.9 + +# podSecurityContext(object) -- for any pods created by the chart. +# Example: +# ```yaml +# fsGroup: 65532 +# ``` +podSecurityContext: {} + +# securityContext(object) -- to add to the container. +# Example: +# ```yaml +# runAsUser: 99 +# ``` +securityContext: {} + +# priorityClassName(string) -- to add to the deployment +priorityClassName: "" + +# tolerations(array) -- for pod assignment +# https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + +# probeTimeoutSeconds(int) -- is the timeouts for the readiness and liveness probes +# https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +probeTimeoutSeconds: 5 + +# readinessProbe(object) -- configures the readiness probe settings. +# This can be tuned to keep proxy pods ready even when the auth is unavailable. +# +# The default values mark the pod unready after one minute of failing readiness probe. +readinessProbe: + # readinessProbe.initialDelaySeconds(int) -- controls the number of seconds after the container has started before + # liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + initialDelaySeconds: 5 + # readinessProbe.periodSeconds(int) -- controls how often (in seconds) to perform the probe. Minimum value is 1. + periodSeconds: 5 + # readinessProbe.failureThreshold(int) -- is the minimum consecutive failures for the probe to be considered failed + # after having succeeded. Minimum value is 1. + failureThreshold: 12 + # readinessProbe.successThreshold(int) -- is the minimum consecutive successes for the probe to be considered + # successful after having failed. Minimum value is 1. + successThreshold: 1 + +# terminationGracePeriodSeconds(int) -- Kubernetes termination grace period +# https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution +# +# This should be greater than 30 seconds as pods are waiting 30 seconds in a preStop hook. +terminationGracePeriodSeconds: 60 From 05f8e349433c125e2ccf8aeb1525dd11906bbea4 Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Thu, 13 Aug 2026 23:18:50 +0000 Subject: [PATCH 301/347] SSO MFA for Admin MFA docs (#67149) * Clarify that SSO MFA is acceptable in Admin Action MFA docs. * Apply suggestions from code review Co-authored-by: mica * Clarify when admin MFA is/isn't enforced. --------- Co-authored-by: mica --- .../authentication/mfa-for-admin-actions.mdx | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/docs/pages/zero-trust-access/authentication/mfa-for-admin-actions.mdx b/docs/pages/zero-trust-access/authentication/mfa-for-admin-actions.mdx index ac815a12afd6..3cd7f4aa8f2c 100644 --- a/docs/pages/zero-trust-access/authentication/mfa-for-admin-actions.mdx +++ b/docs/pages/zero-trust-access/authentication/mfa-for-admin-actions.mdx @@ -44,16 +44,19 @@ their on-disk Teleport certificates. (!docs/pages/includes/edition-prereqs-tabs.mdx!) - (!docs/pages/includes/tctl.mdx!) -- [WebAuthn configured](../management/security/idp-compromise.mdx) on this cluster -- Multi-factor authentication hardware device, such as YubiKey or SoloKey -- A Web browser with [WebAuthn support]( - https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) (if using - SSH or desktop sessions from the Teleport Web UI). +- The following MFA methods can satisfy admin actions configured on the cluster: + - [WebAuthn](../management/security/idp-compromise.mdx) with a hardware + authenticator (such as YubiKey or SoloKey) and a browser with + [WebAuthn support](https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) + - [SSO MFA](../sso/sso-for-mfa.mdx), which delegates the MFA check to your + OIDC or SAML identity provider. ## Require MFA for administrative actions -MFA for administrative actions is automatically enforced for clusters where -WebAuthn is the only form of multi-factor authentication allowed. +MFA for administrative actions is automatically enforced on clusters where +WebAuthn and/or SSO MFA are the only available multi-factors, but not when +OTP is also available. + In a future major version, Teleport may enforce MFA for administrative actions @@ -68,6 +71,9 @@ $ tctl edit cap Update the `cluster_auth_preference` definition to include the following content: + + + ```yaml kind: cluster_auth_preference version: v2 @@ -80,6 +86,39 @@ spec: rp_id: example.com ``` + + + +Requires an OIDC or SAML connector with MFA settings; see +[Configuring SSO for MFA Checks](../sso/sso-for-mfa.mdx). + +```yaml +kind: cluster_auth_preference +version: v2 +metadata: + name: cluster-auth-preference +spec: + second_factors: ["sso"] +``` + + + + +```yaml +kind: cluster_auth_preference +version: v2 +metadata: + name: cluster-auth-preference +spec: + type: local + second_factors: ["webauthn", "sso"] + webauthn: + rp_id: example.com +``` + + + + Save and exit the file. `tctl` will update the remote definition: ```text From dbf752d73b99639cd9b64cfd9fef047806f61863 Mon Sep 17 00:00:00 2001 From: Gus Rivera Date: Fri, 14 Aug 2026 10:42:25 -0500 Subject: [PATCH 302/347] Remove teleport.e submodule Export-Source-Commit: 06d0383a16c52cc9c1025853776c6109a0b7a70d --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index d795eac24c84..e69de29bb2d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "e"] - path = e - url = git@github.com:gravitational/teleport.e.git From 0d7267b3ed3d56bd39d026e08f1c56e3760ce9f7 Mon Sep 17 00:00:00 2001 From: Gus Rivera Date: Fri, 14 Aug 2026 19:17:32 +0000 Subject: [PATCH 303/347] Patches for monorepo migration (#182) Export-Source-Commit: 63b0a257129c3a27f32bb59994df47dd605b1a6b --- Makefile | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 4b21331ac6be..b725bf596246 100644 --- a/Makefile +++ b/Makefile @@ -1546,7 +1546,6 @@ update-tag: cd build.assets/tooling && GOWORK=off CGO_ENABLED=0 go run ./cmd/check -check valid -tag $(GITTAG) git tag $(GITTAG) git tag api/$(GITTAG) - (cd e && git tag $(GITTAG) && git push origin $(GITTAG)) git push $(TAG_REMOTE) $(GITTAG) && git push $(TAG_REMOTE) api/$(GITTAG) # find-any evaluates to non-empty (true) if any of the strings in $(1) are contained in $(2) @@ -1566,6 +1565,21 @@ IS_CLOUD_SEMVER = $(call find-any,$(CLOUD_VERSIONS),$(VERSION)) PROD_VERSIONS = -cloud. IS_PROD_SEMVER = $(if $(findstring -,$(VERSION)),$(call find-any,$(PROD_VERSIONS),$(VERSION)),true) +# TAG_WORKFLOW_REF sets the teleport.e ref that will be used for tag-build and tag-publish +# This can be overriden to choose a specific teleport.e ref to build from +# By default, this parses the VERSION to find the major and maps to a branch +# - v19: master +# - v18: branch/v18 +# - v17: branch/v17 +# +# This is only a temporary measure to allow the monorepo to still continue doing releases from teleport.e +# This will be removed once release workflow is migrated to core +VERSION_MAJOR = $(patsubst v%,%,$(word 1,$(subst ., ,$(VERSION)))) +TAG_WORKFLOW_REF_19 = master +TAG_WORKFLOW_REF_18 = branch/v18 +TAG_WORKFLOW_REF_17 = branch/v17 +TAG_WORKFLOW_REF = $(or $(TAG_WORKFLOW_REF_$(VERSION_MAJOR)),$(error no tag workflow ref configured for VERSION=$(VERSION))) + # Builds a tag build on GitHub Actions. # Starts a tag publish run using e/.github/workflows/tag-build.yaml # for the tag v$(VERSION). @@ -1580,7 +1594,7 @@ tag-build: @which gh >/dev/null 2>&1 || { echo 'gh command needed. https://github.com/cli/cli'; exit 1; } gh workflow run tag-build.yaml \ --repo gravitational/teleport.e \ - --ref "v$(VERSION)" \ + --ref "$(TAG_WORKFLOW_REF)" \ -f "oss-teleport-repo=$(shell gh repo view --json nameWithOwner --jq .nameWithOwner)" \ -f "oss-teleport-ref=v$(VERSION)" \ -f "cloud-only=$(CLOUD_ONLY)" \ @@ -1601,7 +1615,7 @@ tag-publish: @which gh >/dev/null 2>&1 || { echo 'gh command needed. https://github.com/cli/cli'; exit 1; } gh workflow run tag-publish.yaml \ --repo gravitational/teleport.e \ - --ref "v$(VERSION)" \ + --ref "$(TAG_WORKFLOW_REF)" \ -f "oss-teleport-repo=$(shell gh repo view --json nameWithOwner --jq .nameWithOwner)" \ -f "oss-teleport-ref=v$(VERSION)" \ -f "cloud-only=$(CLOUD_ONLY)" \ From a9e65b44ec40e17a49c20bc9d76dcf3f92ad5f7f Mon Sep 17 00:00:00 2001 From: Roman Tkachenko Date: Mon, 17 Aug 2026 20:22:20 +0000 Subject: [PATCH 304/347] Remove RFDs (#240) Export-Source-Commit: c091c8a66a40c100dc8a427d72da1196c5d2d47e --- AGENTS.md | 2 +- .../agentic-identity-framework.mdx | 6 +++--- docs/pages/connect-your-client/teleport-clients/vnet.mdx | 4 ++-- docs/pages/enroll-resources/application-access/vnet.mdx | 2 +- .../database-access/auto-user-provisioning/aws-redshift.mdx | 2 +- .../database-access/auto-user-provisioning/mariadb.mdx | 2 +- .../database-access/auto-user-provisioning/mongodb.mdx | 2 +- .../database-access/auto-user-provisioning/mysql.mdx | 2 +- .../database-access/auto-user-provisioning/postgres.mdx | 4 ++-- .../enroll-resources/desktop-access/directory-sharing.mdx | 4 ++-- .../server-access/openssh/openssh-agentless.mdx | 2 +- .../server-access/openssh/openssh-manual-install.mdx | 2 +- .../reference/architecture/machine-id-architecture.mdx | 2 +- docs/pages/reference/architecture/tls-routing.mdx | 2 +- .../reference/machine-workload-identity/configuration.mdx | 2 +- .../zero-trust-access/authentication/joining-sessions.mdx | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3914f4bd99aa..3f408154c591 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ - Data corruption, durability failures, or irreversible loss scenarios - Concurrency hazards that can cause outages or data races - Reliability regressions: crash loops, panics, deadlocks, unbounded retries, nil pointer dereferences -- Adherence to the guidelines defined in [RFD 153](./rfd/0153-resource-guidelines.md) of Teleport resource definitations, gRPC, backend storage, and cache APIs. +- Adherence to the guidelines defined in [RFD 153](https://github.com/gravitational/teleport/blob/8ff3d34581424302182c217e4d8d3be45bf2b0b7/rfd/0153-resource-guidelines.md) of Teleport resource definitions, gRPC, backend storage, and cache APIs. ### Documentation diff --git a/docs/pages/agentic-identity-framework/agentic-identity-framework.mdx b/docs/pages/agentic-identity-framework/agentic-identity-framework.mdx index 113b85fc63d2..5cbceca23841 100644 --- a/docs/pages/agentic-identity-framework/agentic-identity-framework.mdx +++ b/docs/pages/agentic-identity-framework/agentic-identity-framework.mdx @@ -120,9 +120,9 @@ The framework is organized into four layers that build on each other: identity, Agents operate on behalf of principals while preserving approval/authorization workflows.
    Date: Tue, 18 Aug 2026 12:47:56 +0000 Subject: [PATCH 305/347] Fix Vite config warning (#302) Export-Source-Commit: cbf3b2a9d9fdb304090aee9c4d6c8a16d7b3116d --- tsconfig.node.json | 1 + web/packages/build/vite/{apphash.ts => apphash.mts} | 0 web/packages/build/vite/{config.ts => config.mts} | 6 +++--- web/packages/build/vite/gen-event-reference-config.mts | 4 ++-- web/packages/build/vite/{guard-wasm.ts => guard-wasm.mts} | 0 web/packages/build/vite/{html.ts => html.mts} | 0 web/packages/teleport/vite.config.mts | 4 ++-- 7 files changed, 8 insertions(+), 7 deletions(-) rename web/packages/build/vite/{apphash.ts => apphash.mts} (100%) rename web/packages/build/vite/{config.ts => config.mts} (98%) rename web/packages/build/vite/{guard-wasm.ts => guard-wasm.mts} (100%) rename web/packages/build/vite/{html.ts => html.mts} (100%) diff --git a/tsconfig.node.json b/tsconfig.node.json index 1685f8671a3f..57f777f348cc 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -6,6 +6,7 @@ "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "esModuleInterop": true, "skipLibCheck": true, "strict": false, diff --git a/web/packages/build/vite/apphash.ts b/web/packages/build/vite/apphash.mts similarity index 100% rename from web/packages/build/vite/apphash.ts rename to web/packages/build/vite/apphash.mts diff --git a/web/packages/build/vite/config.ts b/web/packages/build/vite/config.mts similarity index 98% rename from web/packages/build/vite/config.ts rename to web/packages/build/vite/config.mts index 1c841a5f6203..2be6141c38b0 100644 --- a/web/packages/build/vite/config.ts +++ b/web/packages/build/vite/config.mts @@ -27,9 +27,9 @@ import { defineConfig, type ProxyOptions, type UserConfig } from 'vite'; import { compression } from 'vite-plugin-compression2'; import wasm from 'vite-plugin-wasm'; -import { generateAppHashFile } from './apphash'; -import { guardWasmPlugin } from './guard-wasm'; -import { htmlPlugin, transformPlugin } from './html'; +import { generateAppHashFile } from './apphash.mts'; +import { guardWasmPlugin } from './guard-wasm.mts'; +import { htmlPlugin, transformPlugin } from './html.mts'; import { reactPlugin } from './react.mjs'; const DEFAULT_PROXY_TARGET = 'localhost:3080'; diff --git a/web/packages/build/vite/gen-event-reference-config.mts b/web/packages/build/vite/gen-event-reference-config.mts index ee5e5c6ae79a..7a499dcf197d 100644 --- a/web/packages/build/vite/gen-event-reference-config.mts +++ b/web/packages/build/vite/gen-event-reference-config.mts @@ -3,11 +3,11 @@ import path from 'node:path'; import { defineConfig } from 'vite'; const rootDirectory = path.resolve( - __dirname, + import.meta.dirname, '../../teleport/src/services/audit/gen-event-reference' ); const outputDirectory = path.resolve( - __dirname, + import.meta.dirname, '../../teleport/src/services/audit/gen-event-reference/dist' ); diff --git a/web/packages/build/vite/guard-wasm.ts b/web/packages/build/vite/guard-wasm.mts similarity index 100% rename from web/packages/build/vite/guard-wasm.ts rename to web/packages/build/vite/guard-wasm.mts diff --git a/web/packages/build/vite/html.ts b/web/packages/build/vite/html.mts similarity index 100% rename from web/packages/build/vite/html.ts rename to web/packages/build/vite/html.mts diff --git a/web/packages/teleport/vite.config.mts b/web/packages/teleport/vite.config.mts index dc7625047ed1..81f9638f2dd6 100644 --- a/web/packages/teleport/vite.config.mts +++ b/web/packages/teleport/vite.config.mts @@ -18,9 +18,9 @@ import { resolve } from 'path'; -import { createViteConfig } from '../build/vite/config'; +import { createViteConfig } from '../build/vite/config.mts'; -const rootDirectory = resolve(__dirname, '../../..'); +const rootDirectory = resolve(import.meta.dirname, '../../..'); const outputDirectory = resolve(rootDirectory, 'webassets/teleport'); const config = createViteConfig(rootDirectory, outputDirectory); From 490ebd42acf82fc53b8a19f7827e28aacdc05f90 Mon Sep 17 00:00:00 2001 From: "teleport-renovate[bot]" <275214890+teleport-renovate[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:44:56 +0000 Subject: [PATCH 306/347] Update Go toolchain version to v1.26.6 (#206) Export-Source-Commit: ca738042bc0afb4eb77373c129093e817e4d280d --- api/go.mod | 2 +- assets/aws/go.mod | 2 +- assets/backport/go.mod | 2 +- build.assets/Dockerfile-grpcbox | 2 +- build.assets/tooling/go.mod | 2 +- build.assets/tools/benchstat/go.mod | 2 +- build.assets/tools/gci/go.mod | 2 +- build.assets/tools/goda/go.mod | 2 +- build.assets/tools/gotestsum/go.mod | 2 +- build.assets/versions.mk | 2 +- docs/config.json | 2 +- examples/access-plugin-minimal/go.mod | 2 +- examples/api-sync-roles/go.mod | 2 +- examples/desktop-registration/go.mod | 2 +- examples/go-client/go.mod | 2 +- examples/mcp-servers/verify-teleport-jwt-mcp-go/go.mod | 2 +- examples/service-discovery-api-client/go.mod | 2 +- examples/teleport-usage/go.mod | 2 +- examples/workload-clusters/go.mod | 2 +- go.mod | 2 +- integrations/event-handler/go.mod | 2 +- integrations/terraform-mwi/go.mod | 2 +- integrations/terraform/go.mod | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/api/go.mod b/api/go.mod index 783e190b7109..2e21b7b96410 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/api -go 1.26.5 +go 1.26.6 require ( github.com/charlievieth/strcase v0.0.6 diff --git a/assets/aws/go.mod b/assets/aws/go.mod index d850542a639c..cd1418a8a251 100644 --- a/assets/aws/go.mod +++ b/assets/aws/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport-ami-update -go 1.26.5 +go 1.26.6 require ( github.com/alecthomas/kingpin/v2 v2.3.2 // replaced diff --git a/assets/backport/go.mod b/assets/backport/go.mod index 8efb5e8724c1..763c9d939039 100644 --- a/assets/backport/go.mod +++ b/assets/backport/go.mod @@ -1,6 +1,6 @@ module github.com/teleport/assets/backport -go 1.26.5 +go 1.26.6 require ( github.com/google/go-github/v41 v41.0.0 diff --git a/build.assets/Dockerfile-grpcbox b/build.assets/Dockerfile-grpcbox index 75f946b1c1fe..24d69d482b5e 100644 --- a/build.assets/Dockerfile-grpcbox +++ b/build.assets/Dockerfile-grpcbox @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM docker.io/golang:1.26.5 +FROM docker.io/golang:1.26.6 # Image layers go from less likely to most likely to change. RUN apt-get update && \ diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index 209a6c06cd85..09a7e0be21d8 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/build.assets/tooling -go 1.26.5 +go 1.26.6 tool ( github.com/gravitational/teleport/build.assets/tooling/cmd/helm-janitor diff --git a/build.assets/tools/benchstat/go.mod b/build.assets/tools/benchstat/go.mod index 57c60807236d..84ef968240ad 100644 --- a/build.assets/tools/benchstat/go.mod +++ b/build.assets/tools/benchstat/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/build.assets/tools/benchstat -go 1.26.5 +go 1.26.6 tool golang.org/x/perf/cmd/benchstat diff --git a/build.assets/tools/gci/go.mod b/build.assets/tools/gci/go.mod index 524b2bb93289..73b126b0db78 100644 --- a/build.assets/tools/gci/go.mod +++ b/build.assets/tools/gci/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/build.assets/tools/gci -go 1.26.5 +go 1.26.6 tool github.com/daixiang0/gci diff --git a/build.assets/tools/goda/go.mod b/build.assets/tools/goda/go.mod index 4c5800292af8..765da1d57d0f 100644 --- a/build.assets/tools/goda/go.mod +++ b/build.assets/tools/goda/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/build.assets/tools/goda -go 1.26.5 +go 1.26.6 tool github.com/loov/goda diff --git a/build.assets/tools/gotestsum/go.mod b/build.assets/tools/gotestsum/go.mod index 5771b7471205..763db0e1af99 100644 --- a/build.assets/tools/gotestsum/go.mod +++ b/build.assets/tools/gotestsum/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/build.assets/tools/gotestsum -go 1.26.5 +go 1.26.6 tool gotest.tools/gotestsum diff --git a/build.assets/versions.mk b/build.assets/versions.mk index 51dd75cd2deb..9b5526efebbb 100644 --- a/build.assets/versions.mk +++ b/build.assets/versions.mk @@ -2,7 +2,7 @@ # This file can be included in other Makefiles to avoid duplication. # Sync with devbox.json. -GOLANG_VERSION ?= go1.26.5 +GOLANG_VERSION ?= go1.26.6 GOLANGCI_LINT_VERSION ?= v2.12.2 # NOTE: Remember to update engines.node in package.json to match the major version. diff --git a/docs/config.json b/docs/config.json index 4861c445d38e..57319741105f 100644 --- a/docs/config.json +++ b/docs/config.json @@ -69,7 +69,7 @@ "major_version": "19", "version": "19.0.0-dev", "url": "teleport.example.com", - "golang": "1.26.5", + "golang": "1.26.6", "plugin": { "version": "13.3.7" }, diff --git a/examples/access-plugin-minimal/go.mod b/examples/access-plugin-minimal/go.mod index 1ce944ce6917..6271fbb6719c 100644 --- a/examples/access-plugin-minimal/go.mod +++ b/examples/access-plugin-minimal/go.mod @@ -1,6 +1,6 @@ module teleport-sheets -go 1.26.5 +go 1.26.6 require ( github.com/gravitational/teleport/api v0.0.0-20251120161518-a8e2cdc30b24 diff --git a/examples/api-sync-roles/go.mod b/examples/api-sync-roles/go.mod index 19a57783824f..bdc6fa90ce2f 100644 --- a/examples/api-sync-roles/go.mod +++ b/examples/api-sync-roles/go.mod @@ -1,6 +1,6 @@ module sync-roles -go 1.26.5 +go 1.26.6 require ( github.com/gravitational/teleport/api v0.0.0-20251120161518-a8e2cdc30b24 diff --git a/examples/desktop-registration/go.mod b/examples/desktop-registration/go.mod index f0cd5e2c69df..c1efe6b3680c 100644 --- a/examples/desktop-registration/go.mod +++ b/examples/desktop-registration/go.mod @@ -1,6 +1,6 @@ module teleport-desktop-registration -go 1.26.5 +go 1.26.6 require github.com/gravitational/teleport/api v0.0.0-20251120161518-a8e2cdc30b24 diff --git a/examples/go-client/go.mod b/examples/go-client/go.mod index 7b1fb08d68b3..46e67953e43c 100644 --- a/examples/go-client/go.mod +++ b/examples/go-client/go.mod @@ -1,6 +1,6 @@ module go-client -go 1.26.5 +go 1.26.6 require github.com/gravitational/teleport/api v0.0.0-20251120161518-a8e2cdc30b24 diff --git a/examples/mcp-servers/verify-teleport-jwt-mcp-go/go.mod b/examples/mcp-servers/verify-teleport-jwt-mcp-go/go.mod index 2ee778d27a6e..42da7932d3e9 100644 --- a/examples/mcp-servers/verify-teleport-jwt-mcp-go/go.mod +++ b/examples/mcp-servers/verify-teleport-jwt-mcp-go/go.mod @@ -1,6 +1,6 @@ module verify-teleport-jwt-mcp-go -go 1.26.5 +go 1.26.6 require ( github.com/auth0/go-jwt-middleware/v2 v2.3.0 diff --git a/examples/service-discovery-api-client/go.mod b/examples/service-discovery-api-client/go.mod index 14223c137791..ab1ab8ac17d7 100644 --- a/examples/service-discovery-api-client/go.mod +++ b/examples/service-discovery-api-client/go.mod @@ -1,6 +1,6 @@ module register-app-service -go 1.26.5 +go 1.26.6 require ( github.com/gravitational/teleport/api v0.0.0-20251120161518-a8e2cdc30b24 diff --git a/examples/teleport-usage/go.mod b/examples/teleport-usage/go.mod index e6f31291d17d..c04cd289414c 100644 --- a/examples/teleport-usage/go.mod +++ b/examples/teleport-usage/go.mod @@ -1,6 +1,6 @@ module usage-script -go 1.26.5 +go 1.26.6 require ( github.com/aws/aws-sdk-go-v2 v1.38.0 diff --git a/examples/workload-clusters/go.mod b/examples/workload-clusters/go.mod index fb5eaa11e06e..75a2ae4605ee 100644 --- a/examples/workload-clusters/go.mod +++ b/examples/workload-clusters/go.mod @@ -1,6 +1,6 @@ module workload-clusters -go 1.26.5 +go 1.26.6 require github.com/gravitational/teleport/api v0.0.0-20260428152919-24b2608bf3f5 diff --git a/go.mod b/go.mod index 794f4836619f..64ef83d6574b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport -go 1.26.5 +go 1.26.6 ignore ( ./build diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 5ab528aa3ab4..9f7d075e6056 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/integrations/event-handler -go 1.26.5 +go 1.26.6 require ( github.com/DataDog/datadog-agent/pkg/template v0.79.1 diff --git a/integrations/terraform-mwi/go.mod b/integrations/terraform-mwi/go.mod index 71187a5bfe29..f40cc4fb6fa8 100644 --- a/integrations/terraform-mwi/go.mod +++ b/integrations/terraform-mwi/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/integrations/terraform-mwi -go 1.26.5 +go 1.26.6 require ( github.com/gravitational/teleport v0.0.0-00010101000000-000000000000 diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index 6a16be2ad2e2..23cc97935798 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -1,6 +1,6 @@ module github.com/gravitational/teleport/integrations/terraform -go 1.26.5 +go 1.26.6 // TF provider dependencies require ( From f3a8e939f41de637c1879bb8e72240ed18951265 Mon Sep 17 00:00:00 2001 From: Ghassan Gedeon Achi Date: Tue, 18 Aug 2026 19:02:19 +0000 Subject: [PATCH 307/347] Fix `tctl access-review` table from overflowing terminal width (#190) Export-Source-Commit: 5c488f3ad10371625e4ad061c4d3ef0dd9702995 --- lib/asciitable/table.go | 21 +++++++--- .../accessgraph/access_review_command.go | 42 ++++++++++++------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/asciitable/table.go b/lib/asciitable/table.go index 8420adbe25fa..1a311757fef2 100644 --- a/lib/asciitable/table.go +++ b/lib/asciitable/table.go @@ -33,6 +33,17 @@ import ( "golang.org/x/term" ) +// Layout constants, for callers sizing columns against a terminal width: a +// column occupies max(MinColumnWidth, widest cell+ColumnPadding). +const ( + // MinColumnWidth is the narrowest a column renders, whatever its contents. + MinColumnWidth = 5 + // ColumnPadding follows every cell, including the last one on a row. + ColumnPadding = 1 + // TruncationSuffix is appended to cells over [Column.MaxCellLength]. + TruncationSuffix = "..." +) + // Column represents a column in the table. type Column struct { Title string @@ -103,16 +114,16 @@ func MakeTableWithTruncatedColumn(columnOrder []string, rows [][]string, truncat } if column.MaxCellLength > maxColWidth { column.MaxCellLength = maxColWidth - totalLen += column.MaxCellLength + 4 // "..." + totalLen += column.MaxCellLength + len(TruncationSuffix) + ColumnPadding // "..." } else { - totalLen += column.MaxCellLength + 1 // +1 for column separator + totalLen += column.MaxCellLength + ColumnPadding // +1 for column separator } columns = append(columns, column) } for _, column := range columns { if column.Title == truncatedColumn { - column.MaxCellLength = max(width-totalLen-len("... "), 0) + column.MaxCellLength = max(width-totalLen-len(TruncationSuffix)-ColumnPadding, 0) } t.AddColumn(column) } @@ -151,7 +162,7 @@ func (t *Table) truncateCell(colIndex int, cell string) (string, bool) { if maxCellLength == 0 || len(cell) <= maxCellLength { return cell, false } - truncatedCell := fmt.Sprintf("%v...", cell[:maxCellLength]) + truncatedCell := cell[:maxCellLength] + TruncationSuffix footnoteLabel := t.columns[colIndex].FootnoteLabel if footnoteLabel == "" { return truncatedCell, false @@ -179,7 +190,7 @@ func (t *Table) String() string { // WriteTo writes the full table to [w] or else returns an error. func (t *Table) WriteTo(w io.Writer) error { - writer := tabwriter.NewWriter(w, 5, 0, 1, ' ', 0) + writer := tabwriter.NewWriter(w, MinColumnWidth, 0, ColumnPadding, ' ', 0) template := strings.Repeat("%v\t", len(t.columns)) // Header and separator. diff --git a/tool/tctl/common/accessgraph/access_review_command.go b/tool/tctl/common/accessgraph/access_review_command.go index bd09d95826c6..de728ee076f6 100644 --- a/tool/tctl/common/accessgraph/access_review_command.go +++ b/tool/tctl/common/accessgraph/access_review_command.go @@ -125,7 +125,7 @@ func (c *AccessGraphCommand) AccessReview(ctx context.Context, client *accessgra output.ActivityUnavailable = utils.AllowWhitespace(*resp.IacError) } if truncated { - output.Warnings = append(output.Warnings, fmt.Sprintf("results truncated at %d identities; narrow --query for the full set", args.limit)) + output.Warnings = append(output.Warnings, fmt.Sprintf("results truncated at %d identities; narrow --query or increase --limit for the full set", args.limit)) } return writeOutput(c.stdout, output, args.format, func(w io.Writer) error { @@ -501,9 +501,12 @@ func accessReviewDetailedRows(output accessReviewOutput, showActivity bool) ([]s return headers, rows } -const resourceColumn = "Resource" - -const resourceColumnFloor = 16 +const ( + // resourceColumn is the header of the column we truncate to fit the terminal. + resourceColumn = "Resource" + // resourceColumnFloor is the narrowest we will truncate resourceColumn to. + resourceColumnFloor = 16 +) func writeAccessTable(out io.Writer, headers []string, rows [][]string) error { table := buildAccessTable(headers, rows, terminalWidth(out)) @@ -511,30 +514,39 @@ func writeAccessTable(out io.Writer, headers []string, rows [][]string) error { return trace.Wrap(err) } -// buildAccessTable caps only the Resource column to the space the others leave, -// so wide columns are never clipped just to fit the table width. The cap ignores -// the "..." asciitable appends on truncation, so a clipped Resource can overflow -// width by a few columns; the terminal wraps it. +// buildAccessTable lets every column render at its full width and truncates +// only resourceColumn to the space they leave over, so no column is clipped +// just to fit the table width. The table exceeds width only when that space +// falls under resourceColumnFloor, in which case the terminal wraps it. func buildAccessTable(headers []string, rows [][]string, width int) asciitable.Table { - used := 0 + // Measure what the other columns cost, and how much room Resource wants, so + // we can tell whether it needs truncating at all. + used, resourceWidth := 0, 0 for i, h := range headers { - if h == resourceColumn { - continue - } colWidth := len(h) for _, row := range rows { if i < len(row) && len(row[i]) > colWidth { colWidth = len(row[i]) } } - used += colWidth + 1 // +1 for tabwriter's column padding + if h == resourceColumn { + resourceWidth = colWidth + continue + } + used += max(colWidth+asciitable.ColumnPadding, asciitable.MinColumnWidth) } + // Room left for Resource's own contents, its padding excluded. + available := width - used - asciitable.ColumnPadding + t := asciitable.MakeTable([]string{}) for _, h := range headers { col := asciitable.Column{Title: h} - if h == resourceColumn { - col.MaxCellLength = max(width-used, resourceColumnFloor) + // A zero MaxCellLength leaves the column unbounded, so bound Resource + // only when it genuinely overflows; the suffix asciitable appends on + // truncation comes out of the same room. + if h == resourceColumn && resourceWidth > available { + col.MaxCellLength = max(available-len(asciitable.TruncationSuffix), resourceColumnFloor) } t.AddColumn(col) } From 6d906dc57b5c859401e3f2d0175917e32371b3b6 Mon Sep 17 00:00:00 2001 From: Gustavo Medori Date: Tue, 18 Aug 2026 20:20:40 +0000 Subject: [PATCH 308/347] Migrating PR from gravitational/teleport repo (#232) Export-Source-Commit: ead22275e58458e55a2316e849ff5fed138eea27 --- .../xcschemes/LogBackends.xcscheme | 66 +++++ mobile/TeleportKit/Package.swift | 19 +- .../LogBackends/ConsoleLogHandler.swift | 12 +- .../{ => Formatting}/Level+Formatting.swift | 0 .../LogBackends/Formatting/LogFormatter.swift | 67 +++++ .../Metadata+Formatting.swift | 7 +- .../RotatingFileLogHandler.swift | 50 ++++ .../Rotating File/RotatingFileWriter.swift | 268 ++++++++++++++++++ .../File System/FileSystemClient.swift | 64 +++++ .../File System/WriteableFileClient.swift | 49 ++++ .../Sources/SystemClients/SystemClients.swift | 3 + .../RotatingFileWriterTests.swift | 105 +++++++ .../LogBackendsTests/TemporaryDirectory.swift | 36 +++ .../Verify/Verify.xcodeproj/project.pbxproj | 70 +++++ .../Verify/App Lifecycle/VerifyAppModel.swift | 5 +- .../SecureEnclaveCredentialStore.swift | 7 +- .../Enrollment/EnrollDeviceViewModel.swift | 4 +- .../Features/Landing/LandingViewModel.swift | 4 +- mobile/Verify/Verify/Info.plist | 4 - .../Persistence/SQLite/AppDatabase.swift | 4 +- 20 files changed, 811 insertions(+), 33 deletions(-) create mode 100644 mobile/TeleportKit/.swiftpm/xcode/xcshareddata/xcschemes/LogBackends.xcscheme rename mobile/TeleportKit/Sources/LogBackends/{ => Formatting}/Level+Formatting.swift (100%) create mode 100644 mobile/TeleportKit/Sources/LogBackends/Formatting/LogFormatter.swift rename mobile/TeleportKit/Sources/LogBackends/{ => Formatting}/Metadata+Formatting.swift (89%) create mode 100644 mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileLogHandler.swift create mode 100644 mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileWriter.swift create mode 100644 mobile/TeleportKit/Sources/SystemClients/File System/FileSystemClient.swift create mode 100644 mobile/TeleportKit/Sources/SystemClients/File System/WriteableFileClient.swift create mode 100644 mobile/TeleportKit/Tests/LogBackendsTests/RotatingFileWriterTests.swift create mode 100644 mobile/TeleportKit/Tests/LogBackendsTests/TemporaryDirectory.swift diff --git a/mobile/TeleportKit/.swiftpm/xcode/xcshareddata/xcschemes/LogBackends.xcscheme b/mobile/TeleportKit/.swiftpm/xcode/xcshareddata/xcschemes/LogBackends.xcscheme new file mode 100644 index 000000000000..58eb0cfa4ba2 --- /dev/null +++ b/mobile/TeleportKit/.swiftpm/xcode/xcshareddata/xcschemes/LogBackends.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/TeleportKit/Package.swift b/mobile/TeleportKit/Package.swift index 629d8a82dba2..d71ff8559fbf 100644 --- a/mobile/TeleportKit/Package.swift +++ b/mobile/TeleportKit/Package.swift @@ -20,7 +20,7 @@ import PackageDescription let package = Package( name: "TeleportKit", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .macOS(.v26)], products: [ .library(name: "SystemClients", targets: ["SystemClients"]), .library(name: "LogBackends", targets: ["LogBackends"]), @@ -29,6 +29,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-dependencies", .upToNextMajor(from: "1.14.0")), .package(url: "https://github.com/pointfreeco/swift-sharing", .upToNextMajor(from: "2.9.1")), .package(url: "https://github.com/apple/swift-log", .upToNextMajor(from: "1.14.0")), + .package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.6.0")), ], targets: [ .target( @@ -42,7 +43,19 @@ let package = Package( .target( name: "LogBackends", dependencies: [ + .collections, .logging, + .dependencies, + "SystemClients", + ], + ), + .testTarget( + name: "LogBackendsTests", + dependencies: [ + .dependencies, + .logging, + "LogBackends", + "SystemClients", ], ), ], @@ -68,6 +81,10 @@ extension Target.Dependency { name: "Logging", package: "swift-log", ) + fileprivate static let collections: Self = .product( + name: "Collections", + package: "swift-collections", + ) } // MARK: - Build Settings diff --git a/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift b/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift index bae5289b259a..ceb90062ffba 100644 --- a/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift +++ b/mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/ +import Dependencies import Foundation public import Logging @@ -21,6 +22,9 @@ public import Logging public struct ConsoleLogHandler: LogHandler { public let label: String + @Dependency(\.date.now) + private var now + private let timestampFormmater = Date.ISO8601FormatStyle( dateSeparator: .dash, dateTimeSeparator: .space, @@ -46,11 +50,7 @@ public struct ConsoleLogHandler: LogHandler { } public func log(event: LogEvent) { - let timestamp = timestampFormmater.format(.now) - let metadataToLog = metadata.merging(event.metadata ?? [:]) { _, new in new } - - let output = "\(timestamp) \(event.level.formatted) \(event.file):\(event.line) | \(event.message) \(metadataToLog.formatted)" - - print(output) + let logMessage = LogFormatter.format(label: label, event: event, handlerMetadata: metadata, timestamp: now) + print(logMessage) } } diff --git a/mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift b/mobile/TeleportKit/Sources/LogBackends/Formatting/Level+Formatting.swift similarity index 100% rename from mobile/TeleportKit/Sources/LogBackends/Level+Formatting.swift rename to mobile/TeleportKit/Sources/LogBackends/Formatting/Level+Formatting.swift diff --git a/mobile/TeleportKit/Sources/LogBackends/Formatting/LogFormatter.swift b/mobile/TeleportKit/Sources/LogBackends/Formatting/LogFormatter.swift new file mode 100644 index 000000000000..a3446d0e582f --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Formatting/LogFormatter.swift @@ -0,0 +1,67 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Foundation +import Logging + +/// LogFormatter doesn't currently maintain any local state, so it's simplest to implement it as an enum with static +/// functions. +enum LogFormatter { + private static let timestampFormatter = Date.ISO8601FormatStyle( + dateSeparator: .dash, + dateTimeSeparator: .space, + timeSeparator: .colon, + timeZoneSeparator: .colon, + includingFractionalSeconds: true, + timeZone: .autoupdatingCurrent, + ) + + /// Formats the log message + /// - Parameters: + /// - label: The label of the logger + /// - event: The event to log + /// - handlerMetadata: The metadata for the `LogHandler` (note: events carry their own metadata and this formatter + /// automatically coalesces them with the `LogHandler` metadata) + /// - timestamp: The time at which the logging event took place + /// - Returns: A formatted log message suitable for output to a debug console or writing to a file + static func format( + label: String, + event: LogEvent, + handlerMetadata: Logger.Metadata, + timestamp: Date, + ) -> String { + let metadata = handlerMetadata.merging(event.metadata ?? [:]) { _, eventValue in eventValue } + var components: [String] = [ + timestampFormatter.format(timestamp), + event.level.formatted, + "[\(label)]", + "\(event.file):\(event.line)", + "|", + "\(event.message)", + ] + + let formattedMetadata = metadata.formatted + if !formattedMetadata.isEmpty { + components.append(formattedMetadata) + } + + if let error = event.error { + components.append("❌ error=\(error)") + } + + return components.joined(separator: " ") + } +} diff --git a/mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift b/mobile/TeleportKit/Sources/LogBackends/Formatting/Metadata+Formatting.swift similarity index 89% rename from mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift rename to mobile/TeleportKit/Sources/LogBackends/Formatting/Metadata+Formatting.swift index 3ef3d2e09653..038715314676 100644 --- a/mobile/TeleportKit/Sources/LogBackends/Metadata+Formatting.swift +++ b/mobile/TeleportKit/Sources/LogBackends/Formatting/Metadata+Formatting.swift @@ -25,12 +25,7 @@ extension Logger.Metadata { var formatted: String { guard !isEmpty else { return "" } let metadataAsStrings = map { key, value in - let prefix = if key.lowercased().localizedStandardContains("error") { - "❌" - } else { - "🔸" - } - return "\(prefix) \(key)=\(value)" + "🔸 \(key)=\(value)" } return metadataAsStrings.joined(separator: " ") } diff --git a/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileLogHandler.swift b/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileLogHandler.swift new file mode 100644 index 000000000000..13950f6eeefd --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileLogHandler.swift @@ -0,0 +1,50 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Dependencies +import Foundation +import Logging + +/// A log handler that writes to a rotating file on disk. +public struct RotatingFileLogHandler: LogHandler { + public let label: String + + @Dependency(\.date.now) + private var now + + private let writer: RotatingFileWriter + + public init(label: String, writer: RotatingFileWriter) { + self.label = label + self.writer = writer + } + + // MARK: LogHandler Conformance + + public var logLevel: Logger.Level = .info + + public var metadata: Logger.Metadata = [:] + + public subscript(metadataKey key: String) -> Logger.Metadata.Value? { + get { metadata[key] } + set { metadata[key] = newValue } + } + + public func log(event: LogEvent) { + let logMessage = LogFormatter.format(label: label, event: event, handlerMetadata: metadata, timestamp: now) + writer.enqueue(logMessage: "\(logMessage)\n") + } +} diff --git a/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileWriter.swift b/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileWriter.swift new file mode 100644 index 000000000000..9e81c5e89a09 --- /dev/null +++ b/mobile/TeleportKit/Sources/LogBackends/Rotating File/RotatingFileWriter.swift @@ -0,0 +1,268 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Dependencies +import DequeModule +public import Foundation +import Synchronization +import SystemClients + +/// Opens a file for writing which rotates to a new file whenever the original is at capacity. +public final class RotatingFileWriter: Sendable { + // MARK: Mutable State + + /// An inbox that maintains the pending items to be written. + /// + /// This mutext protects pending-item bookkeeping only. Filesystem work must never occur while holding this mutex, + /// keeping enqueue operations independent of slow I/O. + private let inbox = Mutex(Inbox()) + + /// A handle to the currently open/active file. + /// + /// This mutex protects the active file state separately from the inbox mutext because it may be held during + /// synchronous filesystem operations. + private let fileState = Mutex(FileState()) + + // MARK: Immutable State + + private let activeFileURL: URL + private let configuration: Configuration + private let queue = DispatchQueue(label: "com.goteleport.teleportkit.rotating-file-writer") + private let fileSystemClient: FileSystemClient + + public convenience init(fileURL: URL) { + self.init(fileURL: fileURL, configuration: .live) + } + + init(fileURL: URL, configuration: Configuration) { + self.activeFileURL = fileURL + self.configuration = configuration + + // Retrieving the dependency once during init is safe because the file system implementation should not + // change mid-process. + @Dependency(\.fileSystemClient) + var fileSystemClient + + self.fileSystemClient = fileSystemClient + } + + /// Waits for all previously enqueued records to be processed and synchronizes the active file. + public func flush() async throws { + try await withCheckedThrowingContinuation { continuation in + // Enqueuing the continuation allows us to ensure all earlier items are processed before the active file is + // synchronized and the continuation resumes. + enqueue(item: .flush(continuation)) + } + } + + /// Enqueues a log message to be written to disk + func enqueue(logMessage: String) { + enqueue(item: .logMessage(logMessage)) + } +} + +// MARK: - Inbox Processing + +extension RotatingFileWriter { + /// Adds an item to the inbox and schedules processing if it isn't already scheduled or active. + private func enqueue(item: PendingItem) { + let shouldScheduleProcessing = inbox.withLock { inbox in + inbox.append(item) + } + + guard shouldScheduleProcessing else { return } + queue.async { + self.processInbox() + } + } + + /// The top level inbox processing function that iterates through the pending items and performs their corresponding + /// operation. + private func processInbox() { + inbox.withLock { $0.beginProcessing() } + + while let item = inbox.withLock({ $0.nextPendingItem() }) { + switch item { + case let .logMessage(record): + do { + try append(record: record) + } catch { + recordFailure(error) + } + case let .droppedRecordNotice(count): + do { + try append(droppedRecordNoticeFor: count) + } catch { + recordFailure(error) + } + case let .flush(continuation): + processFlushRequest(continuation) + } + } + } +} + +// MARK: - File Operations + +extension RotatingFileWriter { + /// Appends the record to the currently active file. + /// - Parameter record: The record to write to disk. + private func append(record: String) throws { + let record = truncateRecordIfNeeded(Data(record.utf8)) + try openActiveFileIfNeeded() + try rotateActiveFileIfNeeded(forAppendingByteCount: record.count) + + try fileState.withLock { fileState in + guard let fileClient = fileState.fileClient else { return } + _ = try fileClient.seekToEnd() + try fileClient.write(data: record) + } + } + + /// Appends a line to the log file indicating that some number of records were dropped, perhaps due to overflowing + /// the queue. + /// - Parameter count: The number of log records + private func append(droppedRecordNoticeFor count: Int) throws { + // TODO: Implement dropped record logging + } + + /// Truncates a record if its size exceeds the max size of a single log file. + /// - Parameter record: The record to truncate + /// - Returns: The record, truncated only if necessary + private func truncateRecordIfNeeded(_ record: Data) -> Data { + // TODO: Truncate the record + record + } + + /// Opens a file for appending if no active file is yet open. + private func openActiveFileIfNeeded() throws { + try fileState.withLock { fileState in + guard fileState.fileClient == nil else { return } + + try fileSystemClient.createDirectory(url: activeFileURL.deletingLastPathComponent()) + if !fileSystemClient.fileExists(url: activeFileURL) { + try fileSystemClient.createFile(url: activeFileURL, contents: nil) + } + fileState.fileClient = try fileSystemClient.openFileForWriting(url: activeFileURL) + } + } + + /// Rotates the active file if needed. + /// + /// Rotation is "needed" when appending a record of the indicated count would cause the log file to exceed its + /// maximum capacity. + /// - Parameter byteCount: The number of bytes we intend to append to the log file. + private func rotateActiveFileIfNeeded(forAppendingByteCount byteCount: Int) throws { + // TODO: Implement rotation + } + + /// Flushes all pending writes to disk via the file handle, ensuring all records are persisted. + /// - Parameter continuation: The continuation to resume when the synchronize operation is done. + private func processFlushRequest(_ continuation: CheckedContinuation) { + do { + try fileState.withLock { fileState in + try fileState.fileClient?.synchronize() + } + continuation.resume() + } catch { + continuation.resume(throwing: error) + } + } + + private func recordFailure(_ error: any Error) {} +} + +// MARK: - Supporting Types + +extension RotatingFileWriter { + struct Configuration { + static let live = Configuration( + maximumFileSize: 4 * 1024 * 1024, + maximumArchiveCount: 3, + ) + + /// The maximum size any individual file is allowed to be, in number of bytes + let maximumFileSize: Int + + /// The maximum number of archived files. This does not include the file actively being written to. + /// + /// For example, if the maximum archive count is 3, then at most the rotating file writer will govern 4 total + /// files: the 3 archive files and the one active file. + let maximumArchiveCount: Int + } + + private struct Inbox { + var pendingItems: Deque = [] + var processingState = ProcessingState.idle + + /// Appends an item to the pendingItems queue and marks the inbox as scheduled for processing + /// - Parameter item: The item to queue up + /// - Returns: True if a new processing job should be started. False otherwise. + mutating func append(_ item: PendingItem) -> Bool { + pendingItems.append(item) + + guard processingState == .idle else { return false } + processingState = .scheduled + return true + } + + /// Marks the inbox as being actively processed. + mutating func beginProcessing() { + assert(processingState == .scheduled) + processingState = .processing + } + + /// Retrieves the next item from the queue. If the queue is empty, that means processing is done and we mark the + /// inbox as idle. + /// - Returns: The next item to process from the queue, if the queue is non-empty. Nil otherwise. + mutating func nextPendingItem() -> PendingItem? { + assert(processingState == .processing) + + guard !pendingItems.isEmpty else { + processingState = .idle + return nil + } + + return pendingItems.removeFirst() + } + } + + /// Enumerates the possible states the inbox can be in with respect to processing. + /// + /// Lifecycle: idle → scheduled → processing → idle. + /// + /// Whenever the mutex is released, a nonempty inbox must have processing scheduled or active. + private enum ProcessingState { + case idle + case scheduled + case processing + } + + /// Contains mutable state that the worker needs in order to write to disk. + struct FileState { + var fileClient: WritableFileClient? = nil + } + + /// An enumeration of the various items that can be enqueued in the inbox + private enum PendingItem { + /// A log message to be written to disk + case logMessage(String) + /// An indicator saying a certain number of records were dropped. + case droppedRecordNotice(Int) + /// A request to flush the current file handle to disk + case flush(CheckedContinuation) + } +} diff --git a/mobile/TeleportKit/Sources/SystemClients/File System/FileSystemClient.swift b/mobile/TeleportKit/Sources/SystemClients/File System/FileSystemClient.swift new file mode 100644 index 000000000000..c197d63a2d2e --- /dev/null +++ b/mobile/TeleportKit/Sources/SystemClients/File System/FileSystemClient.swift @@ -0,0 +1,64 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import DependenciesMacros +public import Foundation + +/// An interface to the filesystem operations used by TeleportKit. +/// +/// As TeleportKit needs more filesystem capabilities, it's perfectly natural for this client to grow over time. +@DependencyClient +public struct FileSystemClient: Sendable { + public var createDirectory: @Sendable (_ url: URL) throws -> Void + public var fileExists: @Sendable (_ url: URL) -> Bool = { _ in false } + public var createFile: @Sendable (_ url: URL, _ contents: Data?) throws -> Void + public var openFileForWriting: @Sendable (_ url: URL) throws -> WritableFileClient + public var moveItem: @Sendable (_ sourceURL: URL, _ destinationURL: URL) throws -> Void + public var removeItem: @Sendable (_ url: URL) throws -> Void +} + +public enum FileSystemClientError: Error { + /// File creation failure doesn't surface an error, but rather returns a `Bool`, so we have our own error + case couldNotCreateFile(path: String) +} + +extension FileSystemClient { + public static let liveValue = FileSystemClient( + createDirectory: { url in + try FileManager.default.createDirectory( + at: url, + withIntermediateDirectories: true, + ) + }, + fileExists: { url in + FileManager.default.fileExists(atPath: url.path) + }, + createFile: { url, contents in + guard FileManager.default.createFile(atPath: url.path, contents: contents) else { + throw FileSystemClientError.couldNotCreateFile(path: url.path(percentEncoded: false)) + } + }, + openFileForWriting: { url in + try WritableFileClient.liveValue(FileHandle(forWritingTo: url)) + }, + moveItem: { sourceURL, destinationURL in + try FileManager.default.moveItem(at: sourceURL, to: destinationURL) + }, + removeItem: { url in + try FileManager.default.removeItem(at: url) + }, + ) +} diff --git a/mobile/TeleportKit/Sources/SystemClients/File System/WriteableFileClient.swift b/mobile/TeleportKit/Sources/SystemClients/File System/WriteableFileClient.swift new file mode 100644 index 000000000000..9842a7e397e5 --- /dev/null +++ b/mobile/TeleportKit/Sources/SystemClients/File System/WriteableFileClient.swift @@ -0,0 +1,49 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import DependenciesMacros +public import Foundation + +/// An interface to an open writable file. +/// +/// This interface exists so that code which uses a writeable FileHandle can be tested. For documentation on what the +/// individual functions do, refer to the correspondingly named functions on `FileHandle`. +@DependencyClient +public struct WritableFileClient: Sendable { + public var seekToEnd: @Sendable () throws -> UInt64 + public var write: @Sendable (_ data: Data) throws -> Void + public var synchronize: @Sendable () throws -> Void + public var close: @Sendable () throws -> Void +} + +extension WritableFileClient { + static func liveValue(_ fileHandle: FileHandle) -> WritableFileClient { + WritableFileClient( + seekToEnd: { + try fileHandle.seekToEnd() + }, + write: { data in + try fileHandle.write(contentsOf: data) + }, + synchronize: { + try fileHandle.synchronize() + }, + close: { + try fileHandle.close() + }, + ) + } +} diff --git a/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift b/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift index ade0788a2e2f..1714fb5ec7d5 100644 --- a/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift +++ b/mobile/TeleportKit/Sources/SystemClients/SystemClients.swift @@ -20,4 +20,7 @@ import DependenciesMacros extension DependencyValues { @DependencyEntry(liveValue: SerialNumberClient.liveValue) public nonisolated var serialNumberClient = SerialNumberClient() + + @DependencyEntry(liveValue: FileSystemClient.liveValue) + public nonisolated var fileSystemClient: FileSystemClient } diff --git a/mobile/TeleportKit/Tests/LogBackendsTests/RotatingFileWriterTests.swift b/mobile/TeleportKit/Tests/LogBackendsTests/RotatingFileWriterTests.swift new file mode 100644 index 000000000000..452e7040bac6 --- /dev/null +++ b/mobile/TeleportKit/Tests/LogBackendsTests/RotatingFileWriterTests.swift @@ -0,0 +1,105 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Dependencies +import Foundation +import Logging +import SystemClients +import Testing +@testable import LogBackends + +struct RotatingFileWriterTests { + @Test + func `flush appends all previously enqueued records in order`() async throws { + try await withTemporaryDirectory { directoryURL in + let fileURL = directoryURL.appending(path: "events.log") + let writer = makeWriter(fileURL: fileURL) + + writer.enqueue(logMessage: "first\n") + writer.enqueue(logMessage: "second\n") + try await writer.flush() + + let contents = try? String(contentsOf: fileURL, encoding: .utf8) + #expect(contents == "first\nsecond\n") + } + } + + @Test + func `records enqueued after a completed flush are processed`() async throws { + try await withTemporaryDirectory { directoryURL in + let fileURL = directoryURL.appending(path: "events.log") + let writer = makeWriter(fileURL: fileURL) + + writer.enqueue(logMessage: "first\n") + try await writer.flush() + + writer.enqueue(logMessage: "second\n") + try await writer.flush() + + let contents = try? String(contentsOf: fileURL, encoding: .utf8) + #expect(contents == "first\nsecond\n") + } + } + + @Test + func `writing creates missing parent directories`() async throws { + try await withTemporaryDirectory { directoryURL in + let fileURL = directoryURL.appending(path: "nested/logs/events.log") + let writer = makeWriter(fileURL: fileURL) + + writer.enqueue(logMessage: "record\n") + try await writer.flush() + + let contents = try? String(contentsOf: fileURL, encoding: .utf8) + #expect(contents == "record\n") + } + } + + @Test + func `handler sends its formatted record to the shared writer`() async throws { + try await withTemporaryDirectory { directoryURL in + let fileURL = directoryURL.appending(path: "events.log") + let writer = makeWriter(fileURL: fileURL) + let handler = withDependencies { + $0.date.now = Date(timeIntervalSince1970: 0) + } operation: { + RotatingFileLogHandler(label: "test.logger", writer: writer) + } + + handler.log(event: LogEvent( + level: .info, + message: "hello from the handler", + metadata: nil, + source: nil, + file: "Module/File.swift", + function: "run()", + line: 42, + )) + try await writer.flush() + + let contents = try? String(contentsOf: fileURL, encoding: .utf8) + #expect(contents?.contains("hello from the handler") == true) + } + } +} + +private func makeWriter(fileURL: URL) -> RotatingFileWriter { + withDependencies { + $0.fileSystemClient = FileSystemClient.liveValue + } operation: { + RotatingFileWriter(fileURL: fileURL) + } +} diff --git a/mobile/TeleportKit/Tests/LogBackendsTests/TemporaryDirectory.swift b/mobile/TeleportKit/Tests/LogBackendsTests/TemporaryDirectory.swift new file mode 100644 index 000000000000..e21a5474dcfc --- /dev/null +++ b/mobile/TeleportKit/Tests/LogBackendsTests/TemporaryDirectory.swift @@ -0,0 +1,36 @@ +// Teleport +// Copyright (C) 2026 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 http://www.gnu.org/licenses/ + +import Foundation +import Testing + +func withTemporaryDirectory( + _ operation: (URL) async throws -> Result +) async throws -> Result { + let directoryURL = FileManager.default.temporaryDirectory + .appending(path: "TeleportKitTests-\(UUID())", directoryHint: .isDirectory) + try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true) + + defer { + do { + try FileManager.default.removeItem(at: directoryURL) + } catch { + Issue.record("Failed to remove temporary test directory at \(directoryURL.path): \(error)") + } + } + + return try await operation(directoryURL) +} diff --git a/mobile/Verify/Verify.xcodeproj/project.pbxproj b/mobile/Verify/Verify.xcodeproj/project.pbxproj index c798732a67e1..1562eb3d9a52 100644 --- a/mobile/Verify/Verify.xcodeproj/project.pbxproj +++ b/mobile/Verify/Verify.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 0C94C8CE2FA35F3D00C13C9A /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C94C8CD2FA35ED900C13C9A /* libresolv.9.tbd */; }; CD65C8012FF711CE002FB1CD /* IdentifiedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = CD65C8002FF711CE002FB1CD /* IdentifiedCollections */; }; + CD67F9AF302A74B20043D017 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CD67F9AE302A74B20043D017 /* Logging */; }; + CD67F9B1302A74BD0043D017 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CD67F9B0302A74BD0043D017 /* Logging */; }; + CD67F9B3302A76000043D017 /* Dependencies in Frameworks */ = {isa = PBXBuildFile; productRef = CD67F9B2302A76000043D017 /* Dependencies */; }; + CD67F9B5302A76000043D017 /* DependenciesMacros in Frameworks */ = {isa = PBXBuildFile; productRef = CD67F9B4302A76000043D017 /* DependenciesMacros */; }; CD7999232FE33AE000133048 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD79991B2FE33AE000133048 /* Core.framework */; }; CD7999242FE33AE000133048 /* Core.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CD79991B2FE33AE000133048 /* Core.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CD79992C2FE33F0600133048 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CD79992B2FE33EFF00133048 /* libresolv.9.tbd */; }; @@ -23,6 +27,8 @@ CDAA8BB22FFD55CC00E6CA87 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB12FFD55CC00E6CA87 /* GRDB */; }; CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */; }; CDAA8BB72FFD5A1300E6CA87 /* SQLiteDataTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CDAA8BB62FFD5A1300E6CA87 /* SQLiteDataTestSupport */; }; + CDEA38EA302CD5630004B164 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = CDEA38E9302CD5630004B164 /* Collections */; }; + CDEA38EC302CD5770004B164 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = CDEA38EB302CD5770004B164 /* Collections */; }; CDFCE7053022A9D4002E6773 /* LogBackends in Frameworks */ = {isa = PBXBuildFile; productRef = CDFCE7043022A9D4002E6773 /* LogBackends */; }; CDFCE7073022A9D4002E6773 /* LogBackends in Frameworks */ = {isa = PBXBuildFile; productRef = CDFCE7063022A9D4002E6773 /* LogBackends */; }; /* End PBXBuildFile section */ @@ -102,10 +108,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CD67F9AF302A74B20043D017 /* Logging in Frameworks */, CDFCE7053022A9D4002E6773 /* LogBackends in Frameworks */, CD9AEFAC3022782200E2FC14 /* SystemClients in Frameworks */, CDAA8BB52FFD5A1300E6CA87 /* SQLiteData in Frameworks */, + CD67F9B5302A76000043D017 /* DependenciesMacros in Frameworks */, 0C94C8CE2FA35F3D00C13C9A /* libresolv.9.tbd in Frameworks */, + CDEA38EA302CD5630004B164 /* Collections in Frameworks */, + CD67F9B3302A76000043D017 /* Dependencies in Frameworks */, CDAA8BB22FFD55CC00E6CA87 /* GRDB in Frameworks */, CD65C8012FF711CE002FB1CD /* IdentifiedCollections in Frameworks */, CD7999232FE33AE000133048 /* Core.framework in Frameworks */, @@ -117,6 +127,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CDEA38EC302CD5770004B164 /* Collections in Frameworks */, + CD67F9B1302A74BD0043D017 /* Logging in Frameworks */, CDFCE7073022A9D4002E6773 /* LogBackends in Frameworks */, CD9AEFAE3022782900E2FC14 /* SystemClients in Frameworks */, CD79992C2FE33F0600133048 /* libresolv.9.tbd in Frameworks */, @@ -209,6 +221,10 @@ CDAA8BB42FFD5A1300E6CA87 /* SQLiteData */, CD9AEFAB3022782200E2FC14 /* SystemClients */, CDFCE7043022A9D4002E6773 /* LogBackends */, + CD67F9AE302A74B20043D017 /* Logging */, + CD67F9B2302A76000043D017 /* Dependencies */, + CD67F9B4302A76000043D017 /* DependenciesMacros */, + CDEA38E9302CD5630004B164 /* Collections */, ); productName = Verify; productReference = 0C94C8522FA232E600C13C9A /* Verify.app */; @@ -238,6 +254,8 @@ CD7999342FE346B800133048 /* DependenciesMacros */, CD9AEFAD3022782900E2FC14 /* SystemClients */, CDFCE7063022A9D4002E6773 /* LogBackends */, + CD67F9B0302A74BD0043D017 /* Logging */, + CDEA38EB302CD5770004B164 /* Collections */, ); productName = Core; productReference = CD79991B2FE33AE000133048 /* Core.framework */; @@ -310,6 +328,8 @@ CDAA8BB32FFD5A1200E6CA87 /* XCRemoteSwiftPackageReference "sqlite-data" */, CD920CEE301D13500027C579 /* XCRemoteSwiftPackageReference "swift-sharing" */, CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */, + CD67F9AD302A74B20043D017 /* XCRemoteSwiftPackageReference "swift-log" */, + CDEA38E8302CD5630004B164 /* XCRemoteSwiftPackageReference "swift-collections" */, ); preferredProjectObjectVersion = 77; productRefGroup = 0C94C8532FA232E600C13C9A /* Products */; @@ -586,6 +606,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Verify/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Teleport Verify"; + INFOPLIST_KEY_NSCameraUsageDescription = "Teleport Verify uses the built-in QR code scanner to receive enrollment data from the Teleport Web UI"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Teleport Verify uses Face ID to confirm your presence when proving this device's identity"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -634,6 +656,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Verify/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Teleport Verify"; + INFOPLIST_KEY_NSCameraUsageDescription = "Teleport Verify uses the built-in QR code scanner to receive enrollment data from the Teleport Web UI"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Teleport Verify uses Face ID to confirm your presence when proving this device's identity"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -872,6 +896,14 @@ minimumVersion = 1.1.1; }; }; + CD67F9AD302A74B20043D017 /* XCRemoteSwiftPackageReference "swift-log" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-log"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.14.0; + }; + }; CD7999312FE345D400133048 /* XCRemoteSwiftPackageReference "swift-dependencies" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-dependencies"; @@ -924,6 +956,14 @@ minimumVersion = 0.63.3; }; }; + CDEA38E8302CD5630004B164 /* XCRemoteSwiftPackageReference "swift-collections" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-collections.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.6.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -932,6 +972,26 @@ package = CD65C7FF2FF711CE002FB1CD /* XCRemoteSwiftPackageReference "swift-identified-collections" */; productName = IdentifiedCollections; }; + CD67F9AE302A74B20043D017 /* Logging */ = { + isa = XCSwiftPackageProductDependency; + package = CD67F9AD302A74B20043D017 /* XCRemoteSwiftPackageReference "swift-log" */; + productName = Logging; + }; + CD67F9B0302A74BD0043D017 /* Logging */ = { + isa = XCSwiftPackageProductDependency; + package = CD67F9AD302A74B20043D017 /* XCRemoteSwiftPackageReference "swift-log" */; + productName = Logging; + }; + CD67F9B2302A76000043D017 /* Dependencies */ = { + isa = XCSwiftPackageProductDependency; + package = CD7999312FE345D400133048 /* XCRemoteSwiftPackageReference "swift-dependencies" */; + productName = Dependencies; + }; + CD67F9B4302A76000043D017 /* DependenciesMacros */ = { + isa = XCSwiftPackageProductDependency; + package = CD7999312FE345D400133048 /* XCRemoteSwiftPackageReference "swift-dependencies" */; + productName = DependenciesMacros; + }; CD7999282FE33B7E00133048 /* SwiftLintBuildToolPlugin */ = { isa = XCSwiftPackageProductDependency; package = CDDEB8752FE1F5F10052BC27 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */; @@ -997,6 +1057,16 @@ package = CDDEB8752FE1F5F10052BC27 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */; productName = "plugin:SwiftLintBuildToolPlugin"; }; + CDEA38E9302CD5630004B164 /* Collections */ = { + isa = XCSwiftPackageProductDependency; + package = CDEA38E8302CD5630004B164 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = Collections; + }; + CDEA38EB302CD5770004B164 /* Collections */ = { + isa = XCSwiftPackageProductDependency; + package = CDEA38E8302CD5630004B164 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = Collections; + }; CDFCE7043022A9D4002E6773 /* LogBackends */ = { isa = XCSwiftPackageProductDependency; package = CD921947302148B80027C579 /* XCLocalSwiftPackageReference "../TeleportKit" */; diff --git a/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift b/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift index 4b37d00c1ef8..cc548f85b3ad 100644 --- a/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift +++ b/mobile/Verify/Verify/App Lifecycle/VerifyAppModel.swift @@ -42,10 +42,7 @@ extension VerifyAppModel { landingViewModel.navigateToDeviceEnrollment(with: deepLink) } } catch { - logger.warning("Failed to parse deep link", metadata: [ - "scannedURL": "\(url)", - "error": .string(String(describing: error)), - ]) + logger.warning("Failed to parse deep link", error: error, metadata: ["scannedURL": "\(url)"]) landingViewModel.showParserError(errorMessage: error.localizedDescription) } } diff --git a/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift index 1e88baff162b..0c00fbde5ce7 100644 --- a/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift +++ b/mobile/Verify/Verify/Device Trust/Credentials/SecureEnclaveCredentialStore.swift @@ -230,9 +230,10 @@ extension SecureEnclaveCredentialStore { ) else { if let error { - logger.error("Could not create the Device Trust access-control policy", metadata: [ - "error": "\(error.takeRetainedValue())", - ]) + logger.error( + "Could not create the Device Trust access-control policy", + error: error.takeRetainedValue(), + ) } throw DeviceTrustCredentialError.accessControlCreationFailed diff --git a/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift b/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift index 69273273fa4a..835fd771c186 100644 --- a/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift +++ b/mobile/Verify/Verify/Features/Enrollment/EnrollDeviceViewModel.swift @@ -90,9 +90,7 @@ class EnrollDeviceViewModel { loadingState = .success("fake-token-\(cluster?.id.uuidString ?? "(nil)")") } catch { - logger.error("Failed to request enrollment token", metadata: [ - "error": .string(String(describing: error)), - ]) + logger.error("Failed to request enrollment token", error: error) loadingState = .failure(error) } } diff --git a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift index c814893afbdd..6879324f23b7 100644 --- a/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift +++ b/mobile/Verify/Verify/Features/Landing/LandingViewModel.swift @@ -181,9 +181,7 @@ extension LandingViewModel { try deleteOperation().execute(db) } } catch { - logger.warning("Failed to forget clusters", metadata: [ - "error": .string(String(describing: error)), - ]) + logger.warning("Failed to forget clusters", error: error) destination = .notice(AlertState( title: { TextState("Could Not Forget Clusters") diff --git a/mobile/Verify/Verify/Info.plist b/mobile/Verify/Verify/Info.plist index 9e0f895cb287..fa817fb92277 100644 --- a/mobile/Verify/Verify/Info.plist +++ b/mobile/Verify/Verify/Info.plist @@ -2,10 +2,6 @@ - NSCameraUsageDescription - Teleport Verify uses the built-in QR code scanner to receive enrollment data from the Teleport Web UI - NSFaceIDUsageDescription - Teleport Verify uses Face ID to confirm your presence when proving this device's identity CFBundleURLTypes diff --git a/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift b/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift index 9f8ebf3ad96f..e3ebf0c8cd88 100644 --- a/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift +++ b/mobile/Verify/Verify/Persistence/SQLite/AppDatabase.swift @@ -87,9 +87,7 @@ extension AppDatabase { #if DEBUG fatalError("Database initialization failed: \(error)") #else - logger.critical("Database initialization failed; falling back to in-memory database", metadata: [ - "error": .string(String(describing: error)), - ]) + logger.critical("Database initialization failed; falling back to in-memory database", error: error) database = makeInMemoryDatabase() #endif } From 6dbccdd8adcee1f6c6951dbefba93448a18f939d Mon Sep 17 00:00:00 2001 From: Luke Okraszewski Date: Wed, 19 Aug 2026 12:05:07 +0000 Subject: [PATCH 309/347] Linux: fix potential deadlock/segfault when calling into libnss for user mgmt (#295) Export-Source-Commit: 12f28f2b1aa27c84df9f608557b8dc6f0de6cbb2 --- .golangci.yml | 9 ++ lib/client/api.go | 4 +- lib/client/db/mysql/optionfile.go | 4 +- lib/client/db/postgres/servicefile.go | 4 +- lib/devicetrust/native/device_darwin.go | 4 +- lib/devicetrust/native/device_linux.go | 4 +- lib/integrations/azureoidc/private.go | 4 +- lib/integrations/azureoidc/provision.go | 4 +- .../authorizedkeys/authorized_keys.go | 10 +- .../authorizedkeys/users_list_linux.go | 74 ----------- lib/srv/desktop/linux_server.go | 6 +- lib/srv/desktop/x11/xsession.go | 4 +- lib/srv/mcp/stdio.go | 4 +- lib/srv/regular/sshserver.go | 3 +- lib/srv/term.go | 3 +- lib/srv/usermgmt_linux.go | 9 +- lib/sshutils/reexec/reexec.go | 3 +- lib/tbot/bot/destination/directory.go | 4 +- lib/tbot/botfs/fs_linux.go | 7 +- .../connectmycomputer/connectmycomputer.go | 4 +- lib/utils/disk.go | 8 +- session/host/hostusers.go | 6 +- session/host/user/common.go | 22 ++++ session/host/user/doc.go | 42 ++++++ .../host/user/user_darwin.go | 6 +- session/host/user/user_forward.go | 48 +++++++ session/host/user/user_linux_cgo.go | 120 ++++++++++++++++++ .../host/user/user_other.go | 4 +- session/reexec/reexec.go | 17 +-- session/reexec/reexecsftp/sftp.go | 4 +- session/shell/shell_unix.go | 5 +- tool/tbot/init.go | 17 +-- tool/teleport/common/teleport.go | 4 +- tool/tsh/common/tsh.go | 4 +- 34 files changed, 325 insertions(+), 150 deletions(-) delete mode 100644 lib/secretsscanner/authorizedkeys/users_list_linux.go create mode 100644 session/host/user/common.go create mode 100644 session/host/user/doc.go rename lib/secretsscanner/authorizedkeys/users_list_darwin.go => session/host/user/user_darwin.go (93%) create mode 100644 session/host/user/user_forward.go create mode 100644 session/host/user/user_linux_cgo.go rename lib/secretsscanner/authorizedkeys/users_list_other.go => session/host/user/user_other.go (94%) diff --git a/.golangci.yml b/.golangci.yml index 36e1fd50f045..59e4dbb81cc4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -393,6 +393,9 @@ linters: - pattern: ^ssh\.(Dial|NewClient|NewClientConn)$ pkg: ^golang\.org/x/crypto/ssh$ msg: Use api/ssh package to construct SSH clients with proper defaults + - pkg: '^os/user$' + pattern: '^user\.(Lookup|LookupId|LookupGroup|LookupGroupId|Current|User\.GroupIds)$' + msg: 'os/user lookup APIs potentially unsafe; use the session/host/user wrapper instead' - pattern: ^ssh\.(Dial|NewClientWithTimeout|NewClientConnWithTimeout)$ pkg: ^github\.com/gravitational/teleport/api/observability/tracing/ssh$ msg: Use api/ssh package to construct SSH clients with proper defaults @@ -567,6 +570,12 @@ linters: path: ^api/ssh/ text: tracessh.NewClientConnWithTimeout + # Allow session/host/user and tests to use os/user functions directly. + - path: '(_test\.go$|^api/|^integration/helpers/|^lib/utils/testutils|^session/host/user/)' + text: "session/host/user" + linters: + - forbidigo + # TODO: Fix forced type assertions in the rest of the codebase and remove this exclusion so that the linter can # enforce this rule everywhere. - linters: diff --git a/lib/client/api.go b/lib/client/api.go index a0170b981833..e6ff087edf08 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -32,7 +32,6 @@ import ( "net" "net/url" "os" - "os/user" "path/filepath" "runtime" "slices" @@ -103,6 +102,7 @@ import ( "github.com/gravitational/teleport/lib/utils" logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/utils/set" + hostuser "github.com/gravitational/teleport/session/host/user" ) const ( @@ -5012,7 +5012,7 @@ func loopbackPool(proxyAddr string) *x509.CertPool { // Username returns the current user's username func Username() (string, error) { - u, err := user.Current() + u, err := hostuser.Current() if err != nil { return "", trace.Wrap(err) } diff --git a/lib/client/db/mysql/optionfile.go b/lib/client/db/mysql/optionfile.go index 02716ffa2f23..eefea41bc4e1 100644 --- a/lib/client/db/mysql/optionfile.go +++ b/lib/client/db/mysql/optionfile.go @@ -20,7 +20,6 @@ package mysql import ( "os" - "os/user" "path/filepath" "strconv" "strings" @@ -29,6 +28,7 @@ import ( "gopkg.in/ini.v1" "github.com/gravitational/teleport/lib/client/db/profile" + hostuser "github.com/gravitational/teleport/session/host/user" ) func init() { @@ -51,7 +51,7 @@ type OptionFile struct { func DefaultConfigPath() (string, error) { home, err := os.UserHomeDir() if err != nil || home == "" { - usr, err := user.Current() + usr, err := hostuser.Current() if err != nil { return "", trace.ConvertSystemError(err) } diff --git a/lib/client/db/postgres/servicefile.go b/lib/client/db/postgres/servicefile.go index 369b0a54cc13..f4a3077f96d7 100644 --- a/lib/client/db/postgres/servicefile.go +++ b/lib/client/db/postgres/servicefile.go @@ -20,7 +20,6 @@ package postgres import ( "os" - "os/user" "path/filepath" "strconv" "strings" @@ -29,6 +28,7 @@ import ( "gopkg.in/ini.v1" "github.com/gravitational/teleport/lib/client/db/profile" + hostuser "github.com/gravitational/teleport/session/host/user" ) func init() { @@ -52,7 +52,7 @@ func defaultConfigPath() (string, error) { // TODO(r0mant): Check PGSERVICEFILE and PGSYSCONFDIR env vars as well. home, err := os.UserHomeDir() if err != nil || home == "" { - usr, err := user.Current() + usr, err := hostuser.Current() if err != nil { return "", trace.ConvertSystemError(err) } diff --git a/lib/devicetrust/native/device_darwin.go b/lib/devicetrust/native/device_darwin.go index e35620abd2b3..d2a9b2905710 100644 --- a/lib/devicetrust/native/device_darwin.go +++ b/lib/devicetrust/native/device_darwin.go @@ -35,7 +35,6 @@ import ( "io/fs" "log/slog" "os/exec" - "os/user" "strings" "sync" "unsafe" @@ -46,6 +45,7 @@ import ( devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/lib/darwin" + hostuser "github.com/gravitational/teleport/session/host/user" ) func enrollDeviceInit() (*devicepb.EnrollDeviceInit, error) { @@ -115,7 +115,7 @@ func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) return nil, trace.Wrap(statusErrorFromC(res)) } - osUser, err := user.Current() + osUser, err := hostuser.Current() if err != nil { return nil, trace.Wrap(err, "reading current user") } diff --git a/lib/devicetrust/native/device_linux.go b/lib/devicetrust/native/device_linux.go index ef00fbc94c50..e12b14cfacc0 100644 --- a/lib/devicetrust/native/device_linux.go +++ b/lib/devicetrust/native/device_linux.go @@ -29,7 +29,6 @@ import ( "log/slog" "os" "os/exec" - "os/user" "time" "github.com/google/go-attestation/attest" @@ -39,6 +38,7 @@ import ( "github.com/gravitational/teleport" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/lib/linux" + hostuser "github.com/gravitational/teleport/session/host/user" ) // deviceStateFolderName starts without a "." on Linux systems. @@ -167,7 +167,7 @@ func collectDeviceData(mode CollectDataMode) (*devicepb.DeviceCollectedData, err baseBoardSerialNumber = dmiInfo.BoardSerial } - u, err := user.Current() + u, err := hostuser.Current() if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/integrations/azureoidc/private.go b/lib/integrations/azureoidc/private.go index 932bc5c61d6f..7594f3adf6b7 100644 --- a/lib/integrations/azureoidc/private.go +++ b/lib/integrations/azureoidc/private.go @@ -25,7 +25,6 @@ import ( "net/http" "net/url" "os" - "os/user" "path" "path/filepath" "strings" @@ -34,6 +33,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/lib/defaults" + hostuser "github.com/gravitational/teleport/session/host/user" ) type msalTokenCache struct { @@ -51,7 +51,7 @@ type exchangeResponse struct { // getRefreshTokens returns all current refresh tokens from the Azure CLI token cache. func getRefreshTokens() ([]msalToken, error) { - usr, err := user.Current() + usr, err := hostuser.Current() if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/integrations/azureoidc/provision.go b/lib/integrations/azureoidc/provision.go index 7cecf1f58893..e7487770da1c 100644 --- a/lib/integrations/azureoidc/provision.go +++ b/lib/integrations/azureoidc/provision.go @@ -23,13 +23,13 @@ import ( "fmt" "os" "os/exec" - "os/user" "path/filepath" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/gravitational/trace" "github.com/gravitational/teleport/lib/msgraph" + hostuser "github.com/gravitational/teleport/session/host/user" ) // createGraphClient creates a new graph client from ambient credentials (Azure CLI credentials cache). @@ -60,7 +60,7 @@ func EnsureAZLogin(ctx context.Context) error { } func getAzureDir() (string, error) { - usr, err := user.Current() + usr, err := hostuser.Current() if err != nil { return "", trace.Wrap(err) } diff --git a/lib/secretsscanner/authorizedkeys/authorized_keys.go b/lib/secretsscanner/authorizedkeys/authorized_keys.go index ae7f57364621..fc90ec72b3ce 100644 --- a/lib/secretsscanner/authorizedkeys/authorized_keys.go +++ b/lib/secretsscanner/authorizedkeys/authorized_keys.go @@ -43,11 +43,7 @@ import ( clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" "github.com/gravitational/teleport/api/types/accessgraph" "github.com/gravitational/teleport/api/utils/retryutils" -) - -var ( - // ErrUnsupportedPlatform is returned when the operating system is not supported. - ErrUnsupportedPlatform = errors.New("unsupported platform") + hostuser "github.com/gravitational/teleport/session/host/user" ) // Watcher watches for changes to authorized_keys files @@ -96,7 +92,7 @@ func NewWatcher(ctx context.Context, config WatcherConfig) (*Watcher, error) { switch platform := getOS(config); platform { case constants.LinuxOS, constants.DarwinOS: default: - return nil, trace.Wrap(ErrUnsupportedPlatform) + return nil, trace.Wrap(hostuser.ErrUnsupportedPlatform) } if config.HostID == "" { @@ -112,7 +108,7 @@ func NewWatcher(ctx context.Context, config WatcherConfig) (*Watcher, error) { config.Clock = clockwork.NewRealClock() } if config.getHostUsers == nil { - config.getHostUsers = getHostUsers + config.getHostUsers = hostuser.GetHostUsers } w := &Watcher{ diff --git a/lib/secretsscanner/authorizedkeys/users_list_linux.go b/lib/secretsscanner/authorizedkeys/users_list_linux.go deleted file mode 100644 index 9e750d212387..000000000000 --- a/lib/secretsscanner/authorizedkeys/users_list_linux.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 authorizedkeys - -/* -#define _XOPEN_SOURCE 500 -#include -*/ -import "C" - -import ( - "os/user" - "strconv" - "sync" - - "github.com/gravitational/trace" -) - -// pwentLock should be acquired when using MT-Unsafe race:pwent functions (i.e. -// setpwent/getpwent/endpwent). -var pwentLock sync.Mutex - -// getHostUsers returns the list of all users on the host from the user -// directory (depending on system configuration this can be /etc/passwd, -// LDAP...). -func getHostUsers() ([]user.User, error) { - pwentLock.Lock() - defer pwentLock.Unlock() - - C.setpwent() - defer C.endpwent() - - var results []user.User - for { - result, err := C.getpwent() - // cgo error convention, check the return value before errno - if result != nil { - results = append(results, passwdC2Go(result)) - continue - } - if err != nil { - return nil, trace.Wrap(err) - } - return results, nil - } -} - -// passwdC2Go converts `passwd` struct from C to golang native struct -func passwdC2Go(passwdC *C.struct_passwd) user.User { - name := C.GoString(passwdC.pw_name) - return user.User{ - Name: name, - Username: name, - Uid: strconv.FormatUint(uint64(passwdC.pw_uid), 10), - Gid: strconv.FormatUint(uint64(passwdC.pw_gid), 10), - HomeDir: C.GoString(passwdC.pw_dir), - } -} diff --git a/lib/srv/desktop/linux_server.go b/lib/srv/desktop/linux_server.go index 76a038e89448..ca334f0e3116 100644 --- a/lib/srv/desktop/linux_server.go +++ b/lib/srv/desktop/linux_server.go @@ -26,7 +26,6 @@ import ( "maps" "net" "os" - "os/user" "regexp" "slices" "strconv" @@ -65,6 +64,7 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" logutils "github.com/gravitational/teleport/lib/utils/log" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/reexec" ) @@ -749,11 +749,11 @@ func (sess *linuxSession) handleClientHello(m *tdpb.ClientHello) error { } func (sess *linuxSession) changeAuthorityFileOwnership(m *tdpb.ClientHello) error { - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return trace.Wrap(err, "failed to get current user") } - targetUser, err := user.Lookup(m.Username) + targetUser, err := hostuser.Lookup(m.Username) if err != nil { return trace.Wrap(err, "failed to lookup user %s", m.Username) } diff --git a/lib/srv/desktop/x11/xsession.go b/lib/srv/desktop/x11/xsession.go index b0ab7ce6e200..6abc347fba84 100644 --- a/lib/srv/desktop/x11/xsession.go +++ b/lib/srv/desktop/x11/xsession.go @@ -23,7 +23,6 @@ import ( "log/slog" "os" "os/exec" - "os/user" "path/filepath" "regexp" "strings" @@ -36,6 +35,7 @@ import ( reexecutils "github.com/gravitational/teleport/lib/sshutils/reexec" logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/session/envutils" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/reexec" "github.com/gravitational/teleport/session/reexec/reexecconstants" ) @@ -126,7 +126,7 @@ func StartTeleportExecXSession(ctx context.Context, cfg *XSessionConfig) (*reexe return nil, trace.BadParameter("missing parameter ChildLogConfig") } - if _, err := user.Lookup(cfg.Login); err != nil { + if _, err := hostuser.Lookup(cfg.Login); err != nil { return nil, trace.Wrap(err) } diff --git a/lib/srv/mcp/stdio.go b/lib/srv/mcp/stdio.go index 264391aa9ba5..8b97ed4bdf7c 100644 --- a/lib/srv/mcp/stdio.go +++ b/lib/srv/mcp/stdio.go @@ -26,7 +26,6 @@ import ( "net" "os" "os/exec" - "os/user" "sync" "syscall" "time" @@ -36,6 +35,7 @@ import ( "github.com/gravitational/teleport/lib/utils/mcputils" hostutils "github.com/gravitational/teleport/session/host" + hostuser "github.com/gravitational/teleport/session/host/user" ) // handleAuthErrStdio starts a stdio message reader and replies with the auth @@ -238,7 +238,7 @@ func makeExecServerRunner(ctx context.Context, session *sessionHandler) (stdioSe } // Set host user. - hostUser, err := user.Lookup(mcpSpec.RunAsHostUser) + hostUser, err := hostuser.Lookup(mcpSpec.RunAsHostUser) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index b2f1ed9924a7..06c57ea92377 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -75,6 +75,7 @@ import ( "github.com/gravitational/teleport/lib/sshutils" reexecutils "github.com/gravitational/teleport/lib/sshutils/reexec" "github.com/gravitational/teleport/lib/utils" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/networking" "github.com/gravitational/teleport/session/networking/x11" "github.com/gravitational/teleport/session/pam/pamcfg" @@ -1103,7 +1104,7 @@ func (s *Server) startAuthorizedKeysManager(ctx context.Context, auth authclient Clock: s.clock, }, ) - if errors.Is(err, authorizedkeysreporter.ErrUnsupportedPlatform) { + if errors.Is(err, hostuser.ErrUnsupportedPlatform) { return nil } else if err != nil { return trace.Wrap(err) diff --git a/lib/srv/term.go b/lib/srv/term.go index 4da82245e89c..42edd3202d1d 100644 --- a/lib/srv/term.go +++ b/lib/srv/term.go @@ -41,6 +41,7 @@ import ( tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh" rsession "github.com/gravitational/teleport/lib/session" reexecutils "github.com/gravitational/teleport/lib/sshutils/reexec" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/reexec" "github.com/gravitational/teleport/session/reexec/reexecconstants" ) @@ -535,7 +536,7 @@ func getOwner(login string, lookupUser LookupUser, lookupGroup LookupGroup) (int // setOwner changes the owner and mode of the TTY. func (t *terminal) setOwner() error { - uid, gid, mode, err := getOwner(t.serverContext.Identity.Login, user.Lookup, user.LookupGroup) + uid, gid, mode, err := getOwner(t.serverContext.Identity.Login, hostuser.Lookup, hostuser.LookupGroup) if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/usermgmt_linux.go b/lib/srv/usermgmt_linux.go index fcf0b589cebd..9a92c266d385 100644 --- a/lib/srv/usermgmt_linux.go +++ b/lib/srv/usermgmt_linux.go @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/session/host" + hostuser "github.com/gravitational/teleport/session/host/user" ) // HostUsersProvisioningBackend is used to implement HostUsersBackend @@ -74,7 +75,7 @@ func newHostSudoersBackend(uuid string) (HostSudoersBackend, error) { // Lookup implements host user information lookup func (*HostUsersProvisioningBackend) Lookup(username string) (*user.User, error) { - usr, err := user.Lookup(username) + usr, err := hostuser.Lookup(username) if err != nil { if !errors.Is(err, user.UnknownUserError(username)) && strings.Contains(err.Error(), "no such file or directory") { return nil, trace.Wrap(err, "looking up user %q, sources configured for passwd in host's /etc/nsswitch.conf may be misconfigured", username) @@ -88,17 +89,17 @@ func (*HostUsersProvisioningBackend) Lookup(username string) (*user.User, error) // UserGIDs returns the list of group IDs for a user func (*HostUsersProvisioningBackend) UserGIDs(u *user.User) ([]string, error) { - return u.GroupIds() + return hostuser.GroupIds(u) } // LookupGroup host group information lookup func (*HostUsersProvisioningBackend) LookupGroup(name string) (*user.Group, error) { - return user.LookupGroup(name) + return hostuser.LookupGroup(name) } // LookupGroup host group information lookup by GID func (*HostUsersProvisioningBackend) LookupGroupByID(gid string) (*user.Group, error) { - return user.LookupGroupId(gid) + return hostuser.LookupGroupId(gid) } // UpdateUser sets a user's groups and default shell, replacing their existing groups. diff --git a/lib/sshutils/reexec/reexec.go b/lib/sshutils/reexec/reexec.go index 061ac354d229..079a0c18bdd5 100644 --- a/lib/sshutils/reexec/reexec.go +++ b/lib/sshutils/reexec/reexec.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/trace" decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1" + hostuser "github.com/gravitational/teleport/session/host/user" ) // ErrorContext contains context used to enrich child process launch errors. @@ -65,7 +66,7 @@ func ChildErrorWithContext(errMsg string, context *ErrorContext) string { unknownUserError := user.UnknownUserError(context.Login) switch { case strings.Contains(errMsg, "failed to open PAM context"): // PAM errors are often cause by an unknown user. - if _, err := user.Lookup(context.Login); errors.Is(err, unknownUserError) { + if _, err := hostuser.Lookup(context.Login); errors.Is(err, unknownUserError) { if ambiguousHostUserDenial { return ambiguousHostUserError() } diff --git a/lib/tbot/bot/destination/directory.go b/lib/tbot/bot/destination/directory.go index 3e32ed8651a2..eb76161b3649 100644 --- a/lib/tbot/bot/destination/directory.go +++ b/lib/tbot/bot/destination/directory.go @@ -24,7 +24,6 @@ import ( "fmt" "io/fs" "os" - "os/user" "path" "path/filepath" "strings" @@ -37,6 +36,7 @@ import ( "github.com/gravitational/teleport/lib/tbot/botfs" "github.com/gravitational/teleport/lib/tbot/internal/encoding" "github.com/gravitational/teleport/lib/utils" + hostuser "github.com/gravitational/teleport/session/host/user" ) const DirectoryType = "directory" @@ -233,7 +233,7 @@ func (dd *Directory) ACLsEnabled() bool { // This will not attempt to correct any issues, but will cause a hard failure if // `acls: required` is configured and issues are detected. func (dd *Directory) verifyLegacyACLs(keys []string) error { - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { // user.Current will fail if the user id does not exist in /etc/passwd // as is the case with some containerized environments. diff --git a/lib/tbot/botfs/fs_linux.go b/lib/tbot/botfs/fs_linux.go index ce5947d0544c..b6097c26586f 100644 --- a/lib/tbot/botfs/fs_linux.go +++ b/lib/tbot/botfs/fs_linux.go @@ -39,6 +39,7 @@ import ( "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/set" + hostuser "github.com/gravitational/teleport/session/host/user" ) // mostACLRead is a permission mode granting readonly access to a file. @@ -485,7 +486,7 @@ func resolveACLReaderSelector(s *ACLSelector, dir bool) (acl.Entry, error) { }, nil } - user, err := user.Lookup(s.User) + user, err := hostuser.Lookup(s.User) if err != nil { return acl.Entry{}, trace.Wrap(err) } @@ -506,7 +507,7 @@ func resolveACLReaderSelector(s *ACLSelector, dir bool) (acl.Entry, error) { }, nil } - group, err := user.LookupGroup(s.Group) + group, err := hostuser.LookupGroup(s.Group) if err != nil { return acl.Entry{}, trace.Wrap(err) } @@ -711,7 +712,7 @@ func GetOwner(fileInfo fs.FileInfo) (*user.User, error) { return nil, trace.NotImplemented("Cannot verify file ownership on this platform.") } - user, err := user.LookupId(strconv.Itoa(int(info.Uid))) + user, err := hostuser.LookupId(strconv.Itoa(int(info.Uid))) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/teleterm/services/connectmycomputer/connectmycomputer.go b/lib/teleterm/services/connectmycomputer/connectmycomputer.go index aa40d5181cc5..ec4ad2be9f1c 100644 --- a/lib/teleterm/services/connectmycomputer/connectmycomputer.go +++ b/lib/teleterm/services/connectmycomputer/connectmycomputer.go @@ -23,7 +23,6 @@ import ( "fmt" "log/slog" "os" - "os/user" "path/filepath" "slices" "strings" @@ -42,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/teleterm/clusters" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/hostid" + hostuser "github.com/gravitational/teleport/session/host/user" ) type RoleSetup struct { @@ -98,7 +98,7 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity } } - systemUser, err := user.Current() + systemUser, err := hostuser.Current() if err != nil { return noCertsReloaded, trace.Wrap(err) } diff --git a/lib/utils/disk.go b/lib/utils/disk.go index 8078d98a0172..608e6fd140e4 100644 --- a/lib/utils/disk.go +++ b/lib/utils/disk.go @@ -30,6 +30,8 @@ import ( "syscall" "github.com/gravitational/trace" + + hostuser "github.com/gravitational/teleport/session/host/user" ) // PercentUsed returns percentage of disk space used. The percentage of disk @@ -93,12 +95,12 @@ func CanUserWriteTo(path string) (bool, error) { var usr *user.User if ogUser := os.Getenv("SUDO_USER"); ogUser != "" { - usr, err = user.Lookup(ogUser) + usr, err = hostuser.Lookup(ogUser) if err != nil { return false, trace.NotFound("could not determine original user: %+v", err) } } else { - usr, err = user.Current() + usr, err = hostuser.Current() if err != nil { return false, trace.NotFound("could not determine current user: %+v", err) } @@ -116,7 +118,7 @@ func CanUserWriteTo(path string) (bool, error) { } // file and user have a group in common - groupIDs, err := usr.GroupIds() + groupIDs, err := hostuser.GroupIds(usr) if err != nil { return false, trace.NotFound("could not determine current user group ids: %+v", err) } diff --git a/session/host/hostusers.go b/session/host/hostusers.go index 144786ddbe6d..d1b2027a7296 100644 --- a/session/host/hostusers.go +++ b/session/host/hostusers.go @@ -34,6 +34,8 @@ import ( "syscall" "github.com/gravitational/trace" + + hostuser "github.com/gravitational/teleport/session/host/user" ) // man GROUPADD(8), exit codes section @@ -181,7 +183,7 @@ func UserDel(username string) (exitCode int, err error) { if err != nil { return -1, trace.NotFound("cant find userdel binary: %s", err) } - u, err := user.Lookup(username) + u, err := hostuser.Lookup(username) if err != nil { return -1, trace.Wrap(err) } @@ -390,7 +392,7 @@ func GetHostUserCredential(localUser *user.User) (*syscall.Credential, error) { } // Lookup supplementary groups for the user. - userGroups, err := localUser.GroupIds() + userGroups, err := hostuser.GroupIds(localUser) if err != nil { return nil, trace.Wrap(err) } diff --git a/session/host/user/common.go b/session/host/user/common.go new file mode 100644 index 000000000000..46fa9000e396 --- /dev/null +++ b/session/host/user/common.go @@ -0,0 +1,22 @@ +// Teleport +// Copyright (C) 2026 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 user + +import "errors" + +// ErrUnsupportedPlatform is returned when the operating system is not supported. +var ErrUnsupportedPlatform = errors.New("unsupported platform") diff --git a/session/host/user/doc.go b/session/host/user/doc.go new file mode 100644 index 000000000000..0f64e8bb36e7 --- /dev/null +++ b/session/host/user/doc.go @@ -0,0 +1,42 @@ +// Teleport +// Copyright (C) 2026 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 user provides a thin concurrency safe wrapper around the +// `os/user` APIs for Linux when CGO is enabled. +// +// On Linux, the `os/user` package may call into C-based NSS backends via cgo. +// Some NSS implementations (or configurations) are not safe for concurrent +// use and can deadlock or crash when lookups are performed concurrently. +// +// To avoid such failures, this package serializes calls to a small set of `os/user` +// APIs on linux+cgo builds. +// Non-Linux or non-cgo builds forward calls to the stdlib without locking. +// +// References: +// +// - Debian bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=831390 +// - Teleport issue: https://github.com/gravitational/teleport/issues/69662 +// +// Note that this package is placed in `session/host` specifically because the `session` tree +// is effectively a submodule within Teleport which guards against any imports from Teleport itself. +// This was done to optimize the session helper load times and avoid bleeding runtime dependencies into the +// session helper which would incease the load time and degrade latency. Since Teleport still calls +// into the session helper code in the same process for which a mutex must be shared, the user package +// is placed here despite less than ideal code organization. +package user + +// TODO(okraport): revisit this module placement and investigate moving it to lib/utils/user if possible. +// TODO(okraport): revisit this module to consider replacement with getent process shell out to avoid the mutex. diff --git a/lib/secretsscanner/authorizedkeys/users_list_darwin.go b/session/host/user/user_darwin.go similarity index 93% rename from lib/secretsscanner/authorizedkeys/users_list_darwin.go rename to session/host/user/user_darwin.go index 7eeb334bfbfc..d13a0663d903 100644 --- a/lib/secretsscanner/authorizedkeys/users_list_darwin.go +++ b/session/host/user/user_darwin.go @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package authorizedkeys +package user /* #include @@ -31,10 +31,10 @@ import ( "github.com/gravitational/trace" ) -// getHostUsers returns the list of all users on the host from the user +// GetHostUsers returns the list of all users on the host from the user // directory (depending on system configuration this can be /etc/passwd, // LDAP...). -func getHostUsers() ([]user.User, error) { +func GetHostUsers() ([]user.User, error) { // on darwin the setpwent/getpwent/endpwent functions use thread-local // storage so there's no need for a global lock but we must call the whole // sequence from the same thread diff --git a/session/host/user/user_forward.go b/session/host/user/user_forward.go new file mode 100644 index 000000000000..86b3495027bc --- /dev/null +++ b/session/host/user/user_forward.go @@ -0,0 +1,48 @@ +//go:build !linux || !cgo + +// Teleport +// Copyright (C) 2026 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 user + +import ( + osu "os/user" + + "github.com/gravitational/trace" +) + +// Lookup wraps [os/user.Lookup]. +func Lookup(username string) (*osu.User, error) { return osu.Lookup(username) } + +// LookupId wraps [os/user.LookupId]. +func LookupId(id string) (*osu.User, error) { return osu.LookupId(id) } + +// LookupGroup wraps [os/user.LookupGroup]. +func LookupGroup(name string) (*osu.Group, error) { return osu.LookupGroup(name) } + +// LookupGroupId wraps [os/user.LookupGroupId]. +func LookupGroupId(id string) (*osu.Group, error) { return osu.LookupGroupId(id) } + +// Current wraps [os/user.Current]. +func Current() (*osu.User, error) { return osu.Current() } + +// GroupIds wraps [os/user.User.GroupIds]. +func GroupIds(u *osu.User) ([]string, error) { + if u == nil { + return nil, trace.BadParameter("user cannot be nil") + } + return u.GroupIds() +} diff --git a/session/host/user/user_linux_cgo.go b/session/host/user/user_linux_cgo.go new file mode 100644 index 000000000000..e16a4aabb45a --- /dev/null +++ b/session/host/user/user_linux_cgo.go @@ -0,0 +1,120 @@ +//go:build linux && cgo + +// Teleport +// Copyright (C) 2026 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 user + +/* +#define _XOPEN_SOURCE 500 +#include +*/ +import "C" + +import ( + osuser "os/user" + "strconv" + "sync" + + "github.com/gravitational/trace" +) + +var packageMU sync.Mutex + +// Lookup wraps [os/user.Lookup] with process-wide serialization. +func Lookup(username string) (*osuser.User, error) { + packageMU.Lock() + defer packageMU.Unlock() + return osuser.Lookup(username) +} + +// LookupId wraps [os/user.LookupId] with process-wide serialization. +func LookupId(id string) (*osuser.User, error) { + packageMU.Lock() + defer packageMU.Unlock() + return osuser.LookupId(id) +} + +// LookupGroup wraps [os/user.LookupGroup] with process-wide serialization. +func LookupGroup(name string) (*osuser.Group, error) { + packageMU.Lock() + defer packageMU.Unlock() + return osuser.LookupGroup(name) +} + +// LookupGroupId wraps [os/user.LookupGroupId] with process-wide serialization. +func LookupGroupId(id string) (*osuser.Group, error) { + packageMU.Lock() + defer packageMU.Unlock() + return osuser.LookupGroupId(id) +} + +// Current wraps [os/user.Current] with process-wide serialization. +func Current() (*osuser.User, error) { + packageMU.Lock() + defer packageMU.Unlock() + return osuser.Current() +} + +// LookGroupIdsup wraps [os/user.User.GroupIds] with process-wide serialization. +func GroupIds(u *osuser.User) ([]string, error) { + if u == nil { + return nil, trace.BadParameter("user cannot be nil") + } + packageMU.Lock() + defer packageMU.Unlock() + return u.GroupIds() +} + +// GetHostUsers returns the list of all users on the host from the user +// directory (depending on system configuration this can be /etc/passwd, +// LDAP...). +func GetHostUsers() ([]osuser.User, error) { + // A lock should be acquired when using MT-Unsafe race:pwent functions (i.e. + // setpwent/getpwent/endpwent). Since `getpwent` can follow the same path + // into libnss we take the package lock. + packageMU.Lock() + defer packageMU.Unlock() + + C.setpwent() + defer C.endpwent() + + var results []osuser.User + for { + result, err := C.getpwent() + // cgo error convention, check the return value before errno + if result != nil { + results = append(results, passwdC2Go(result)) + continue + } + if err != nil { + return nil, trace.Wrap(err) + } + return results, nil + } +} + +// passwdC2Go converts `passwd` struct from C to golang native struct +func passwdC2Go(passwdC *C.struct_passwd) osuser.User { + name := C.GoString(passwdC.pw_name) + return osuser.User{ + Name: name, + Username: name, + Uid: strconv.FormatUint(uint64(passwdC.pw_uid), 10), + Gid: strconv.FormatUint(uint64(passwdC.pw_gid), 10), + HomeDir: C.GoString(passwdC.pw_dir), + } +} diff --git a/lib/secretsscanner/authorizedkeys/users_list_other.go b/session/host/user/user_other.go similarity index 94% rename from lib/secretsscanner/authorizedkeys/users_list_other.go rename to session/host/user/user_other.go index 5355306b8502..f086722e8a65 100644 --- a/lib/secretsscanner/authorizedkeys/users_list_other.go +++ b/session/host/user/user_other.go @@ -16,7 +16,7 @@ //go:build !darwin && !linux -package authorizedkeys +package user import ( "os/user" @@ -29,7 +29,7 @@ var alwaysFalse bool // getHostUsers returns ErrUnsupportedPlatform because this platform is not // supported. On supported platforms, it returns the list of all users on the // host from the user directory. -func getHostUsers() ([]user.User, error) { +func GetHostUsers() ([]user.User, error) { if alwaysFalse { // thwart the well-meaning intentions of staticcheck return nil, nil diff --git a/session/reexec/reexec.go b/session/reexec/reexec.go index 8df968a44955..d8a8d8b2ac74 100644 --- a/session/reexec/reexec.go +++ b/session/reexec/reexec.go @@ -48,6 +48,7 @@ import ( "github.com/gravitational/teleport/session/auditd" "github.com/gravitational/teleport/session/envutils" "github.com/gravitational/teleport/session/host" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/logconstants" "github.com/gravitational/teleport/session/loginuid" "github.com/gravitational/teleport/session/networking" @@ -468,7 +469,7 @@ func RunCommand() (exitErr error, err error) { WtmpdbFile: c.UaccMetadata.WtmpdbPath, }) - localUser, err := user.Lookup(c.Login) + localUser, err := hostuser.Lookup(c.Login) if err != nil { if uaccErr := uaccHandler.FailedLogin(c.Login, &c.UaccMetadata.RemoteAddr); uaccErr != nil { slog.DebugContext(ctx, "unable to write failed login attempt to uacc", "error", uaccErr) @@ -671,8 +672,8 @@ type osWrapper struct { func newOsWrapper() *osWrapper { return &osWrapper{ - LookupGroup: user.LookupGroup, - LookupUser: user.Lookup, + LookupGroup: hostuser.LookupGroup, + LookupUser: hostuser.Lookup, CommandContext: exec.CommandContext, } } @@ -698,7 +699,7 @@ func (s *systemUser) UID() string { } func (s *systemUser) GroupIds() ([]string, error) { - return s.u.GroupIds() + return hostuser.GroupIds(s.u) } // startNewParker starts a new parker process only if the requested user has been created @@ -810,7 +811,7 @@ func RunNetworking() (code int, err error) { // Once the PAM stack is called with parent process permissions, set the process uid // and gid to the requested user. This way, the user's networking requests will be // done with the user's permissions. - localUser, err := user.Lookup(c.Login) + localUser, err := hostuser.Lookup(c.Login) if err != nil { return reexecconstants.RemoteCommandFailure, trace.NotFound("%s", err) } @@ -1231,7 +1232,7 @@ func openFileAsUser(localUser *user.User, path string) (file *os.File, err error return nil, trace.Wrap(err) } - strIDs, err := localUser.GroupIds() + strIDs, err := hostuser.GroupIds(localUser) if err != nil { return nil, trace.Wrap(err) } @@ -1480,7 +1481,7 @@ var accessibleHomeDirMu sync.Mutex // hasAccessibleHomeDir checks if the current user has access to an existing home directory. func hasAccessibleHomeDir() error { // this should usually be fetching a cached value - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return trace.Wrap(err) } @@ -1522,7 +1523,7 @@ func hasAccessibleHomeDir() error { // errors will be returned, which means a missing, inaccessible, or otherwise invalid home directory will result // in a return of (false, nil) func checkHomeDir(localUser *user.User) (bool, error) { - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return false, trace.Wrap(err) } diff --git a/session/reexec/reexecsftp/sftp.go b/session/reexec/reexecsftp/sftp.go index 3edf39d51c37..8012b76425eb 100644 --- a/session/reexec/reexecsftp/sftp.go +++ b/session/reexec/reexecsftp/sftp.go @@ -26,7 +26,6 @@ import ( "io" "log/slog" "os" - "os/user" "path" "path/filepath" "strings" @@ -38,6 +37,7 @@ import ( "github.com/pkg/sftp" "golang.org/x/sys/unix" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/reexec/safefile" "github.com/gravitational/teleport/session/sftputils" ) @@ -305,7 +305,7 @@ func RunSFTP(logger *slog.Logger) error { } defer auditFile.Close() - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return trace.Wrap(err) } diff --git a/session/shell/shell_unix.go b/session/shell/shell_unix.go index 2de21be2724d..f3a75d2ef620 100644 --- a/session/shell/shell_unix.go +++ b/session/shell/shell_unix.go @@ -37,18 +37,19 @@ import "C" import ( "context" "log/slog" - "os/user" "strings" "syscall" "unsafe" "github.com/gravitational/trace" + + hostuser "github.com/gravitational/teleport/session/host/user" ) // getLoginShell determines the login shell for a given username func getLoginShell(username string) (string, error) { // See if the username is valid. - _, err := user.Lookup(username) + _, err := hostuser.Lookup(username) if err != nil { return "", trace.Wrap(err) } diff --git a/tool/tbot/init.go b/tool/tbot/init.go index ecbbc833e8ea..42fa7fc6e886 100644 --- a/tool/tbot/init.go +++ b/tool/tbot/init.go @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/lib/tbot/cli" "github.com/gravitational/teleport/lib/tbot/config" "github.com/gravitational/teleport/lib/tbot/identity" + hostuser "github.com/gravitational/teleport/session/host/user" ) // RootUID is the UID of the root user @@ -207,7 +208,7 @@ func ensurePermissions( return trace.BadParameter("File %s is expected to be a file but is a directory", path) } - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return trace.Wrap(err) } @@ -319,12 +320,12 @@ func parseOwnerString(owner string) (*user.User, *user.Group, error) { return nil, nil, trace.BadParameter("invalid owner string: %q", owner) } - ownerUser, err := user.Lookup(ownerParts[0]) + ownerUser, err := hostuser.Lookup(ownerParts[0]) if err != nil { return nil, nil, trace.Wrap(err) } - ownerGroup, err := user.LookupGroup(ownerParts[1]) + ownerGroup, err := hostuser.LookupGroup(ownerParts[1]) if err != nil { return nil, nil, trace.Wrap(err) } @@ -358,12 +359,12 @@ func getOwner(cliOwner, defaultOwner string) (*user.User, *user.Group, error) { log.DebugContext(context.TODO(), "Will use current user as owner") // Otherwise, return the current user and group - currentUser, err := user.Current() + currentUser, err := hostuser.Current() if err != nil { return nil, nil, trace.Wrap(err) } - currentGroup, err := user.LookupGroupId(currentUser.Gid) + currentGroup, err := hostuser.LookupGroupId(currentUser.Gid) if err != nil { return nil, nil, trace.Wrap(err) } @@ -383,17 +384,17 @@ func getAndTestACLOptions(initCmd *cli.InitCommand, destDir string) (*user.User, return nil, nil, nil, trace.BadParameter("--reader-user must be set") } - botUser, err := user.Lookup(initCmd.BotUser) + botUser, err := hostuser.Lookup(initCmd.BotUser) if err != nil { return nil, nil, nil, trace.Wrap(err) } - botGroup, err := user.LookupGroupId(botUser.Gid) + botGroup, err := hostuser.LookupGroupId(botUser.Gid) if err != nil { return nil, nil, nil, trace.Wrap(err) } - readerUser, err := user.Lookup(initCmd.ReaderUser) + readerUser, err := hostuser.Lookup(initCmd.ReaderUser) if err != nil { return nil, nil, nil, trace.Wrap(err) } diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index b972c1775dd1..eec29d663aa8 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -27,7 +27,6 @@ import ( "maps" "net/url" "os" - "os/user" "path" "path/filepath" "runtime" @@ -61,6 +60,7 @@ import ( "github.com/gravitational/teleport/lib/utils" logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/versioncontrol" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/reexec" "github.com/gravitational/teleport/session/reexec/reexecconstants" "github.com/gravitational/teleport/session/selinux" @@ -1230,7 +1230,7 @@ func onSCP(scpFlags *scp.Flags) error { } // get user's home dir (it serves as a default destination) - user, err := user.Current() + user, err := hostuser.Current() if err != nil { return trace.Wrap(err) } diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index cae51bd7d7fd..20bdc815db82 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -33,7 +33,6 @@ import ( "os" "os/exec" "os/signal" - "os/user" "path/filepath" "regexp" "runtime" @@ -106,6 +105,7 @@ import ( "github.com/gravitational/teleport/lib/utils/mlock" "github.com/gravitational/teleport/lib/utils/parse" stacksignal "github.com/gravitational/teleport/lib/utils/signal" + hostuser "github.com/gravitational/teleport/session/host/user" "github.com/gravitational/teleport/session/networking/x11" "github.com/gravitational/teleport/session/shell" "github.com/gravitational/teleport/tool/common" @@ -4453,7 +4453,7 @@ func onSSHLatency(cf *CLIConf) error { func runLocalCommand(hostLogin string, command []string) error { if len(command) == 0 { if hostLogin == "" { - user, err := user.Current() + user, err := hostuser.Current() if err != nil { return trace.Wrap(err) } From 89ba17b08f778dc593e6b6fb1c337d7c4431d726 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Wed, 19 Aug 2026 15:00:32 +0000 Subject: [PATCH 310/347] Add knip (#376) Export-Source-Commit: 754d2f1b0238a99b266936a27562314c9c74f85b --- eslint.config.mjs | 20 - knip.jsonc | 201 +++ package.json | 6 +- pnpm-lock.yaml | 1086 +++++------------ web/packages/build/package.json | 2 - web/packages/build/vite/config.mts | 8 +- web/packages/design/package.json | 2 - web/packages/design/src/Button/Button.tsx | 2 +- web/packages/design/src/CardError/index.js | 2 - web/packages/design/src/DataTable/types.ts | 2 +- web/packages/design/src/Label/Label.tsx | 23 +- web/packages/design/src/Label/index.ts | 13 +- .../design/src/LabelState/LabelState.tsx | 22 +- web/packages/design/src/LabelState/index.js | 9 +- web/packages/design/src/Popover/Popover.tsx | 6 +- web/packages/design/src/Popover/index.ts | 3 - .../design/src/RadioButton/RadioButton.tsx | 2 +- web/packages/design/src/SVGIcon/index.ts | 3 - web/packages/design/src/Status/index.ts | 3 +- web/packages/design/src/Tag/Tag.tsx | 2 +- web/packages/design/src/Tag/index.ts | 1 - web/packages/design/src/TextArea/TextArea.tsx | 2 +- web/packages/design/src/datetime/format.ts | 5 - web/packages/design/src/index.ts | 8 +- web/packages/design/src/system/index.ts | 8 - .../src/theme/utils/colorManipulator.js | 30 +- web/packages/design/src/utils/renderHook.tsx | 2 - web/packages/design/src/utils/testing.tsx | 3 - .../RequestView/RequestDelete/index.ts | 1 - .../RequestView/RequestView.tsx | 2 +- .../AccessRequests/fixtures/index.ts | 6 - .../AnimatedTerminal/AnimatedTerminal.tsx | 124 -- .../AnimatedTerminal/TerminalContent.tsx | 279 ----- .../components/AnimatedTerminal/content.ts | 177 --- .../components/AnimatedTerminal/index.ts | 20 - .../components/ButtonTextWithAddIcon/index.ts | 24 - .../DesktopSession/useDesktopSession.tsx | 2 +- .../shared/components/Editor/Editor.tsx | 325 ----- .../shared/components/Editor/File.tsx | 30 - .../shared/components/Editor/Language.ts | 21 - .../shared/components/Editor/Tabs.tsx | 4 +- .../shared/components/Editor/index.ts | 21 - .../components/EmptyState/EmptyState.tsx | 6 +- .../shared/components/FieldSelect/index.ts | 7 +- .../shared/components/FieldSelect/shared.tsx | 6 - .../components/FileTransfer/FileTransfer.tsx | 2 +- .../shared/components/FileTransfer/index.ts | 1 - .../components/Search/SearchPagination.tsx | 55 - .../shared/components/Search/index.ts | 1 - .../shared/components/Select/types.ts | 2 +- .../UnifiedResources/shared/StatusInfo.tsx | 2 +- .../shared/viewItemsFactory.ts | 6 +- .../components/UserDisplayName/index.ts | 1 - .../shared/components/Validation/rules.ts | 4 +- .../shared/components/Window/Window.tsx | 36 - .../components/Window/WindowComponents.tsx | 71 -- .../components/Window/WindowTitleBar.tsx | 44 - .../shared/components/Window/index.ts | 28 - .../hooks/useInfiniteScroll/testUtils.ts | 2 +- web/packages/shared/libs/tdp/client.ts | 2 +- web/packages/shared/libs/tdp/codec.ts | 2 +- web/packages/shared/package.json | 1 - web/packages/shared/redirects/index.ts | 3 +- .../services/accessRequests/accessRequests.ts | 2 +- .../shared/services/databases/databases.ts | 2 - web/packages/shared/services/mcp/client.ts | 2 +- web/packages/shared/utils/pipe.ts | 33 - web/packages/teleport/package.json | 2 +- .../teleport/src/Account/AccountNew.ts | 25 - .../ManageDevices/wizards/deviceCases.ts | 28 - web/packages/teleport/src/Account/SideNav.tsx | 2 +- web/packages/teleport/src/Account/index.ts | 2 +- .../src/Audit/EventList/EventList.tsx | 5 +- .../src/Audit/EventList/EventListCells.tsx | 51 - .../src/Audit/EventList/EventTypeCell.tsx | 2 +- web/packages/teleport/src/Audit/index.tsx | 2 +- .../src/AuthConnectors/AuthConnectorTile.tsx | 2 +- .../src/AuthConnectors/AuthConnectors.tsx | 3 - .../teleport/src/AuthConnectors/index.ts | 2 +- .../styles/AuthConnectors.styles.ts | 19 +- .../styles/LockedFeatureContainer.styles.ts | 42 - .../Details/BotInstanceDetails.tsx | 2 +- .../BotInstances/List/BotInstancesList.tsx | 2 +- .../teleport/src/BotInstances/hooks.ts | 6 +- .../KubernetesLabelsSelect.tsx | 4 +- .../Bots/Add/GitHubActionsK8s/templates.ts | 2 +- .../src/Bots/Add/GitHubActionsSsh/index.ts | 1 - .../Add/GitHubActionsSsh/useGitHubSshFlow.tsx | 2 +- .../src/Bots/Add/Shared/FlowButtons.tsx | 2 +- .../src/Bots/Add/Shared/kubernetes.ts | 7 - .../src/Bots/List/EmptyState/EmptyState.tsx | 28 +- web/packages/teleport/src/Bots/List/index.ts | 19 - web/packages/teleport/src/Bots/hooks.ts | 8 +- web/packages/teleport/src/Bots/types.ts | 19 - .../Clusters/ManageCluster/ManageCluster.tsx | 8 +- .../src/Clusters/ManageCluster/index.ts | 19 - .../src/Console/DocumentNodes/useNodes.ts | 2 +- .../Console/DocumentSsh/ShareSession/index.ts | 21 - .../teleport/src/Console/Tabs/Tabs.tsx | 2 +- .../src/Console/consoleContextProvider.tsx | 2 +- .../src/DeviceTrust/DeviceList/DeviceList.tsx | 2 +- .../teleport/src/DeviceTrust/types.ts | 6 - .../SetupAccess/SetupAccess.tsx | 2 +- .../DeployService/AutoDeploy/AutoDeploy.tsx | 2 +- .../teleport/src/Discover/Database/config.ts | 5 +- .../src/Discover/Fixtures/fixtures.tsx | 2 +- .../src/Discover/Overview/SmallTabs.ts | 32 - .../src/Discover/SelectResource/Tile.tsx | 5 +- .../src/Discover/SelectResource/constants.ts | 30 - .../SelectResource/getMarketingTermMatches.ts | 2 +- .../src/Discover/SelectResource/index.ts | 2 - .../SelectResource/resources/resources.tsx | 4 +- .../src/Discover/SelectResource/testUtils.ts | 12 +- .../Server/DownloadScript/DownloadScript.tsx | 11 +- .../teleport/src/Discover/Shared/Aws/error.ts | 10 - .../useConnectionDiagnostic.ts | 2 - .../teleport/src/Discover/Shared/Header.tsx | 14 +- .../Shared/SecurityGroupPicker/index.ts | 1 - .../src/Discover/Shared/SetupAccess/index.ts | 2 - .../teleport/src/Discover/Shared/Step.tsx | 55 - .../teleport/src/Discover/Shared/Text.tsx | 10 +- .../teleport/src/Discover/Shared/index.ts | 12 +- .../src/Instances/VersionsFilterPanel.tsx | 11 +- web/packages/teleport/src/Instances/index.ts | 19 - .../AwsConsole/AwsRolesAnywhereSetup.tsx | 2 +- .../Integrations/Enroll/AwsConsole/index.ts | 19 - .../Integrations/Enroll/Cloud/Aws/types.ts | 2 +- .../Integrations/Enroll/Cloud/Azure/types.ts | 4 +- .../Enroll/Cloud/Shared/InfoGuide.tsx | 4 +- .../Integrations/Enroll/Cloud/Shared/index.ts | 1 - .../Integrations/Enroll/IntegrationEnroll.tsx | 2 +- .../src/Integrations/Enroll/Shared/index.ts | 7 +- .../src/Integrations/Enroll/Shared/state.ts | 2 +- .../src/Integrations/Enroll/common.tsx | 12 +- .../src/Integrations/IntegrationList.tsx | 2 +- .../Operations/useIntegrationOperation.ts | 2 +- .../src/Integrations/shared/StatusLabel.tsx | 6 +- .../status/AwsOidc/Details/Rds.tsx | 2 +- .../teleport/src/JoinTokens/JoinTokens.tsx | 2 +- .../src/JoinTokens/UpsertJoinTokenDialog.tsx | 2 +- .../teleport/src/LocksV2/Locks/Locks.tsx | 2 +- .../src/LocksV2/NewLock/LockCheckout/index.ts | 19 - .../teleport/src/LocksV2/NewLock/NewLock.tsx | 4 - .../ServerSideSupportedList/Nodes.tsx | 2 +- web/packages/teleport/src/Login/index.ts | 4 - web/packages/teleport/src/Main/index.ts | 8 +- .../ManagedUpdates/ManagedUpdates.tsx | 2 +- .../ManagedUpdates/ManagedUpdates/index.ts | 4 - .../teleport/src/ManagedUpdates/fixtures.ts | 59 +- .../teleport/src/ManagedUpdates/index.ts | 4 - .../src/Navigation/ResourcesSection.tsx | 2 +- .../teleport/src/Navigation/Section.tsx | 8 +- .../teleport/src/Nodes/fixtures/index.ts | 5 - .../teleport/src/Recordings/Recordings.tsx | 2 +- web/packages/teleport/src/Recordings/index.ts | 19 - .../RoleEditor/StandardEditor/Resources.tsx | 2 +- .../StandardEditor/StatefulSection.tsx | 2 +- .../RoleEditor/StandardEditor/validation.ts | 2 +- .../teleport/src/Roles/RoleEditor/index.ts | 19 - web/packages/teleport/src/Roles/index.ts | 2 +- .../SessionRecordings/list/RecordingItem.tsx | 4 +- .../list/SessionSummariesCta.tsx | 4 +- .../view/ActionBar/ActionBar.tsx | 58 - .../SessionRecordings/view/ActionBar/index.ts | 21 - .../view/PlayerTabs/PlayerTabs.tsx | 74 -- .../view/PlayerTabs/index.ts | 23 - .../view/SidebarResizeHandle.tsx | 4 +- .../view/Timeline/constants.ts | 4 +- .../view/player/PlayerControls.tsx | 2 +- .../view/player/tty/types.ts | 10 +- web/packages/teleport/src/Support/Support.tsx | 2 +- web/packages/teleport/src/TopBar/TopBar.tsx | 14 +- web/packages/teleport/src/TopBar/index.ts | 2 +- .../src/UnifiedResources/UnifiedResources.tsx | 2 +- .../src/Users/UserDetails/UserDetails.tsx | 18 +- .../teleport/src/Users/UserDetails/index.ts | 1 - web/packages/teleport/src/Users/useUsers.ts | 4 +- .../src/Welcome/NewCredentials/types.ts | 2 +- .../EmptyState/EmptyState.tsx | 2 +- .../src/components/BannerList/index.tsx | 3 +- .../src/components/CardEmpty/CardEmpty.jsx | 39 - .../src/components/CardEmpty/index.js | 21 - ...tSearcheableTableWithQueryParamSupport.tsx | 55 - .../index.ts | 19 - .../src/components/Dropdown/Dropdown.tsx | 2 +- .../components/InputSearch/InputSearch.jsx | 105 -- .../src/components/InputSearch/index.js | 21 - .../src/components/LabelSelector/index.ts | 21 - .../components/LabelsInput/LabelsInput.tsx | 2 +- .../teleport/src/components/Layout/Layout.tsx | 47 +- .../teleport/src/components/Layout/index.ts | 11 +- .../NewMfaDeviceForm/NewMfaDeviceForm.tsx | 2 +- .../src/components/NewMfaDeviceForm/index.ts | 1 - .../teleport/src/components/Router/Router.tsx | 5 +- .../teleport/src/components/Router/index.ts | 3 - .../src/components/SelectFilters/Pager.tsx | 2 +- .../src/components/SelectFilters/index.ts | 21 - .../components/Wizard/Navigation/Bullet.tsx | 6 +- .../Wizard/Navigation/Navigation.tsx | 2 +- .../src/components/Wizard/Navigation/index.ts | 2 - .../hooks/useUrlFiltering/useUrlFiltering.ts | 4 +- web/packages/teleport/src/config.ts | 18 +- .../teleport/src/lib/term/protobuf.ts | 2 +- web/packages/teleport/src/lib/term/tty.ts | 2 +- .../teleport/src/services/agents/types.ts | 2 +- .../teleport/src/services/apps/types.ts | 2 +- .../gen-event-reference.ts | 6 +- .../teleport/src/services/audit/makeEvent.ts | 8 +- .../teleport/src/services/audit/types.ts | 4 +- .../teleport/src/services/auth/auth.ts | 14 +- .../teleport/src/services/bot/consts.ts | 2 +- .../teleport/src/services/bot/index.ts | 2 +- .../teleport/src/services/bot/types.ts | 10 +- .../src/services/clusters/makeCluster.ts | 5 - .../teleport/src/services/databases/types.ts | 8 +- .../src/services/discovery/discovery.ts | 2 +- .../teleport/src/services/history/history.ts | 2 +- .../teleport/src/services/history/index.ts | 4 +- .../teleport/src/services/instances/index.ts | 25 - .../teleport/src/services/instances/types.ts | 6 +- .../teleport/src/services/integrations/aws.ts | 6 +- .../src/services/integrations/index.ts | 2 +- .../src/services/integrations/integrations.ts | 5 - .../services/integrations/oktaStatusTypes.ts | 2 +- .../src/services/integrations/types.ts | 34 +- .../teleport/src/services/joinToken/types.ts | 10 +- .../teleport/src/services/locks/locks.ts | 10 +- .../teleport/src/services/nodes/types.ts | 5 - .../teleport/src/services/queryHelpers.ts | 12 +- .../teleport/src/services/recordings/types.ts | 4 +- .../src/services/resources/resource.ts | 23 - .../teleport/src/services/resources/types.ts | 12 +- .../src/services/storageService/types.ts | 4 +- .../teleport/src/services/user/index.ts | 3 +- .../teleport/src/services/user/makeAcl.ts | 4 +- .../src/services/user/makeUserContext.ts | 2 +- .../teleport/src/services/user/types.ts | 6 +- .../services/userEvent/accessListEvents.ts | 4 +- .../teleport/src/services/userEvent/types.ts | 6 +- .../teleport/src/services/userGroups/types.ts | 8 +- .../src/services/version/unsupported.ts | 5 +- web/packages/teleport/src/stores/index.ts | 4 +- .../teleport/src/test/helpers/instances.ts | 8 +- .../teleport/src/test/helpers/roles.ts | 14 - .../teleport/src/test/helpers/userEvents.ts | 22 - .../teleport/src/test/helpers/users.ts | 6 - web/packages/teleport/src/types.ts | 8 +- .../teleport/src/useClusterVersion.ts | 2 +- web/packages/teleterm/eslint.config.mjs | 28 - .../awaitableSender/awaitableSender.ts | 2 +- .../src/mainProcess/protocolHandler.ts | 2 +- .../services/appUpdater/autoUpdatesStatus.ts | 4 +- .../teleterm/src/services/appUpdater/index.ts | 1 - .../src/services/config/configService.ts | 4 +- .../src/services/pty/ptyHost/windowsPty.ts | 2 +- .../teleterm/src/services/tshd/app.ts | 4 +- .../src/services/tshd/cloneableClient.ts | 2 +- .../src/services/tshd/fixtures/mocks.ts | 18 +- .../teleterm/src/services/tshd/index.ts | 1 - .../teleterm/src/services/tshd/testHelpers.ts | 6 +- .../AccessRequests/AccessRequestsContext.tsx | 2 +- .../ClusterLogin/ClusterLogin.tsx | 2 +- .../ui/ConnectMyComputer/NavigationMenu.tsx | 42 +- .../connectMyComputerContext.tsx | 2 +- .../DocumentAccessRequests.tsx | 4 +- .../useAccessRequests.tsx | 2 +- .../ui/DocumentCluster/resourcesContext.tsx | 2 +- .../teleterm/src/ui/Search/actions.tsx | 2 +- .../src/ui/Search/pickers/ActionPicker.tsx | 10 +- .../TopBar/Connections/connectionsContext.tsx | 4 +- .../teleterm/src/ui/appContextProvider.tsx | 2 +- .../ui/services/clusters/clustersService.ts | 2 +- .../src/ui/services/modals/modalsService.ts | 26 +- .../accessRequestsService.ts | 106 -- .../documentsService/types.ts | 2 +- 275 files changed, 896 insertions(+), 4059 deletions(-) delete mode 100644 eslint.config.mjs create mode 100644 knip.jsonc delete mode 100644 web/packages/shared/components/AnimatedTerminal/AnimatedTerminal.tsx delete mode 100644 web/packages/shared/components/AnimatedTerminal/TerminalContent.tsx delete mode 100644 web/packages/shared/components/AnimatedTerminal/content.ts delete mode 100644 web/packages/shared/components/AnimatedTerminal/index.ts delete mode 100644 web/packages/shared/components/ButtonTextWithAddIcon/index.ts delete mode 100644 web/packages/shared/components/Editor/Editor.tsx delete mode 100644 web/packages/shared/components/Editor/File.tsx delete mode 100644 web/packages/shared/components/Editor/Language.ts delete mode 100644 web/packages/shared/components/Editor/index.ts delete mode 100644 web/packages/shared/components/Search/SearchPagination.tsx delete mode 100644 web/packages/shared/components/Window/Window.tsx delete mode 100644 web/packages/shared/components/Window/WindowComponents.tsx delete mode 100644 web/packages/shared/components/Window/WindowTitleBar.tsx delete mode 100644 web/packages/shared/components/Window/index.ts delete mode 100644 web/packages/shared/utils/pipe.ts delete mode 100644 web/packages/teleport/src/Account/AccountNew.ts delete mode 100644 web/packages/teleport/src/Audit/EventList/EventListCells.tsx delete mode 100644 web/packages/teleport/src/AuthConnectors/styles/LockedFeatureContainer.styles.ts delete mode 100644 web/packages/teleport/src/Bots/List/index.ts delete mode 100644 web/packages/teleport/src/Clusters/ManageCluster/index.ts delete mode 100644 web/packages/teleport/src/Console/DocumentSsh/ShareSession/index.ts delete mode 100644 web/packages/teleport/src/Discover/Overview/SmallTabs.ts delete mode 100644 web/packages/teleport/src/Discover/SelectResource/constants.ts delete mode 100644 web/packages/teleport/src/Discover/Shared/Step.tsx delete mode 100644 web/packages/teleport/src/Instances/index.ts delete mode 100644 web/packages/teleport/src/Integrations/Enroll/AwsConsole/index.ts delete mode 100644 web/packages/teleport/src/LocksV2/NewLock/LockCheckout/index.ts delete mode 100644 web/packages/teleport/src/Recordings/index.ts delete mode 100644 web/packages/teleport/src/Roles/RoleEditor/index.ts delete mode 100644 web/packages/teleport/src/SessionRecordings/view/ActionBar/ActionBar.tsx delete mode 100644 web/packages/teleport/src/SessionRecordings/view/ActionBar/index.ts delete mode 100644 web/packages/teleport/src/SessionRecordings/view/PlayerTabs/PlayerTabs.tsx delete mode 100644 web/packages/teleport/src/SessionRecordings/view/PlayerTabs/index.ts delete mode 100644 web/packages/teleport/src/components/CardEmpty/CardEmpty.jsx delete mode 100644 web/packages/teleport/src/components/CardEmpty/index.js delete mode 100644 web/packages/teleport/src/components/ClientSearcheableTableWithQueryParamSupport/ClientSearcheableTableWithQueryParamSupport.tsx delete mode 100644 web/packages/teleport/src/components/ClientSearcheableTableWithQueryParamSupport/index.ts delete mode 100644 web/packages/teleport/src/components/InputSearch/InputSearch.jsx delete mode 100644 web/packages/teleport/src/components/InputSearch/index.js delete mode 100644 web/packages/teleport/src/components/LabelSelector/index.ts delete mode 100644 web/packages/teleport/src/components/SelectFilters/index.ts delete mode 100644 web/packages/teleport/src/services/instances/index.ts delete mode 100644 web/packages/teleterm/eslint.config.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 552640ca662a..000000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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 eslint from './web/packages/build/eslint.config.mjs'; -export default eslint; diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 000000000000..e55bcfe1979a --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,201 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "workspaces": { + ".": { + "entry": [ + // Required from an inline actions/github-script block in docs-amplify.yaml. + ".github/scripts/post-preview-links.js", + // Run by `node --test`. + ".github/scripts/test-*.js", + "web/.storybook/main.mts", + "web/.storybook/preview.tsx", + "web/.storybook/vite.config.mts", + "web/.storybook/mocks/**/*.{ts,tsx,js}" + ], + "project": [ + ".github/scripts/**/*.js", + "web/.storybook/**/*.{ts,tsx,mts,mjs,js}", + "web/@types/*.d.ts" + ], + "ignoreDependencies": [ + // Only the generated gRPC clients under gen/proto/ts reference this, and those are not + // part of any project. + "@grpc/grpc-js", + // Referenced from a JSDoc type annotation in jest.config.js. + "@jest/types", + // Named as a string in web/.storybook/main.mts `addons`. + "@storybook/addon-vitest", + // Loaded by name through `jsPlugins` in .oxlintrc.jsonc. The plugins pull in eslint + // itself at runtime via @typescript-eslint/utils. + "eslint", + "eslint-plugin-jest-dom", + "eslint-plugin-storybook", + "eslint-plugin-testing-library", + "eslint-plugin-unused-imports" + ] + }, + "web/packages/build": { + "entry": [ + "vite/**/*.{ts,mts,mjs}", + "jest/**/*.{ts,js}", + "storybook.ts", + "**/*.test.{ts,tsx,js,jsx}" + ], + // These are loaded by name through the SWC plugin config in vite/react.mjs. + "ignoreDependencies": [ + "@gravitational/swc-plugin-jsx-marker", + "@gravitational/swc-plugin-styled-components", + "@swc/core" + ] + }, + "web/packages/design": { + "entry": [ + "src/**/*.story.{ts,tsx,js,jsx}", + "src/**/*.test.{ts,tsx,js,jsx}" + ], + "project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}", "@types/*.d.ts"], + "paths": { + "design/*": ["./src/*"], + "design": ["./src/"] + }, + // Compile-time only assertions, checked by tsc rather than imported anywhere. + "ignore": ["src/**/*.type-test.tsx"] + }, + "web/packages/shared": { + "entry": ["**/*.story.{ts,tsx,js,jsx}", "**/*.test.{ts,tsx,js,jsx}"], + "project": ["**/*.{ts,tsx,js,jsx,mts,mjs}", "!libs/ironrdp/pkg/**"], + "paths": { + "design/*": ["../design/src/*"], + "design": ["../design/src/"], + "shared/*": ["./*"], + "gen-proto-ts/*": ["../../../gen/proto/ts/*"] + }, + "ignoreDependencies": [ + // Reached through the `design/*` path alias rather than the package name. + "@gravitational/design", + // Browser polyfill, imported as the bare Node builtin specifier. + "events" + ] + }, + "web/packages/teleport": { + "entry": [ + "babel.config.js", + "index.html", + "src/**/*.story.{ts,tsx,js,jsx}", + "src/**/*.test.{ts,tsx,js,jsx}", + "src/services/audit/gen-event-reference/index.ts" + ], + "project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}"], + "paths": { + "build/*": ["../build/*"], + "shared/*": ["../shared/*"], + "design/*": ["../design/src/*"], + "design": ["../design/src/"], + "teleport/*": ["./src/*"], + "teleport": ["./src/"], + "teleterm/*": ["../teleterm/src/*"], + "e-teleport/*": ["../../../e/web/teleport/src/*"], + "gen-proto-ts/*": ["../../../gen/proto/ts/*"] + }, + "ignoreDependencies": [ + // Reached through path aliases rather than package names. + "@gravitational/design", + "@gravitational/shared", + "@gravitational/teleport", + // Declared here but named as strings by web/packages/build/.babelrc.js. + "babel-plugin-transform-import-meta", + "babel-plugin-transform-vite-meta-env", + "babel-plugin-transform-vite-meta-hot", + // Browser polyfill, imported as the bare Node builtin specifier. + "events" + ] + }, + "web/packages/teleterm": { + "entry": [ + "electron-builder-config.js", + "index.html", + "src/main.ts", + "src/preload.ts", + "src/sharedProcess/sharedProcess.ts", + "src/agentCleanupDaemon/agentCleanupDaemon.js", + "src/**/*.story.{ts,tsx,js,jsx}", + "src/**/*.test.{ts,tsx,js,jsx}", + "src/**/*Process.mjs" + ], + "project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}", "csp.ts"], + "paths": { + "build/*": ["../build/*"], + "shared/*": ["../shared/*"], + "design/*": ["../design/src/*"], + "design": ["../design/src/"], + "teleport/*": ["../teleport/src/*"], + "teleterm/*": ["./src/*"], + "gen-proto-ts/*": ["../../../gen/proto/ts/*"] + }, + "ignoreDependencies": [ + // Reached through path aliases rather than package names. + "@gravitational/design", + "@gravitational/shared", + // Browser polyfill, imported as the bare Node builtin specifier. + "events" + ] + }, + "e/web/teleport": { + "entry": [ + "babel.config.js", + "index.html", + "src/**/*.story.{ts,tsx,js,jsx}", + "src/**/*.test.{ts,tsx,js,jsx}", + "src/SessionRecordings/summary/generate-mitre-lookup.ts" + ], + "project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}"], + // Generated protobuf bindings. + "ignore": ["src/**/*_pb.ts", "src/**/*_pb.*.ts"], + "paths": { + "build/*": ["../../../web/packages/build/*"], + "shared/*": ["../../../web/packages/shared/*"], + "design/*": ["../../../web/packages/design/src/*"], + "design": ["../../../web/packages/design/src/"], + "teleport/*": ["../../../web/packages/teleport/src/*"], + "teleport": ["../../../web/packages/teleport/src/"], + "teleterm/*": ["../../../web/packages/teleterm/src/*"], + "e-teleport/*": ["./src/*"], + "gen-proto-ts/*": ["../../../gen/proto/ts/*"] + } + }, + "e2e": { + "entry": ["tests/**/*.{ts,tsx}", "scripts/**/*.{ts,tsx,js,mjs}"], + "project": [ + "{tests,scripts,helpers,aws,config,node,runner}/**/*.{ts,tsx,js,mjs}" + ] + }, + "e/e2e": { + "entry": ["tests/**/*.{ts,tsx}"], + "project": ["{tests,helpers,config}/**/*.{ts,tsx,js,mjs}"] + } + }, + "ignoreBinaries": [ + // Run by .github/workflows/doc-tests.yaml against the separately checked out docs site. + "spellcheck", + "markdown-lint", + "build", + "make" + ], + "ignoreUnresolved": [ + // Provided transitively by jest. + "babel-jest", + // Declared by web/packages/teleport but named as strings by web/packages/build/.babelrc.js. + "babel-plugin-transform-import-meta", + "babel-plugin-transform-vite-meta-env", + "babel-plugin-transform-vite-meta-hot", + // Deliberately missing, so the code under test has to handle a failed spawn. + "./testProcess-nonExistent.js", + "./testProcess-nonExisting.mjs" + ], + "rules": { + // The design system deliberately keeps deprecated aliases (OutlineDanger, the FieldSelect + // default export) alongside their replacements. + "duplicates": "off", + "enumMembers": "off" + } +} diff --git a/package.json b/package.json index ee610fe52edd..bbaec324a4c5 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,12 @@ "package-term": "pnpm --filter=@gravitational/teleterm package", "storybook": "./web/scripts/run-storybook.sh", "storybook-smoke-test": "storybook dev -p 9002 -c web/.storybook --ci --smoke-test", - "test-storybook": "NODE_TLS_REJECT_UNAUTHORIZED=0 test-storybook -c web/.storybook --url https://localhost:9002 --skipTags=skip-test --browsers=chromium", "test": "jest", "test-coverage": "jest --coverage && web/scripts/print-coverage-link.sh", "test-update-snapshot": "pnpm run test --updateSnapshot", "tdd": "jest --watch", "oxlint": "oxlint --quiet --report-unused-disable-directives-severity error web/ e/ e2e/", - "lint": "pnpm oxlint && pnpm format-check", + "lint": "pnpm oxlint && pnpm format-check && pnpm knip", "lint-fix": "pnpm oxlint --fix && pnpm format", "type-check": "tsc --build", "optimize-resource-icons": "svgo --multipass --quiet -rf web/packages/design/src/ResourceIcon/assets", @@ -27,6 +26,7 @@ "format": "oxfmt 'e/**/*.{ts,tsx,js,jsx,mts}' 'web/**/*.{ts,tsx,js,jsx,mts}' 'e2e/**/*.{ts,tsx,js,jsx,mts}'", "process-icons": "pnpm optimize-resource-icons && node web/packages/design/src/Icon/script/script.js && pnpm oxfmt 'web/packages/design/src/Icon/**/*.tsx'", "list-installed-versions": "./web/scripts/list-installed-versions.sh", + "knip": "KNIP=1 knip", "postinstall": "pnpm generate-theme", "generate-theme": "design-system typegen", "nop": "exit 0" @@ -46,7 +46,6 @@ "@types/react-dom": "^19.2.4", "@types/react-transition-group": "^4.4.12", "@types/wicg-file-system-access": "^2023.10.7", - "cspell": "^10.0.1", "eslint": "^10.8.1", "eslint-plugin-jest-dom": "^5.10.1", "eslint-plugin-storybook": "^10.5.7", @@ -54,6 +53,7 @@ "eslint-plugin-unused-imports": "^4.4.1", "jest": "^30.4.2", "jsdom-testing-mocks": "^1.16.0", + "knip": "^6.32.2", "msw": "2.15.0", "msw-storybook-addon": "^3.0.0", "oxfmt": "^0.62.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 591bdaa5b764..3fd52790fddf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,7 +132,7 @@ importers: version: 10.5.7(react@19.2.8)(storybook@10.5.7) '@storybook/react-vite': specifier: ^10.5.7 - version: 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + version: 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) '@testing-library/jest-dom': specifier: ^7.0.0 version: 7.0.0(@testing-library/dom@10.4.1) @@ -163,30 +163,30 @@ importers: '@types/wicg-file-system-access': specifier: ^2023.10.7 version: 2023.10.7 - cspell: - specifier: ^10.0.1 - version: 10.0.1 eslint: specifier: ^10.8.1 - version: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + version: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) eslint-plugin-jest-dom: specifier: ^5.10.1 - version: 5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) + version: 5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)) eslint-plugin-storybook: specifier: ^10.5.7 - version: 10.5.7(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2) + version: 10.5.7(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2) eslint-plugin-testing-library: specifier: 7.16.2 - version: 7.16.2(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + version: 7.16.2(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) eslint-plugin-unused-imports: specifier: ^4.4.1 - version: 4.4.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) + version: 4.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)) jest: specifier: ^30.4.2 version: 30.4.2(@types/node@24.13.3)(babel-plugin-macros@3.1.0)(supports-color@8.1.1) jsdom-testing-mocks: specifier: ^1.16.0 version: 1.16.0 + knip: + specifier: ^6.32.2 + version: 6.32.2 msw: specifier: 2.15.0 version: 2.15.0(@types/node@24.13.3)(typescript@7.0.2) @@ -216,7 +216,7 @@ importers: version: 7.0.2 vite: specifier: ^8.2.1 - version: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + version: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) e/e2e: dependencies: @@ -266,13 +266,10 @@ importers: version: 1.15.47(@swc/helpers@0.5.23) '@vitejs/plugin-react-swc': specifier: ^4.3.3 - version: 4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + version: 4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) babel-plugin-styled-components: specifier: ^2.3.0 version: 2.3.0(@babel/core@7.29.7(supports-color@8.1.1))(styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(supports-color@8.1.1) - globals: - specifier: ^17.9.0 - version: 17.9.0 jest-environment-jsdom: specifier: ^30.4.1 version: 30.4.1(supports-color@8.1.1) @@ -287,7 +284,7 @@ importers: version: 2.5.3 vite-plugin-wasm: specifier: ^3.6.0 - version: 3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + version: 3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) web/packages/design: dependencies: @@ -301,9 +298,6 @@ importers: specifier: ^5.1.5 version: 5.1.5 devDependencies: - '@gravitational/build': - specifier: workspace:* - version: link:../build '@types/styled-system': specifier: ^5.1.25 version: 5.1.25 @@ -414,6 +408,9 @@ importers: '@gravitational/teleport': specifier: workspace:* version: 'link:' + ace-builds: + specifier: 1.44.0 + version: 1.44.0 babel-plugin-transform-import-meta: specifier: 2.3.3 version: 2.3.3(@babel/core@7.29.7(supports-color@8.1.1)) @@ -492,7 +489,7 @@ importers: version: 6.8.9(supports-color@8.1.1) electron-vite: specifier: ^5.0.0 - version: 5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + version: 5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) events: specifier: 3.3.0 version: 3.3.0 @@ -1234,240 +1231,6 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - '@cspell/cspell-bundled-dicts@10.0.1': - resolution: {integrity: sha512-WvkSDNX4Uyyj/ZgbPO6L38iFNMfK1EqsH1FteRiI2qLz6QZMXRFrIt12OqiWIplzZDDaVpBH9FCJOPJll0fjCQ==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-json-reporter@10.0.1': - resolution: {integrity: sha512-/nes1RGILec3WCBcoMOd0byNTBtnJuPaVz/+ZzqYkLtY7x58VMcBG5kyP6hPyN8cIwjRADE/SR43gwdXuqk/FA==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-performance-monitor@10.0.1': - resolution: {integrity: sha512-9tVcHXwRnbazUv4WSG0h3MqV4+LgmLNgSALAQUflPPW0EMxTf7C4Dmv9cgxJyCEQrdnVKCr58nPPaahhz9LJUg==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-pipe@10.0.1': - resolution: {integrity: sha512-HPeXMD9AZ3V/qPkvQaPcak+C7cJ2z7JTHN8smd6J8L2aThLRky2cHc2OyeaHPSHB7WA47b4z2n5u5nawZhv5VQ==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-resolver@10.0.1': - resolution: {integrity: sha512-PIzkZHD1fGUQx1XteK2d1iQ0Mzq/maYcoB4jkvAiiR6WqP3MWYNKFdI9z+R5pOq5KgMfW+5Ig1q0oSR6h8irlA==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-service-bus@10.0.1': - resolution: {integrity: sha512-y6NcIGP2IdXaBL4PVH8vxsr7K27wzz3Ech87UtUtrDSXAiVEOvXgAIknEOUVp59rTlUE8Rn4IRURC6f/hgMyfw==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-types@10.0.1': - resolution: {integrity: sha512-kLgLShnWADDVreKC63pBrWkcvxgZzFIfO34Jhx/SWfuOIA3cD8AXT+HjyuLfoGJ7mUb58hv2kUziKzEy4INb1w==} - engines: {node: '>=22.18.0'} - - '@cspell/cspell-worker@10.0.1': - resolution: {integrity: sha512-L2bJerfuYOls2wEknm8FmynLtj/G7O4UqX9I/HznRggEW6i2yZIxagDetpVDNowpyavNHJ3SJtUFiyMiZc16Sw==} - engines: {node: '>=22.18.0'} - - '@cspell/dict-ada@4.1.1': - resolution: {integrity: sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==} - - '@cspell/dict-al@1.1.1': - resolution: {integrity: sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==} - - '@cspell/dict-aws@4.0.17': - resolution: {integrity: sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==} - - '@cspell/dict-bash@4.2.3': - resolution: {integrity: sha512-ljUZoKHbDqw5Sx0qpL2qTUlmkmr+vhZH/sCNrNaBZKTbdgiswErSnIF1jRbGmEitJNxHRHWsuZyVgnTGfVO1Yw==} - - '@cspell/dict-companies@3.2.11': - resolution: {integrity: sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==} - - '@cspell/dict-cpp@7.0.2': - resolution: {integrity: sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==} - - '@cspell/dict-cryptocurrencies@5.0.5': - resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} - - '@cspell/dict-csharp@4.0.8': - resolution: {integrity: sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==} - - '@cspell/dict-css@4.1.2': - resolution: {integrity: sha512-+ylGoKdwZ2sVOCOnU2Eq5wDZx+RaVX3HoKyNHGGsFvhSw6IidQ6tH/mAPKBDofViHJoWCPNlklE0lTr6MDG3QA==} - - '@cspell/dict-dart@2.3.2': - resolution: {integrity: sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==} - - '@cspell/dict-data-science@2.0.14': - resolution: {integrity: sha512-jl6Ds4u5u5JT+yY30pWQpAbdCHfy3lCcNkLbpL/AZKoUaLEoXbaYsps9xQtvD7DyaiXxiLZkdH2yHHXtoFtZyg==} - - '@cspell/dict-django@4.1.6': - resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==} - - '@cspell/dict-docker@1.1.17': - resolution: {integrity: sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==} - - '@cspell/dict-dotnet@5.0.13': - resolution: {integrity: sha512-xPp7jMnFpOri7tzmqmm/dXMolXz1t2bhNqxYkOyMqXhvs08oc7BFs+EsbDY0X7hqiISgeFZGNqn0dOCr+ncPYw==} - - '@cspell/dict-elixir@4.0.8': - resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} - - '@cspell/dict-en-common-misspellings@2.1.12': - resolution: {integrity: sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==} - - '@cspell/dict-en-gb-mit@3.1.24': - resolution: {integrity: sha512-Oowb/Uzkh7OmDRdCcETzMc9imEb4IpLlHJXoYjX8A8DS2X/54gqSjI915JFB8hKtFjBko5OM0BLQ+6cZhFEMmQ==} - - '@cspell/dict-en_us@4.4.35': - resolution: {integrity: sha512-xWpxBCc/FzzMMo/A+0qwARVaIIhR0Ql8yhhv4rvsvg+GfQF+LG9yzg2GwTM5N2rjvzmM3nKuR9zxFZq2I6fJSg==} - - '@cspell/dict-filetypes@3.0.18': - resolution: {integrity: sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==} - - '@cspell/dict-flutter@1.1.1': - resolution: {integrity: sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==} - - '@cspell/dict-fonts@4.0.6': - resolution: {integrity: sha512-aR/0csY01dNb0A1tw/UmN9rKgHruUxsYsvXu6YlSBJFu60s26SKr/k1o4LavpHTQ+lznlYMqAvuxGkE4Flliqw==} - - '@cspell/dict-fsharp@1.1.1': - resolution: {integrity: sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==} - - '@cspell/dict-fullstack@3.2.9': - resolution: {integrity: sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==} - - '@cspell/dict-gaming-terms@1.1.2': - resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} - - '@cspell/dict-git@3.1.0': - resolution: {integrity: sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==} - - '@cspell/dict-golang@6.0.26': - resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==} - - '@cspell/dict-google@1.0.9': - resolution: {integrity: sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==} - - '@cspell/dict-haskell@4.0.6': - resolution: {integrity: sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==} - - '@cspell/dict-html-symbol-entities@4.0.5': - resolution: {integrity: sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==} - - '@cspell/dict-html@4.0.15': - resolution: {integrity: sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==} - - '@cspell/dict-java@5.0.12': - resolution: {integrity: sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==} - - '@cspell/dict-julia@1.1.1': - resolution: {integrity: sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==} - - '@cspell/dict-k8s@1.0.12': - resolution: {integrity: sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==} - - '@cspell/dict-kotlin@1.1.1': - resolution: {integrity: sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==} - - '@cspell/dict-latex@5.1.0': - resolution: {integrity: sha512-qxT4guhysyBt0gzoliXYEBYinkAdEtR2M7goRaUH0a7ltCsoqqAeEV8aXYRIdZGcV77gYSobvu3jJL038tlPAw==} - - '@cspell/dict-lorem-ipsum@4.0.5': - resolution: {integrity: sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==} - - '@cspell/dict-lua@4.0.8': - resolution: {integrity: sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==} - - '@cspell/dict-makefile@1.0.5': - resolution: {integrity: sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==} - - '@cspell/dict-markdown@2.0.17': - resolution: {integrity: sha512-H8bAxih6U8NOnSPL7R8My+tqjaB4tmnJTjERuz4zYqmf+cH+5xshX3UVgKlwWFcyjsYfv/zEDuRdMctQv1q6HQ==} - peerDependencies: - '@cspell/dict-css': ^4.1.2 - '@cspell/dict-html': ^4.0.15 - '@cspell/dict-html-symbol-entities': ^4.0.5 - '@cspell/dict-typescript': ^3.2.3 - - '@cspell/dict-monkeyc@1.0.12': - resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==} - - '@cspell/dict-node@5.0.9': - resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} - - '@cspell/dict-npm@5.2.41': - resolution: {integrity: sha512-To3xsfRmMBYVXtWVEdUgV35M9a/JZ54dSuoY6m6D3uHKKL3I326Wmy4xifZ3PU8MQaWhyEH7zbIcUEtKwTQMcA==} - - '@cspell/dict-php@4.1.1': - resolution: {integrity: sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==} - - '@cspell/dict-powershell@5.0.15': - resolution: {integrity: sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==} - - '@cspell/dict-public-licenses@2.0.16': - resolution: {integrity: sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==} - - '@cspell/dict-python@4.2.27': - resolution: {integrity: sha512-Rj6xQgYS4X6ienjgAZF+njA0GRY4oSPouJWv0vfikCTn6EWlfk0V6Dy1HP3Migj1O+IC2NmespgVq+BZNSp8OA==} - - '@cspell/dict-r@2.1.1': - resolution: {integrity: sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==} - - '@cspell/dict-ruby@5.1.1': - resolution: {integrity: sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==} - - '@cspell/dict-rust@4.1.2': - resolution: {integrity: sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==} - - '@cspell/dict-scala@5.0.9': - resolution: {integrity: sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==} - - '@cspell/dict-shell@1.2.0': - resolution: {integrity: sha512-PVctvT22lJ49niMiakO8xieY7ELCAzjSqhejWR7bAMb5AZ9F4WDEs+XdGMnoVHWeXq7K5rcepLPmEJb+37zzIw==} - - '@cspell/dict-software-terms@5.2.2': - resolution: {integrity: sha512-0CaYd6TAsKtEoA7tNswm1iptEblTzEe3UG8beG2cpSTHk7afWIVMtJLgXDv0f/Li67Lf3Z1Jf3JeXR7GsJ2TRw==} - - '@cspell/dict-sql@2.2.1': - resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} - - '@cspell/dict-svelte@1.0.7': - resolution: {integrity: sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==} - - '@cspell/dict-swift@2.0.6': - resolution: {integrity: sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==} - - '@cspell/dict-terraform@1.1.3': - resolution: {integrity: sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==} - - '@cspell/dict-typescript@3.2.3': - resolution: {integrity: sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==} - - '@cspell/dict-vue@3.0.5': - resolution: {integrity: sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==} - - '@cspell/dict-zig@1.0.0': - resolution: {integrity: sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==} - - '@cspell/dynamic-import@10.0.1': - resolution: {integrity: sha512-mP1gdq00aIcH8HxNMqnH11X6BKxLcneDtFgl/ecjIKnaGKwi44m8AndP5Kr4ODaYdl8UUw9O3dJh7KaQXnLHZQ==} - engines: {node: '>=22.18.0'} - - '@cspell/filetypes@10.0.1': - resolution: {integrity: sha512-Z5S35giU5IW49fBBq6BksUbE8PC4IYPfaKuwl5Nl9jkf/OkAKiBmCowKX45NzRUQInwK/GSqqIUifrNeI6LdLw==} - engines: {node: '>=22.18.0'} - - '@cspell/rpc@10.0.1': - resolution: {integrity: sha512-axSRKv3zEAmBm66iD/FV/MPmE4/Yf7c3PZiwTW894Yd3iEhtn3KPKeTrqQ2/tDrhB1Z2qTsap/Hue0MK4o5WXg==} - engines: {node: '>=22.18.0'} - - '@cspell/strong-weak-map@10.0.1': - resolution: {integrity: sha512-lenN1DVyPi8nJLSMSJJ670ddTjyiruLueuSZO1qLcxBqUhgxDt/mALu9N/1m6WdOVcg6m/5cLiZVg2KOo2UzRw==} - engines: {node: '>=22.18.0'} - - '@cspell/url@10.0.1': - resolution: {integrity: sha512-abYYgI29wJhWIfWTYrYuzRYDcHQUQ1N5ylnhxYn1NJnIQMqUWGLbDmt12JABtZ+R6h6UNatQrS7rhP86etvJyQ==} - engines: {node: '>=22.18.0'} - '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -2686,42 +2449,84 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.143.0': + resolution: {integrity: sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm64@0.127.0': resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.143.0': + resolution: {integrity: sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-darwin-arm64@0.127.0': resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.143.0': + resolution: {integrity: sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.127.0': resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.143.0': + resolution: {integrity: sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-freebsd-x64@0.127.0': resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.143.0': + resolution: {integrity: sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': + resolution: {integrity: sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': + resolution: {integrity: sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2729,6 +2534,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': + resolution: {integrity: sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-musl@0.127.0': resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2736,6 +2548,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.143.0': + resolution: {integrity: sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2743,6 +2562,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': + resolution: {integrity: sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2750,6 +2576,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': + resolution: {integrity: sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2757,6 +2590,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': + resolution: {integrity: sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2764,6 +2604,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': + resolution: {integrity: sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.127.0': resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2771,6 +2618,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.143.0': + resolution: {integrity: sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-musl@0.127.0': resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2778,12 +2632,25 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.143.0': + resolution: {integrity: sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-openharmony-arm64@0.127.0': resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.143.0': + resolution: {integrity: sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-wasm32-wasi@0.127.0': resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2795,18 +2662,36 @@ packages: cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': + resolution: {integrity: sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': + resolution: {integrity: sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.127.0': resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.143.0': + resolution: {integrity: sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} @@ -4194,6 +4079,7 @@ packages: '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xterm/addon-fit@0.11.0': resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} @@ -4756,9 +4642,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - array-timsort@1.0.3: - resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} - asn1js@3.0.10: resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} engines: {node: '>=12.0.0'} @@ -5012,18 +4895,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chalk-template@1.1.2: - resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} - engines: {node: '>=14.16'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -5126,10 +5001,6 @@ packages: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} - comment-json@5.0.0: - resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} - engines: {node: '>= 6'} - compare-version@0.1.2: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} @@ -5172,47 +5043,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - cspell-config-lib@10.0.1: - resolution: {integrity: sha512-hMpo/0j6k7pbiqrLDOLJKD2IGP9XwhjKf2miiM6p84Xeo4nyuFZaxxDCQ68R851HSYFrrdltgpoipMbj1h2Tnw==} - engines: {node: '>=22.18.0'} - - cspell-dictionary@10.0.1: - resolution: {integrity: sha512-3cZ659vgsZWkzGQJR/sNqGDVt/OnvTSieLKI76V++4t1bHJfochb9ZrrwsuMsb1VPGiyqClUP1/O6WrefF/FVg==} - engines: {node: '>=22.18.0'} - - cspell-gitignore@10.0.1: - resolution: {integrity: sha512-wN23U61Mx6qPJN3CesOmBU9vnbJ0jQm/ylK0iaVui3CcnO7Zzl5qLu5mPHUzGQGm8yso6qjyxqo16Ho7LpZGOQ==} - engines: {node: '>=22.18.0'} - hasBin: true - - cspell-glob@10.0.1: - resolution: {integrity: sha512-7bII9J3aSSpZDwhx7w+zfQXbMxHZQ3be0ilUp5bHrsjz6o07v/NqOHMGcwKdPn1sw2dxDz9sv057xE5pqXnSdw==} - engines: {node: '>=22.18.0'} - - cspell-grammar@10.0.1: - resolution: {integrity: sha512-xC9AFYmaI9wsO//a7S5tdDGKGJVD5UEEsTg+Up2fi7lPfXIryisYmV6tePNL1SEg0idYss4ja8LUZ3Mib09BjQ==} - engines: {node: '>=22.18.0'} - hasBin: true - - cspell-io@10.0.1: - resolution: {integrity: sha512-8C2ka07faxflnaqEBO3pektS21XViE/SEHT7F5ZD1ou7FyMR5u3xawTBJSczClfsxLt/WYeztBYrpmGAjmjksw==} - engines: {node: '>=22.18.0'} - - cspell-lib@10.0.1: - resolution: {integrity: sha512-RpsIPiLzc4/YMW8BMRKpyJ81x439qjYWcqgdKeXnMkbKM88J9PexzutfFf/4v97v96KzfNitEzMpbI0uj8OeUg==} - engines: {node: '>=22.18.0'} - - cspell-trie-lib@10.0.1: - resolution: {integrity: sha512-BFvhalSkRQFjKrZ//FKK7fRGrZFpifnxB5AwCkzsIsBZqicsfafcQ1xP21qpb0QqyV/IomjNgviG+tRJs+0rMw==} - engines: {node: '>=22.18.0'} - peerDependencies: - '@cspell/cspell-types': 10.0.1 - - cspell@10.0.1: - resolution: {integrity: sha512-Gg6w/flT3fKfl3la62hfTnhtNnDQ+9mU7kUhVqw/axl/Ms4oENw0oJMkWFIoj4f6nL/SDPz7KcPXd2XbkKFNmQ==} - engines: {node: '>=22.18.0'} - hasBin: true - css-color-keywords@1.0.0: resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} engines: {node: '>=4'} @@ -5531,10 +5361,6 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - env-paths@4.0.0: - resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==} - engines: {node: '>=20'} - err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -5696,10 +5522,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-equals@6.0.0: - resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==} - engines: {node: '>=6.0.0'} - fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -5731,6 +5553,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5787,6 +5612,11 @@ packages: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -5823,10 +5653,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gensequence@8.0.8: - resolution: {integrity: sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==} - engines: {node: '>=20'} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5859,6 +5685,9 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.14.1: + resolution: {integrity: sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5884,14 +5713,6 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} - global-directory@5.0.0: - resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} - engines: {node: '>=20'} - - globals@17.9.0: - resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} - engines: {node: '>=18'} - globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -5999,10 +5820,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-fresh@4.0.0: - resolution: {integrity: sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==} - engines: {node: '>=22.15'} - import-in-the-middle@3.3.3: resolution: {integrity: sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==} engines: {node: '>=18'} @@ -6012,9 +5829,6 @@ packages: engines: {node: '>=8'} hasBin: true - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6030,10 +5844,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@6.0.0: - resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} - engines: {node: ^20.17.0 || >=22.9.0} - internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} @@ -6092,10 +5902,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-safe-filename@0.1.1: - resolution: {integrity: sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==} - engines: {node: '>=20'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -6318,6 +6124,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6382,6 +6192,11 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + knip@6.32.2: + resolution: {integrity: sha512-WXTXbmocrw7gqm1A1TQvFN0OgJ7hUSU6E1g6SPRIzzHFogUBhXByc7cYeOFVtJ2uODg7DP4VbESYBYnfbtBYsg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} @@ -6781,6 +6596,10 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.143.0: + resolution: {integrity: sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-resolver@11.24.2: resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} @@ -7198,6 +7017,9 @@ packages: resolve-pathname@3.0.0: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -7337,8 +7159,8 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - smol-toml@1.7.0: - resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} source-map-js@1.2.1: @@ -7464,6 +7286,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} @@ -7691,6 +7517,10 @@ packages: engines: {node: '>=16.20.0'} hasBin: true + unbash@4.0.10: + resolution: {integrity: sha512-b7zoBQvpWp0vuN5q2vK2RRBR2SvuruQAs50DApdDveBSn3eSYd84IaHodFqQIMlvY9K2VnyBUEXgwOBuGU9GBg==} + engines: {node: '>=14'} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -7858,12 +7688,6 @@ packages: yaml: optional: true - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -7871,6 +7695,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -7975,10 +7803,6 @@ packages: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} - xdg-basedir@5.1.0: - resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} - engines: {node: '>=12'} - xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -9144,228 +8968,6 @@ snapshots: '@colors/colors@1.6.0': {} - '@cspell/cspell-bundled-dicts@10.0.1': - dependencies: - '@cspell/dict-ada': 4.1.1 - '@cspell/dict-al': 1.1.1 - '@cspell/dict-aws': 4.0.17 - '@cspell/dict-bash': 4.2.3 - '@cspell/dict-companies': 3.2.11 - '@cspell/dict-cpp': 7.0.2 - '@cspell/dict-cryptocurrencies': 5.0.5 - '@cspell/dict-csharp': 4.0.8 - '@cspell/dict-css': 4.1.2 - '@cspell/dict-dart': 2.3.2 - '@cspell/dict-data-science': 2.0.14 - '@cspell/dict-django': 4.1.6 - '@cspell/dict-docker': 1.1.17 - '@cspell/dict-dotnet': 5.0.13 - '@cspell/dict-elixir': 4.0.8 - '@cspell/dict-en-common-misspellings': 2.1.12 - '@cspell/dict-en-gb-mit': 3.1.24 - '@cspell/dict-en_us': 4.4.35 - '@cspell/dict-filetypes': 3.0.18 - '@cspell/dict-flutter': 1.1.1 - '@cspell/dict-fonts': 4.0.6 - '@cspell/dict-fsharp': 1.1.1 - '@cspell/dict-fullstack': 3.2.9 - '@cspell/dict-gaming-terms': 1.1.2 - '@cspell/dict-git': 3.1.0 - '@cspell/dict-golang': 6.0.26 - '@cspell/dict-google': 1.0.9 - '@cspell/dict-haskell': 4.0.6 - '@cspell/dict-html': 4.0.15 - '@cspell/dict-html-symbol-entities': 4.0.5 - '@cspell/dict-java': 5.0.12 - '@cspell/dict-julia': 1.1.1 - '@cspell/dict-k8s': 1.0.12 - '@cspell/dict-kotlin': 1.1.1 - '@cspell/dict-latex': 5.1.0 - '@cspell/dict-lorem-ipsum': 4.0.5 - '@cspell/dict-lua': 4.0.8 - '@cspell/dict-makefile': 1.0.5 - '@cspell/dict-markdown': 2.0.17(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3) - '@cspell/dict-monkeyc': 1.0.12 - '@cspell/dict-node': 5.0.9 - '@cspell/dict-npm': 5.2.41 - '@cspell/dict-php': 4.1.1 - '@cspell/dict-powershell': 5.0.15 - '@cspell/dict-public-licenses': 2.0.16 - '@cspell/dict-python': 4.2.27 - '@cspell/dict-r': 2.1.1 - '@cspell/dict-ruby': 5.1.1 - '@cspell/dict-rust': 4.1.2 - '@cspell/dict-scala': 5.0.9 - '@cspell/dict-shell': 1.2.0 - '@cspell/dict-software-terms': 5.2.2 - '@cspell/dict-sql': 2.2.1 - '@cspell/dict-svelte': 1.0.7 - '@cspell/dict-swift': 2.0.6 - '@cspell/dict-terraform': 1.1.3 - '@cspell/dict-typescript': 3.2.3 - '@cspell/dict-vue': 3.0.5 - '@cspell/dict-zig': 1.0.0 - - '@cspell/cspell-json-reporter@10.0.1': - dependencies: - '@cspell/cspell-types': 10.0.1 - - '@cspell/cspell-performance-monitor@10.0.1': {} - - '@cspell/cspell-pipe@10.0.1': {} - - '@cspell/cspell-resolver@10.0.1': - dependencies: - global-directory: 5.0.0 - - '@cspell/cspell-service-bus@10.0.1': {} - - '@cspell/cspell-types@10.0.1': {} - - '@cspell/cspell-worker@10.0.1': - dependencies: - cspell-lib: 10.0.1 - - '@cspell/dict-ada@4.1.1': {} - - '@cspell/dict-al@1.1.1': {} - - '@cspell/dict-aws@4.0.17': {} - - '@cspell/dict-bash@4.2.3': - dependencies: - '@cspell/dict-shell': 1.2.0 - - '@cspell/dict-companies@3.2.11': {} - - '@cspell/dict-cpp@7.0.2': {} - - '@cspell/dict-cryptocurrencies@5.0.5': {} - - '@cspell/dict-csharp@4.0.8': {} - - '@cspell/dict-css@4.1.2': {} - - '@cspell/dict-dart@2.3.2': {} - - '@cspell/dict-data-science@2.0.14': {} - - '@cspell/dict-django@4.1.6': {} - - '@cspell/dict-docker@1.1.17': {} - - '@cspell/dict-dotnet@5.0.13': {} - - '@cspell/dict-elixir@4.0.8': {} - - '@cspell/dict-en-common-misspellings@2.1.12': {} - - '@cspell/dict-en-gb-mit@3.1.24': {} - - '@cspell/dict-en_us@4.4.35': {} - - '@cspell/dict-filetypes@3.0.18': {} - - '@cspell/dict-flutter@1.1.1': {} - - '@cspell/dict-fonts@4.0.6': {} - - '@cspell/dict-fsharp@1.1.1': {} - - '@cspell/dict-fullstack@3.2.9': {} - - '@cspell/dict-gaming-terms@1.1.2': {} - - '@cspell/dict-git@3.1.0': {} - - '@cspell/dict-golang@6.0.26': {} - - '@cspell/dict-google@1.0.9': {} - - '@cspell/dict-haskell@4.0.6': {} - - '@cspell/dict-html-symbol-entities@4.0.5': {} - - '@cspell/dict-html@4.0.15': {} - - '@cspell/dict-java@5.0.12': {} - - '@cspell/dict-julia@1.1.1': {} - - '@cspell/dict-k8s@1.0.12': {} - - '@cspell/dict-kotlin@1.1.1': {} - - '@cspell/dict-latex@5.1.0': {} - - '@cspell/dict-lorem-ipsum@4.0.5': {} - - '@cspell/dict-lua@4.0.8': {} - - '@cspell/dict-makefile@1.0.5': {} - - '@cspell/dict-markdown@2.0.17(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3)': - dependencies: - '@cspell/dict-css': 4.1.2 - '@cspell/dict-html': 4.0.15 - '@cspell/dict-html-symbol-entities': 4.0.5 - '@cspell/dict-typescript': 3.2.3 - - '@cspell/dict-monkeyc@1.0.12': {} - - '@cspell/dict-node@5.0.9': {} - - '@cspell/dict-npm@5.2.41': {} - - '@cspell/dict-php@4.1.1': {} - - '@cspell/dict-powershell@5.0.15': {} - - '@cspell/dict-public-licenses@2.0.16': {} - - '@cspell/dict-python@4.2.27': - dependencies: - '@cspell/dict-data-science': 2.0.14 - - '@cspell/dict-r@2.1.1': {} - - '@cspell/dict-ruby@5.1.1': {} - - '@cspell/dict-rust@4.1.2': {} - - '@cspell/dict-scala@5.0.9': {} - - '@cspell/dict-shell@1.2.0': {} - - '@cspell/dict-software-terms@5.2.2': {} - - '@cspell/dict-sql@2.2.1': {} - - '@cspell/dict-svelte@1.0.7': {} - - '@cspell/dict-swift@2.0.6': {} - - '@cspell/dict-terraform@1.1.3': {} - - '@cspell/dict-typescript@3.2.3': {} - - '@cspell/dict-vue@3.0.5': {} - - '@cspell/dict-zig@1.0.0': {} - - '@cspell/dynamic-import@10.0.1': - dependencies: - '@cspell/url': 10.0.1 - import-meta-resolve: 4.2.0 - - '@cspell/filetypes@10.0.1': {} - - '@cspell/rpc@10.0.1': {} - - '@cspell/strong-weak-map@10.0.1': {} - - '@cspell/url@10.0.1': {} - '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -9822,9 +9424,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))': dependencies: - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -10228,11 +9830,11 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@7.0.2) - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) optionalDependencies: typescript: 7.0.2 @@ -10617,51 +10219,99 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.143.0': + optional: true + '@oxc-parser/binding-android-arm64@0.127.0': optional: true + '@oxc-parser/binding-android-arm64@0.143.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.127.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.143.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.127.0': optional: true + '@oxc-parser/binding-darwin-x64@0.143.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.127.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.143.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.143.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.143.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.143.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.127.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.143.0': + optional: true + '@oxc-parser/binding-wasm32-wasi@0.127.0': dependencies: '@emnapi/core': 1.9.2 @@ -10672,12 +10322,21 @@ snapshots: '@oxc-parser/binding-win32-arm64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.143.0': + optional: true + '@oxc-project/types@0.127.0': {} '@oxc-project/types@0.143.0': {} @@ -11050,24 +10709,24 @@ snapshots: transitivePeerDependencies: - react - '@storybook/builder-vite@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': + '@storybook/builder-vite@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0))': dependencies: - '@storybook/csf-plugin': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@storybook/csf-plugin': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) ts-dedent: 2.3.0 - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': + '@storybook/csf-plugin@10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0))': dependencies: storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.1 - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) '@storybook/global@5.0.0': {} @@ -11084,11 +10743,11 @@ snapshots: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@storybook/react-vite@10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': + '@storybook/react-vite@10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) '@rollup/pluginutils': 5.4.0 - '@storybook/builder-vite': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@storybook/builder-vite': 10.5.7(esbuild@0.28.1)(storybook@10.5.7)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) '@storybook/react': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2) empathic: 2.0.1 magic-string: 0.30.21 @@ -11098,7 +10757,7 @@ snapshots: resolve: 1.22.12 storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) tsconfig-paths: 4.2.0 - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -11523,13 +11182,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2)': + '@typescript-eslint/utils@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)) '@typescript-eslint/scope-manager': 8.66.0 '@typescript-eslint/types': 8.66.0 '@typescript-eslint/typescript-estree': 8.66.0(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -11703,11 +11362,11 @@ snapshots: '@visulima/boxen@2.0.10': {} - '@vitejs/plugin-react-swc@4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0))': + '@vitejs/plugin-react-swc@4.3.3(@swc/helpers@0.5.23)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 '@swc/core': 1.15.47(@swc/helpers@0.5.23) - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) transitivePeerDependencies: - '@swc/helpers' @@ -12989,8 +12648,6 @@ snapshots: aria-query@5.3.2: {} - array-timsort@1.0.3: {} - asn1js@3.0.10: dependencies: pvtsutils: 1.3.6 @@ -13290,17 +12947,11 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk-template@1.1.2: - dependencies: - chalk: 5.6.2 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - char-regex@1.0.2: {} check-error@2.1.3: {} @@ -13388,11 +13039,6 @@ snapshots: commander@5.1.0: {} - comment-json@5.0.0: - dependencies: - array-timsort: 1.0.3 - esprima: 4.0.1 - compare-version@0.1.2: {} concat-map@0.0.1: {} @@ -13433,96 +13079,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cspell-config-lib@10.0.1: - dependencies: - '@cspell/cspell-types': 10.0.1 - comment-json: 5.0.0 - smol-toml: 1.7.0 - yaml: 2.9.0 - - cspell-dictionary@10.0.1: - dependencies: - '@cspell/cspell-performance-monitor': 10.0.1 - '@cspell/cspell-pipe': 10.0.1 - '@cspell/cspell-types': 10.0.1 - cspell-trie-lib: 10.0.1(@cspell/cspell-types@10.0.1) - fast-equals: 6.0.0 - - cspell-gitignore@10.0.1: - dependencies: - '@cspell/url': 10.0.1 - cspell-glob: 10.0.1 - cspell-io: 10.0.1 - - cspell-glob@10.0.1: - dependencies: - '@cspell/url': 10.0.1 - picomatch: 4.0.5 - - cspell-grammar@10.0.1: - dependencies: - '@cspell/cspell-pipe': 10.0.1 - '@cspell/cspell-types': 10.0.1 - - cspell-io@10.0.1: - dependencies: - '@cspell/cspell-service-bus': 10.0.1 - '@cspell/url': 10.0.1 - - cspell-lib@10.0.1: - dependencies: - '@cspell/cspell-bundled-dicts': 10.0.1 - '@cspell/cspell-performance-monitor': 10.0.1 - '@cspell/cspell-pipe': 10.0.1 - '@cspell/cspell-resolver': 10.0.1 - '@cspell/cspell-types': 10.0.1 - '@cspell/dynamic-import': 10.0.1 - '@cspell/filetypes': 10.0.1 - '@cspell/rpc': 10.0.1 - '@cspell/strong-weak-map': 10.0.1 - '@cspell/url': 10.0.1 - cspell-config-lib: 10.0.1 - cspell-dictionary: 10.0.1 - cspell-glob: 10.0.1 - cspell-grammar: 10.0.1 - cspell-io: 10.0.1 - cspell-trie-lib: 10.0.1(@cspell/cspell-types@10.0.1) - env-paths: 4.0.0 - gensequence: 8.0.8 - import-fresh: 4.0.0 - resolve-from: 5.0.0 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - xdg-basedir: 5.1.0 - - cspell-trie-lib@10.0.1(@cspell/cspell-types@10.0.1): - dependencies: - '@cspell/cspell-types': 10.0.1 - - cspell@10.0.1: - dependencies: - '@cspell/cspell-json-reporter': 10.0.1 - '@cspell/cspell-performance-monitor': 10.0.1 - '@cspell/cspell-pipe': 10.0.1 - '@cspell/cspell-types': 10.0.1 - '@cspell/cspell-worker': 10.0.1 - '@cspell/dynamic-import': 10.0.1 - '@cspell/url': 10.0.1 - ansi-regex: 6.2.2 - chalk: 5.6.2 - chalk-template: 1.1.2 - commander: 14.0.3 - cspell-config-lib: 10.0.1 - cspell-dictionary: 10.0.1 - cspell-gitignore: 10.0.1 - cspell-glob: 10.0.1 - cspell-io: 10.0.1 - cspell-lib: 10.0.1 - fast-json-stable-stringify: 2.1.0 - flatted: 3.4.4 - semver: 7.8.5 - tinyglobby: 0.2.17 - css-color-keywords@1.0.0: {} css-mediaquery@0.1.2: {} @@ -13811,7 +13367,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)): + electron-vite@5.0.0(@swc/core@1.15.47(@swc/helpers@0.5.23))(supports-color@8.1.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) @@ -13819,7 +13375,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) optionalDependencies: '@swc/core': 1.15.47(@swc/helpers@0.5.23) transitivePeerDependencies: @@ -13857,10 +13413,6 @@ snapshots: env-paths@3.0.0: {} - env-paths@4.0.0: - dependencies: - is-safe-filename: 0.1.1 - err-code@2.0.3: {} error-ex@1.3.4: @@ -13980,35 +13532,35 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-jest-dom@5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)): + eslint-plugin-jest-dom@5.10.1(@testing-library/dom@10.4.1)(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)): dependencies: - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) requireindex: 1.2.0 optionalDependencies: '@testing-library/dom': 10.4.1 - eslint-plugin-storybook@10.5.7(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2): + eslint-plugin-storybook@10.5.7(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2): dependencies: '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) storybook: 10.5.7(@storybook/react-vite@10.5.7)(@types/react@19.2.18)(prettier@3.8.1)(react@19.2.8) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-testing-library@7.16.2(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2): + eslint-plugin-testing-library@7.16.2(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2): dependencies: '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@7.0.2) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-unused-imports@4.4.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)): + eslint-plugin-unused-imports@4.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)): dependencies: - eslint: 10.8.1(jiti@2.6.1)(supports-color@8.1.1) + eslint: 10.8.1(jiti@2.7.0)(supports-color@8.1.1) eslint-scope@9.1.2: dependencies: @@ -14021,9 +13573,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1): + eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.6.1)(supports-color@8.1.1)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@8.1.1) '@eslint/config-helpers': 0.7.0 @@ -14054,7 +13606,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -14115,8 +13667,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-equals@6.0.0: {} - fast-fifo@1.3.2: {} fast-glob@3.3.3: @@ -14151,6 +13701,10 @@ snapshots: dependencies: bser: 2.1.1 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -14208,6 +13762,10 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -14247,8 +13805,6 @@ snapshots: function-bind@1.1.2: {} - gensequence@8.0.8: {} - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -14281,6 +13837,10 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.14.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -14323,12 +13883,6 @@ snapshots: serialize-error: 7.0.1 optional: true - global-directory@5.0.0: - dependencies: - ini: 6.0.0 - - globals@17.9.0: {} - globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -14451,8 +14005,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-fresh@4.0.0: {} - import-in-the-middle@3.3.3: dependencies: cjs-module-lexer: 2.2.0 @@ -14464,8 +14016,6 @@ snapshots: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 - import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -14477,8 +14027,6 @@ snapshots: inherits@2.0.4: {} - ini@6.0.0: {} - internmap@1.0.1: {} internmap@2.0.3: {} @@ -14515,8 +14063,6 @@ snapshots: is-potential-custom-element-name@1.0.1: {} - is-safe-filename@0.1.1: {} - is-stream@2.0.1: {} is-wsl@3.1.1: @@ -14925,6 +14471,8 @@ snapshots: jiti@2.6.1: {} + jiti@2.7.0: {} + js-tokens@4.0.0: {} js-yaml@3.15.1: @@ -15001,6 +14549,22 @@ snapshots: dependencies: json-buffer: 3.0.1 + knip@6.32.2: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + formatly: 0.3.0 + get-tsconfig: 4.14.1 + jiti: 2.7.0 + oxc-parser: 0.143.0 + oxc-resolver: 11.24.2 + picomatch: 4.0.5 + smol-toml: 1.8.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.10 + yaml: 2.9.0 + zod: 4.4.3 + kuler@2.0.0: {} lazy-val@1.0.5: {} @@ -15372,6 +14936,30 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + oxc-parser@0.143.0: + dependencies: + '@oxc-project/types': 0.143.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.143.0 + '@oxc-parser/binding-android-arm64': 0.143.0 + '@oxc-parser/binding-darwin-arm64': 0.143.0 + '@oxc-parser/binding-darwin-x64': 0.143.0 + '@oxc-parser/binding-freebsd-x64': 0.143.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.143.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.143.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.143.0 + '@oxc-parser/binding-linux-arm64-musl': 0.143.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.143.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.143.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.143.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.143.0 + '@oxc-parser/binding-linux-x64-gnu': 0.143.0 + '@oxc-parser/binding-linux-x64-musl': 0.143.0 + '@oxc-parser/binding-openharmony-arm64': 0.143.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.143.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.143.0 + '@oxc-parser/binding-win32-x64-msvc': 0.143.0 + oxc-resolver@11.24.2: optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.24.2 @@ -15836,6 +15424,8 @@ snapshots: resolve-pathname@3.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -15960,7 +15550,7 @@ snapshots: slash@5.1.0: {} - smol-toml@1.7.0: {} + smol-toml@1.8.0: {} source-map-js@1.2.1: {} @@ -16001,7 +15591,7 @@ snapshots: dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.1.0(react@19.2.8) - '@storybook/react-vite': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)) + '@storybook/react-vite': 10.5.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.7)(supports-color@8.1.1)(typescript@7.0.2)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)) '@testing-library/dom': 10.4.1 '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.3(@testing-library/dom@10.4.1) @@ -16089,6 +15679,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + style-mod@4.1.3: {} styled-components@6.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): @@ -16343,6 +15935,8 @@ snapshots: '@typescript/typescript-win32-arm64': 7.0.2 '@typescript/typescript-win32-x64': 7.0.2 + unbash@4.0.10: {} + undici-types@7.18.2: {} undici@6.28.0: {} @@ -16470,11 +16064,11 @@ snapshots: transitivePeerDependencies: - rollup - vite-plugin-wasm@3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0)): + vite-plugin-wasm@3.6.0(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0)): dependencies: - vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0) - vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.11)(yaml@2.9.0): + vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -16485,20 +16079,18 @@ snapshots: '@types/node': 24.13.3 esbuild: 0.28.1 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 tsx: 4.23.11 yaml: 2.9.0 - vscode-languageserver-textdocument@1.0.12: {} - - vscode-uri@3.1.0: {} - w3c-keyname@2.2.8: {} w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -16604,8 +16196,6 @@ snapshots: is-wsl: 3.1.1 powershell-utils: 0.1.0 - xdg-basedir@5.1.0: {} - xml-name-validator@5.0.0: {} xmlbuilder@15.1.1: {} diff --git a/web/packages/build/package.json b/web/packages/build/package.json index 4e842388b198..b501eb9d89a2 100644 --- a/web/packages/build/package.json +++ b/web/packages/build/package.json @@ -2,7 +2,6 @@ "name": "@gravitational/build", "version": "1.0.1", "description": "build scripts", - "main": "index.js", "license": "Apache-2.0", "repository": { "type": "git", @@ -19,7 +18,6 @@ "@swc/core": "^1.15.47", "@vitejs/plugin-react-swc": "^4.3.3", "babel-plugin-styled-components": "^2.3.0", - "globals": "^17.9.0", "jest-environment-jsdom": "^30.4.1", "jest-fail-on-console": "^3.3.4", "rollup-plugin-visualizer": "^7.0.1", diff --git a/web/packages/build/vite/config.mts b/web/packages/build/vite/config.mts index 2be6141c38b0..5b57677a47ab 100644 --- a/web/packages/build/vite/config.mts +++ b/web/packages/build/vite/config.mts @@ -40,9 +40,13 @@ export function createViteConfig( outputDirectory: string ) { return defineConfig(({ mode }) => { + // Knip loads this config to enumerate entry points. There is no dev server in that context, so + // skip the parts of the setup it cannot satisfy: the proxy target notice and the certificates. + const isKnip = !!process.env.KNIP; + let target = resolveTargetURL(process.env.PROXY_TARGET); - if (mode === 'development') { + if (mode === 'development' && !isKnip) { if (process.env.PROXY_TARGET) { // eslint-disable-next-line no-console console.log( @@ -213,7 +217,7 @@ export function createViteConfig( key: readFileSync(process.env.VITE_HTTPS_KEY), cert: readFileSync(process.env.VITE_HTTPS_CERT), }; - } else { + } else if (!isKnip) { const certsDirectory = resolve(rootDirectory, 'web/certs'); if (!existsSync(certsDirectory)) { diff --git a/web/packages/design/package.json b/web/packages/design/package.json index 8c5145e6ef08..2bf2a8ca2ec7 100644 --- a/web/packages/design/package.json +++ b/web/packages/design/package.json @@ -2,7 +2,6 @@ "name": "@gravitational/design", "version": "1.0.0", "description": "Gravitational design system", - "main": "index.js", "license": "Apache-2.0", "repository": { "type": "git", @@ -15,7 +14,6 @@ "styled-system": "^5.1.5" }, "devDependencies": { - "@gravitational/build": "workspace:*", "@types/styled-system": "^5.1.25", "jest-styled-components": "^7.4.0" } diff --git a/web/packages/design/src/Button/Button.tsx b/web/packages/design/src/Button/Button.tsx index ef1ea504c35f..9d4ddb5ddab9 100644 --- a/web/packages/design/src/Button/Button.tsx +++ b/web/packages/design/src/Button/Button.tsx @@ -116,7 +116,7 @@ export const Button = ({ Button.displayName = 'Button'; -export type ThemedButtonProps = ButtonProps & { +type ThemedButtonProps = ButtonProps & { theme: Theme; fill: ButtonFill; intent: ButtonIntent; diff --git a/web/packages/design/src/CardError/index.js b/web/packages/design/src/CardError/index.js index 422add8bb361..f4ce355b9303 100644 --- a/web/packages/design/src/CardError/index.js +++ b/web/packages/design/src/CardError/index.js @@ -22,7 +22,6 @@ import CardError, { LoginFailed, LogoutFailed, NotFound, - Offline, BadRequest, } from './CardError'; @@ -32,7 +31,6 @@ export { LoginFailed, AccessDenied, NotFound, - Offline, LogoutFailed, BadRequest, }; diff --git a/web/packages/design/src/DataTable/types.ts b/web/packages/design/src/DataTable/types.ts index ba04efcfb23e..295917572c61 100644 --- a/web/packages/design/src/DataTable/types.ts +++ b/web/packages/design/src/DataTable/types.ts @@ -122,7 +122,7 @@ type TableColumnBase = { export type PagerPosition = 'top' | 'bottom' | 'both'; -export type PaginationConfig = { +type PaginationConfig = { pageSize?: number; /** * "undefined" will show both pagers if data on current page is some diff --git a/web/packages/design/src/Label/Label.tsx b/web/packages/design/src/Label/Label.tsx index 7249678da17c..6e5d7f63f890 100644 --- a/web/packages/design/src/Label/Label.tsx +++ b/web/packages/design/src/Label/Label.tsx @@ -240,28 +240,7 @@ export const Primary = (props: LabelPropsWithoutKind) => ( /** @deprecated Use `` from `design/Tag`. */ export const Secondary = (props: LabelPropsWithoutKind) => (